import fs from 'fs'; import path from 'path'; import { fileURLToPath } from 'url'; import Fastify from 'fastify'; import fastifyCors from '@fastify/cors'; import fastifyStatic from '@fastify/static'; import config from './config/index.js'; import dbPlugin from './plugins/db.js'; import trackerPlugin from './plugins/tracker.js'; import routes from './routes/index.js'; const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); export async function buildApp(opts = {}) { const fastify = Fastify({ logger: { level: opts.logLevel || 'info', }, ...opts, }); // config 데코레이터 등록 fastify.decorate('config', config); // CORS await fastify.register(fastifyCors, { origin: true, }); // 플러그인 await fastify.register(dbPlugin); await fastify.register(trackerPlugin); // 라우트 await fastify.register(routes, { prefix: '/api' }); // 헬스 체크 fastify.get('/api/health', async () => { return { status: 'ok', timestamp: new Date().toISOString() }; }); // 정적 파일 서빙 (프론트엔드 빌드 결과물) const distPath = path.join(__dirname, '../dist'); if (fs.existsSync(distPath)) { await fastify.register(fastifyStatic, { root: distPath, prefix: '/', }); // SPA fallback fastify.setNotFoundHandler((request, reply) => { if (request.url.startsWith('/api/')) { return reply.code(404).send({ error: 'Not found' }); } return reply.sendFile('index.html'); }); } return fastify; }