93 lines
3.0 KiB
JavaScript
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;
|
|
}
|