Добавлены API сайтов и операторов

Этот коммит содержится в:
Виктор
2026-05-08 21:08:04 +09:00
родитель ed81f8bb6e
Коммит 5ba7bd846b
+112
Просмотреть файл
@@ -3,9 +3,11 @@ import cors from '@fastify/cors';
import rateLimit from '@fastify/rate-limit'; import rateLimit from '@fastify/rate-limit';
import websocket from '@fastify/websocket'; import websocket from '@fastify/websocket';
import { randomUUID } from 'node:crypto'; import { randomUUID } from 'node:crypto';
import { nanoid } from 'nanoid';
import { config } from './config.js'; import { config } from './config.js';
import { import {
createVisitorMessage, createVisitorMessage,
db,
getOrCreateOpenConversation, getOrCreateOpenConversation,
getSiteByWidgetKey, getSiteByWidgetKey,
getStats, getStats,
@@ -58,6 +60,21 @@ function getClientKey(siteId, visitorId) {
return `${siteId || 'unknown_site'}:${visitorId || 'unknown_visitor'}`; return `${siteId || 'unknown_site'}:${visitorId || 'unknown_visitor'}`;
} }
function normalizeDomain(value) {
return String(value || '')
.trim()
.toLowerCase()
.replace(/^https?:\/\//, '')
.replace(/^www\./, '')
.split('/')[0]
.split(':')[0];
}
function makeWidgetKey(domain) {
const normalized = normalizeDomain(domain).replace(/[^a-z0-9]+/g, '_').replace(/^_+|_+$/g, '');
return `site_${normalized || nanoid(8)}`;
}
function sendJson(connection, payload) { function sendJson(connection, payload) {
try { try {
connection.send(JSON.stringify(payload)); connection.send(JSON.stringify(payload));
@@ -175,6 +192,101 @@ app.get('/api/admin/messages', async (request) => {
}; };
}); });
app.get('/api/admin/sites', async () => ({
ok: true,
sites: db.prepare(`
SELECT
sites.*,
COUNT(DISTINCT visitors.id) AS visitors_count,
COUNT(DISTINCT conversations.id) AS conversations_count,
COUNT(DISTINCT site_operators.operator_id) AS operators_count
FROM sites
LEFT JOIN visitors ON visitors.site_id = sites.id
LEFT JOIN conversations ON conversations.site_id = sites.id
LEFT JOIN site_operators ON site_operators.site_id = sites.id
GROUP BY sites.id
ORDER BY sites.created_at DESC
`).all()
}));
app.post('/api/admin/sites', async (request, reply) => {
const body = request.body || {};
const domain = normalizeDomain(body.domain);
const name = String(body.name || domain || '').trim();
const widgetKey = String(body.widgetKey || makeWidgetKey(domain)).trim();
if (!domain) {
reply.code(400);
return { ok: false, error: 'domain is required' };
}
const id = widgetKey;
try {
db.prepare(`
INSERT INTO sites (id, name, domain, widget_key, is_active)
VALUES (?, ?, ?, ?, 1)
`).run(id, name || domain, domain, widgetKey);
return {
ok: true,
site: db.prepare('SELECT * FROM sites WHERE id = ?').get(id)
};
} catch (error) {
reply.code(400);
return { ok: false, error: error.message };
}
});
app.get('/api/admin/operators', async () => ({
ok: true,
operators: db.prepare(`
SELECT
operators.*,
GROUP_CONCAT(sites.domain, ', ') AS sites
FROM operators
LEFT JOIN site_operators ON site_operators.operator_id = operators.id
LEFT JOIN sites ON sites.id = site_operators.site_id
GROUP BY operators.id
ORDER BY operators.updated_at DESC
`).all()
}));
app.post('/api/admin/site-operators', async (request, reply) => {
const body = request.body || {};
const siteId = String(body.siteId || '').trim();
const operatorId = String(body.operatorId || '').trim();
if (!siteId || !operatorId) {
reply.code(400);
return { ok: false, error: 'siteId and operatorId are required' };
}
const site = db.prepare('SELECT id FROM sites WHERE id = ? AND is_active = 1').get(siteId);
const operator = db.prepare('SELECT id FROM operators WHERE id = ? AND is_active = 1').get(operatorId);
if (!site || !operator) {
reply.code(404);
return { ok: false, error: 'site or operator not found' };
}
db.prepare(`
INSERT OR IGNORE INTO site_operators (site_id, operator_id)
VALUES (?, ?)
`).run(siteId, operatorId);
return { ok: true };
});
app.delete('/api/admin/site-operators', async (request) => {
const body = request.body || {};
const siteId = String(body.siteId || '').trim();
const operatorId = String(body.operatorId || '').trim();
db.prepare('DELETE FROM site_operators WHERE site_id = ? AND operator_id = ?').run(siteId, operatorId);
return { ok: true };
});
app.get('/api/admin/telegram/settings', async () => ({ app.get('/api/admin/telegram/settings', async () => ({
ok: true, ok: true,
settings: getTelegramSettings(), settings: getTelegramSettings(),