| import fastify, { FastifyInstance } from 'fastify'; |
| import cors from '@fastify/cors'; |
| import multipart from '@fastify/multipart'; |
| import jwt from '@fastify/jwt'; |
| import rateLimit from '@fastify/rate-limit'; |
| import { prisma } from './services/prisma'; |
| import { runWithTenant } from '@repo/database'; |
| import { whatsappRoutes } from './routes/whatsapp'; |
| import { studentRoutes } from './routes/student'; |
| import { adminRoutes } from './routes/admin'; |
| import { organizationRoutes } from './routes/organizations'; |
| import { aiRoutes } from './routes/ai'; |
| import { paymentRoutes } from './routes/payments'; |
| import { analyticsRoutes } from './routes/analytics'; |
| import { billingRoutes } from './routes/billing'; |
| import { notificationRoutes } from './routes/notifications'; |
| import { authRoutes } from './routes/auth'; |
| import campaignRoutes from './routes/campaigns'; |
| import { internalRoutes } from './routes/internal'; |
| import { superAdminRoutes } from './routes/super-admin'; |
| import { setupErrorHandler } from './utils/errors'; |
| import { injectTenantConfig } from './middleware/tenant'; |
| import { validateApiKey } from './middleware/validateApiKey'; |
| import { verifyJwt } from './middleware/verifyJwt'; |
| import { enforceOrgIsolation } from './middleware/enforceOrgIsolation'; |
|
|
| export async function buildApp() { |
| const server: FastifyInstance = fastify({ |
| logger: process.env.NODE_ENV === 'test' ? false : true, |
| disableRequestLogging: process.env.NODE_ENV === 'production' |
| }); |
|
|
| server.decorate('prisma', prisma); |
|
|
| const corsOrigins = process.env.CORS_ORIGINS |
| ? process.env.CORS_ORIGINS.split(',').map(o => o.trim()) |
| : ['https://admin.xamle.studio', 'https://xamle.studio']; |
|
|
| await server.register(cors, { |
| origin: corsOrigins, |
| methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'], |
| allowedHeaders: ['Content-Type', 'Authorization', 'x-api-key', 'x-organization-id'], |
| credentials: true |
| }); |
|
|
| await server.register(multipart, { |
| limits: { fileSize: 10 * 1024 * 1024 } |
| }); |
|
|
| await server.register(jwt, { |
| secret: (() => { |
| if (!process.env.JWT_SECRET) throw new Error('JWT_SECRET env var is required'); |
| return process.env.JWT_SECRET; |
| })() |
| }); |
|
|
| await server.register(rateLimit, { |
| max: 200, |
| timeWindow: '1 minute', |
| keyGenerator: (req) => req.ip |
| }); |
|
|
| setupErrorHandler(server); |
|
|
| |
| server.register(async (scope) => { |
| scope.addHook('preHandler', async (request, reply) => { |
| const isApiKey = await validateApiKey(request); |
|
|
| if (isApiKey) { |
| request.organizationId = request.headers['x-organization-id'] as string; |
| } else { |
| await verifyJwt(request, reply); |
| if (reply.sent) return; |
|
|
| await enforceOrgIsolation(request, reply); |
| if (reply.sent) return; |
| } |
|
|
| await injectTenantConfig(request, reply); |
| if (reply.sent) return; |
|
|
| if (request.organizationId) { |
| return new Promise((resolve) => { |
| runWithTenant(request.organizationId as string, resolve); |
| }); |
| } |
| }); |
|
|
| scope.register(adminRoutes, { prefix: '/v1/admin' }); |
| scope.register(organizationRoutes, { prefix: '/v1/organizations' }); |
| scope.register(aiRoutes, { prefix: '/v1/ai' }); |
| scope.register(paymentRoutes, { prefix: '/v1/payments' }); |
| scope.register(analyticsRoutes, { prefix: '/v1/analytics' }); |
| scope.register(billingRoutes, { prefix: '/v1/billing' }); |
| scope.register(notificationRoutes, { prefix: '/v1/notifications' }); |
| scope.register(campaignRoutes, { prefix: '/v1/organizations' }); |
| }); |
|
|
| |
| |
| server.register(async (scope) => { |
| scope.addHook('preHandler', async (request, reply) => { |
| await verifyJwt(request, reply); |
| }); |
| scope.register(superAdminRoutes, { prefix: '/v1/super-admin' }); |
| }); |
|
|
| server.register(whatsappRoutes, { prefix: '/v1/whatsapp' }); |
| server.register(studentRoutes, { prefix: '/v1/student' }); |
| server.register(authRoutes, { prefix: '/v1/auth' }); |
|
|
| |
| server.register(async (scope) => { |
| scope.addHook('preHandler', async (request, reply) => { |
| const isApiKey = await validateApiKey(request); |
| if (!isApiKey) { |
| reply.code(401).send({ error: 'Unauthorized' }); |
| } |
| }); |
| scope.register(internalRoutes); |
| }); |
|
|
| return server; |
| } |
|
|