87 lines
3.0 KiB
TypeScript
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;
|
|
}
|