/** * Server – Fastify configuration with CORS, static, and WebSocket */ import Fastify from 'fastify'; import cors from '@fastify/cors'; import staticPlugin from '@fastify/static'; import websocket from '@fastify/websocket'; import { join, dirname } from 'path'; import { fileURLToPath } from 'url'; import type { DatabaseInterface } from './database/DatabaseInterface.js'; import { AuthService } from './auth/AuthService.js'; import { registerYjsWebSocket, initYjsPersistence, closeYjsPersistence } from './websocket/yjsServer.js'; const __dirname = dirname(fileURLToPath(import.meta.url)); export interface ServerOptions { port?: number; host?: string; db: DatabaseInterface; } export async function createServer(opts: ServerOptions) { const fastify = Fastify({ logger: true }); // CORS — restrictive origin list await fastify.register(cors, { origin: (origin: string | undefined, cb: (err: Error | null, allow: boolean) => void) => { // Allow requests with no origin (same-origin, curl, etc.) if (!origin) return cb(null, true); // Allow known origins const allowed = ['https://web-cad-neu.server.media-on.de', 'http://localhost:5173', 'http://localhost:8082']; if (allowed.includes(origin)) return cb(null, true); cb(new Error('Not allowed by CORS'), false); }, methods: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS'], }); // WebSocket await fastify.register(websocket, { options: { maxPayload: 1048576 }, }); // Static files (serve frontend build if exists) const frontendDist = join(__dirname, '..', '..', 'frontend', 'dist'); try { await fastify.register(staticPlugin, { root: frontendDist, prefix: '/', }); } catch { // Frontend dist may not exist in dev mode } // Health check fastify.get('/api/health', async () => ({ status: 'ok', timestamp: new Date().toISOString() })); // Register route modules const { registerProjectRoutes } = await import('./routes/projects.js'); const { registerDrawingRoutes } = await import('./routes/drawings.js'); const { registerLayerRoutes } = await import('./routes/layers.js'); const { registerElementRoutes } = await import('./routes/elements.js'); const { registerBlockRoutes } = await import('./routes/blocks.js'); const { registerSettingsRoutes } = await import('./routes/settings.js'); const { registerAuthRoutes } = await import('./routes/auth.js'); const { registerUserRoutes } = await import('./routes/users.js'); const { registerAIRoutes } = await import('./routes/ai.js'); const { registerNotificationRoutes } = await import('./routes/notifications.js'); const { registerShareRoutes } = await import('./routes/shares.js'); const { registerGlobalBlockRoutes } = await import('./routes/globalBlocks.js'); const authService = new AuthService(opts.db); // Auth middleware — protect all /api/ routes except auth login/register fastify.addHook('onRequest', async (request: any, reply: any) => { const url = request.url; // Skip auth endpoints and health check if (url === '/api/auth/login' || url === '/api/auth/register' || url === '/api/auth/logout' || url === '/api/health') return; // Skip non-API routes (static files, WebSocket) if (!url.startsWith('/api/')) return; const auth = request.headers.authorization; if (!auth || !auth.startsWith('Bearer ')) { return reply.code(401).send({ error: 'Authentication required' }); } const token = auth.slice(7); const user = authService.getUserFromSession(token); if (!user) { return reply.code(401).send({ error: 'Invalid or expired session' }); } (request as any).user = user; }); registerAuthRoutes(fastify, authService); registerProjectRoutes(fastify, opts.db, authService); registerDrawingRoutes(fastify, opts.db, authService); registerLayerRoutes(fastify, opts.db, authService); registerElementRoutes(fastify, opts.db, authService); registerBlockRoutes(fastify, opts.db, authService); registerSettingsRoutes(fastify, opts.db, authService); registerUserRoutes(fastify, opts.db, authService); registerAIRoutes(fastify, authService); registerNotificationRoutes(fastify, opts.db, authService); registerShareRoutes(fastify, opts.db, authService); registerGlobalBlockRoutes(fastify, opts.db, authService); // Yjs collaboration WebSocket registerYjsWebSocket(fastify, authService); return fastify; }