Files
family_wishlist/apps/backend/src/app.ts

87 lines
3.0 KiB
TypeScript

import Fastify, { type FastifyInstance } from 'fastify';
import helmet from '@fastify/helmet';
import sensible from '@fastify/sensible';
import { ZodError } from 'zod';
import { env } from './config/env.js';
import { HttpError } from './utils/errors.js';
import prismaPlugin from './plugins/prisma.js';
import corsPlugin from './plugins/cors.js';
import rateLimitPlugin from './plugins/rate-limit.js';
import authPlugin from './plugins/auth.js';
import guestPlugin from './plugins/guest.js';
import staticPlugin from './plugins/static.js';
import multipartPlugin from './plugins/multipart.js';
import authRoutes from './modules/auth/auth.routes.js';
import profileRoutes from './modules/profile/profile.routes.js';
import wishesRoutes from './modules/wishes/wishes.routes.js';
import imagesRoutes from './modules/images/images.routes.js';
import publicRoutes from './modules/public/public.routes.js';
import metaRoutes from './modules/meta/meta.routes.js';
import { registerPurgeTrashJob } from './jobs/purge-trash.js';
export async function buildApp(): Promise<FastifyInstance> {
const app = Fastify({
logger: {
level: env.LOG_LEVEL,
transport:
env.NODE_ENV === 'development'
? { target: 'pino-pretty', options: { translateTime: 'HH:MM:ss', singleLine: true } }
: undefined,
redact: {
paths: ['req.body.password', 'req.headers.cookie', 'req.headers.authorization'],
remove: true,
},
},
trustProxy: true,
bodyLimit: 1 * 1024 * 1024,
});
app.setErrorHandler((err, request, reply) => {
if (err instanceof HttpError) {
return reply.code(err.statusCode).send({
error: err.code,
message: err.message,
details: err.details,
});
}
if (err instanceof ZodError) {
return reply.code(400).send({
error: 'VALIDATION',
message: 'Invalid input',
details: err.flatten(),
});
}
if ((err as { statusCode?: number }).statusCode === 429) {
return reply.code(429).send({ error: 'RATE_LIMITED', message: err.message });
}
request.log.error({ err }, 'Unhandled error');
return reply.code(500).send({ error: 'INTERNAL', message: 'Internal server error' });
});
await app.register(helmet, {
contentSecurityPolicy: false,
crossOriginResourcePolicy: { policy: 'cross-origin' },
});
await app.register(sensible);
await app.register(corsPlugin);
await app.register(rateLimitPlugin);
await app.register(authPlugin);
await app.register(guestPlugin);
await app.register(staticPlugin);
await app.register(multipartPlugin);
await app.register(prismaPlugin);
await app.register(metaRoutes, { prefix: '/api' });
await app.register(authRoutes, { prefix: '/api/auth' });
await app.register(profileRoutes, { prefix: '/api/profile' });
await app.register(wishesRoutes, { prefix: '/api/wishes' });
await app.register(imagesRoutes, { prefix: '/api/wishes' });
await app.register(publicRoutes, { prefix: '/api/public' });
registerPurgeTrashJob(app);
return app;
}