const crypto = require('crypto') const express = require('express') const multer = require('multer') const db = require('../include/db') const bot = require('./bot') const fs = require('fs') const cookieParser = require('cookie-parser') const app = express.Router() const upload = multer({ storage: multer.memoryStorage(), limits: { fileSize: 1_000_000 // 1mb } }) const sessions = {} app.use((req, res, next) => { if (req.path == '/customer/login' || req.path == '/customer/register' || req.path == '/customer/activate') 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() }) // CUSTOMER app.post('/customer/login', (req, res, next) => { res.locals.email = req.body?.email res.locals.password = req.body?.password let customer_id = db .prepare(` select id from customers where is_active = 1 and ( email is not null and email = :email and password is not null and password = :password or email is null and password is null and telegram_user_id = :telegram_id ) `) .pluck(true) .get(res.locals) if (!customer_id && !res.locals.email && !res.locals.password) { customer_id = db .prepare(`insert into customers (telegram_user_id, is_active) values (:telegram_id, 1) returning id`) .safeIntegers(true) .pluck(true) .get(res.locals) } if (!customer_id) throw Error('AUTH_ERROR::401') res.locals.customer_id = customer_id db .prepare(`update customers set telegram_user_id = :telegram_id where id = :customer_id and email is not null`) .run(res.locals) const asid = crypto.randomBytes(64).toString('hex') req.session = sessions[asid] = {asid, customer_id } res.setHeader('Set-Cookie', [`asid=${asid};httpOnly;path=/api/admin`]) res.status(200).json({success: true}) }) app.get('/customer/logout', (req, res, next) => { delete sessions[req.session.asid] res.setHeader('Set-Cookie', [`asid=; expired; httpOnly`]) res.status(200).json({success: true}) }) app.post('/customer/register', (req, res, next) => { const email = String(req.body.email).trim() const password = String(req.body.password).trim() const validateEmail = email => 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,}))$/) if (!validateEmail(email)) throw Error('INCORRECT_EMAIL::400') if (!password) throw Error('EMPTY_PASSWORD::400') const row = db .prepare('select id from customers where email = :email') .run({email}) if (row) throw Error('DUPLICATE_EMAIL::400') const key = crypto.randomBytes(32).toString('hex') const info = db .prepare('insert into customers (email, password, activation_key) values (:email, :password, :key)') .run({email, password, key}) // To-Do: SEND MAIL console.log(`http://127.0.0.1:3000/api/customer/activate?key=${key}`) res.status(200).json({success: true, data: key}) }) app.get('/customer/activate', (req, res, next) => { const row = db .prepare('update customers set is_active = 1 where activation_key = :key returning id') .get({key: req.query.key}) if (!row || !row.id) throw Error('BAD_ACTIVATION_KEY::400') res.status(200).json({success: true}) }) 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_group_id from customers where id = :customer_id and is_active = 1 `) .get(res.locals) if (row?.upload_group_id) { row.upload_group = db .prepare(`select id, name, telegram_id from groups where id = :group_id and project_id is null`) .safeIntegers(true) .get({ group_id: row.upload_group_id}) delete row.upload_group_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) 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}) }) // PROJECT app.get('/project', (req, res, next) => { const where = req.query.id ? ' and id = ' + parseInt(req.query.id) : '' const rows = db .prepare(` select id, name, description, logo from projects where customer_id = :customer_id ${where} and is_deleted <> 1 order by name `) .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.post('/project', (req, res, next) => { res.locals.name = req.body?.name res.locals.description = req.body?.description res.locals.logo = req.body?.logo const id = db .prepare(` insert into projects (customer_id, name, description, logo) values (:customer_id, :name, :description, :logo) returning id `) .pluck(true) .get(res.locals) res.status(200).json({success: true, data: id}) }) 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 const info = db .prepareUpdate( 'projects', ['name', 'description', 'logo'], res.locals, ['id', 'customer_id']) .run(res.locals) if (info.changes == 0) throw Error('NOT_FOUND::404') res.status(200).json({success: true}) }) app.delete('/project/:pid(\\d+)', async (req, res, next) => { res.locals.id = req.params.pid const info = db .prepare('update projects set id_deleted = 1 where id = :id and customer_id = :customer_id') .run(res.locals) if (info.changes == 0) throw Error('NOT_FOUND::404') const groupIds = db .prepare(`select id from groups where project_id = :id`) .pluck(true) .all(res.locals) for (const groupId of groupIds) { await bot.sendMessage(groupId, 'Проект удален') await bot.leaveGroup(groupId) } db.prepare(`updates groups set project_id = null where id in (${ groupIds.join(', ')})`).run() res.status(200).json({success: true}) }) app.use ('/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_deleted <> 1') .get(res.locals) if (!row) throw Error('ACCESS_DENIED::401') next() }) // USER app.get('/project/:pid(\\d+)/user', (req, res, next) => { const where = req.query.id ? ' and id = ' + parseInt(req.query.id) : '' const rows = db .prepare(` select u.id, u.telegram_id, u.firstname, u.lastname, u.username, u.photo, ud.fullname, ud.role, ud.department, ud.is_blocked from users u left join user_details ud on u.id = ud.user_id and ud.project_id = :project_id where id in ( select user_id from group_users where group_id in (select id from groups where project_id = :project_id) ) ${where} `) .safeIntegers(true) .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+)/user/:uid(\\d+)', (req, res, next) => { res.redirect(req.baseUrl + `/project/${req.params.pid}/user?id=${req.params.uid}`) }) 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.role = req.body?.role res.locals.department = req.body?.department res.locals.is_blocked = req.body?.is_blocked const info = db .prepareUpdate('user_details', ['fullname', 'role', 'department', 'is_blocked'], res.locals, ['user_id', 'project_id'] ) .all(res.locals) if (info.changes == 0) throw Error('NOT_FOUND::404') res.status(200).json({success: true}) }) 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 app.get('/project/:pid(\\d+)/company', (req, res, next) => { const where = req.query.id ? ' and id = ' + parseInt(req.query.id) : '' const rows = db .prepare(` select id, name, email, phone, 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 ${where} order by name `) .all(res.locals) rows.forEach(row => row.users = JSON.parse(row.users || '[]')) 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+)/company/:cid(\\d+)', (req, res, next) => { res.redirect(req.baseUrl + `/project/${req.params.pid}/company?id=${req.params.cid}`) }) app.post('/project/:pid(\\d+)/company', (req, res, next) => { res.locals.name = req.body?.name 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, email, phone, site, description, logo) values (:project_id, :name, :email, :phone, :site, :description, :logo) returning id `) .pluck(res.locals) .get(res.locals) res.status(200).json({success: true, data: id}) }) 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.email = req.body?.email res.locals.phone = req.body?.phone res.locals.site = req.body?.site res.locals.description = req.body?.description const info = db .prepareUpdate( 'companies', ['name', 'email', 'phone', 'site', 'description'], 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+)/company/:cid(\\d+)', (req, res, next) => { res.locals.company_id = parseInt(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}) }) 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, is_channel, user_count, bot_can_ban from groups where project_id = :project_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.uid}`) }) app.delete('/project/:pid(\\d+)/group/:gid(\\d+)', async (req, res, next) => { res.locals.group_id = parseInt(req.params.gid) const info = db .prepare(`update groups set project_id = null where id = :group_id and project_id = :project_id`) .run(res.locals) if (info.changes == 0) throw Error('NOT_FOUND::404') await bot.sendMessage(res.locals.group_id, 'Группа удалена из проекта') res.status(200).json({success: true}) }) 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 group_users where group_id in (select id from groups 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}) }) module.exports = app