Files
chat/backend/src/server.js
T

79 строки
2.0 KiB
JavaScript

import Fastify from 'fastify';
import cors from '@fastify/cors';
import rateLimit from '@fastify/rate-limit';
import websocket from '@fastify/websocket';
import { config } from './config.js';
const app = Fastify({
logger: {
transport: config.app.env === 'development' ? { target: 'pino-pretty' } : undefined
},
trustProxy: config.app.trustProxy
});
await app.register(cors, {
origin(origin, callback) {
if (!origin) return callback(null, true);
const allowed = new Set([config.cors.adminOrigin, config.cors.widgetOrigin]);
callback(null, allowed.has(origin));
},
credentials: true
});
await app.register(rateLimit, {
max: 120,
timeWindow: '1 minute'
});
await app.register(websocket);
const clients = new Map();
app.get('/health', async () => ({
ok: true,
service: 'raspi-chat-backend',
env: config.app.env
}));
app.get('/api/config/public', async () => ({
apiUrl: config.app.publicApiUrl,
wsUrl: config.app.publicWsUrl
}));
app.post('/api/widget/message', async (request) => {
const body = request.body || {};
const siteId = String(body.siteId || '');
const visitorId = String(body.visitorId || '');
const message = String(body.message || '').trim();
if (!siteId || !visitorId || !message) {
return { ok: false, error: 'siteId, visitorId and message are required' };
}
request.log.info({ siteId, visitorId }, 'Widget message received');
return {
ok: true,
status: 'accepted',
note: 'Telegram forwarding and persistence will be implemented next.'
};
});
app.get('/ws', { websocket: true }, (connection, request) => {
const visitorId = request.query?.visitorId || crypto.randomUUID();
clients.set(visitorId, connection);
connection.on('close', () => {
clients.delete(visitorId);
});
connection.send(JSON.stringify({ type: 'connected', visitorId }));
});
try {
await app.listen({ host: config.app.host, port: config.app.port });
} catch (error) {
app.log.error(error);
process.exit(1);
}