1- import express from 'express' ;
1+ import path from 'node:path' ;
2+
23import cors from 'cors' ;
4+ import express from 'express' ;
35
4- import { healthRouter } from './routes/health' ;
6+ import type {
7+ AdminPinServiceImpl ,
8+ DbProvider ,
9+ JwtSessionManagerImpl ,
10+ TotpServiceImpl ,
11+ UserAuthService ,
12+ WhitelistServiceImpl ,
13+ } from '@fieldstack/core' with { "resolution-mode" : "import" } ;
14+
15+ import { validateEnv } from './config/env' ;
516import { errorHandler } from './middleware/error' ;
17+ import { createAuthRouter } from './routes/auth' ;
18+ import { healthRouter } from './routes/health' ;
619
7- export function createApp ( ) {
20+ // ── App 팩토리 ────────────────────────────────────────────────
21+
22+ export interface AppServices {
23+ jwtManager : JwtSessionManagerImpl ;
24+ whitelist : WhitelistServiceImpl ;
25+ adminPin : AdminPinServiceImpl ;
26+ totpService : TotpServiceImpl ;
27+ userAuth : UserAuthService ;
28+ }
29+
30+ export function createApp ( services ?: AppServices ) {
831 const app = express ( ) ;
932
1033 // ── Middleware ────────────────────────────────────────────────
11- // 개발: 모든 origin 허용 / 프로덕션: 단일 포트 통합 서빙이므로 CORS 불필요
1234 if ( process . env [ 'NODE_ENV' ] !== 'production' ) {
1335 app . use ( cors ( ) ) ;
1436 }
@@ -19,18 +41,57 @@ export function createApp() {
1941 // ── Core routes ───────────────────────────────────────────────
2042 app . use ( '/health' , healthRouter ) ;
2143
22- // TODO(Phase 1.9.3): 인증 라우트 마운트 (POST /auth/login 등)
23- // TODO(Phase 1.9.2): 모듈 라우트 마운트 (DB 초기화 완료 후)
44+ if ( services ) {
45+ app . use ( '/auth' , createAuthRouter ( services ) ) ;
46+ }
2447
2548 // ── Error handler (반드시 마지막) ─────────────────────────────
2649 app . use ( errorHandler ) ;
2750
2851 return app ;
2952}
3053
31- // ── DB 초기화 (서버 시작 시 호출) ────────────────────────────
54+ // ── DB 초기화 ──────────────────── ────────────────────────────
3255
33- export async function initDb ( ) : Promise < void > {
56+ export async function initDb ( ) : Promise < DbProvider > {
3457 const { getDb } = await import ( '@fieldstack/core' ) ;
35- await getDb ( ) ;
58+ return getDb ( ) ;
59+ }
60+
61+ // ── 마이그레이션 실행 ─────────────────────────────────────────
62+
63+ export async function runMigrations ( db : DbProvider ) : Promise < void > {
64+ const { FileMigrationRunner } = await import ( '@fieldstack/core' ) ;
65+ const coreDir = path . join ( __dirname , 'db' , 'migrations' , 'core' ) ;
66+ const runner = new FileMigrationRunner ( db , 'core' , coreDir ) ;
67+ await runner . run ( ) ;
68+ }
69+
70+ // ── 서비스 초기화 ─────────────────────────────────────────────
71+
72+ export async function initServices ( db : DbProvider ) : Promise < AppServices > {
73+ const env = validateEnv ( process . env ) ;
74+
75+ const {
76+ JwtSessionManagerImpl,
77+ WhitelistServiceImpl,
78+ AdminPinServiceImpl,
79+ TotpServiceImpl,
80+ UserAuthService,
81+ } = await import ( '@fieldstack/core' ) ;
82+
83+ const accessSecret = env . JWT_SECRET ?? 'dev-access-secret-change-in-production' ;
84+ const refreshSecret = env . JWT_REFRESH_SECRET ?? 'dev-refresh-secret-change-in-production' ;
85+
86+ if ( env . NODE_ENV === 'production' && ! env . JWT_SECRET ) {
87+ throw new Error ( '[fieldstack][api] JWT_SECRET must be set in production' ) ;
88+ }
89+
90+ const jwtManager = new JwtSessionManagerImpl ( db , accessSecret , refreshSecret ) ;
91+ const whitelist = new WhitelistServiceImpl ( db ) ;
92+ const adminPin = new AdminPinServiceImpl ( db ) ;
93+ const totpService = new TotpServiceImpl ( db , env . TOTP_ISSUER ) ;
94+ const userAuth = new UserAuthService ( db , jwtManager , whitelist , totpService ) ;
95+
96+ return { jwtManager, whitelist, adminPin, totpService, userAuth } ;
3697}
0 commit comments