Files
our_recipe_atlas/src/app.js
T
2026-08-13 18:36:31 +09:00

93 lines
3.0 KiB
JavaScript

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;
}