import Fastify from 'fastify'; import { ZodError } from 'zod'; import { loadConfig } from './config/env.js'; import authPlugin from './plugins/auth.js'; import mongoPlugin from './plugins/mongo.js'; import staticPlugin from './plugins/static.js'; import accessRoutes from './routes/access.routes.js'; import importRoutes from './routes/import.routes.js'; import recipeRoutes from './routes/recipe.routes.js'; import { createSourceExtractor } from './services/extractors/index.js'; import { ImageStorageService } from './services/image-storage.service.js'; import { RecipeParserService } from './services/recipe-parser.service.js'; import { AppError } from './utils/errors.js'; function registerErrorHandling(app) { app.setErrorHandler((error, request, reply) => { if (error instanceof ZodError) { return reply.code(400).send({ error: { code: 'VALIDATION_ERROR', message: error.issues[0]?.message ?? '입력값을 확인해 주세요.', details: error.issues, }, }); } if (error instanceof AppError) { if (error.statusCode >= 500) request.log.error({ error }, error.message); return reply.code(error.statusCode).send({ error: { code: error.code, message: error.message }, }); } request.log.error({ error }, '처리되지 않은 서버 오류'); return reply.code(500).send({ error: { code: 'INTERNAL_ERROR', message: '요청을 처리하지 못했습니다.', }, }); }); app.setNotFoundHandler((_request, reply) => reply.code(404).send({ error: { code: 'NOT_FOUND', message: '요청한 경로를 찾을 수 없습니다.' }, })); } export async function buildApp({ config = loadConfig(), dependencies = {} } = {}) { const app = Fastify({ logger: config.nodeEnv === 'test' ? false : { level: config.logLevel }, trustProxy: config.nodeEnv === 'production', }); app.decorate('config', config); registerErrorHandling(app); if ( dependencies.allowedEmailRepository && dependencies.recipeRepository && dependencies.userRepository ) { app.decorate('allowedEmailRepository', dependencies.allowedEmailRepository); app.decorate('recipeRepository', dependencies.recipeRepository); app.decorate('userRepository', dependencies.userRepository); } else { await app.register(mongoPlugin, { config }); } await app.register(authPlugin, { config, googleClient: dependencies.googleClient, }); app.decorate( 'sourceExtractor', dependencies.sourceExtractor ?? createSourceExtractor({ config }), ); app.decorate( 'recipeParser', dependencies.recipeParser ?? new RecipeParserService(config.minimax), ); app.decorate( 'imageStorage', dependencies.imageStorage ?? new ImageStorageService({ root: config.imageRoot }), ); await app.register(accessRoutes); await app.register(importRoutes); await app.register(recipeRoutes); await app.register(staticPlugin, { config }); await app.ready(); return app; }