refactor: 파일 분리 리팩토링 (routes, services, tools, utils)

아키텍처 개선:
- index.ts: 921줄 → 205줄 (77% 감소)
- openai-service.ts: 1,356줄 → 148줄 (89% 감소)

새로운 디렉토리 구조:
- src/routes/ - Webhook, API, Health check 핸들러
  - webhook.ts (287줄)
  - api.ts (318줄)
  - health.ts (14줄)

- src/services/ - 비즈니스 로직
  - bank-sms-parser.ts (143줄)
  - deposit-matcher.ts (88줄)

- src/tools/ - Function Calling 도구 모듈화
  - weather-tool.ts (37줄)
  - search-tool.ts (156줄)
  - domain-tool.ts (725줄)
  - deposit-tool.ts (183줄)
  - utility-tools.ts (60줄)
  - index.ts (104줄) - 도구 레지스트리

- src/utils/ - 유틸리티 함수
  - email-decoder.ts - Quoted-Printable 디코더

타입 에러 수정:
- routes/webhook.ts: text undefined 체크
- summary-service.ts: D1 타입 캐스팅
- summary-service.ts: Workers AI 타입 처리
- n8n-service.ts: Workers AI 타입 + 미사용 변수 제거

빌드 검증:
- TypeScript 타입 체크 통과
- Wrangler dev 로컬 빌드 성공

문서:
- REFACTORING_SUMMARY.md 추가
- ROUTE_ARCHITECTURE.md 추가

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
kappa
2026-01-19 15:36:17 +09:00
parent 3bf42947a7
commit ab6c9a2efa
18 changed files with 2578 additions and 1958 deletions

View File

@@ -0,0 +1,88 @@
import { BankNotification } from '../types';
/**
* 자동 매칭 결과
*/
export interface MatchResult {
transactionId: number;
userId: number;
amount: number;
}
/**
* 입금 SMS와 대기 중인 거래를 자동 매칭
*
* 매칭 조건:
* - 입금자명 앞 7글자 일치 (은행 SMS가 7글자까지만 표시)
* - 금액 일치
* - 상태가 'pending'인 거래
*
* 매칭 성공 시:
* - deposit_transactions.status = 'confirmed'
* - user_deposits.balance 증가
* - bank_notifications.matched_transaction_id 업데이트
*
* @param db - D1 Database 인스턴스
* @param notificationId - bank_notifications 테이블의 ID
* @param notification - 파싱된 은행 알림
* @returns 매칭 결과 또는 null (매칭 실패)
*/
export async function matchPendingDeposit(
db: D1Database,
notificationId: number,
notification: BankNotification
): Promise<MatchResult | null> {
// 매칭 조건: 입금자명(앞 7글자) + 금액이 일치하는 pending 거래
// 은행 SMS는 입금자명이 7글자까지만 표시됨
const pendingTx = await db.prepare(
`SELECT dt.id, dt.user_id, dt.amount
FROM deposit_transactions dt
WHERE dt.status = 'pending'
AND dt.type = 'deposit'
AND SUBSTR(dt.depositor_name, 1, 7) = ?
AND dt.amount = ?
ORDER BY dt.created_at ASC
LIMIT 1`
).bind(notification.depositorName, notification.amount).first<{
id: number;
user_id: number;
amount: number;
}>();
if (!pendingTx) {
console.log('[matchPendingDeposit] 매칭되는 pending 거래 없음');
return null;
}
console.log('[matchPendingDeposit] 매칭 발견:', pendingTx);
try {
// 트랜잭션: 거래 확정 + 잔액 증가 + 알림 매칭 업데이트
await db.batch([
db.prepare(
"UPDATE deposit_transactions SET status = 'confirmed', confirmed_at = CURRENT_TIMESTAMP WHERE id = ?"
).bind(pendingTx.id),
db.prepare(
'UPDATE user_deposits SET balance = balance + ?, updated_at = CURRENT_TIMESTAMP WHERE user_id = ?'
).bind(pendingTx.amount, pendingTx.user_id),
db.prepare(
'UPDATE bank_notifications SET matched_transaction_id = ? WHERE id = ?'
).bind(pendingTx.id, notificationId),
]);
console.log('[matchPendingDeposit] 매칭 완료:', {
transactionId: pendingTx.id,
userId: pendingTx.user_id,
amount: pendingTx.amount,
});
return {
transactionId: pendingTx.id,
userId: pendingTx.user_id,
amount: pendingTx.amount,
};
} catch (error) {
console.error('[matchPendingDeposit] DB 업데이트 실패:', error);
throw error;
}
}