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 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 { 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 }) // ── 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/' }) // ── 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) startJobs() return fastify }