import Fastify, { FastifyRequest, FastifyReply } from 'fastify' import jwt from '@fastify/jwt' import cookie from '@fastify/cookie' import cors from '@fastify/cors' import helmet from '@fastify/helmet' import rateLimit from '@fastify/rate-limit' import multipart from '@fastify/multipart' import staticFiles from '@fastify/static' import { join } from 'path' import { config } from './config' import './types' // side-effect: augments fastify types import fastifyWebsocket from '@fastify/websocket' import wsRoutes from './routes/ws' import authRoutes from './routes/auth' import hotelsRoutes from './routes/hotels' import roomsRoutes from './routes/rooms' import bookingsRoutes from './routes/bookings' import housekeepingRoutes from './routes/housekeeping' import channelsRoutes from './routes/channels' import usersRoutes from './routes/users' import netupRoutes from './routes/netup' import guestsRoutes from './routes/guests' import bookingGuestsRoutes from './routes/booking-guests' import hotelSettingsRoutes from './routes/hotel-settings' import rentalRoutes from './routes/rental' import categoriesRoutes from './routes/categories' import tariffsRoutes from './routes/tariffs' import ratePeriodsRoutes from './routes/rate-periods' import rateOverridesRoutes from './routes/rate-overrides' import uploadRoutes from './routes/upload' import housekeepingSettingsRoutes from './routes/housekeeping-settings' import notificationsRoutes from './routes/notifications' import scheduleRoutes from './routes/schedule' import loyaltyRoutes from './routes/loyalty' import chatRoutes from './routes/chat' import workstationRoutes from './routes/workstations' import agentReleaseRoutes from './routes/agent-release' import wifiSettingsRoutes, { wifiAuthVerify } from './routes/wifi-settings' import { setupAgentWsRoute } from './agent-ws' import { startJobs } from './jobs' export async function buildApp() { const fastify = Fastify({ logger: { level: config.nodeEnv === 'production' ? 'warn' : 'info', ...(config.nodeEnv !== 'production' && { transport: { target: 'pino-pretty', options: { colorize: true } }, }), }, bodyLimit: 20 * 1024 * 1024, // 20MB — for base64 photo uploads trustProxy: true, // get real IP from X-Real-IP / X-Forwarded-For via nginx }) // ── File uploads & static ───────────────────────────────────────────────── await fastify.register(multipart) const uploadsDir = process.env.UPLOADS_DIR ?? join(process.cwd(), 'uploads') await fastify.register(staticFiles, { root: uploadsDir, prefix: '/uploads/' }) const agentUpdatesDir = process.env.AGENT_UPDATES_DIR ?? join(process.cwd(), '..', 'agent-updates') await fastify.register(staticFiles, { root: agentUpdatesDir, prefix: '/agent-updates/', decorateReply: false }) // ── WebSocket ────────────────────────────────────────────────────────────── await fastify.register(fastifyWebsocket) // ── Security ─────────────────────────────────────────────────────────────── await fastify.register(helmet, { contentSecurityPolicy: false, crossOriginResourcePolicy: { policy: 'cross-origin' } }) await fastify.register(cors, { origin: config.cors.origins, credentials: true, methods: ['GET', 'POST', 'PATCH', 'PUT', 'DELETE', 'OPTIONS'], }) await fastify.register(rateLimit, { max: 200, timeWindow: '1 minute', }) // ── Cookies & JWT ────────────────────────────────────────────────────────── await fastify.register(cookie) await fastify.register(jwt, { secret: config.jwt.secret, }) // Authenticate decorator used as onRequest hook in routes fastify.decorate( 'authenticate', async (request: FastifyRequest, reply: FastifyReply) => { try { await request.jwtVerify() } catch (err) { reply.code(401).send({ error: 'Unauthorized' }) } }, ) // ── Health check ─────────────────────────────────────────────────────────── fastify.get('/health', async () => ({ status: 'ok', ts: new Date().toISOString() })) // ── Routes ───────────────────────────────────────────────────────────────── await fastify.register(wsRoutes) await fastify.register(authRoutes) await fastify.register(hotelsRoutes) await fastify.register(roomsRoutes) await fastify.register(bookingsRoutes) await fastify.register(housekeepingRoutes) await fastify.register(channelsRoutes) await fastify.register(usersRoutes) await fastify.register(netupRoutes) await fastify.register(guestsRoutes) await fastify.register(bookingGuestsRoutes) await fastify.register(hotelSettingsRoutes) await fastify.register(rentalRoutes) await fastify.register(categoriesRoutes) await fastify.register(tariffsRoutes) await fastify.register(ratePeriodsRoutes) await fastify.register(rateOverridesRoutes) await fastify.register(uploadRoutes) await fastify.register(housekeepingSettingsRoutes) await fastify.register(notificationsRoutes) await fastify.register(scheduleRoutes) await fastify.register(loyaltyRoutes) await fastify.register(chatRoutes) await fastify.register(workstationRoutes) await fastify.register(agentReleaseRoutes) await fastify.register(wifiSettingsRoutes) await fastify.register(wifiAuthVerify) await fastify.register(setupAgentWsRoute) startJobs() return fastify }