Files
tgCrewUser/backend/apps/admin.js
2025-04-14 10:27:58 +03:00

496 lines
14 KiB
JavaScript

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