503 lines
14 KiB
JavaScript
503 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 = {}
|
|
const emailCache = {} // key = email, value = code
|
|
|
|
app.use((req, res, next) => {
|
|
if (req.path == '/auth/email' || req.path == '/auth/telegram' || req.path == '/auth/register' || req.path == '/auth/logout')
|
|
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 is_blocked = 0 and 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 is_blocked = 0 and telegram_id = :telegram_id`)
|
|
.pluck(true)
|
|
.get(res.locals) || db
|
|
.prepare(`replace into customers (telegram_id, is_blocked) values (:telegram_id, 0) returning id`)
|
|
.pluck(true)
|
|
.get(res.locals)
|
|
|
|
createSession(req, res, customer_id)
|
|
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})
|
|
})
|
|
|
|
app.post('/auth/register', (req, res, next) => {
|
|
const email = String(req.body.email ?? '').trim()
|
|
const code = String(req.body.code ?? '').trim()
|
|
const password = String(req.body.password ?? '').trim()
|
|
|
|
if (email) {
|
|
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')
|
|
|
|
const customer_id = db
|
|
.prepare('select id from customers where email = :email')
|
|
.pluck(true)
|
|
.get({email})
|
|
|
|
if (customer_id)
|
|
throw Error('USED_EMAIL::400')
|
|
}
|
|
|
|
if (email && !code) {
|
|
const code = Math.random().toString().substr(2, 4)
|
|
emailCache[email] = code
|
|
// To-Do: send email
|
|
console.log(`${email} => ${code}`)
|
|
}
|
|
|
|
if (email && code && !password) {
|
|
if (emailCache[email] != code)
|
|
throw Error('INCORRECT_CODE::400')
|
|
}
|
|
|
|
if (email && code && password) {
|
|
if (password.length < 8)
|
|
throw Error('INCORRECT_PASSWORD::400')
|
|
|
|
db
|
|
.prepare('insert into customers (email, password, is_blocked) values (:email, :password, 0)')
|
|
.run({email, password})
|
|
}
|
|
|
|
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_group_id
|
|
from customers
|
|
where id = :customer_id
|
|
`)
|
|
.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 |