feat: add Drizzle enums for schema

Made-with: Cursor
This commit is contained in:
Anton
2026-03-04 13:37:55 +03:00
parent 53525dcd52
commit a7394c4d9d
12 changed files with 749 additions and 0 deletions

76
src/app.ts Normal file
View File

@@ -0,0 +1,76 @@
import Fastify, { FastifyInstance } from 'fastify';
import { AppError } from './utils/errors.js';
import databasePlugin from './plugins/database.js';
import redisPlugin from './plugins/redis.js';
import securityPlugin from './plugins/security.js';
import rateLimitPlugin from './plugins/rateLimit.js';
import { env } from './config/env.js';
import { randomUUID } from 'node:crypto';
export async function buildApp(): Promise<FastifyInstance> {
const isDev = env.NODE_ENV === 'development';
const app = Fastify({
logger: {
level: isDev ? 'debug' : 'info',
transport:
isDev
? {
target: 'pino-pretty',
options: {
translateTime: 'HH:MM:ss Z',
ignore: 'pid,hostname',
},
}
: undefined,
},
requestIdHeader: 'x-request-id',
requestIdLogLabel: 'requestId',
genReqId: () => randomUUID(),
});
app.setErrorHandler((err: unknown, request, reply) => {
const error = err as Error & { statusCode?: number; validation?: unknown };
request.log.error({ err }, error.message);
if (err instanceof AppError) {
const statusCode = err.statusCode;
const payload = err.toJSON();
if (err.code === 'RATE_LIMIT_EXCEEDED' && 'retryAfter' in err) {
reply.header('Retry-After', String((err as AppError & { retryAfter?: number }).retryAfter ?? 60));
}
return reply.status(statusCode).send(payload);
}
if (error.validation) {
return reply.status(422).send({
error: {
code: 'VALIDATION_ERROR',
message: 'Validation failed',
details: error.validation,
},
});
}
const statusCode = error.statusCode ?? 500;
return reply.status(statusCode).send({
error: {
code: 'INTERNAL_ERROR',
message: env.NODE_ENV === 'production' ? 'Internal server error' : error.message,
},
});
});
app.addHook('onRequest', async (request, reply) => {
reply.header('x-request-id', request.id);
});
await app.register(redisPlugin);
await app.register(databasePlugin);
await app.register(securityPlugin);
await app.register(rateLimitPlugin);
app.get('/health', async () => ({ status: 'ok', timestamp: new Date().toISOString() }));
return app;
}