diff --git a/backend/src/server.js b/backend/src/server.js new file mode 100644 index 0000000..7bdccc2 --- /dev/null +++ b/backend/src/server.js @@ -0,0 +1,78 @@ +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); +}