diff --git a/backend/app.js b/backend/app.js deleted file mode 100644 index 8f5d343..0000000 --- a/backend/app.js +++ /dev/null @@ -1,96 +0,0 @@ -const express = require('express') -const bodyParser = require('body-parser') -const cookieParser = require('cookie-parser') -const crypto = require('crypto') -const fs = require('fs') -const util = require('util') -const bot = require('./apps/bot') - -const app = express() - -app.use(bodyParser.json()) -app.use(cookieParser()) - -BigInt.prototype.toJSON = function () { - return Number(this) -} - -app.use((req, res, next) => { - if(!(req.body instanceof Object)) - return next() - - const escapeHtml = str => str.replace(/&/g, '&').replace(//g, '>').replace(/"/g, '"').replace(/'/g, ''') - Object - .keys(req.body || {}) - .filter(key => typeof(req.body[key]) == 'string' && key != 'password') - .map(key => req.body[key] = escapeHtml(req.body[key])) - - next() -}) - -// cors -app.use((req, res, next) => { - res.set({ - 'Access-Control-Allow-Origin': '*', - 'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE', - 'Access-Control-Allow-Headers': 'Accept,Accept-Language,Content-Language,Content-Type,Authorization,Cookie,X-Requested-With,Origin,Host', - 'Access-Control-Allow-Credentials': true - }) - - return req.method == 'OPTIONS' ? res.status(200).json({success: true}) : next() -}) - -app.post('(/api/admin/customer/login|/api/miniapp/user/login)', (req, res, next) => { - const data = Object.assign({}, req.query) - delete data.hash - const hash = req.query?.hash - - const BOT_TOKEN = '7236504417:AAGVaodw3cRwGlf-jAhwnYb51OHaXcgpW8k' - const dataCheckString = Object.keys(data).sort().map((key) => `${key}=${data[key]}`).join("\n") - const secretKey = crypto.createHmac("sha256", "WebAppData").update(BOT_TOKEN).digest() - const hmac = crypto.createHmac("sha256", secretKey).update(dataCheckString).digest("hex") - - const timeDiff = Date.now() / 1000 - data.auth_date - - if (hmac !== req.query.hash) // || timeDiff > 10) - throw Error('ACCESS_DENIED::401') - - const user = JSON.parse(req.query.user) - res.locals.telegram_id = user.id - res.locals.start_param = req.query.start_param - - if (!res.locals.telegram_id) - throw Error('ACCESS_DENIED::500') - - next() -}) - - -app.use('/api/admin', require('./apps/admin')) -app.use('/api/miniapp', require('./apps/miniapp')) - -app.use((err, req, res, next) => { - console.error(`Error for ${req.path}: ${err}`) - - let message, code - //if (err.code == 'SQLITE_ERROR' || err.code == 'SQLITE_CONSTRAINT_CHECK') { - // message = 'DATABASE_ERROR' - //code = err.code == 'SQLITE_CONSTRAINT_CHECK' ? 400 : 500 - //} else { - [message, code = 500] = err.message.split('::') - //} - - res.status(res.statusCode == 200 ? 500 : res.statusCode).json({success: false, error: { message, code}}) -}) - -app.use(express.static('public')) - -const PORT = process.env.PORT || 3000 -app.listen(PORT, async () => { - console.log(`Listening at port ${PORT}`) - bot.start( - process.env.API_ID || 26746106, - process.env.API_HASH || '29e5f83c04e635fa583721473a6003b5', - process.env.BOT_TOKEN || '7236504417:AAGVaodw3cRwGlf-jAhwnYb51OHaXcgpW8k' - ) -}) \ No newline at end of file diff --git a/backend/apps/admin.js b/backend/apps/admin.js deleted file mode 100644 index 0cb3013..0000000 --- a/backend/apps/admin.js +++ /dev/null @@ -1,766 +0,0 @@ -const crypto = require('crypto') -const express = require('express') -const db = require('../include/db') -const bot = require('./bot') -const fs = require('fs') - -const app = express.Router() - -const sessions = {} -const cache = { - // email -> code - register: {}, - upgrade: {}, - recovery: {}, - 'change-password': {}, - 'change-email': {}, - 'change-email2': {} -} - -function checkEmail(email){ - return String(email) - .toLowerCase() - .match(/^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/) -} - -function sendEmail(email, subject, message) { - console.log(`${email} --> ${subject}: ${message}`) -} - -function checkPassword(password) { - return password.length >= 8 -} - -app.use((req, res, next) => { - const public = [ - '/auth/email', - '/auth/telegram', - '/auth/email/register', - '/auth/email/recovery', - '/auth/logout' - ] - - if (public.includes(req.path)) - return next() - - const asid = req.query.asid || req.cookies.asid - req.session = sessions[asid] - if (!req.session) - throw Error('ACCESS_DENIED::401') - - res.locals.customer_id = req.session.customer_id - next() -}) - -// AUTH -function createSession(req, res, customer_id) { - if (!customer_id) - throw Error('AUTH_ERROR::500') - - res.locals.customer_id = customer_id - const asid = crypto.randomBytes(64).toString('hex') - req.session = sessions[asid] = {asid, customer_id } - res.setHeader('Set-Cookie', [`asid=${asid};httpOnly;path=/api/admin`]) -} - -app.post('/auth/email', (req, res, next) => { - res.locals.email = req.body?.email - res.locals.password = req.body?.password - - const customer_id = db - .prepare(`select id from customers where email = :email and password is not null and password = :password `) - .pluck(true) - .get(res.locals) - - if (!customer_id) - throw Error('AUTH_ERROR::401') - - createSession(req, res, customer_id) - res.status(200).json({success: true}) -}) - -app.post('/auth/telegram', (req, res, next) => { - let customer_id = db - .prepare(`select id from customers where telegram_id = :telegram_id`) - .pluck(true) - .get(res.locals) || db - .prepare(`replace into customers (telegram_id) values (:telegram_id) returning id`) - .pluck(true) - .get(res.locals) - - createSession(req, res, customer_id) - res.status(200).json({success: true}) -}) - -/* - Регистрация нового клиента/Перевод авторизации с TG на email выполняется за ТРИ последовательных вызова - 1. Отравляется email. Если email корректный и уже неиспользуется, то сервер возвращает ОК и на указанный email отправляется код. - 2. Отправляется email + код из письма. Если указан корректный код, то сервер отвечает ОК. - 3. Отправляется email + код из письма + желаемый пароль. Если все ОК, то сервер создает учетную запись и возвращает ОК. -*/ -app.post('/auth/email/:action(register|upgrade)', (req, res, next) => { - const email = String(req.body.email ?? '').trim() - const code = String(req.body.code ?? '').trim() - const password = String(req.body.password ?? '').trim() - const action = req.params.action - - const stepNo = email && !code ? 1 : email && code && !password ? 2 : email && code && password ? 3 : -1 - if (stepNo == -1) - throw Error('BAD_STEP::400') - - if (stepNo == 1) { - if (!checkEmail(email)) - throw Error('INCORRECT_EMAIL::400') - - const customer_id = db - .prepare('select id from customers where email = :email') - .pluck(true) - .get({email}) - - if (customer_id) - throw Error('USED_EMAIL::400') - - const code = Math.random().toString().substr(2, 4) - cache[action][email] = code - sendEmail(email, action.toUpperCase(), `${email} => ${code}`) - } - - if (stepNo == 2) { - if (cache[action][email] != code) - throw Error('INCORRECT_CODE::400') - } - - if (stepNo == 3) { - if (!checkPassword(password)) - throw Error('INCORRECT_PASSWORD::400') - - const query = action == 'register' ? 'insert into customers (email, password) values (:email, :password)' : - 'update customers set email = :email, password = :password where id = :id' - db.prepare(query).run({email, password, id: res.locals.customer_id}) - - delete cache[action][email] - } - - res.status(200).json({success: true}) -}) - - -/* - Смена email выполняется за ЧЕТЫРЕ последовательных вызовов - 1. Отравляется пустой закпрос. Сервер на email пользователя из базы отправляет код. - 2. Отправляется код из письма. Если указан корректный код, то сервер отвечает ОК. - 3. Отправляется код из письма + новый email. Сервер отправляет код2 на новый email. - 4. Отправлются оба кода и новый email. Если они проходят проверку, то сервер меняет email пользователя на новый и возвращает ОК. -*/ -app.post('/auth/email/change-email', (req, res, next) => { - const email2 = String(req.body.email ?? '').trim() - const code = String(req.body.code ?? '').trim() - const code2 = String(req.body.code2 ?? '').trim() - - const email = db - .prepare('select email from customers where id = :customer_id') - .pluck(true) - .get(res.locals) - - const stepNo = !code ? 1 : code && !email ? 2 : code && email && !code2 ? 3 : code && email && code2 ? 4 : -1 - if (stepNo == -1) - throw Error('BAD_STEP::400') - - if (stepNo == 1) { - const code = Math.random().toString().substr(2, 4) - cache['change-email'][email] = code - sendEmail(email, 'CHANGE-EMAIL', `${email} => ${code}`) - } - - if (stepNo == 2) { - if (cache['change-email'][email] != code) - throw Error('INCORRECT_CODE::400') - } - - if (stepNo == 3) { - if (!checkEmail(email2)) - throw Error('INCORRECT_EMAIL::400') - - const code2 = Math.random().toString().substr(2, 4) - cache['change-email2'][email2] = code2 - sendEmail(email2, 'CHANGE-EMAIL2', `${email2} => ${code2}`) - } - - if (stepNo == 4) { - if (cache['change-email'][email] != code || cache['change-email2'][email2] != code2) - throw Error('INCORRECT_CODE::400') - - const info = db - .prepare('update customers set email = :email where id = :customer_id') - .run(res.locals) - - if (info.changes == 0) - throw Error('BAD_REQUEST::400') - - delete cache['change-email'][email] - delete cache['change-email2'][email2] - } - - res.status(200).json({success: true}) -}) - -/* - Смена пароля/восстановление доступа выполняется за ТРИ последовательных вызова - 1. Отравляется пустой закпрос для смены запоса и email, в случае восстановления доступа. Сервер на email отправляет код. - 2. Отправляется email + код из письма. Если указан корректный код, то сервер отвечает ОК. - 3. Отправляется email + код из письма + новый пароль. Сервер изменяет пароль и возвращает ОК. -*/ -app.post('/auth/email/:action(change-password|recovery)', (req, res, next) => { - const code = String(req.body.code ?? '').trim() - const password = String(req.body.password) - const action = req.params.action - - const email = action == 'change-password' ? db - .prepare('select email from customers where id = :customer_id') - .pluck(true) - .get(res.locals) : - String(req.body.email ?? '').trim() - - const stepNo = action == 'change-password' ? - (!code && !password ? 1 : code && !password ? 2 : code && password ? 3 : -1) : - (!email && !code && !password ? 1 : email && code && !password ? 2 : email && code && password ? 3 : -1) - if (stepNo == -1) - throw Error('BAD_STEP::400') - - if (stepNo == 1) { - if (!checkEmail(email)) - throw Error('INCORRECT_EMAIL::400') - - const code = Math.random().toString().substr(2, 4) - cache[action][email] = code - sendEmail(email, action.toUpperCase(), `${email} => ${code}`) - } - - if (stepNo == 2) { - if (cache[action][email] != code) - throw Error('INCORRECT_CODE::400') - } - - if (stepNo == 3) { - if (cache[action][email] != code) - throw Error('INCORRECT_CODE::400') - - if (!checkPassword(password)) - throw Error('INCORRECT_PASSWORD::400') - - const info = db - .prepare('update customers set password = :password where email = :email') - .run({ email, password }) - - if (info.changes == 0) - throw Error('BAD_REQUEST::400') - - delete cache[action][email] - } - - res.status(200).json({success: true}) -}) - -app.get('/auth/logout', (req, res, next) => { - if (req.session?.asid) - delete sessions[req.session.asid] - - res.setHeader('Set-Cookie', [`asid=; expired; httpOnly;path=/api/admin`]) - res.status(200).json({success: true}) -}) - -// CUSTOMER -app.get('/customer/profile', (req, res, next) => { - const row = db - .prepare(` - select id, name, email, plan, - coalesce(json_balance, '{}') json_balance, coalesce(json_company, '{}') json_company, - upload_chat_id, generate_key(-id, :time) upload_token - from customers - where id = :customer_id - `) - .get(res.locals) - - if (row?.upload_chat_id) { - row.upload_chat = db - .prepare(`select id, name, telegram_id from chats where id = :chat_id and project_id is null`) - .safeIntegers(true) - .get({ chat_id: row.upload_chat_id}) - delete row.upload_chat_id - } - - for (const key in row) { - if (key.startsWith('json_')) { - row[key.substr(5)] = JSON.parse(row[key]) - delete row[key] - } - } - - res.status(200).json({success: true, data: row}) -}) - -app.put('/customer/profile', (req, res, next) => { - if (req.body.company instanceof Object) - req.body.json_company = JSON.stringify(req.body.company) - else - delete req.body?.json_company - - const info = db - .prepareUpdate( - 'customers', - ['name', 'password', 'json_company'], - req.body, - ['id']) - .run(Object.assign({}, req.body, {id: req.session.customer_id})) - - if (info.changes == 0) - throw Error('NOT_FOUND::404') - - res.status(200).json({success: true}) -}) - -app.get('/customer/settings', (req, res, next) => { - const row = db - .prepare(`select coalesce(json_settings, '{}') from customers where id = :customer_id`) - .pluck(true) - .get(res.locals) - - res.status(200).json({success: true, data: JSON.parse(row)}) -}) - -app.put('/customer/settings', (req, res, next) => { - res.locals.json_settings = JSON.stringify(req.body || {}) - - db - .prepare(`update customers set json_settings = :json_settings where id = :customer_id`) - .run(res.locals) - - res.status(200).json({success: true}) -}) - -// PROJECT -function getProject(id, customer_id) { - const row = db - .prepare(` - select id, name, description, logo, is_logo_bg, is_archived, - (select count(*) from chats where project_id = p.id) chat_count, - (select count(distinct user_id) from chat_users where chat_id in (select id from chats where project_id = p.id)) user_count - from projects p - where customer_id = :customer_id and p.id = :id - order by name - `) - .get({id, customer_id}) - - if (!row) - throw Error('NOT_FOUND::404') - - row.is_archived = Boolean(row.is_archived) - row.is_logo_bg = Boolean(row.is_logo_bg) - - return row -} - -app.get('/project', (req, res, next) => { - const data = db - .prepare(` - select id, name, description, logo, is_logo_bg, is_archived, - (select count(*) from chats where project_id = p.id) chat_count, - (select count(distinct user_id) from chat_users where chat_id in (select id from chats where project_id = p.id)) user_count - from projects p - where customer_id = :customer_id - order by name - `) - .all(res.locals) - - data.forEach(row => { - row.is_archived = Boolean(row.is_archived) - row.is_logo_bg = Boolean(row.is_logo_bg) - }) - - res.status(200).json({success: true, data}) -}) - -app.post('/project', (req, res, next) => { - res.locals.name = req.body?.name - res.locals.description = req.body?.description - res.locals.logo = req.body?.logo - res.locals.is_logo_bg = 'is_logo_bg' in req.body ? +req.body.is_logo_bg : undefined - - const id = db - .prepare(` - insert into projects (customer_id, name, description, logo, is_logo_bg) - values (:customer_id, :name, :description, :logo, :is_logo_bg) - returning id - `) - .pluck(true) - .get(res.locals) - - const data = getProject(id, res.locals.customer_id) - res.status(200).json({success: true, data}) -}) - -app.use ('(/project/:pid(\\d+)/*|/project/:pid(\\d+))', (req, res, next) => { - res.locals.project_id = parseInt(req.params.pid) - - const row = db - .prepare('select 1 from projects where id = :project_id and customer_id = :customer_id and is_archived <> 1') - .get(res.locals) - - if (!row) - throw Error('ACCESS_DENIED::401') - - next() -}) - -app.get('/project/:pid(\\d+)', (req, res, next) => { - const data = getProject(req.params.pid, res.locals.customer_id) - res.status(200).json({success: true, data}) -}) - -app.put('/project/:pid(\\d+)', (req, res, next) => { - res.locals.id = req.params.pid - res.locals.name = req.body?.name - res.locals.description = req.body?.description - res.locals.logo = req.body?.logo - res.locals.is_logo_bg = 'is_logo_bg' in req.body ? +req.body.is_logo_bg : undefined - - const info = db - .prepareUpdate( - 'projects', - ['name', 'description', 'logo', 'is_logo_bg'], - res.locals, - ['id', 'customer_id']) - .run(res.locals) - - if (info.changes == 0) - throw Error('NOT_FOUND::404') - - const data = getProject(req.params.pid, res.locals.customer_id) - res.status(200).json({success: true, data}) -}) - -app.put('/project/:pid(\\d+)/:action(archive|restore)', async (req, res, next) => { - res.locals.id = req.params.pid - res.locals.is_archived = +(req.params.action == 'archive') - - const info = db - .prepare(` - update projects - set is_archived = :is_archived - where id = :id and customer_id = :customer_id and coalesce(is_archived, 0) = not :is_archived - `) - .run(res.locals) - - if (info.changes == 0) - throw Error('BAD_REQUEST::400') - - const chatIds = db - .prepare(`select id from chats where project_id = :id`) - .pluck(true) - .all(res.locals) - - for (const chatId of chatIds) { - await bot.sendMessage(chatId, res.locals.is_archived ? 'Проект помещен в архив. Отслеживание сообщений прекращено.' : 'Проект восстановлен из архива.') - } - - const data = getProject(req.params.pid, res.locals.customer_id) - res.status(200).json({success: true, data}) -}) - -// USER -function getUser(id, project_id) { - const row = db - .prepare(` - select u.id, u.telegram_id, u.firstname, u.lastname, u.username, u.photo, - ud.fullname, ud.email, ud.phone, ud.role, ud.department, ud.is_blocked, - cu.company_id - from users u - left join user_details ud on u.id = ud.user_id and ud.project_id = :project_id - left join company_users cu on u.id = cu.user_id - where id = :id - `) - .safeIntegers(true) - .all({id, project_id}) - - if (!row) - throw Error('NOT_FOUND::404') - - row.is_blocked = Boolean(row.is_blocked) - - return row -} - -app.get('/project/:pid(\\d+)/user', (req, res, next) => { - const data = db - .prepare(` - select u.id, u.telegram_id, u.firstname, u.lastname, u.username, u.photo, - ud.fullname, ud.email, ud.phone, ud.role, ud.department, ud.is_blocked, - cu.company_id - from users u - left join user_details ud on u.id = ud.user_id and ud.project_id = :project_id - left join company_users cu on u.id = cu.user_id - where id in ( - select user_id - from chat_users - where chat_id in (select id from chats where project_id = :project_id) - ) - `) - .safeIntegers(true) - .all(res.locals) - - data.forEach(row => { - row.is_blocked = Boolean(row.is_blocked) - }) - - res.status(200).json({success: true, data}) -}) - -app.get('/project/:pid(\\d+)/user/:uid(\\d+)', (req, res, next) => { - const data = getUser(req.params.uid, req.params.pid) - res.status(200).json({success: true, data}) -}) - -app.put('/project/:pid(\\d+)/user/:uid(\\d+)', (req, res, next) => { - res.locals.user_id = parseInt(req.params.uid) - - res.locals.fullname = req.body?.fullname - res.locals.email = req.body?.email - res.locals.phone = req.body?.phone - res.locals.role = req.body?.role - res.locals.department = req.body?.department - res.locals.is_blocked = 'is_blocked' in req.body ? +req.body.is_blocked : undefined - - const info = db - .prepareUpsert('user_details', - ['fullname', 'email', 'phone', 'role', 'department', 'is_blocked'], - res.locals, - ['user_id', 'project_id'] - ) - .run(res.locals) - - const data = getUser(req.params.uid, req.params.pid) - res.status(200).json({success: true, data}) -}) - -app.get('/project/:pid(\\d+)/token', (req, res, next) => { - res.locals.time = Math.floor(Date.now() / 1000) - - const key = db - .prepare('select generate_key(id, :time) from projects where id = :project_id and customer_id = :customer_id') - .pluck(true) - .get(res.locals) - - if (!key) - throw Error('NOT_FOUND::404') - - res.status(200).json({success: true, data: key}) -}) - -// COMPANY -function getCompany(id, project_id) { - const row = db - .prepare(` - select id, name, address, email, phone, site, description, logo, - (select json_group_array(user_id) from company_users where company_id = c.id) users - from companies c - where c.id = :id and project_id = :project_id - order by name - `) - .get({id, project_id}) - - if (!row) - throw Error('NOT_FOUND::404') - - return row -} - -app.get('/project/:pid(\\d+)/company', (req, res, next) => { - const data = db - .prepare(` - select id, name, address, email, phone, site, description, logo, - (select json_group_array(user_id) from company_users where company_id = c.id) users - from companies c - where project_id = :project_id - order by name - `) - .all(res.locals) - - data.forEach(row => row.users = JSON.parse(row.users || '[]')) - - res.status(200).json({success: true, data}) -}) - -app.get('/project/:pid(\\d+)/company/:cid(\\d+)', (req, res, next) => { - const data = getCompany(req.params.cid, req.params.pid) - res.status(200).json({success: true, data}) -}) - -app.post('/project/:pid(\\d+)/company', (req, res, next) => { - res.locals.name = req.body?.name - res.locals.address = req.body?.address - res.locals.email = req.body?.email - res.locals.phone = req.body?.phone - res.locals.site = req.body?.site - res.locals.description = req.body?.description - res.locals.logo = req.body?.logo - - const id = db - .prepare(` - insert into companies (project_id, name, address, email, phone, site, description, logo) - values (:project_id, :name, :address, :email, :phone, :site, :description, :logo) - returning id - `) - .pluck(true) - .get(res.locals) - - const data = getCompany(id, req.params.pid) - res.status(200).json({success: true, data}) -}) - -app.put('/project/:pid(\\d+)/company/:cid(\\d+)', (req, res, next) => { - res.locals.id = parseInt(req.params.cid) - res.locals.name = req.body?.name - res.locals.address = req.body?.address - res.locals.email = req.body?.email - res.locals.phone = req.body?.phone - res.locals.site = req.body?.site - res.locals.description = req.body?.description - res.locals.logo = req.body?.logo - - const info = db - .prepareUpdate( - 'companies', - ['name', 'address', 'email', 'phone', 'site', 'description', 'logo'], - res.locals, - ['id', 'project_id']) - .run(res.locals) - - if (info.changes == 0) - throw Error('NOT_FOUND::404') - - const data = getCompany(req.params.cid, req.params.pid) - res.status(200).json({success: true, data}) -}) - -app.delete('/project/:pid(\\d+)/company/:cid(\\d+)', (req, res, next) => { - res.locals.company_id = req.params.cid - - const info = db - .prepare(`delete from companies where id = :company_id and project_id = :project_id`) - .run(res.locals) - - if (info.changes == 0) - throw Error('NOT_FOUND::404') - - res.status(200).json({success: true, data: {id: req.params.cid}}) -}) - -app.put('/project/:pid(\\d+)/company/:cid(\\d+)/user', (req, res, next) => { - res.locals.company_id = parseInt(req.params.cid) - - // Проверка, что есть доступ к компании - const row = db - .prepare('select 1 from companies where id = :company_id and project_id = :project_id') - .get(res.locals) - - if (!row) - throw Error('NOT_FOUND::404') - - const user_ids = req.body instanceof Array ? [...new Set(req.body.map(e => parseInt(e)))] : [] - - // Проверка, что пользователи имеют доступ к проекту - let rows = db - .prepare(` - select user_id - from chat_users - where chat_id in (select id from chats where project_id = :project_id) - `) - .pluck(true) // .raw? - .get(res.locals) - - if (user_ids.some(user_id => !rows.contains(user_id))) - throw Error('INACCESSABLE_MEMBER::400') - - // Проверка, что пользователи не участвуют в других компаниях на проекте - rows = db - .prepare(` - select user_id - from company_users - where company_id in (select id from companies where id <> :company_id and project_id = :project_id) - `) - .pluck(true) // .raw? - .get(res.locals) - - if (user_ids.some(user_id => !rows.contains(user_id))) - throw Error('USED_MEMBER::400') - - db - .prepare(`delete from company_users where company_id = :company_id`) - .run(res.locals) - - db - .prepare(` - insert into company_users (company_id, user_id) - select :company_id, value from json_each(:json_ids) - `) - .run(res.locals, {json_ids: JSON.stringify(user_ids)}) - - res.status(200).json({success: true}) -}) - -// CHATS -function getChat(id, project_id) { - const row = db - .prepare(` - select id, name, telegram_id, is_channel, description, logo, user_count, bot_can_ban - from chats c - where c.id = :id and project_id = :project_id - `) - .all({id, project_id}) - - if (!row) - throw Error('NOT_FOUND::404') - - row.is_channel = Boolean(row.is_channel) - row.bot_can_ban = Boolean(row.bot_can_ban) - - return row -} - -app.get('/project/:pid(\\d+)/chat', (req, res, next) => { - const data = db - .prepare(` - select id, name, telegram_id, is_channel, description, logo, user_count, bot_can_ban - from chats - where project_id = :project_id - `) - .all(res.locals) - - data.forEach(row => { - row.is_channel = Boolean(row.is_channel) - row.bot_can_ban = Boolean(row.bot_can_ban) - }) - - res.status(200).json({success: true, data}) -}) - -app.get('/project/:pid(\\d+)/chat/:gid(\\d+)', (req, res, next) => { - const data = getChat(req.params.gid, req.params.pid) - res.status(200).json({success: true, data}) -}) - -app.delete('/project/:pid(\\d+)/chat/:gid(\\d+)', async (req, res, next) => { - res.locals.chat_id = req.params.gid - const info = db - .prepare(`update chats set project_id = null where id = :chat_id and project_id = :project_id`) - .run(res.locals) - - if (info.changes == 0) - throw Error('NOT_FOUND::404') - - await bot.sendMessage(res.locals.chat_id, 'Чат удален из проекта') - - res.status(200).json({success: true, data: {id: req.params.gid}}) -}) - -module.exports = app \ No newline at end of file diff --git a/backend/apps/bot.js b/backend/apps/bot.js deleted file mode 100644 index 0f84d29..0000000 --- a/backend/apps/bot.js +++ /dev/null @@ -1,627 +0,0 @@ -const db = require('../include/db') - -const { Api, TelegramClient } = require('telegram') -const { StringSession } = require('telegram/sessions') -const { Button } = require('telegram/tl/custom/button') -const { CustomFile } = require('telegram/client/uploads') - -let session -let client -let BOT_ID -const BOT_NAME = 'ready_or_not_2025_bot' - -function registerUser (telegramId) { - db - .prepare(`insert or ignore into users (telegram_id) values (:telegram_id)`) - .safeIntegers(true) - .run({ telegram_id: telegramId }) - - return db - .prepare(`select id from users where telegram_id = :telegram_id`) - .safeIntegers(true) - .pluck(true) - .get({ telegram_id: telegramId }) -} - -function updateUser (userId, data) { - const info = db - .prepare(` - update users set firstname = :firstname, lastname = :lastname, username = :username, - access_hash = :access_hash, language_code = :language_code where id = :user_id - `) - .safeIntegers(true) - .run({ - user_id: userId, - firstname: data.firstName, - lastname: data.lastName, - username: data.username, - access_hash: data.accessHash.value, - language_code: data.langCode - }) - - return info.changes == 1 -} - -async function updateUserPhoto (userId, data) { - const photoId = data.photo?.photoId?.value - if (!photoId) - return - - const tgUserId = db - .prepare(`select telegram_id from users where id = :user_id and coalesce(photo_id, 0) <> :photo_id`) - .safeIntegers(true) - .pluck(true) - .get({user_id: userId, photo_id: photoId }) - - if (!tgUserId) - return - - const photo = await client.invoke(new Api.photos.GetUserPhotos({ - userId: new Api.PeerUser({ userId: tgUserId }), - maxId: photoId, - offset: -1, - limit: 1, - })); - - const file = await client.downloadFile(new Api.InputPhotoFileLocation({ - id: photoId, - accessHash: photo.photos[0]?.accessHash, - fileReference: Buffer.from('random'), - thumbSize: 'a', - }, {})) - - db - .prepare(`update users set photo_id = :photo_id, photo = :photo where id = :user_id`) - .safeIntegers(true) - .run({ user_id: userId, photo_id: photoId, photo: 'data:image/jpg;base64,' + file.toString('base64') }) -} - -async function registerChat (telegramId, isChannel) { - const chat = db - .prepare(`select id, name, is_channel, access_hash from chats where telegram_id = :telegram_id`) - .safeIntegers(true) - .get({telegram_id: telegramId}) - - if (chat && chat.access_hash && chat.is_channel == isChannel && chat.name) - return chat.id - - const entity = isChannel ? { channelId: telegramId } : { chatId: telegramId } - const tgChat = await client.getEntity( isChannel ? - new Api.InputPeerChannel(entity) : - new Api.InputPeerChat(entity) - ) - - const chatId = db - .prepare(`replace into chats (telegram_id, is_channel, access_hash, name) values (:telegram_id, :is_channel, :access_hash, :name) returning id`) - .safeIntegers(true) - .pluck(true) - .get({ - telegram_id: telegramId, - is_channel: +isChannel, - access_hash: tgChat.accessHash.value, - name: tgChat.title - }) - - await updateChat(chatId) - - return chatId -} - -async function updateChat (chatId) { - const chat = db - .prepare(`select id, telegram_id, access_hash, is_channel from chats where id = :id`) - .safeIntegers(true) - .get({id: chatId}) - - const peer = chat.is_channel ? - new Api.InputPeerChannel({ channelId: chat.telegram_id, accessHash: chat.access_hash }) : - new Api.InputPeerChat({ chatId: chat.telegram_id, accessHash: chat.access_hash }) - - const data = chat.is_channel ? - await client.invoke(new Api.channels.GetFullChannel({ channel: peer })) : - await client.invoke(new Api.messages.GetFullChat({ chatId: chat.telegram_id, accessHash: chat.access_hash })) - - const file = data?.fullChat?.chatPhoto ? await client.downloadFile(new Api.InputPeerPhotoFileLocation({ peer, photoId: data.fullChat.chatPhoto?.id }, {})) : null - logo = file ? 'data:image/jpg;base64,' + file.toString('base64') : null - - db - .prepare(`update chats set description = :description, logo = :logo, user_count = :user_count, last_update_time = :last_update_time where id = :id`) - .safeIntegers(true) - .run({ - id: chatId, - description: data.fullChat.about, - logo, - user_count: data.fullChat.participantsCount - (data.users || []).filter(user => user.bot).length, - last_update_time: Math.floor(Date.now() / 1000) - }) -} - -async function attachChat(chatId, projectId) { - const chat = db - .prepare(`update chats set project_id = :project_id where id = :chat_id returning telegram_id, access_hash, is_channel`) - .safeIntegers(true) - .get({ chat_id: chatId, project_id: projectId }) - - if (!chat.telegram_id) - return console.error('Can\'t attach chat: ' + chatId + ' to project: ' + projectId) - - const peer = chat.is_channel ? - new Api.InputPeerChannel({ channelId: chat.telegram_id, accessHash: chat.access_hash }) : - new Api.InputPeerChat({ chatId: chat.telegram_id, accessHash: chat.access_hash }) - - const message = db - .prepare(`select (select name from customers where id = p.customer_id) || ' >> ' || p.name from projects p where id = :project_id`) - .pluck(true) - .get({project_id: projectId}) - - const resultBtn = await client.sendMessage(peer, { - message, - buttons: client.buildReplyMarkup([[Button.url('Открыть проект', `https://t.me/${BOT_NAME}/userapp?startapp=` + projectId)]]) - }) - - await client.invoke(new Api.messages.UpdatePinnedMessage({ - peer, - id: resultBtn.id, - unpin: false - })) -} - -async function reloadChatUsers(chatId, onlyReset) { - db - .prepare(`delete from chat_users where chat_id = :chat_id`) - .run({ chat_id: chatId }) - - if (onlyReset) - return - - const chat = db - .prepare(`select telegram_id, is_channel, access_hash from chats where id = :chat_id`) - .get({ chat_id: chatId}) - - if (!chat) - return - - const tgChatId = chat.telegram_id - const isChannel = chat.is_channel - const accessHash = chat.access_hash - - const result = isChannel ? - await client.invoke(new Api.channels.GetParticipants({ - channel: new Api.PeerChannel({ channelId: tgChatId, accessHash }), - filter: new Api.ChannelParticipantsRecent(), - limit: 999999, - offset: 0 - })) : await client.invoke(new Api.messages.GetFullChat({ chatId: tgChatId, accessHash })) - - const users = result.users.filter(user => !user.bot) - for (const user of users) { - const userId = registerUser(user.id.value, user) - - if (updateUser(userId, user)) { - await updateUserPhoto (userId, user) - - db - .prepare(`insert or ignore into chat_users (chat_id, user_id) values (:chat_id, :user_id)`) - .run({ chat_id: chatId, user_id: userId }) - } - } - - db - .prepare(`update chats set user_count = (select count(1) from chat_users where chat_id = :chat_id) where id = :chat_id`) - .run({ chat_id: chatId}) -} - -async function registerUpload(data) { - if (!data.projectId || !data.media) - return console.error ('registerUpload: ' + (data.projectId ? 'media' : 'project id') + ' is missing') - - const customer_id = db - .prepare(`select customer_id from projects where project_id = :project_id`) - .pluck(true) - .get({project_id: data.projectId}) - - if (!customer_id) - return console.error ('registerUpload: The customer is not found for project: ' + data.projectId) - - const chat = db - .prepare( - `select id, telegram_id, project_id, is_channel, access_hash from chats - where id = (select upload_chat_id from customers where id = :customer_id`) - .safeIntegers(true) - .get({ customer_id }) - - if (!chat || !chat.telegram_id || chat.id == data.originchatId) - return console.error ('registerUpload: The upload chat is not set for customer: ' + customer_id) - - const peer = chat.is_channel ? - new Api.PeerChannel({ channelId: chat.telegram_id, accessHash: chat.access_hash }) : - new Api.PeerChat({ chatlId: chat.telegram_id, accessHash: chat.access_hash }) - - let resultId = 0 - - try { - const result = await client.invoke(new Api.messages.SendMedia({ - peer, - media: data.media, - message: data.caption || '', - background: true, - silent: true - })) - - const update = result.updates.find(u => - (u.className == 'UpdateNewMessage' || u.className == 'UpdateNewChannelMessage') && - u.message.className == 'Message' && - (u.message.peerId.channelId?.value == chat.telegram_id || u.message.peerId.chatId?.value == chat.telegram_id) && - u.message.media) - - const udoc = update?.message?.media?.document - if (udoc) { - resultId = db - .prepare(` - insert into files (project_id, origin_chat_id, origin_message_id, chat_id, message_id, - file_id, access_hash, filename, mime, caption, size, published_by, published, parent_type, parent_id) - values (:project_id, :origin_chat_id, :origin_message_id, :chat_id, :message_id, - :file_id, :access_hash, :filename, :mime, :caption, :size, :published_by, :published, :parent_type, :parent_id) - returning id - `) - .safeIntegers(true) - .pluck(true) - .get({ - project_id: data.projectId, - origin_chat_id: data.originchatId, - origin_message_id: data.originMessageId, - chat_id: chat.id, - message_id: update.message.id, - file_id: udoc.id.value, - filename: udoc.attributes.find(attr => attr.className == 'DocumentAttributeFilename')?.fileName, - access_hash: udoc.accessHash.value, - mime: udoc.mimeType, - caption: data.caption, - size: udoc.size.value, - published_by: data.publishedBy, - published: data.published, - parent_type: data.parentType, - parent_id: data.parentId - }) - } - - } catch (err) { - //fs.appendFileSync('./1.log', '\n\nERR:' + err.message + ':' + JSON.stringify (err.stack)+'\n\n') - console.error('registerUpload: ' + err.message) - } - - return resultId -} - -async function onNewServiceMessage (msg, isChannel) { - const action = msg.action || {} - const tgChatId = isChannel ? msg.peerId?.channelId?.value : msg.peerId?.chatId?.value - const chatId = await registerChat(tgChatId, isChannel) - - // Сhat rename - if (action.className == 'MessageActionChatEditTitle') { - const info = db - .prepare(` - update chats - set name = :name, is_channel = :is_channel, last_update_time = :last_update_time - where telegram_id = :telegram_id - `) - .safeIntegers(true) - .run({ - name: action.title, - is_channel: +isChannel, - last_update_time: Math.floor (Date.now() / 1000), - telegram_id: tgChatId - }) - - if (info.changes == 0) - console.error('onNewServiceMessage: Can\'t update a chat title: ' + tgChatId) - } - - // Chat to Channel - if (action.className == 'MessageActionChatMigrateTo') { - const info = db - .prepare(` - update chats - set telegram_id = :new_telegram_id, name = :name, is_channel = 1, last_update_time = :last_update_time - where telegram_id = :old_telegram_id - `) - .safeIntegers(true) - .run({ - name: action.title, - last_update_time: Date.now() / 1000, - old_telegram_id: tgChatId, - new_telegram_id: action.channelId.value - }) - - if (info.changes == 0) - console.error('onNewServiceMessage: Can\'t apply a chat migration to channel: ' + tgChatId) - } - - // User/s un/register - if (action.className == 'MessageActionChatAddUser' || action.className == 'MessageActionChatDeleteUser' || - action.className == 'MessageActionChannelAddUser' || action.className == 'MessageActionChannelDeleteUser' - ) { - - const tgUserIds = [action.user, action.users, action.userId].flat().filter(Boolean).map(e => BigInt(e.value)) - const isAdd = action.className == 'MessageActionChatAddUser' || action.className == 'MessageActionChannelAddUser' - - if (tgUserIds.indexOf(BOT_ID) == -1) { - // Add/remove non-bot users - for (const tgUserId of tgUserIds) { - const userId = registerUser(tgUserId) - - if (isAdd) { - try { - const user = await client.getEntity(new Api.PeerUser({ userId: tgUserId })) - updateUser(userId, user) - await updateUserPhoto (userId, user) - } catch (err) { - console.error(msg.className + ', ' + userId + ': ' + err.message) - } - } - - const query = isAdd ? - `insert or ignore into chat_users (chat_id, user_id) values (:chat_id, :user_id)` : - `delete from chat_users where chat_id = :chat_id and user_id = :user_id` - db - .prepare(query) - .run({ chat_id: chatId, user_id: userId }) - } - } - } -} - -async function onNewMessage (msg, isChannel) { - const tgChatId = isChannel ? msg.peerId?.channelId?.value : msg.peerId?.chatId?.value - const chatId = await registerChat(tgChatId, isChannel) - - // Document is detected - if (msg.media?.document) { - const doc = msg.media.document - - const projectId = db - .prepare(`select project_id from chats where telegram_id = :telegram_id`) - .safeIntegers(true) - .pluck(true) - .get({telegram_id: tgChatId}) - - const media = new Api.InputMediaDocument({ - id: new Api.InputDocument({ - id: doc.id.value, - accessHash: doc.accessHash.value, - fileReference: doc.fileReference - }) - }) - - await registerUpload({ - projectId, - media, - caption: msg.message, - originchatId: chatId, - originMessageId: msg.id, - parentType: 0, - publishedBy: registerUser (msg.fromId?.userId?.value), - published: msg.date - }) - } - - if (msg.message?.startsWith(`/start@${BOT_NAME} KEY-`) || msg.message?.startsWith('KEY-')) { - const rows = db - .prepare(` - select 1 from chats where id = :chat_id and project_id is not null - union all - select 1 from customers where upload_chat_id = :chat_id - `) - .all({ chat_id: chatId }) - - if (rows.length) - return await sendMessage(chatId, 'Чат уже используется') - - const rawkey = msg.message.substr(msg.message?.indexOf('KEY-')) - const [_, time64, key] = rawkey.split('-') - const now = Math.floor(Date.now() / 1000) - const time = Buffer.from(time64, 'base64') - - if (now - 3600 >= time && time >= now) - return await sendMessage(chatId, 'Время действия ключа для привязки истекло') - - const row = db - .prepare(` - select (select id from projects where generate_key(id, :time) = :rawkey) project_id, - (select id from customers where generate_key(-id, :time) = :rawkey) customer_id - `) - .get({ rawkey, time }) - - if (row.project_id) { - await attachChat(chatId, row.project_id) - await reloadChatUsers(chatId) - } - - if (row.customer_id) { - const info = db - .prepare(`update customers set upload_chat_id = :chat_id where id = :customer_id`) - .safeIntegers(true) - .run({ customer_id: row.customer_id, chat_id: chatId }) - - if (info.changes == 0) - console.error('Can\'t set upload chat: ' + chatId + ' to customer: ' + row.customer_id) - } - } -} - -async function onNewUserMessage (msg) { - if (msg.message == '/start' && msg.peerId?.className == 'PeerUser') { - const tgUserId = msg.peerId?.userId?.value - const userId = registerUser(tgUserId) - - try { - const user = await client.getEntity(new Api.PeerUser({ userId: tgUserId })) - updateUser(userId, user) - await updateUserPhoto (userId, user) - - const appButton = new Api.KeyboardButtonWebView({ - text: "Open Mini-App", // Текст на кнопке - url: "https://h5sj0gpz-3000.euw.devtunnels.ms/", // URL вашего Mini-App (HTTPS!) - }); - - - const inputPeer = new Api.InputPeerUser({userId: tgUserId, accessHash: user.accessHash.value}) - await client.sendMessage(inputPeer, { - message: 'Сообщение от бота', - buttons: client.buildReplyMarkup([ - [Button.url('Админка', `https://t.me/${BOT_NAME}/userapp?startapp=admin`)], - [Button.url('Пользователь', `https://t.me/${BOT_NAME}/userapp?startapp=user`)], - [appButton] - ]) - }) - } catch (err) { - console.error(msg.className + ', ' + userId + ': ' + err.message) - } - } -} - -async function onUpdatePaticipant (update, isChannel) { - const tgChatId = isChannel ? update.channelId?.value : update.chatlId?.value - if (!tgChatId || update.userId?.value != BOT_ID) - return - - const chatId = await registerChat (tgChatId, isChannel) - - const isBan = update.prevParticipant && !update.newParticipant - const isAdd = (!update.prevParticipant || update.prevParticipant?.className == 'ChannelParticipantBanned') && update.newParticipant - - if (isBan || isAdd) - await reloadChatUsers(chatId, isBan) - - if (isBan) { - db - .prepare(`update chats set project_id = null where id = :chat_id`) - .run({chat_id: chatId}) - } - - const botCanBan = update.newParticipant?.adminRights?.banUsers || 0 - db - .prepare(`update chats set bot_can_ban = :bot_can_ban where id = :chat_id`) - .run({chat_id: chatId, bot_can_ban: +botCanBan}) -} - -async function uploadFile(projectId, fileName, mime, data, parentType, parentId, publishedBy) { - const file = await client.uploadFile({ file: new CustomFile(fileName, data.length, '', data), workers: 1 }) - - const media = new Api.InputMediaUploadedDocument({ - file, - mimeType: mime, - attributes: [new Api.DocumentAttributeFilename({ fileName })] - }) - - return await registerUpload({ - projectId, - media, - parentType, - parentId, - publishedBy, - published: Math.floor(Date.now() / 1000) - }) -} - -async function downloadFile(projectId, fileId) { - const file = db - .prepare(` - select file_id, access_hash, '' thumbSize, filename, mime - from files where id = :id and project_id = :project_id - `) - .safeIntegers(true) - .get({project_id: projectId, id: fileId}) - - if (!file) - return false - - const result = await client.downloadFile(new Api.InputDocumentFileLocation({ - id: file.file_id, - accessHash: file.access_hash, - fileReference: Buffer.from(file.filename), - thumbSize: '' - }, {})) - - return { - filename: file.filename, - mime: file.mime, - size: result.length, - data: result - } -} - -async function sendMessage (chatId, message) { - const chat = db - .prepare(`select telegram_id, is_channel from chats where id = :chat_id`) - .get({ chat_id: chatId}) - - if (!chat) - return - - const entity = chat.is_channel ? { channelId: chat.telegram_id } : { chatId: chat.telegram_id } - const inputPeer = await client.getEntity( chat.is_channel ? - new Api.InputPeerChannel(entity) : - new Api.InputPeerChat(entity) - ) - - await client.sendMessage(inputPeer, {message}) - - const delay = ms => new Promise(resolve => setTimeout(resolve, ms)) - await delay(1000) -} - -async function leaveChat (chatId) { - const chat = db - .prepare(`select telegram_id, access_hash, is_channel from chats where id = :chat_id`) - .get({ chat_id: chatId}) - - if (!chat) - return - - if (chat.is_channel) { - const inputPeer = await client.getEntity(new Api.InputPeerChannel({ channelId: chat.telegram_id, accessHash: chat.access_hash })) - await client.invoke(new Api.channels.LeaveChannel({ channel: inputPeer })) - } else { - await client.invoke(new Api.messages.DeleteChatUser({ chatId: chat.telegram_id, userId: this.id, accessHash: chat.access_hash })) - } -} - -async function start (apiId, apiHash, botAuthToken, sid) { - BOT_ID = BigInt(botAuthToken.split(':')[0]) - - session= new StringSession(sid || '') - client = new TelegramClient(session, apiId, apiHash, {}) - - client.addEventHandler(async (update) => { - if (update.className == 'UpdateConnectionState') - return - - try { - // console.log(update) - - if (update.className == 'UpdateNewMessage' || update.className == 'UpdateNewChannelMessage') { - const msg = update?.message - const isChannel = update.className == 'UpdateNewChannelMessage' - - if (!msg) - return - - const result = msg.peerId?.className == 'PeerUser' ? await onNewUserMessage(msg) : - msg.className == 'MessageService' ? await onNewServiceMessage(msg, isChannel) : - await onNewMessage(msg, isChannel) - } - - if (update.className == 'UpdateChatParticipant' || update.className == 'UpdateChannelParticipant') - await onUpdatePaticipant(update, update.className == 'UpdateChannelParticipant') - } catch (err) { - console.error(err) - } - }) - - await client.start({botAuthToken}) -} - -module.exports = { start, uploadFile, downloadFile, reloadChatUsers, sendMessage } - diff --git a/backend/apps/miniapp.js b/backend/apps/miniapp.js deleted file mode 100644 index 7a72498..0000000 --- a/backend/apps/miniapp.js +++ /dev/null @@ -1,643 +0,0 @@ -const express = require('express') -const multer = require('multer') -const crypto = require('crypto') -const fs = require('fs') -const contentDisposition = require('content-disposition') - -const bot = require('./bot') -const db = require('../include/db') - -const app = express.Router() -const upload = multer({ - storage: multer.memoryStorage(), - limits: { - fileSize: 10_000_000 // 10mb - } -}) - -function hasAccess(project_id, user_id) { - return !!db - .prepare(` - select 1 - from group_users - where user_id = :user_id and - group_id in (select id from groups where project_id = :project_id) and - not exists(select 1 from user_details where user_id = :user_id and project_id = :project_id and is_blocked = 1) and - not exists(select 1 from projects where id = :project_id and is_deleted = 1) - `) - .get({project_id, user_id}) -} - -const sessions = {} - -app.use((req, res, next) => { - if (req.path == '/user/login') - return next() - - const sid = req.query.sid || req.cookies.sid - req.session = sessions[sid] - if (!req.session) - throw Error('ACCESS_DENIED::401') - - res.locals.user_id = req.session.user_id - next() -}) - - -app.post('/user/login', (req, res, next) => { - db - .prepare(`insert or ignore into users (telegram_id) values (:telegram_id)`) - .safeIntegers(true) - .run(res.locals) - - const user_id = db - .prepare(`select id from users where telegram_id = :telegram_id`) - .safeIntegers(true) - .pluck(true) - .get(res.locals) - - const sid = crypto.randomBytes(64).toString('hex') - req.session = sessions[sid] = {sid, user_id} - res.setHeader('Set-Cookie', [`sid=${sid};httpOnly;path=/`]) - res.locals.user_id = user_id - - res.status(200).json({success: true}) -}) - -app.get('/project', (req, res, next) => { - const where = req.query.id ? ' and p.id = ' + parseInt(req.query.id) : '' - - const rows = db - .prepare(` - select p.id, p.name, p.description, p.logo, - c.name customer_name, c.upload_group_id <> 0 has_upload - from projects p - inner join customers c on p.customer_id = c.id - where p.id in ( - select project_id - from groups - where id in (select group_id from group_users where user_id = :user_id) - ) and not exists(select 1 from user_details where user_id = :user_id and project_id = p.id and is_blocked = 1) - ${where} and is_deleted <> 1 - `) - .all(res.locals) - - if (where && rows.length == 0) - throw Error('NOT_FOUND::404') - - res.status(200).json({success: true, data: where ? rows[0] : rows}) -}) - -app.get('/project/:pid(\\d+)', (req, res, next) => { - res.redirect(req.baseUrl + `/project?id=${req.params.pid}`) -}) - -app.use('/project/:pid(\\d+)/*', (req, res, next) => { - res.locals.project_id = parseInt(req.params.pid) - - if (!hasAccess(res.locals.project_id, res.locals.user_id)) - throw Error('ACCESS_DENIED::401') - - const row = db - .prepare('select customer_id from projects where id = :project_id') - .get(res.locals) - - res.locals.customer_id = row.customer_id - - next() -}) - -app.get('/project/:pid(\\d+)/user', (req, res, next) => { - const where = req.query.id ? ' and u.id = ' + parseInt(req.query.id) : '' - - const users = db - .prepare(` - with actuals (user_id) as ( - select distinct user_id - from group_users - where group_id in (select id from groups where project_id = :project_id) - and group_id in (select group_id from group_users where user_id = :user_id) - ), - contributors (user_id) as ( - select created_by from tasks where project_id = :project_id - union - select assigned_to from tasks where project_id = :project_id - union - select created_by from meetings where project_id = :project_id - union - select published_by from documents where project_id = :project_id - ), - members (user_id, is_leave) as ( - select user_id, 0 is_leave from actuals - union all - select user_id, 1 is_leave from contributors where user_id not in (select user_id from actuals) - ) - select u.id, - u.telegram_id, - u.username, - u.firstname, - u.lastname, - u.photo, - u.json_phone_projects, - ud.fullname, - ud.role, - ud.department, - ud.is_blocked, - (select company_id - from company_users - where user_id = u.id and - company_id in (select id from companies where project_id = :project_id)) company_id, - m.is_leave - from users u - inner join members m on u.id = m.user_id - left join user_details ud on ud.user_id = u.id and ud.project_id = :project_id - where 1 = 1 ${where} - `) - .all(res.locals) - - const companies = db - .prepare('select id, name, email, phone, site, description from companies where project_id = :project_id') - .all(res.locals) - .reduce((companies, row) => { - companies[row.id] = row - return companies - }, {}) - - const mappings = {} - const company_id = users.find(m => m.id == res.locals.user_id).company_id - if (company_id) { - res.locals.company_id = company_id - - db - .prepare('select show_as_id, show_to_id from company_mappings where project_id = :project_id and company_id = :company_id') - .all(res.locals) - .forEach(row => mappings[row.show_to_id] = row.show_to_id) - } - - users.forEach(m => { - m.company = companies[mappings[m.company_id] || m.company_id] - delete m.company_id - }) - - users.forEach(m => { - const isHide = JSON.parse(m.json_phone_projects || []).indexOf(res.locals.project_id) == -1 - if (isHide) - delete m.phone - delete m.json_phone_projects - }) - - if (where && users.length == 0) - throw Error('NOT_FOUND::404') - - res.status(200).json({success: true, data: where ? users[0] : users}) -}) - -app.get('/project/:pid(\\d+)/user/reload', async (req, res, next) => { - const groupIds = db - .prepare(`select id from groups where project_id = :project_id`) - .all(res.locals) - .map(e => e.id) - - const sleep = ms => new Promise(resolve => setTimeout(resolve, ms)) - - for (const groupId of groupIds) { - await bot.reloadGroupUsers(groupId) - await sleep(1000) - } - - res.status(200).json({success: true}) -}) - -app.get('/project/:pid(\\d+)/group', (req, res, next) => { - const where = req.query.id ? ' and id = ' + parseInt(req.query.id) : '' - - const rows = db - .prepare(` - select id, name, telegram_id - from groups - where project_id = :project_id and id in (select group_id from group_users where user_id = :user_id) - ${where} - `) - .all(res.locals) - - if (where && rows.length == 0) - throw Error('NOT_FOUND::404') - - res.status(200).json({success: true, data: where ? rows[0] : rows}) -}) - -app.get('/project/:pid(\\d+)/group/:gid(\\d+)', (req, res, next) => { - res.redirect(req.baseUrl + `/project/${req.params.pid}/group?id=${req.params.gid}`) -}) - -// TASK -app.get('/project/:pid(\\d+)/task', (req, res, next) => { - const where = req.query.id ? ' and t.id = ' + parseInt(req.query.id) : '' - - const rows = db - .prepare(` - select id, name, created_by, assigned_to, priority, status, time_spent, create_date, plan_date, close_date, - (select json_group_array(user_id) from task_users where task_id = t.id) observers, - (select json_group_array(id) from documents where parent_type = 1 and parent_id = t.id) attachments - from tasks t - where project_id = :project_id and - (created_by = :user_id or assigned_to = :user_id or exists(select 1 from task_users where task_id = t.id and user_id = :user_id)) - ${where} - `) - .all(res.locals) - - rows.forEach(row => { - row.observers = JSON.parse(row.observers) - row.attachments = JSON.parse(row.attachments) - }) - - if (where && rows.length == 0) - throw Error('NOT_FOUND::404') - - res.status(200).json({success: true, data: where ? rows[0] : rows}) -}) - -app.get('/project/:pid(\\d+)/task/:tid(\\d+)', (req, res, next) => { - res.redirect(req.baseUrl + `/project/${req.params.pid}/task?id=${req.params.tid}`) -}) - -app.post('/project/:pid(\\d+)/task', (req, res, next) => { - res.locals.name = req.body?.name - res.locals.status = parseInt(req.body?.status) - res.locals.priority = parseInt(req.body?.priority) - res.locals.assigned_to = req.body?.assigned_to ? parseInt(req.body?.assigned_to) : undefined - res.locals.create_date = Math.floor(Date.now() / 1000) - res.locals.plan_date = req.body?.plan_date ? parseInt(req.body?.plan_date) : undefined - - if (res.locals.assigned_to && !hasAccess(res.locals.project_id, res.locals.assigned_to)) - throw Error('INCORRECT_ASSIGNED_TO::400') - - const id = db - .prepare(` - insert into tasks (project_id, name, created_by, assigned_to, priority, status, create_date, plan_date) - values (:project_id, :name, :user_id, :assigned_to, :priority, :status, :create_date, :plan_date) - returning id - `) - .pluck(true) - .get(res.locals) - - res.status(200).json({success: true, data: id}) -}) - -app.use('/project/:pid(\\d+)/task/:tid(\\d+)*', (req, res, next) => { - res.locals.task_id = req.params.tid - - const task = db - .prepare(` - select created_by, assigned_to - from tasks - where id = :task_id and project_id = :project_id - and (created_by = :user_id or assigned_to = :user_id or exists(select 1 from task_users where task_id = :task_id and user_id = :user_id)) - `) - .get(res.locals) - - if (!task) - throw Error('NOT_FOUND::404') - - res.locals.is_author = task.created_by == res.locals.user_id - res.locals.is_assigned = task.assigned_to == res.locals.user_id - - next() -}) - -app.put('/project/:pid(\\d+)/task/:tid(\\d+)', (req, res, next) => { - if (!res.locals.is_author && !res.locals.is_assigned) - throw Error('ACCESS_DENIED::401') - - res.locals.id = res.locals.task_id - res.locals.name = req.body?.name - res.locals.status = parseInt(req.body?.status) - res.locals.priority = parseInt(req.body?.priority) - res.locals.assigned_to = req.body?.assigned_to ? parseInt(req.body?.assigned_to) : undefined - res.locals.plan_date = req.body?.plan_date ? parseInt(req.body?.plan_date) : undefined - - const columns = res.locals.is_author ? ['name', 'assigned_to', 'priority', 'status', 'plan_date', 'time_spent'] : ['status', 'time_spent'] - const info = db - .prepareUpdate('tasks', columns, res.locals, ['id', 'project_id']) - .run(res.locals) - - if (info.changes == 0) - throw Error('NOT_FOUND::404') - - res.status(200).json({success: true}) -}) - -app.delete('/project/:pid(\\d+)/task/:tid(\\d+)', (req, res, next) => { - if (!res.locals.is_author) - throw Error('ACCESS_DENIED::401') - - const info = db - .prepare(`delete from tasks where id = :task_id and project_id = :project_id and created_by = :user_id`) - .run(res.locals) - - if (info.changes == 0) - throw Error('NOT_FOUND::404') - - res.status(200).json({success: true}) -}) - -app.put('/project/:pid(\\d+)/task/:tid(\\d+)/observer', (req, res, next) => { - if (!res.locals.is_author && !res.locals.is_assigned) - throw Error('ACCESS_DENIED::401') - - const user_ids = req.body instanceof Array ? [...new Set(req.body.map(e => parseInt(e)))] : [] - - // Проверка, что выбранные пользователи имеют доступ к проекту - let rows = db - .prepare(` - select user_id - from group_users - where group_id in (select id from groups where project_id = :project_id) - `) - .pluck(true) - .all(res.locals) - - if (user_ids.some(user_id => !rows.contains(user_id))) - throw Error('INACCESSABLE_USER::400') - - res.locals.json_ids = JSON.stringify(user_ids) - - db - .prepare(` - delete from task_users where task_id = :task_id; - insert into task_users (task_id, user_id) select :task_id, value from json_each(:json_ids) - `) - .run(res.locals) - - res.status(200).json({success: true}) -}) - -// MEETINGS -app.get('/project/:pid(\\d+)/meeting', (req, res, next) => { - const where = req.query.id ? ' and m.id = ' + parseInt(req.query.id) : '' - - const rows = db - .prepare(` - select id, name, description, created_by, meet_date, - (select json_group_array(user_id) from meeting_users where meeting_id = m.id) participants, - (select json_group_array(id) from documents where parent_type = 2 and parent_id = m.id) attachments - from meetings m - where project_id = :project_id and - (created_by = :user_id or exists(select 1 from meeting_users where meeting_id = m.id and user_id = :user_id)) - ${where} - `) - .all(res.locals) - - rows.forEach(row => { - row.participants = JSON.parse(row.participants) - row.attachments = JSON.parse(row.attachments) - }) - - if (where && rows.length == 0) - throw Error('NOT_FOUND::404') - - res.status(200).json({success: true, data: where ? rows[0] : rows}) -}) - -app.get('/project/:pid(\\d+)/meeting/:mid(\\d+)', (req, res, next) => { - res.redirect(req.baseUrl + `/project/${req.params.pid}/meeting?id=${req.params.mid}`) -}) - -app.post('/project/:pid(\\d+)/meeting', (req, res, next) => { - res.locals.name = req.body?.name - res.locals.description = req.body?.description - res.locals.meet_date = req.body?.meet_date ? parseInt(req.body?.meet_date) : undefined - - const id = db - .prepare(` - insert into meetings (project_id, name, description, created_by, meet_date) - values (:project_id, :name, :description, :user_id, :meet_date) - returning id - `) - .pluck(true) - .get(res.locals) - - res.status(200).json({success: true, data: id}) -}) - -app.use('/project/:pid(\\d+)/meeting/:mid(\\d+)*', (req, res, next) => { - res.locals.meeting_id = req.params.mid - - const meeting = db - .prepare(` - select created_by - from meetings - where id = :meeting_id and project_id = :project_id - and (created_by = :user_id or exists(select 1 from meeting_users where meeting_id = :meeting_id and user_id = :user_id)) - `) - .get(res.locals) - - if (!meeting) - throw Error('NOT_FOUND::404') - - res.locals.is_author = meeting.created_by == res.locals.user_id - - next() -}) - -app.put('/project/:pid(\\d+)/meeting/:mid(\\d+)', (req, res, next) => { - if (!res.locals.is_author) - throw Error('ACCESS_DENIED::401') - - res.locals.id = res.locals.meeting_id - res.locals.name = req.body?.name - res.locals.description = req.body?.description - res.locals.meet_date = req.body?.meet_date ? parseInt(req.body?.meet_date) : undefined - - const info = db - .prepareUpdate('meetings', ['name', 'description', 'meet_date'], res.locals, ['id', 'project_id']) - .run(res.locals) - - if (info.changes == 0) - throw Error('NOT_FOUND::404') - - res.status(200).json({success: true}) -}) - -app.delete('/project/:pid(\\d+)/meeting/:mid(\\d+)', (req, res, next) => { - if (!res.locals.is_author) - throw Error('ACCESS_DENIED::401') - - const info = db - .prepare(`delete from meetings where id = :meeting_id and project_id = :project_id and created_by = :user_id`) - .run(res.locals) - - if (info.changes == 0) - throw Error('NOT_FOUND::404') - - res.status(200).json({success: true}) -}) - -app.put('/project/:pid(\\d+)/meeting/:mid(\\d+)/participants', (req, res, next) => { - if (!res.locals.is_author) - throw Error('ACCESS_DENIED::401') - - const user_ids = req.body instanceof Array ? [...new Set(req.body.map(e => parseInt(e)))] : [] - - // Проверка, что выбранные пользователи имеют доступ к проекту - let rows = db - .prepare(` - select user_id - from group_users - where group_id in (select id from groups where project_id = :project_id) - `) - .pluck(true) // .raw? - .all(res.locals) - - if (user_ids.some(user_id => rows.indexOf(user_id)) == -1) - throw Error('INACCESSABLE_USER::400') - - db - .prepare(`delete from meeting_users where meeting_id = :meeting_id`) - .run(res.locals) - - res.locals.json_ids = JSON.stringify(user_ids) - db - .prepare(`insert into meeting_users (meeting_id, user_id) select :meeting_id, value from json_each(:json_ids)`) - .run(res.locals) - - res.status(200).json({success: true}) -}) - -// DOCUMENTS -app.get('/project/:pid(\\d+)/document', (req, res, next) => { - const ids = String(req.query.id).split(',').map(e => parseInt(e)).filter(e => e > 0) - const where = ids.length > 0 ? ' and id in (' + ids.join(', ') + ')' : '' - - // Документы - // 1. Из групп, которые есть в проекте и в которых участвует пользователь - // 2. Из задач проекта, где пользователь автор, ответсвенный или наблюдатель - // 3. Из встреч на проекте, где пользователь создатель или участник - // To-Do: отдавать готовую ссылку --> как минимум GROUP_ID надо заменить на tgGroupId - const rows = db - .prepare(` - select id, origin_group_id, origin_message_id, filename, mime, caption, size, published_by, parent_id, parent_type - from documents d - where project_id = :project_id ${where} and ( - origin_group_id in (select group_id from group_users where user_id = :user_id) - or - parent_type = 1 and parent_id in ( - select id - from tasks t - where project_id = :project_id and (created_by = :user_id or assigned_to = :user_id or exists(select 1 from task_users where task_id = t.id and user_id = :user_id)) - ) - or - parent_type = 2 and parent_id in ( - select id - from meetings m - where project_id = :project_id and (created_by = :user_id or exists(select 1 from meeting_users where meeting_id = m.id and user_id = :user_id)) - ) - ) - `) - .all(res.locals) - - if (where && rows.length == 0) - throw Error('NOT_FOUND::404') - - res.status(200).json({success: true, data: ids.length == 1 ? rows[0] : rows}) -}) - -app.post('/project/:pid(\\d+)/:type(task|meeting)/:id(\\d+)/attach', upload.any(), async (req, res, next) => { - const parentType = req.params.type == 'task' ? 1 : 2 - const parentId = req.params.id - - const ids = [] - for (const file of req.files) { - const id = await bot.uploadDocument(res.locals.project_id, file.originalname, file.mimetype, file.buffer, parentType, parentId, res.locals.user_id) - ids.push(id) - } - - res.redirect(req.baseUrl + `/project/${req.params.pid}/document?id=` + ids.join(',')) -}) - -app.use('/project/:pid(\\d+)/document/:did(\\d+)', (req, res, next) => { - res.locals.document_id = req.params.did - - const doc = db - .prepare(`select * from documents where id = :document_id and project_id = :project_id`) - .get(res.locals) - - if (!doc) - throw Error('NOT_FOUND::404') - - if (doc.parent_type == 0) { - res.locals.group_id = doc.group_id - const row = db - .prepare(`select 1 from group_users where group_id = :group_id and user_id = :user_id`) - .get(res.locals) - - if (row) { - res.locals.can_download = true - } - } else { - res.locals.parent_id = doc.parent_id - const parent = doc.parent_type == 1 ? 'task' : 'meeting' - - const row = db - .prepare(` - select 1 - from ${parent}s - where id = :parent_id and project_id = :project_id or - exists(select 1 from ${parent}_users where ${parent}_id = :parent_id and user_id = :user_id) - `) - .get(res.locals) - - if (row) { - res.locals.can_download = true - res.locals.can_delete = doc.published_by == res.locals.user_id - } - } - - next() -}) - -app.get('/project/:pid(\\d+)/document/:did(\\d+)', async (req, res, next) => { - if (!res.locals.can_download) - throw Error('NOT_FOUND::404') - - const file = await bot.downloadDocument(res.locals.project_id, res.locals.document_id) - res.writeHead(200, { - 'Content-Length': file.size, - 'Content-Type': file.mime, - 'Content-Disposition': contentDisposition(file.filename) - }) - - res.end(file.data) -}) - -app.delete('/project/:pid(\\d+)/document/:id(\\d+)', (req, res, next) => { - if (!res.locals.can_delete) - throw Error('NOT_FOUND::404') - - const doc = db - .prepare(`delete from documents where id = :id and project_id = :project_id`) - .run(res.locals) - - res.status(200).json({success: true}) -}) - -app.get('/settings', (req, res, next) => { - const row = db - .prepare(`select coalesce(json_settings, '{}') from users where id = :user_id`) - .pluck(true) - .get(res.locals) - - res.status(200).json({success: true, data: JSON.parse(row)}) -}) - -app.put('/settings', (req, res, next) => { - res.locals.json_settings = JSON.stringify(req.body || {}) - - const row = db - .prepare(`update users set json_settings = :json_settings where id = :user_id`) - .run(res.locals) - - res.status(200).json({success: true}) -}) - -module.exports = app \ No newline at end of file diff --git a/backend/data/db.sqlite b/backend/data/db.sqlite deleted file mode 100644 index 438da8f..0000000 Binary files a/backend/data/db.sqlite and /dev/null differ diff --git a/backend/data/db.sqlite-shm b/backend/data/db.sqlite-shm deleted file mode 100644 index 3d2446d..0000000 Binary files a/backend/data/db.sqlite-shm and /dev/null differ diff --git a/backend/data/db.sqlite-wal b/backend/data/db.sqlite-wal deleted file mode 100644 index 1d97cbf..0000000 Binary files a/backend/data/db.sqlite-wal and /dev/null differ diff --git a/backend/data/init.sql b/backend/data/init.sql deleted file mode 100644 index 2fa537c..0000000 --- a/backend/data/init.sql +++ /dev/null @@ -1,171 +0,0 @@ -pragma foreign_keys = off; - -create table if not exists customers ( - id integer primary key autoincrement, - name text check(name is null or trim(name) <> '' and length(name) < 256), - email text check(email is null or trim(email) <> '' and length(email) < 128), - password text check(password is null or length(password) > 7 and length(password) < 64), - telegram_id integer, - plan integer, - json_balance text default '{}', - is_blocked integer default 0, - json_company text default '{}', - upload_chat_id integer, - json_backup_server text default '{}', - json_backup_params text default '{}', - json_settings text default '{}' -) strict; - -create table if not exists projects ( - id integer primary key autoincrement, - customer_id integer references customers(id) on delete cascade, - name text not null check(trim(name) <> '' and length(name) < 256), - description text check(description is null or length(description) < 4096), - logo text, - is_logo_bg integer default 0, - is_archived integer default 0 -) strict; - -create table if not exists chats ( - id integer primary key autoincrement, - project_id integer references projects(id) on delete cascade, - name text, - telegram_id integer, - description text, - logo text, - access_hash integer, - is_channel integer check(is_channel in (0, 1)) default 0, - bot_can_ban integer default 0, - owner_id integer references users(id) on delete set null, - user_count integer, - last_update_time integer -); -create unique index if not exists idx_chats_telegram_id on chats (telegram_id); - -create table if not exists users ( - id integer primary key autoincrement, - telegram_id integer, - access_hash integer, - firstname text check(firstname is null or length(firstname) < 256), - lastname text check(lastname is null or length(lastname) < 256), - username text check(username is null or length(username) < 256), - photo_id integer, - photo text, - language_code text, - json_settings text default '{}' -) strict; -create unique index if not exists idx_users_telegram_id on users (telegram_id); - -create table if not exists user_details ( - user_id integer references users(id) on delete cascade, - project_id integer references projects(id) on delete cascade, - fullname text check(fullname is null or length(fullname) < 256), - email text check(email is null or length(email) < 256), - phone text check(phone is null or length(phone) < 256), - role text check(role is null or length(role) < 256), - department text check(department is null or length(department) < 256), - is_blocked integer check(is_blocked in (0, 1)) default 0, - primary key (user_id, project_id) -) strict; - -create table if not exists tasks ( - id integer primary key autoincrement, - project_id integer references projects(id) on delete cascade, - name text not null check(trim(name) <> '' and length(name) < 4096), - created_by integer references users(id) on delete set null, - assigned_to integer references users(id) on delete set null, - closed_by integer references users(id) on delete set null, - priority integer check(priority in (0, 1, 2, 3, 4, 5)) default 0, - status integer check(status >= 1 and status <= 10) default 1, - time_spent integer check(time_spent is null or time_spent > 0 and time_spent < 44640), -- one month - create_date integer, - plan_date integer, - close_date integer -) strict; - -create table if not exists meetings ( - id integer primary key autoincrement, - project_id integer references projects(id) on delete cascade, - name text not null check(trim(name) <> '' and length(name) < 4096), - description text check(description is null or length(description) < 4096), - created_by integer references users(id) on delete set null, - meet_date integer -) strict; - -create table if not exists files ( - id integer primary key autoincrement, - project_id integer references projects(id) on delete cascade, - origin_chat_id integer references chats(id) on delete set null, - origin_message_id integer, - chat_id integer references chats(id) on delete set null, - message_id integer, - file_id integer, - access_hash integer, - filename text not null check(length(filename) < 256), - mime text check(mime is null or length(mime) < 128), - caption text check(caption is null or length(caption) < 4096), - size integer, - published_by integer references users(id) on delete set null, - published integer, - parent_type integer check(parent_type in (0, 1, 2)) default 0, - parent_id integer, - backup_state integer default 0 -) strict; - -create table if not exists companies ( - id integer primary key autoincrement, - project_id integer references projects(id) on delete cascade, - name text not null check(length(name) < 4096), - address text check(address is null or length(address) < 512), - email text check(email is null or length(email) < 128), - phone text check(phone is null or length(phone) < 128), - site text check(site is null or length(site) < 128), - description text check(description is null or length(description) < 4096), - logo text -) strict; - -create table if not exists company_mappings ( - project_id integer references projects(id) on delete cascade, - company_id integer references companies(id) on delete cascade, - show_as_id integer references companies(id) on delete cascade, - show_to_id integer references companies(id) on delete cascade -) strict; - -create table if not exists task_users ( - task_id integer references tasks(id) on delete cascade, - user_id integer references users(id) on delete cascade, - primary key (task_id, user_id) -) without rowid; - -create table if not exists meeting_users ( - meeting_id integer references meetings(id) on delete cascade, - user_id integer references users(id) on delete cascade, - primary key (meeting_id, user_id) -) without rowid; - -create table if not exists company_users ( - company_id integer references companies(id) on delete cascade, - user_id integer references users(id) on delete cascade, - primary key (company_id, user_id) -) without rowid; - -create table if not exists chat_users ( - chat_id integer references chats(id) on delete cascade, - user_id integer references users(id) on delete cascade, - primary key (chat_id, user_id) -) without rowid; - -pragma foreign_keys = on; - - -create trigger if not exists trg_chats_update after update on chats -when NEW.project_id is null -begin - delete from chat_users where chat_id = NEW.id; -end; - - - - - - diff --git a/backend/include/db.js b/backend/include/db.js deleted file mode 100644 index 7d65f17..0000000 --- a/backend/include/db.js +++ /dev/null @@ -1,57 +0,0 @@ -const fs = require('fs') -const crypto = require('crypto') -const sqlite3 = require('better-sqlite3') - -const db = sqlite3(`./data/db.sqlite`) -db.pragma('journal_mode = WAL') - -db.exec(fs.readFileSync('./data/init.sql', 'utf8')) - -/* -db.exec(`attach database './data/backup.sqlite' as backup`) - -db.function('backup', (tblname, ...values) => { - db - .prepare(`insert into backup.${tblname} select ` + values.map(e => '?').join(', ')) - .run(values) -}) - -const backupQuery = db - .prepare(` - select group_concat(tbl || char(13) || trg, char(13) || char(13)) from ( - select 'create table if not exists backup.' || t.name || ' (' || group_concat(c.name || ' ' || c.type, ', ') || ', time integer);' tbl, - 'create trigger if not exists trg_' || t.name || '_delete after delete on ' || t.name || ' for each row begin ' || - ' select backup (' || t.name || ',' || group_concat('OLD.' || c.name, ', ') || ', strftime(''%s'', ''now'')); end;' trg - from sqlite_master t left join pragma_table_xinfo c on t.tbl_name = c.arg and c.schema = 'main' - where t.sql is not null and t.type = 'table' and t.name <> 'sqlite_sequence' - group by t.type, t.name order by t.type, t.name) - `) - .pluck(true) - .get() -db.exec(backupQuery) -*/ - -db.function('generate_key', (id, time) => { - return [ - 'KEY', - Buffer.from(time + '').toString('base64'), - crypto.createHash('md5').update(`sa${time}-${id}lt`).digest('hex') - ].join('-') -}) - -process.on('exit', () => db.close()) -process.on('SIGHUP', () => process.exit(128 + 1)) -process.on('SIGINT', () => process.exit(128 + 2)) -process.on('SIGTERM', () => process.exit(128 + 15)) - -db.prepareUpdate = function (table, columns, data, where) { - const dataColumns = columns.filter(col => col in data) - if (dataColumns.length == 0) - throw Error('SQLite Error: No data to update') - - return db.prepare(`update "${table}" ` + - `set ` + dataColumns.map(col => `"${col}" = :${col}`).join(', ') + - ` where ` + where.map(col => `"${col}" = :${col}`).join(' and ')) -} - -module.exports = db \ No newline at end of file diff --git a/backend/package-lock.json b/backend/package-lock.json deleted file mode 100644 index d1bef8c..0000000 --- a/backend/package-lock.json +++ /dev/null @@ -1,1990 +0,0 @@ -{ - "name": "telegram-bot", - "version": "1.0.0", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "name": "telegram-bot", - "version": "1.0.0", - "license": "ISC", - "dependencies": { - "better-sqlite3": "^11.8.0", - "body-parser": "^1.20.3", - "content-disposition": "^0.5.4", - "cookie-parser": "^1.4.7", - "express": "^4.21.2", - "express-session": "^1.18.1", - "multer": "^1.4.5-lts.1", - "nodemailer": "^6.9.16", - "telegram": "^2.26.16" - } - }, - "node_modules/@cryptography/aes": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/@cryptography/aes/-/aes-0.1.1.tgz", - "integrity": "sha512-PcYz4FDGblO6tM2kSC+VzhhK62vml6k6/YAkiWtyPvrgJVfnDRoHGDtKn5UiaRRUrvUTTocBpvc2rRgTCqxjsg==", - "license": "GPL-3.0-or-later" - }, - "node_modules/accepts": { - "version": "1.3.8", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", - "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", - "license": "MIT", - "dependencies": { - "mime-types": "~2.1.34", - "negotiator": "0.6.3" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/append-field": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/append-field/-/append-field-1.0.0.tgz", - "integrity": "sha512-klpgFSWLW1ZEs8svjfb7g4qWY0YS5imI82dTg+QahUvJ8YqAY0P10Uk8tTyh9ZGuYEZEMaeJYCF5BFuX552hsw==", - "license": "MIT" - }, - "node_modules/array-flatten": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", - "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", - "license": "MIT" - }, - "node_modules/async-mutex": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/async-mutex/-/async-mutex-0.3.2.tgz", - "integrity": "sha512-HuTK7E7MT7jZEh1P9GtRW9+aTWiDWWi9InbZ5hjxrnRa39KS4BW04+xLBhYNS2aXhHUIKZSw3gj4Pn1pj+qGAA==", - "license": "MIT", - "dependencies": { - "tslib": "^2.3.1" - } - }, - "node_modules/base64-js": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", - "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT" - }, - "node_modules/better-sqlite3": { - "version": "11.9.1", - "resolved": "https://registry.npmjs.org/better-sqlite3/-/better-sqlite3-11.9.1.tgz", - "integrity": "sha512-Ba0KR+Fzxh2jDRhdg6TSH0SJGzb8C0aBY4hR8w8madIdIzzC6Y1+kx5qR6eS1Z+Gy20h6ZU28aeyg0z1VIrShQ==", - "hasInstallScript": true, - "license": "MIT", - "dependencies": { - "bindings": "^1.5.0", - "prebuild-install": "^7.1.1" - } - }, - "node_modules/big-integer": { - "version": "1.6.52", - "resolved": "https://registry.npmjs.org/big-integer/-/big-integer-1.6.52.tgz", - "integrity": "sha512-QxD8cf2eVqJOOz63z6JIN9BzvVs/dlySa5HGSBH5xtR8dPteIRQnBxxKqkNTiT6jbDTF6jAfrd4oMcND9RGbQg==", - "license": "Unlicense", - "engines": { - "node": ">=0.6" - } - }, - "node_modules/bindings": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz", - "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==", - "license": "MIT", - "dependencies": { - "file-uri-to-path": "1.0.0" - } - }, - "node_modules/bl": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", - "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", - "license": "MIT", - "dependencies": { - "buffer": "^5.5.0", - "inherits": "^2.0.4", - "readable-stream": "^3.4.0" - } - }, - "node_modules/bl/node_modules/readable-stream": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", - "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", - "license": "MIT", - "dependencies": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/body-parser": { - "version": "1.20.3", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz", - "integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==", - "license": "MIT", - "dependencies": { - "bytes": "3.1.2", - "content-type": "~1.0.5", - "debug": "2.6.9", - "depd": "2.0.0", - "destroy": "1.2.0", - "http-errors": "2.0.0", - "iconv-lite": "0.4.24", - "on-finished": "2.4.1", - "qs": "6.13.0", - "raw-body": "2.5.2", - "type-is": "~1.6.18", - "unpipe": "1.0.0" - }, - "engines": { - "node": ">= 0.8", - "npm": "1.2.8000 || >= 1.4.16" - } - }, - "node_modules/buffer": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", - "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT", - "dependencies": { - "base64-js": "^1.3.1", - "ieee754": "^1.1.13" - } - }, - "node_modules/buffer-from": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", - "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", - "license": "MIT" - }, - "node_modules/bufferutil": { - "version": "4.0.9", - "resolved": "https://registry.npmjs.org/bufferutil/-/bufferutil-4.0.9.tgz", - "integrity": "sha512-WDtdLmJvAuNNPzByAYpRo2rF1Mmradw6gvWsQKf63476DDXmomT9zUiGypLcG4ibIM67vhAj8jJRdbmEws2Aqw==", - "hasInstallScript": true, - "license": "MIT", - "dependencies": { - "node-gyp-build": "^4.3.0" - }, - "engines": { - "node": ">=6.14.2" - } - }, - "node_modules/busboy": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz", - "integrity": "sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==", - "dependencies": { - "streamsearch": "^1.1.0" - }, - "engines": { - "node": ">=10.16.0" - } - }, - "node_modules/bytes": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", - "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/call-bind-apply-helpers": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", - "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "function-bind": "^1.1.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/call-bound": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", - "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", - "license": "MIT", - "dependencies": { - "call-bind-apply-helpers": "^1.0.2", - "get-intrinsic": "^1.3.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/chownr": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", - "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==", - "license": "ISC" - }, - "node_modules/concat-stream": { - "version": "1.6.2", - "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", - "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", - "engines": [ - "node >= 0.8" - ], - "license": "MIT", - "dependencies": { - "buffer-from": "^1.0.0", - "inherits": "^2.0.3", - "readable-stream": "^2.2.2", - "typedarray": "^0.0.6" - } - }, - "node_modules/content-disposition": { - "version": "0.5.4", - "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", - "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", - "license": "MIT", - "dependencies": { - "safe-buffer": "5.2.1" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/content-type": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", - "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/cookie": { - "version": "0.7.2", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", - "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/cookie-parser": { - "version": "1.4.7", - "resolved": "https://registry.npmjs.org/cookie-parser/-/cookie-parser-1.4.7.tgz", - "integrity": "sha512-nGUvgXnotP3BsjiLX2ypbQnWoGUPIIfHQNZkkC668ntrzGWEZVW70HDEB1qnNGMicPje6EttlIgzo51YSwNQGw==", - "license": "MIT", - "dependencies": { - "cookie": "0.7.2", - "cookie-signature": "1.0.6" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/cookie-signature": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", - "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==", - "license": "MIT" - }, - "node_modules/core-util-is": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", - "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", - "license": "MIT" - }, - "node_modules/d": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/d/-/d-1.0.2.tgz", - "integrity": "sha512-MOqHvMWF9/9MX6nza0KgvFH4HpMU0EF5uUDXqX/BtxtU8NfB0QzRtJ8Oe/6SuS4kbhyzVJwjd97EA4PKrzJ8bw==", - "license": "ISC", - "dependencies": { - "es5-ext": "^0.10.64", - "type": "^2.7.2" - }, - "engines": { - "node": ">=0.12" - } - }, - "node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "license": "MIT", - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/decompress-response": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", - "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", - "license": "MIT", - "dependencies": { - "mimic-response": "^3.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/deep-extend": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", - "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", - "license": "MIT", - "engines": { - "node": ">=4.0.0" - } - }, - "node_modules/depd": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", - "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/destroy": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", - "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", - "license": "MIT", - "engines": { - "node": ">= 0.8", - "npm": "1.2.8000 || >= 1.4.16" - } - }, - "node_modules/detect-libc": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.3.tgz", - "integrity": "sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw==", - "license": "Apache-2.0", - "engines": { - "node": ">=8" - } - }, - "node_modules/dom-serializer": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-1.4.1.tgz", - "integrity": "sha512-VHwB3KfrcOOkelEG2ZOfxqLZdfkil8PtJi4P8N2MMXucZq2yLp75ClViUlOVwyoHEDjYU433Aq+5zWP61+RGag==", - "license": "MIT", - "dependencies": { - "domelementtype": "^2.0.1", - "domhandler": "^4.2.0", - "entities": "^2.0.0" - }, - "funding": { - "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" - } - }, - "node_modules/domelementtype": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", - "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/fb55" - } - ], - "license": "BSD-2-Clause" - }, - "node_modules/domhandler": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-4.3.1.tgz", - "integrity": "sha512-GrwoxYN+uWlzO8uhUXRl0P+kHE4GtVPfYzVLcUxPL7KNdHKj66vvlhiweIHqYYXWlw+T8iLMp42Lm67ghw4WMQ==", - "license": "BSD-2-Clause", - "dependencies": { - "domelementtype": "^2.2.0" - }, - "engines": { - "node": ">= 4" - }, - "funding": { - "url": "https://github.com/fb55/domhandler?sponsor=1" - } - }, - "node_modules/domutils": { - "version": "2.8.0", - "resolved": "https://registry.npmjs.org/domutils/-/domutils-2.8.0.tgz", - "integrity": "sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A==", - "license": "BSD-2-Clause", - "dependencies": { - "dom-serializer": "^1.0.1", - "domelementtype": "^2.2.0", - "domhandler": "^4.2.0" - }, - "funding": { - "url": "https://github.com/fb55/domutils?sponsor=1" - } - }, - "node_modules/dunder-proto": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", - "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", - "license": "MIT", - "dependencies": { - "call-bind-apply-helpers": "^1.0.1", - "es-errors": "^1.3.0", - "gopd": "^1.2.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/ee-first": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", - "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", - "license": "MIT" - }, - "node_modules/encodeurl": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", - "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/end-of-stream": { - "version": "1.4.4", - "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", - "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", - "license": "MIT", - "dependencies": { - "once": "^1.4.0" - } - }, - "node_modules/entities": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/entities/-/entities-2.2.0.tgz", - "integrity": "sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==", - "license": "BSD-2-Clause", - "funding": { - "url": "https://github.com/fb55/entities?sponsor=1" - } - }, - "node_modules/es-define-property": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", - "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-errors": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", - "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-object-atoms": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", - "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es5-ext": { - "version": "0.10.64", - "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.64.tgz", - "integrity": "sha512-p2snDhiLaXe6dahss1LddxqEm+SkuDvV8dnIQG0MWjyHpcMNfXKPE+/Cc0y+PhxJX3A4xGNeFCj5oc0BUh6deg==", - "hasInstallScript": true, - "license": "ISC", - "dependencies": { - "es6-iterator": "^2.0.3", - "es6-symbol": "^3.1.3", - "esniff": "^2.0.1", - "next-tick": "^1.1.0" - }, - "engines": { - "node": ">=0.10" - } - }, - "node_modules/es6-iterator": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/es6-iterator/-/es6-iterator-2.0.3.tgz", - "integrity": "sha512-zw4SRzoUkd+cl+ZoE15A9o1oQd920Bb0iOJMQkQhl3jNc03YqVjAhG7scf9C5KWRU/R13Orf588uCC6525o02g==", - "license": "MIT", - "dependencies": { - "d": "1", - "es5-ext": "^0.10.35", - "es6-symbol": "^3.1.1" - } - }, - "node_modules/es6-symbol": { - "version": "3.1.4", - "resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.4.tgz", - "integrity": "sha512-U9bFFjX8tFiATgtkJ1zg25+KviIXpgRvRHS8sau3GfhVzThRQrOeksPeT0BWW2MNZs1OEWJ1DPXOQMn0KKRkvg==", - "license": "ISC", - "dependencies": { - "d": "^1.0.2", - "ext": "^1.7.0" - }, - "engines": { - "node": ">=0.12" - } - }, - "node_modules/escape-html": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", - "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", - "license": "MIT" - }, - "node_modules/esniff": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/esniff/-/esniff-2.0.1.tgz", - "integrity": "sha512-kTUIGKQ/mDPFoJ0oVfcmyJn4iBDRptjNVIzwIFR7tqWXdVI9xfA2RMwY/gbSpJG3lkdWNEjLap/NqVHZiJsdfg==", - "license": "ISC", - "dependencies": { - "d": "^1.0.1", - "es5-ext": "^0.10.62", - "event-emitter": "^0.3.5", - "type": "^2.7.2" - }, - "engines": { - "node": ">=0.10" - } - }, - "node_modules/etag": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", - "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/event-emitter": { - "version": "0.3.5", - "resolved": "https://registry.npmjs.org/event-emitter/-/event-emitter-0.3.5.tgz", - "integrity": "sha512-D9rRn9y7kLPnJ+hMq7S/nhvoKwwvVJahBi2BPmx3bvbsEdK3W9ii8cBSGjP+72/LnM4n6fo3+dkCX5FeTQruXA==", - "license": "MIT", - "dependencies": { - "d": "1", - "es5-ext": "~0.10.14" - } - }, - "node_modules/expand-template": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz", - "integrity": "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==", - "license": "(MIT OR WTFPL)", - "engines": { - "node": ">=6" - } - }, - "node_modules/express": { - "version": "4.21.2", - "resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz", - "integrity": "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==", - "license": "MIT", - "dependencies": { - "accepts": "~1.3.8", - "array-flatten": "1.1.1", - "body-parser": "1.20.3", - "content-disposition": "0.5.4", - "content-type": "~1.0.4", - "cookie": "0.7.1", - "cookie-signature": "1.0.6", - "debug": "2.6.9", - "depd": "2.0.0", - "encodeurl": "~2.0.0", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "finalhandler": "1.3.1", - "fresh": "0.5.2", - "http-errors": "2.0.0", - "merge-descriptors": "1.0.3", - "methods": "~1.1.2", - "on-finished": "2.4.1", - "parseurl": "~1.3.3", - "path-to-regexp": "0.1.12", - "proxy-addr": "~2.0.7", - "qs": "6.13.0", - "range-parser": "~1.2.1", - "safe-buffer": "5.2.1", - "send": "0.19.0", - "serve-static": "1.16.2", - "setprototypeof": "1.2.0", - "statuses": "2.0.1", - "type-is": "~1.6.18", - "utils-merge": "1.0.1", - "vary": "~1.1.2" - }, - "engines": { - "node": ">= 0.10.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/express-session": { - "version": "1.18.1", - "resolved": "https://registry.npmjs.org/express-session/-/express-session-1.18.1.tgz", - "integrity": "sha512-a5mtTqEaZvBCL9A9aqkrtfz+3SMDhOVUnjafjo+s7A9Txkq+SVX2DLvSp1Zrv4uCXa3lMSK3viWnh9Gg07PBUA==", - "license": "MIT", - "dependencies": { - "cookie": "0.7.2", - "cookie-signature": "1.0.7", - "debug": "2.6.9", - "depd": "~2.0.0", - "on-headers": "~1.0.2", - "parseurl": "~1.3.3", - "safe-buffer": "5.2.1", - "uid-safe": "~2.1.5" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/express-session/node_modules/cookie-signature": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.7.tgz", - "integrity": "sha512-NXdYc3dLr47pBkpUCHtKSwIOQXLVn8dZEuywboCOJY/osA0wFSLlSawr3KN8qXJEyX66FcONTH8EIlVuK0yyFA==", - "license": "MIT" - }, - "node_modules/express/node_modules/cookie": { - "version": "0.7.1", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz", - "integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/ext": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/ext/-/ext-1.7.0.tgz", - "integrity": "sha512-6hxeJYaL110a9b5TEJSj0gojyHQAmA2ch5Os+ySCiA1QGdS697XWY1pzsrSjqA9LDEEgdB/KypIlR59RcLuHYw==", - "license": "ISC", - "dependencies": { - "type": "^2.7.2" - } - }, - "node_modules/file-uri-to-path": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", - "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==", - "license": "MIT" - }, - "node_modules/finalhandler": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz", - "integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==", - "license": "MIT", - "dependencies": { - "debug": "2.6.9", - "encodeurl": "~2.0.0", - "escape-html": "~1.0.3", - "on-finished": "2.4.1", - "parseurl": "~1.3.3", - "statuses": "2.0.1", - "unpipe": "~1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/forwarded": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", - "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/fresh": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", - "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/fs-constants": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", - "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==", - "license": "MIT" - }, - "node_modules/function-bind": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", - "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/get-intrinsic": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", - "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", - "license": "MIT", - "dependencies": { - "call-bind-apply-helpers": "^1.0.2", - "es-define-property": "^1.0.1", - "es-errors": "^1.3.0", - "es-object-atoms": "^1.1.1", - "function-bind": "^1.1.2", - "get-proto": "^1.0.1", - "gopd": "^1.2.0", - "has-symbols": "^1.1.0", - "hasown": "^2.0.2", - "math-intrinsics": "^1.1.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/get-proto": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", - "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", - "license": "MIT", - "dependencies": { - "dunder-proto": "^1.0.1", - "es-object-atoms": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/github-from-package": { - "version": "0.0.0", - "resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz", - "integrity": "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==", - "license": "MIT" - }, - "node_modules/gopd": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", - "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/graceful-fs": { - "version": "4.2.11", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", - "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", - "license": "ISC" - }, - "node_modules/has-symbols": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", - "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/hasown": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", - "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", - "license": "MIT", - "dependencies": { - "function-bind": "^1.1.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/htmlparser2": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-6.1.0.tgz", - "integrity": "sha512-gyyPk6rgonLFEDGoeRgQNaEUvdJ4ktTmmUh/h2t7s+M8oPpIPxgNACWa+6ESR57kXstwqPiCut0V8NRpcwgU7A==", - "funding": [ - "https://github.com/fb55/htmlparser2?sponsor=1", - { - "type": "github", - "url": "https://github.com/sponsors/fb55" - } - ], - "license": "MIT", - "dependencies": { - "domelementtype": "^2.0.1", - "domhandler": "^4.0.0", - "domutils": "^2.5.2", - "entities": "^2.0.0" - } - }, - "node_modules/http-errors": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", - "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", - "license": "MIT", - "dependencies": { - "depd": "2.0.0", - "inherits": "2.0.4", - "setprototypeof": "1.2.0", - "statuses": "2.0.1", - "toidentifier": "1.0.1" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/iconv-lite": { - "version": "0.4.24", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", - "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", - "license": "MIT", - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/ieee754": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", - "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "BSD-3-Clause" - }, - "node_modules/imurmurhash": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", - "license": "MIT", - "engines": { - "node": ">=0.8.19" - } - }, - "node_modules/inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "license": "ISC" - }, - "node_modules/ini": { - "version": "1.3.8", - "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", - "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", - "license": "ISC" - }, - "node_modules/ip-address": { - "version": "9.0.5", - "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-9.0.5.tgz", - "integrity": "sha512-zHtQzGojZXTwZTHQqra+ETKd4Sn3vgi7uBmlPoXVWZqYvuKmtI0l/VZTjqGmJY9x88GGOaZ9+G9ES8hC4T4X8g==", - "license": "MIT", - "dependencies": { - "jsbn": "1.1.0", - "sprintf-js": "^1.1.3" - }, - "engines": { - "node": ">= 12" - } - }, - "node_modules/ipaddr.js": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", - "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", - "license": "MIT", - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/is-typedarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", - "integrity": "sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==", - "license": "MIT" - }, - "node_modules/isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", - "license": "MIT" - }, - "node_modules/jsbn": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-1.1.0.tgz", - "integrity": "sha512-4bYVV3aAMtDTTu4+xsDYa6sy9GyJ69/amsu9sYF2zqjiEoZA5xJi3BrfX3uY+/IekIu7MwdObdbDWpoZdBv3/A==", - "license": "MIT" - }, - "node_modules/math-intrinsics": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", - "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/media-typer": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", - "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/merge-descriptors": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz", - "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==", - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/methods": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", - "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/mime": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", - "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", - "license": "MIT", - "bin": { - "mime": "cli.js" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/mime-db": { - "version": "1.52.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", - "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/mime-types": { - "version": "2.1.35", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", - "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", - "license": "MIT", - "dependencies": { - "mime-db": "1.52.0" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/mimic-response": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", - "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==", - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/minimist": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", - "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/mkdirp": { - "version": "0.5.6", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", - "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", - "license": "MIT", - "dependencies": { - "minimist": "^1.2.6" - }, - "bin": { - "mkdirp": "bin/cmd.js" - } - }, - "node_modules/mkdirp-classic": { - "version": "0.5.3", - "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz", - "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==", - "license": "MIT" - }, - "node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "license": "MIT" - }, - "node_modules/multer": { - "version": "1.4.5-lts.2", - "resolved": "https://registry.npmjs.org/multer/-/multer-1.4.5-lts.2.tgz", - "integrity": "sha512-VzGiVigcG9zUAoCNU+xShztrlr1auZOlurXynNvO9GiWD1/mTBbUljOKY+qMeazBqXgRnjzeEgJI/wyjJUHg9A==", - "license": "MIT", - "dependencies": { - "append-field": "^1.0.0", - "busboy": "^1.0.0", - "concat-stream": "^1.5.2", - "mkdirp": "^0.5.4", - "object-assign": "^4.1.1", - "type-is": "^1.6.4", - "xtend": "^4.0.0" - }, - "engines": { - "node": ">= 6.0.0" - } - }, - "node_modules/napi-build-utils": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-2.0.0.tgz", - "integrity": "sha512-GEbrYkbfF7MoNaoh2iGG84Mnf/WZfB0GdGEsM8wz7Expx/LlWf5U8t9nvJKXSp3qr5IsEbK04cBGhol/KwOsWA==", - "license": "MIT" - }, - "node_modules/negotiator": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", - "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/next-tick": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.1.0.tgz", - "integrity": "sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ==", - "license": "ISC" - }, - "node_modules/node-abi": { - "version": "3.74.0", - "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.74.0.tgz", - "integrity": "sha512-c5XK0MjkGBrQPGYG24GBADZud0NCbznxNx0ZkS+ebUTrmV1qTDxPxSL8zEAPURXSbLRWVexxmP4986BziahL5w==", - "license": "MIT", - "dependencies": { - "semver": "^7.3.5" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/node-gyp-build": { - "version": "4.8.4", - "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.8.4.tgz", - "integrity": "sha512-LA4ZjwlnUblHVgq0oBF3Jl/6h/Nvs5fzBLwdEF4nuxnFdsfajde4WfxtJr3CaiH+F6ewcIB/q4jQ4UzPyid+CQ==", - "license": "MIT", - "bin": { - "node-gyp-build": "bin.js", - "node-gyp-build-optional": "optional.js", - "node-gyp-build-test": "build-test.js" - } - }, - "node_modules/node-localstorage": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/node-localstorage/-/node-localstorage-2.2.1.tgz", - "integrity": "sha512-vv8fJuOUCCvSPjDjBLlMqYMHob4aGjkmrkaE42/mZr0VT+ZAU10jRF8oTnX9+pgU9/vYJ8P7YT3Vd6ajkmzSCw==", - "license": "MIT", - "dependencies": { - "write-file-atomic": "^1.1.4" - }, - "engines": { - "node": ">=0.12" - } - }, - "node_modules/nodemailer": { - "version": "6.10.0", - "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-6.10.0.tgz", - "integrity": "sha512-SQ3wZCExjeSatLE/HBaXS5vqUOQk6GtBdIIKxiFdmm01mOQZX/POJkO3SUX1wDiYcwUOJwT23scFSC9fY2H8IA==", - "license": "MIT-0", - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/object-assign": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/object-inspect": { - "version": "1.13.4", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", - "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/on-finished": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", - "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", - "license": "MIT", - "dependencies": { - "ee-first": "1.1.1" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/on-headers": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz", - "integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", - "license": "ISC", - "dependencies": { - "wrappy": "1" - } - }, - "node_modules/pako": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/pako/-/pako-2.1.0.tgz", - "integrity": "sha512-w+eufiZ1WuJYgPXbV/PO3NCMEc3xqylkKHzp8bxp1uW4qaSNQUkwmLLEc3kKsfz8lpV1F8Ht3U1Cm+9Srog2ug==", - "license": "(MIT AND Zlib)" - }, - "node_modules/parseurl": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", - "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/path-browserify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-1.0.1.tgz", - "integrity": "sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==", - "license": "MIT" - }, - "node_modules/path-to-regexp": { - "version": "0.1.12", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz", - "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==", - "license": "MIT" - }, - "node_modules/prebuild-install": { - "version": "7.1.3", - "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.3.tgz", - "integrity": "sha512-8Mf2cbV7x1cXPUILADGI3wuhfqWvtiLA1iclTDbFRZkgRQS0NqsPZphna9V+HyTEadheuPmjaJMsbzKQFOzLug==", - "license": "MIT", - "dependencies": { - "detect-libc": "^2.0.0", - "expand-template": "^2.0.3", - "github-from-package": "0.0.0", - "minimist": "^1.2.3", - "mkdirp-classic": "^0.5.3", - "napi-build-utils": "^2.0.0", - "node-abi": "^3.3.0", - "pump": "^3.0.0", - "rc": "^1.2.7", - "simple-get": "^4.0.0", - "tar-fs": "^2.0.0", - "tunnel-agent": "^0.6.0" - }, - "bin": { - "prebuild-install": "bin.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/process-nextick-args": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", - "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", - "license": "MIT" - }, - "node_modules/proxy-addr": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", - "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", - "license": "MIT", - "dependencies": { - "forwarded": "0.2.0", - "ipaddr.js": "1.9.1" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/pump": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.2.tgz", - "integrity": "sha512-tUPXtzlGM8FE3P0ZL6DVs/3P58k9nk8/jZeQCurTJylQA8qFYzHFfhBJkuqyE0FifOsQ0uKWekiZ5g8wtr28cw==", - "license": "MIT", - "dependencies": { - "end-of-stream": "^1.1.0", - "once": "^1.3.1" - } - }, - "node_modules/qs": { - "version": "6.13.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", - "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", - "license": "BSD-3-Clause", - "dependencies": { - "side-channel": "^1.0.6" - }, - "engines": { - "node": ">=0.6" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/random-bytes": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/random-bytes/-/random-bytes-1.0.0.tgz", - "integrity": "sha512-iv7LhNVO047HzYR3InF6pUcUsPQiHTM1Qal51DcGSuZFBil1aBBWG5eHPNek7bvILMaYJ/8RU1e8w1AMdHmLQQ==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/range-parser": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", - "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/raw-body": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", - "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", - "license": "MIT", - "dependencies": { - "bytes": "3.1.2", - "http-errors": "2.0.0", - "iconv-lite": "0.4.24", - "unpipe": "1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/rc": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", - "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", - "license": "(BSD-2-Clause OR MIT OR Apache-2.0)", - "dependencies": { - "deep-extend": "^0.6.0", - "ini": "~1.3.0", - "minimist": "^1.2.0", - "strip-json-comments": "~2.0.1" - }, - "bin": { - "rc": "cli.js" - } - }, - "node_modules/readable-stream": { - "version": "2.3.8", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", - "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", - "license": "MIT", - "dependencies": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "node_modules/readable-stream/node_modules/safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "license": "MIT" - }, - "node_modules/real-cancellable-promise": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/real-cancellable-promise/-/real-cancellable-promise-1.2.1.tgz", - "integrity": "sha512-JwhiWJTMMyzFYfpKsiSb8CyQktCi1MZ8ZBn3wXvq28qXDh8Y5dM7RYzgW3r6SV22JTEcof8pRsvDp4GxLmGIxg==", - "license": "MIT" - }, - "node_modules/safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT" - }, - "node_modules/safer-buffer": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", - "license": "MIT" - }, - "node_modules/semver": { - "version": "7.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz", - "integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==", - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/send": { - "version": "0.19.0", - "resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz", - "integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==", - "license": "MIT", - "dependencies": { - "debug": "2.6.9", - "depd": "2.0.0", - "destroy": "1.2.0", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "fresh": "0.5.2", - "http-errors": "2.0.0", - "mime": "1.6.0", - "ms": "2.1.3", - "on-finished": "2.4.1", - "range-parser": "~1.2.1", - "statuses": "2.0.1" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/send/node_modules/encodeurl": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", - "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/send/node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "license": "MIT" - }, - "node_modules/serve-static": { - "version": "1.16.2", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz", - "integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==", - "license": "MIT", - "dependencies": { - "encodeurl": "~2.0.0", - "escape-html": "~1.0.3", - "parseurl": "~1.3.3", - "send": "0.19.0" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/setprototypeof": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", - "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", - "license": "ISC" - }, - "node_modules/side-channel": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", - "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "object-inspect": "^1.13.3", - "side-channel-list": "^1.0.0", - "side-channel-map": "^1.0.1", - "side-channel-weakmap": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/side-channel-list": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", - "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "object-inspect": "^1.13.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/side-channel-map": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", - "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.2", - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.5", - "object-inspect": "^1.13.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/side-channel-weakmap": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", - "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.2", - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.5", - "object-inspect": "^1.13.3", - "side-channel-map": "^1.0.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/simple-concat": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz", - "integrity": "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT" - }, - "node_modules/simple-get": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-4.0.1.tgz", - "integrity": "sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT", - "dependencies": { - "decompress-response": "^6.0.0", - "once": "^1.3.1", - "simple-concat": "^1.0.0" - } - }, - "node_modules/slide": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/slide/-/slide-1.1.6.tgz", - "integrity": "sha512-NwrtjCg+lZoqhFU8fOwl4ay2ei8PaqCBOUV3/ektPY9trO1yQ1oXEfmHAhKArUVUr/hOHvy5f6AdP17dCM0zMw==", - "license": "ISC", - "engines": { - "node": "*" - } - }, - "node_modules/smart-buffer": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", - "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==", - "license": "MIT", - "engines": { - "node": ">= 6.0.0", - "npm": ">= 3.0.0" - } - }, - "node_modules/socks": { - "version": "2.8.4", - "resolved": "https://registry.npmjs.org/socks/-/socks-2.8.4.tgz", - "integrity": "sha512-D3YaD0aRxR3mEcqnidIs7ReYJFVzWdd6fXJYUM8ixcQcJRGTka/b3saV0KflYhyVJXKhb947GndU35SxYNResQ==", - "license": "MIT", - "dependencies": { - "ip-address": "^9.0.5", - "smart-buffer": "^4.2.0" - }, - "engines": { - "node": ">= 10.0.0", - "npm": ">= 3.0.0" - } - }, - "node_modules/sprintf-js": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.3.tgz", - "integrity": "sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==", - "license": "BSD-3-Clause" - }, - "node_modules/statuses": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", - "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/store2": { - "version": "2.14.4", - "resolved": "https://registry.npmjs.org/store2/-/store2-2.14.4.tgz", - "integrity": "sha512-srTItn1GOvyvOycgxjAnPA63FZNwy0PTyUBFMHRM+hVFltAeoh0LmNBz9SZqUS9mMqGk8rfyWyXn3GH5ReJ8Zw==", - "license": "MIT" - }, - "node_modules/streamsearch": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz", - "integrity": "sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==", - "engines": { - "node": ">=10.0.0" - } - }, - "node_modules/string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "license": "MIT", - "dependencies": { - "safe-buffer": "~5.1.0" - } - }, - "node_modules/string_decoder/node_modules/safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "license": "MIT" - }, - "node_modules/strip-json-comments": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", - "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/tar-fs": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.2.tgz", - "integrity": "sha512-EsaAXwxmx8UB7FRKqeozqEPop69DXcmYwTQwXvyAPF352HJsPdkVhvTaDPYqfNgruveJIJy3TA2l+2zj8LJIJA==", - "license": "MIT", - "dependencies": { - "chownr": "^1.1.1", - "mkdirp-classic": "^0.5.2", - "pump": "^3.0.0", - "tar-stream": "^2.1.4" - } - }, - "node_modules/tar-stream": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", - "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", - "license": "MIT", - "dependencies": { - "bl": "^4.0.3", - "end-of-stream": "^1.4.1", - "fs-constants": "^1.0.0", - "inherits": "^2.0.3", - "readable-stream": "^3.1.1" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/tar-stream/node_modules/readable-stream": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", - "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", - "license": "MIT", - "dependencies": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/telegram": { - "version": "2.26.22", - "resolved": "https://registry.npmjs.org/telegram/-/telegram-2.26.22.tgz", - "integrity": "sha512-EIj7Yrjiu0Yosa3FZ/7EyPg9s6UiTi/zDQrFmR/2Mg7pIUU+XjAit1n1u9OU9h2oRnRM5M+67/fxzQluZpaJJg==", - "license": "MIT", - "dependencies": { - "@cryptography/aes": "^0.1.1", - "async-mutex": "^0.3.0", - "big-integer": "^1.6.48", - "buffer": "^6.0.3", - "htmlparser2": "^6.1.0", - "mime": "^3.0.0", - "node-localstorage": "^2.2.1", - "pako": "^2.0.3", - "path-browserify": "^1.0.1", - "real-cancellable-promise": "^1.1.1", - "socks": "^2.6.2", - "store2": "^2.13.0", - "ts-custom-error": "^3.2.0", - "websocket": "^1.0.34" - }, - "optionalDependencies": { - "bufferutil": "^4.0.3", - "utf-8-validate": "^5.0.5" - } - }, - "node_modules/telegram/node_modules/buffer": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", - "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT", - "dependencies": { - "base64-js": "^1.3.1", - "ieee754": "^1.2.1" - } - }, - "node_modules/telegram/node_modules/mime": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/mime/-/mime-3.0.0.tgz", - "integrity": "sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A==", - "license": "MIT", - "bin": { - "mime": "cli.js" - }, - "engines": { - "node": ">=10.0.0" - } - }, - "node_modules/toidentifier": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", - "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", - "license": "MIT", - "engines": { - "node": ">=0.6" - } - }, - "node_modules/ts-custom-error": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/ts-custom-error/-/ts-custom-error-3.3.1.tgz", - "integrity": "sha512-5OX1tzOjxWEgsr/YEUWSuPrQ00deKLh6D7OTWcvNHm12/7QPyRh8SYpyWvA4IZv8H/+GQWQEh/kwo95Q9OVW1A==", - "license": "MIT", - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/tslib": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", - "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", - "license": "0BSD" - }, - "node_modules/tunnel-agent": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", - "integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==", - "license": "Apache-2.0", - "dependencies": { - "safe-buffer": "^5.0.1" - }, - "engines": { - "node": "*" - } - }, - "node_modules/type": { - "version": "2.7.3", - "resolved": "https://registry.npmjs.org/type/-/type-2.7.3.tgz", - "integrity": "sha512-8j+1QmAbPvLZow5Qpi6NCaN8FB60p/6x8/vfNqOk/hC+HuvFZhL4+WfekuhQLiqFZXOgQdrs3B+XxEmCc6b3FQ==", - "license": "ISC" - }, - "node_modules/type-is": { - "version": "1.6.18", - "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", - "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", - "license": "MIT", - "dependencies": { - "media-typer": "0.3.0", - "mime-types": "~2.1.24" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/typedarray": { - "version": "0.0.6", - "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", - "integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==", - "license": "MIT" - }, - "node_modules/typedarray-to-buffer": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz", - "integrity": "sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==", - "license": "MIT", - "dependencies": { - "is-typedarray": "^1.0.0" - } - }, - "node_modules/uid-safe": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/uid-safe/-/uid-safe-2.1.5.tgz", - "integrity": "sha512-KPHm4VL5dDXKz01UuEd88Df+KzynaohSL9fBh096KWAxSKZQDI2uBrVqtvRM4rwrIrRRKsdLNML/lnaaVSRioA==", - "license": "MIT", - "dependencies": { - "random-bytes": "~1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/unpipe": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", - "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/utf-8-validate": { - "version": "5.0.10", - "resolved": "https://registry.npmjs.org/utf-8-validate/-/utf-8-validate-5.0.10.tgz", - "integrity": "sha512-Z6czzLq4u8fPOyx7TU6X3dvUZVvoJmxSQ+IcrlmagKhilxlhZgxPK6C5Jqbkw1IDUmFTM+cz9QDnnLTwDz/2gQ==", - "hasInstallScript": true, - "license": "MIT", - "dependencies": { - "node-gyp-build": "^4.3.0" - }, - "engines": { - "node": ">=6.14.2" - } - }, - "node_modules/util-deprecate": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", - "license": "MIT" - }, - "node_modules/utils-merge": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", - "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", - "license": "MIT", - "engines": { - "node": ">= 0.4.0" - } - }, - "node_modules/vary": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", - "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/websocket": { - "version": "1.0.35", - "resolved": "https://registry.npmjs.org/websocket/-/websocket-1.0.35.tgz", - "integrity": "sha512-/REy6amwPZl44DDzvRCkaI1q1bIiQB0mEFQLUrhz3z2EK91cp3n72rAjUlrTP0zV22HJIUOVHQGPxhFRjxjt+Q==", - "license": "Apache-2.0", - "dependencies": { - "bufferutil": "^4.0.1", - "debug": "^2.2.0", - "es5-ext": "^0.10.63", - "typedarray-to-buffer": "^3.1.5", - "utf-8-validate": "^5.0.2", - "yaeti": "^0.0.6" - }, - "engines": { - "node": ">=4.0.0" - } - }, - "node_modules/wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", - "license": "ISC" - }, - "node_modules/write-file-atomic": { - "version": "1.3.4", - "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-1.3.4.tgz", - "integrity": "sha512-SdrHoC/yVBPpV0Xq/mUZQIpW2sWXAShb/V4pomcJXh92RuaO+f3UTWItiR3Px+pLnV2PvC2/bfn5cwr5X6Vfxw==", - "license": "ISC", - "dependencies": { - "graceful-fs": "^4.1.11", - "imurmurhash": "^0.1.4", - "slide": "^1.1.5" - } - }, - "node_modules/xtend": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", - "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", - "license": "MIT", - "engines": { - "node": ">=0.4" - } - }, - "node_modules/yaeti": { - "version": "0.0.6", - "resolved": "https://registry.npmjs.org/yaeti/-/yaeti-0.0.6.tgz", - "integrity": "sha512-MvQa//+KcZCUkBTIC9blM+CU9J2GzuTytsOUwf2lidtvkx/6gnEp1QvJv34t9vdjhFmha/mUiNDbN0D0mJWdug==", - "deprecated": "Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.", - "license": "MIT", - "engines": { - "node": ">=0.10.32" - } - } - } -} diff --git a/backend/package.json b/backend/package.json deleted file mode 100644 index 8c38a14..0000000 --- a/backend/package.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "name": "telegram-bot", - "version": "1.0.0", - "main": "app.js", - "directories": { - "doc": "docs" - }, - "scripts": { - "test": "echo \"Error: no test specified\" && exit 1" - }, - "author": "", - "license": "ISC", - "description": "", - "dependencies": { - "better-sqlite3": "^11.8.0", - "body-parser": "^1.20.3", - "content-disposition": "^0.5.4", - "cookie-parser": "^1.4.7", - "express": "^4.21.2", - "express-session": "^1.18.1", - "multer": "^1.4.5-lts.1", - "nodemailer": "^6.9.16", - "telegram": "^2.26.16" - } -} diff --git a/backend/public/index.html b/backend/public/index.html deleted file mode 100644 index 254ff81..0000000 --- a/backend/public/index.html +++ /dev/null @@ -1,337 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/backend/public/miniapp.html b/backend/public/miniapp.html deleted file mode 100644 index 7c786fb..0000000 --- a/backend/public/miniapp.html +++ /dev/null @@ -1,65 +0,0 @@ - - - - -
- - -
- -
-
ПРОЕКТЫ
-
EMPTY
-
- -
-
УЧАСТНИКИ
-
-
- - - - - - \ No newline at end of file diff --git a/i18n-2.xlsm b/i18n-2.xlsm index 912d8ec..3e365fa 100644 Binary files a/i18n-2.xlsm and b/i18n-2.xlsm differ diff --git a/package-lock.json b/package-lock.json index 32966f1..25a90a7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,7 +12,6 @@ "@quasar/cli": "^2.5.0", "@quasar/extras": "^1.16.4", "axios": "^1.2.1", - "dayjs": "^1.11.13", "pinia": "^2.0.11", "quasar": "^2.16.0", "vue": "^3.4.18", @@ -4594,12 +4593,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/dayjs": { - "version": "1.11.13", - "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.13.tgz", - "integrity": "sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg==", - "license": "MIT" - }, "node_modules/de-indent": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/de-indent/-/de-indent-1.0.2.tgz", diff --git a/package.json b/package.json index 687f01a..2961338 100644 --- a/package.json +++ b/package.json @@ -17,7 +17,6 @@ "@quasar/cli": "^2.5.0", "@quasar/extras": "^1.16.4", "axios": "^1.2.1", - "dayjs": "^1.11.13", "pinia": "^2.0.11", "quasar": "^2.16.0", "vue": "^3.4.18", diff --git a/quasar.config.ts b/quasar.config.ts index 8312784..833953a 100644 --- a/quasar.config.ts +++ b/quasar.config.ts @@ -60,40 +60,11 @@ export default defineConfig((ctx) => { // extendTsConfig (tsConfig) {} }, - vueRouterMode: 'history', // available values: 'hash', 'history' - // vueDevtools: true, // Должно быть true - // devtool: 'source-map', // Для лучшей отладки - // vueRouterBase, - // vueDevtools, - // vueOptionsAPI: false, - - // rebuildCache: true, // rebuilds Vite/linter/etc cache on startup - - // publicPath: '/', - // analyze: true, - // env: {}, - // rawDefine: {} - // ignorePublicFolder: true, - // minify: false, - // polyfillModulePreload: true, - // distDir - - // extendViteConf (viteConf) {}, - // viteVuePluginOptions: {}, - - // from deepseek + vueRouterMode: 'history', vite: { plugins: [ ['@intlify/unplugin-vue-i18n/vite', { - // if you want to use Vue I18n Legacy API, you need to set `compositionOnly: false` - // compositionOnly: false, - - // if you want to use named tokens in your Vue I18n messages, such as 'Hello {name}', - // you need to set `runtimeOnly: false` - // runtimeOnly: false, - ssr: ctx.modeName === 'ssr', - // you need to set i18n resource including paths ! include: [ fileURLToPath(new URL('./src/i18n', import.meta.url)) ] }], @@ -105,8 +76,12 @@ export default defineConfig((ctx) => { useFlatConfig: true } }, { server: false }] - ] - }, + ], + + // Конфигурация сборки Rollup + build: { + } + } }, // Full list of options: https://v2.quasar.dev/quasar-cli-vite/quasar-config-file#devserver diff --git a/src/App.vue b/src/App.vue index 5acbc42..52017c1 100644 --- a/src/App.vue +++ b/src/App.vue @@ -7,7 +7,6 @@ import { useRouter } from 'vue-router' import { useAuthStore } from 'stores/auth' import { useSettingsStore } from 'stores/settings' - import { useProjectsStore } from 'stores/projects' import { useQuasar } from 'quasar' import type { WebApp } from '@twa-dev/types' @@ -51,7 +50,6 @@ const authStore = useAuthStore() const settingsStore = useSettingsStore() - const projectsStore = useProjectsStore() const startRouteInfo = ref<{ id: number; taskId?: number; meetingId?: number } | null>(null) if (tg.initDataUnsafe.start_param) { @@ -59,9 +57,8 @@ } onMounted(async () => { - console.log('app mount') try { - if (startRouteInfo.value) projectsStore.setStartRouteInfo(startRouteInfo.value) + if (startRouteInfo.value) authStore.setStartRouteInfo(startRouteInfo.value) if (!authStore.isInit) await authStore.init(tg) if (!settingsStore.isInit) await settingsStore.init() } catch { diff --git a/src/boot/axios.ts b/src/boot/axios.ts index aa0b788..d691321 100644 --- a/src/boot/axios.ts +++ b/src/boot/axios.ts @@ -1,34 +1,40 @@ import { defineBoot } from '#q-app/wrappers' -import axios, { type AxiosInstance } from 'axios' +import axios, { type AxiosError } from 'axios' -declare module 'vue' { - interface ComponentCustomProperties { - $axios: AxiosInstance; - $api: AxiosInstance; +class ServerError extends Error { + constructor( + public code: string, + message: string + ) { + super(message) + this.name = 'ServerError' } } -// Be careful when using SSR for cross-request state pollution -// due to creating a Singleton instance here; -// If any client changes this (global) instance, it might be a -// good idea to move this instance creation inside of the -// "export default () => {}" function below (which runs individually -// for each client) const api = axios.create({ baseURL: '/api/miniapp', - withCredentials: true // Важно для работы с cookies + withCredentials: true }) +api.interceptors.response.use( + response => response, + async (error: AxiosError<{ error?: { code: string; message: string } }>) => { + const errorData = error.response?.data?.error || { + code: 'ZERO', + message: error.message || 'Unknown error' + } + + const serverError = new ServerError( + errorData.code, + errorData.message + ) + + return Promise.reject(serverError) + } +) + export default defineBoot(({ app }) => { - // for use inside Vue files (Options API) through this.$axios and this.$api - - app.config.globalProperties.$axios = axios - // ^ ^ ^ this will allow you to use this.$axios (for Vue Options API form) - // so you won't necessarily have to import axios in each vue file - app.config.globalProperties.$api = api - // ^ ^ ^ this will allow you to use this.$api (for Vue Options API form) - // so you can easily perform requests against your app's API }) -export { api } +export { api, ServerError } diff --git a/src/components/companyInfoPersons.vue b/src/components/companyInfoPersons.vue index ce1be05..15a2af1 100644 --- a/src/components/companyInfoPersons.vue +++ b/src/components/companyInfoPersons.vue @@ -49,7 +49,6 @@ ] async function goPersonInfo () { - console.log('update') await router.push({ name: 'person_info' }) } diff --git a/src/components/meetingBlock.vue b/src/components/meetingBlock.vue index 7eb5b8a..f8475d2 100644 --- a/src/components/meetingBlock.vue +++ b/src/components/meetingBlock.vue @@ -296,7 +296,6 @@ return Object.values(validations).every(Boolean) }) - const initialMeeting = ref({} as MeetingParams) onMounted(() => { diff --git a/src/components/pnImageSelector.vue b/src/components/pnImageSelector.vue index 006096c..45e1bfb 100644 --- a/src/components/pnImageSelector.vue +++ b/src/components/pnImageSelector.vue @@ -17,7 +17,7 @@ accept="image/*" /> - - + + - {{ $t(title)}} +
+ {{ $t(title) }} +
+
+ {{ $t(message1) }} +
+
+ {{ $t(message2) }} +
- - {{ $t(message1)}} - - - {{ $t(message2)}} - - -
- + +
+
+
+ +
+
+ +
+
+ + class="w100 q-mt-md q-mb-sm" flat + v-close-popup rounded + @click="emit('close')" + > +
+ + {{$t('close')}} +
+
- -
- {{$t('close')}} - -
-
- +
@@ -81,5 +89,5 @@ - diff --git a/src/components/taskBlock.vue b/src/components/taskBlock.vue index b72e06c..ffc758c 100644 --- a/src/components/taskBlock.vue +++ b/src/components/taskBlock.vue @@ -8,6 +8,7 @@ rounded color="primary" class="w100 q-mt-md q-mb-xs" @click = "emit('update', newFiles)" + :disable="!(isFormValid && (isDirty(initialTask, modelValue) || newFiles.length !== 0))" > {{ $t(btnText) }}
@@ -275,6 +276,7 @@ import { useUsersStore } from 'stores/users' import { useFilesStore } from 'stores/files' import { useI18n } from 'vue-i18n' + import { isDirty } from 'helpers/helpers' import { date } from 'quasar' const { t } = useI18n() const filesStore = useFilesStore() diff --git a/src/css/app.scss b/src/css/app.scss index 6fce49f..8c0c518 100644 --- a/src/css/app.scss +++ b/src/css/app.scss @@ -36,7 +36,7 @@ body { } .main-content { - max-width: 600px; + max-width: var(--body-width); margin: 0 auto; } diff --git a/src/i18n/index.ts b/src/i18n/index.ts index 74f739c..9896f9f 100644 --- a/src/i18n/index.ts +++ b/src/i18n/index.ts @@ -4,4 +4,4 @@ import ruRU from './ru-RU' export default { 'en-US': enUS, 'ru-RU': ruRU -}; +} diff --git a/src/i18n/ru-RU/index.ts b/src/i18n/ru-RU/index.ts index d4f7ecd..2d54ad8 100644 --- a/src/i18n/ru-RU/index.ts +++ b/src/i18n/ru-RU/index.ts @@ -1 +1 @@ -export default { EN: 'EN', RU: 'RU', '': '', error404: 'Тут ничего нет. Как вы сюда попали?', continue: 'Продолжить', back: 'Назад', close: 'Закрыть', month: 'мес.', months: 'мес.', slogan: 'Работайте вместе - это волшебство!', under_construction: 'В разработке.', B: 'Б', kB: 'КБ', MB: 'МБ', GB: 'ГБ', TB: 'ТБ', main__chats: 'Чаты', main__tasks: 'Задачи', main__meetings: 'Совещания', main__files: 'Файлы', main__users: 'Контакты', chats__search: 'Поиск', tasks__search: 'Поиск', tasks__filters: 'Фильтры', tasks__filters_by_participant: 'Типы задач', tasks__filters_to_me: 'Входящие', tasks__filters_from_me: 'Порученные', tasks__filters_observers: 'Отслеживаемые', tasks__filters_not_involved: 'Без моего участия', tasks__filters_by_priority: 'Приоритет задач', tasks__filters_priority_normal: 'Нормальный', tasks__filters_priority_important: 'Важный', tasks__filters_priority_critical: 'Критичный', tasks__filters_continue: 'Продолжить', tasks__filters_reset: 'Сбросить фильтры', tasks__show_archive: 'Показать архив', tasks__hide_archive: 'Скрыть архив', tasks__dialog_cancel_title: 'Отменить задачу?', tasks__dialog_cancel_delete: 'Удалить', tasks__dialog_cancel_ok: 'Отменить', task_create__title_card: 'Создание задачи', task_create__btn: 'Создать', task_edit__title_card: 'Редактировать задачу', task_edit__btn: 'Применить', task_view__title_card: 'Карточка задачи', task_view__go_to_chat: 'к чату', task_view__btn_cancel_task: 'Отклонить', task_view__btn_close_task: 'Выполнено', task_view__dialog_task_done_title: 'Завершить задачу', task_view__dialog_task_done_comment: 'Комментарий', task_view__dialog_task_done_btn: 'Завершить', task_view__dialog_task_cancel_title: 'Отклонить задачу', task_view__dialog_task_cancel_comment: 'Причина', task_view__dialog_task_cancel_btn: 'Отклонить', task_view__dialog_task_files: 'Файлы', task_view__error_comment: 'Необходимо указать', task_block__name: 'Заголовок', task_block__description: 'Описание', task_block__plan: 'Срок выполнения', task_block__date: 'Дата', task_block__time: 'Время', task_block__priority: 'Приоритет', task_block__priority_normal: 'Нормально', task_block__priority_important: 'Важно', task_block__priority_critical: 'Критично', task_block__attached_chat: 'Связанный чат', task_block__attach_files: 'Прикрепленные файлы', task_block__assigned_to: 'Ответственный', task_block__observers: 'Наблюдатели', task_block__attach_chat: 'Прикрепленный чат', task_block__no_chat: 'Без чата', task_block__error_name: 'Укажите название задачи', task_block__error_date: 'Некорректная дата', task_block__error_time: 'Некорректное время', task_item__from_me: 'Я', task_item__to_me: 'Мне', task_item__to_me_from_me: 'Себе', task_item__task_overdue: 'Просрочена', task_item__task_done: 'Выполнена', task_item__task_done_overdue: 'Выполнена (просрочена)', task_item__task_cancel: 'Отменена', task_priority_important: 'Важно', task_priority_critical: 'Критично', meetings__search: 'Поиск', meetings__previous: 'Прошедшие', meetings__previous_hide: 'Сбросить', meeting_create__title_card: 'Создать совещание', meeting_create__btn: 'Создать', meeting_edit__title_card: 'Редактировать совещание', meeting_edit__btn: 'Применить', meeting_view__title_card: 'Карточка совещания', meeting_view__go_to_chat: 'к чату', meeting_page__canceled: 'Отменено', meeting_page__dialog_cancel_title: 'Отменить совещание?', meeting_page__dialog_cancel_ok: 'Подтвердить', meeting_page__dialog_cancel_delete: 'Удалить', meeting_page__dialog_restore_title: 'Возобновить совещание?', meeting_page__dialog_restore_ok: 'Подтвердить', meeting_block__name: 'Тема', meeting_block__description: 'Описание', meeting_block__date: 'Дата', meeting_block__time: 'Время', meeting_block__place: 'Место', meeting_block__attach_chat: 'Прикрепленный чат', meeting_block__participants: 'Участники', meeting_block__attach_files: 'Файлы', meeting_block__error_name: 'Укажите тему совещания', meeting_block__error_date: 'Некорректная дата', meeting_block__error_time: 'Некорректное время', meeting_block__no_chat: 'Без чата', files__search: 'Поиск', files__filters: 'Фильтры', files__filters_extension: 'Расширения (типы)', files__filters_source: 'Источник ', files__filters_source_chats: 'чат', files__filters_source_tasks: 'задача', files__filters_source_meetings: 'совещание', files__filters_by: 'Автор', files__filters_size: 'Размер', files__filters_size_small: 'небольшой (менее 5МБ)', files__filters_size_middle: 'средний (5-25МБ)', files__filters_size_big: 'большой (25-100МБ)', files__filters_size_very_big: 'очень большой (более 100МБ)', files__filters_continue: 'Продолжить', files__filters_reset: 'Сбросить фильтры', header__my_projects: 'Мои проекты', header__all_projects: 'Все проекты', header__projects: 'Проекты', users__search: 'Поиск', user_card__title: 'Карточка пользователя', user_card__name: 'Имя', user_card__phone: 'Телефон', user_card__email: 'Электронная почта', user_card__position: 'Позиция', settings__title: 'Настройки', settings__language: 'Язык', settings__font_size: 'Размер шрифта', file_upload__comment: 'Загрузка только по одному файлу (Android)' } \ No newline at end of file +export default { EN: 'EN', RU: 'RU', '': '', error404: 'Тут ничего нет. Как вы сюда попали?', continue: 'Продолжить', back: 'Назад', close: 'Закрыть', month: 'мес.', months: 'мес.', slogan: 'Работайте вместе - это волшебство!', under_construction: 'В разработке.', B: 'Б', kB: 'КБ', MB: 'МБ', GB: 'ГБ', TB: 'ТБ', main__chats: 'Чаты', main__tasks: 'Задачи', main__meetings: 'Совещания', main__files: 'Файлы', main__users: 'Контакты', chats__search: 'Поиск', tasks__search: 'Поиск', tasks__filters: 'Фильтры', tasks__filters_by_participant: 'Типы задач', tasks__filters_to_me: 'Входящие', tasks__filters_from_me: 'Порученные', tasks__filters_observers: 'Отслеживаемые', tasks__filters_not_involved: 'Без моего участия', tasks__filters_by_priority: 'Приоритет задач', tasks__filters_priority_normal: 'Нормальный', tasks__filters_priority_important: 'Важный', tasks__filters_priority_critical: 'Критичный', tasks__filters_continue: 'Продолжить', tasks__filters_reset: 'Сбросить фильтры', tasks__show_archive: 'Показать архив', tasks__hide_archive: 'Скрыть архив', tasks__dialog_cancel_title: 'Отменить задачу?', tasks__dialog_cancel_delete: 'Удалить', tasks__dialog_cancel_ok: 'Отменить', task_create__title_card: 'Создание задачи', task_create__btn: 'Создать', task_edit__title_card: 'Редактировать задачу', task_edit__btn: 'Изменить', task_view__title_card: 'Карточка задачи', task_view__go_to_chat: 'к чату', task_view__btn_cancel_task: 'Отклонить', task_view__btn_close_task: 'Выполнено', task_view__dialog_task_done_title: 'Завершить задачу', task_view__dialog_task_done_comment: 'Комментарий', task_view__dialog_task_done_btn: 'Завершить', task_view__dialog_task_cancel_title: 'Отклонить задачу', task_view__dialog_task_cancel_comment: 'Причина', task_view__dialog_task_cancel_btn: 'Отклонить', task_view__dialog_task_files: 'Файлы', task_view__error_comment: 'Необходимо указать', task_block__name: 'Заголовок', task_block__description: 'Описание', task_block__plan: 'Срок выполнения', task_block__date: 'Дата', task_block__time: 'Время', task_block__priority: 'Приоритет', task_block__priority_normal: 'Нормально', task_block__priority_important: 'Важно', task_block__priority_critical: 'Критично', task_block__attached_chat: 'Связанный чат', task_block__attach_files: 'Прикрепленные файлы', task_block__assigned_to: 'Ответственный', task_block__observers: 'Наблюдатели', task_block__attach_chat: 'Прикрепленный чат', task_block__no_chat: 'Без чата', task_block__error_name: 'Укажите название задачи', task_block__error_date: 'Некорректная дата', task_block__error_time: 'Некорректное время', task_item__from_me: 'Я', task_item__to_me: 'Мне', task_item__to_me_from_me: 'Себе', task_item__task_overdue: 'Просрочена', task_item__task_done: 'Выполнена', task_item__task_done_overdue: 'Выполнена (просрочена)', task_item__task_cancel: 'Отменена', task_priority_important: 'Важно', task_priority_critical: 'Критично', meetings__search: 'Поиск', meetings__previous: 'Прошедшие', meetings__previous_hide: 'Сбросить', meeting_create__title_card: 'Создать совещание', meeting_create__btn: 'Создать', meeting_edit__title_card: 'Редактировать совещание', meeting_edit__btn: 'Изменить', meeting_view__title_card: 'Карточка совещания', meeting_view__go_to_chat: 'к чату', meeting_page__canceled: 'Отменено', meeting_page__dialog_cancel_title: 'Отменить совещание?', meeting_page__dialog_cancel_ok: 'Подтвердить', meeting_page__dialog_cancel_delete: 'Удалить', meeting_page__dialog_restore_title: 'Возобновить совещание?', meeting_page__dialog_restore_ok: 'Подтвердить', meeting_block__name: 'Тема', meeting_block__description: 'Описание', meeting_block__date: 'Дата', meeting_block__time: 'Время', meeting_block__place: 'Место', meeting_block__attach_chat: 'Прикрепленный чат', meeting_block__participants: 'Участники', meeting_block__attach_files: 'Файлы', meeting_block__error_name: 'Укажите тему совещания', meeting_block__error_date: 'Некорректная дата', meeting_block__error_time: 'Некорректное время', meeting_block__no_chat: 'Без чата', files__search: 'Поиск', files__filters: 'Фильтры', files__filters_extension: 'Расширения (типы)', files__filters_source: 'Источник ', files__filters_source_chats: 'чат', files__filters_source_tasks: 'задача', files__filters_source_meetings: 'совещание', files__filters_by: 'Автор', files__filters_size: 'Размер', files__filters_size_small: 'небольшой (менее 5МБ)', files__filters_size_middle: 'средний (5-25МБ)', files__filters_size_big: 'большой (25-100МБ)', files__filters_size_very_big: 'очень большой (более 100МБ)', files__filters_continue: 'Продолжить', files__filters_reset: 'Сбросить фильтры', header__my_projects: 'Мои проекты', header__all_projects: 'Все проекты', header__projects: 'Проекты', users__search: 'Поиск', user_card__title: 'Карточка пользователя', user_card__name: 'Имя', user_card__phone: 'Телефон', user_card__email: 'Электронная почта', user_card__position: 'Позиция', settings__title: 'Настройки', settings__language: 'Язык', settings__font_size: 'Размер шрифта', file_upload__comment: 'Загрузка только по одному файлу (Android)' } \ No newline at end of file diff --git a/src/pages/MeetingEditPage.vue b/src/pages/MeetingEditPage.vue index 86509a1..4b6e214 100644 --- a/src/pages/MeetingEditPage.vue +++ b/src/pages/MeetingEditPage.vue @@ -10,7 +10,7 @@ diff --git a/src/pages/main/TasksPage.vue b/src/pages/main/TasksPage.vue index b122081..8cedc26 100644 --- a/src/pages/main/TasksPage.vue +++ b/src/pages/main/TasksPage.vue @@ -83,36 +83,38 @@
- + { isInit.value = true } + const startRouteInfo = ref<{ id: number; taskId?: number; meetingId?: number } | null>(null) + + function setStartRouteInfo (info: { id: number; taskId?: number; meetingId?: number } | null) { + startRouteInfo.value = info + } + return { isInit, telegramUserData, + startRouteInfo, + setStartRouteInfo, init } }) diff --git a/src/stores/projects.ts b/src/stores/projects.ts index 5ec6881..2a0527e 100644 --- a/src/stores/projects.ts +++ b/src/stores/projects.ts @@ -71,12 +71,6 @@ export const useProjectsStore = defineStore('projects', () => { const getProjects = computed(() => projects.value) - const startRouteInfo = ref<{ id: number; taskId?: number; meetingId?: number } | null>(null) - - function setStartRouteInfo (info: { id: number; taskId?: number; meetingId?: number } | null) { - startRouteInfo.value = info - } - watch (currentProjectId, async (newId) => { if (newId) await initStores(); else resetStores() }, { flush: 'sync' }) @@ -91,8 +85,6 @@ export const useProjectsStore = defineStore('projects', () => { setCurrentProjectId, initStores, resetStores, - getProjects, - startRouteInfo, - setStartRouteInfo + getProjects } }) diff --git a/src/types/Task.ts b/src/types/Task.ts index 1c9df80..c977999 100644 --- a/src/types/Task.ts +++ b/src/types/Task.ts @@ -11,6 +11,7 @@ interface TaskParams { chat_id: number | null close_files: number[] close_comment: string + [key: string]: unknown } interface Task extends TaskParams {