refactor: migrate API routes to Hono sub-router

- Create Hono router in api.ts
- Convert 6 API endpoints to Hono format:
  - GET /api/deposit/balance
  - POST /api/deposit/deduct
  - POST /api/test
  - POST /api/chat
  - POST /api/contact
  - GET /api/metrics
- Use Hono CORS middleware for /contact
- Remove manual handleApiRequest and handleContactPreflight
- Integrate with main app via app.route('/api', apiRouter)

Benefits:
- Cleaner declarative routing (44 insertions, 48 deletions)
- Built-in CORS middleware
- Better code organization

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
kappa
2026-01-29 09:48:07 +09:00
parent 2756dbe804
commit 86af187aa1
2 changed files with 43 additions and 47 deletions

View File

@@ -1,7 +1,7 @@
import { Env, EmailMessage, ProvisionMessage, MessageBatch } from './types'; import { Env, EmailMessage, ProvisionMessage, MessageBatch } from './types';
import { sendMessage, setWebhook, getWebhookInfo } from './telegram'; import { sendMessage, setWebhook, getWebhookInfo } from './telegram';
import { handleWebhook } from './routes/webhook'; import { handleWebhook } from './routes/webhook';
import { handleApiRequest } from './routes/api'; import { apiRouter } from './routes/api';
import { handleHealthCheck } from './routes/health'; import { handleHealthCheck } from './routes/health';
import { parseBankSMS } from './services/bank-sms-parser'; import { parseBankSMS } from './services/bank-sms-parser';
import { matchPendingDeposit } from './services/deposit-matcher'; import { matchPendingDeposit } from './services/deposit-matcher';
@@ -69,8 +69,8 @@ app.get('/webhook-info', async (c) => {
return c.json(result); return c.json(result);
}); });
// API routes // API routes - use Hono router
app.all('/api/*', (c) => handleApiRequest(c.req.raw, c.env, new URL(c.req.url))); app.route('/api', apiRouter);
// Telegram Webhook // Telegram Webhook
app.post('/webhook', (c) => handleWebhook(c.req.raw, c.env)); app.post('/webhook', (c) => handleWebhook(c.req.raw, c.env));

View File

@@ -1,4 +1,6 @@
import { z } from 'zod'; import { z } from 'zod';
import { Hono } from 'hono';
import { cors } from 'hono/cors';
import { Env } from '../types'; import { Env } from '../types';
import { sendMessage } from '../telegram'; import { sendMessage } from '../telegram';
import { import {
@@ -762,18 +764,6 @@ async function handleContactForm(request: Request, env: Env): Promise<Response>
} }
} }
/**
* OPTIONS /api/contact - CORS preflight for contact API
*
* @param env - Environment bindings
* @returns Response with CORS headers
*/
async function handleContactPreflight(env: Env): Promise<Response> {
return new Response(null, {
headers: getCorsHeaders(env),
});
}
/** /**
* GET /api/metrics - Circuit Breaker 상태 조회 (관리자 전용) * GET /api/metrics - Circuit Breaker 상태 조회 (관리자 전용)
* *
@@ -837,7 +827,7 @@ async function handleMetrics(request: Request, env: Env): Promise<Response> {
} }
/** /**
* API 엔드포인트 처리 (라우터) * API Router (Hono)
* *
* Manual Test: * Manual Test:
* 1. wrangler dev * 1. wrangler dev
@@ -867,41 +857,47 @@ async function handleMetrics(request: Request, env: Env): Promise<Response> {
* curl http://localhost:8787/api/metrics \ * curl http://localhost:8787/api/metrics \
* -H "Authorization: Bearer your-webhook-secret" * -H "Authorization: Bearer your-webhook-secret"
*/ */
export async function handleApiRequest(request: Request, env: Env, url: URL): Promise<Response> { const api = new Hono<{ Bindings: Env }>();
// Deposit API - 잔액 조회 (namecheap-api 전용)
if (url.pathname === '/api/deposit/balance' && request.method === 'GET') {
return handleDepositBalance(request, env, url);
}
// Deposit API - 잔액 차감 (namecheap-api 전용) // CORS middleware for /contact endpoint
if (url.pathname === '/api/deposit/deduct' && request.method === 'POST') { api.use('/contact', cors({
return handleDepositDeduct(request, env); origin: (origin, c) => {
} const allowedOrigin = c.env.HOSTING_SITE_URL || 'https://hosting.anvil.it.com';
return origin === allowedOrigin ? origin : null;
},
allowMethods: ['POST', 'OPTIONS'],
allowHeaders: ['Content-Type'],
}));
// 테스트 API - 메시지 처리 후 응답 직접 반환 // GET /deposit/balance - 잔액 조회 (namecheap-api 전용)
if (url.pathname === '/api/test' && request.method === 'POST') { api.get('/deposit/balance', async (c) => {
return handleTestApi(request, env); const url = new URL(c.req.url);
} return await handleDepositBalance(c.req.raw, c.env, url);
});
// Chat API - 인증된 채팅 API (프로덕션 활성화) // POST /deposit/deduct - 잔액 차감 (namecheap-api 전용)
if (url.pathname === '/api/chat' && request.method === 'POST') { api.post('/deposit/deduct', async (c) => {
return handleChatApi(request, env); return await handleDepositDeduct(c.req.raw, c.env);
} });
// 문의 폼 API (웹사이트용) // POST /test - 테스트 API (개발 환경 전용)
if (url.pathname === '/api/contact' && request.method === 'POST') { api.post('/test', async (c) => {
return handleContactForm(request, env); return await handleTestApi(c.req.raw, c.env);
} });
// CORS preflight for contact API // POST /chat - 인증된 채팅 API (프로덕션)
if (url.pathname === '/api/contact' && request.method === 'OPTIONS') { api.post('/chat', async (c) => {
return handleContactPreflight(env); return await handleChatApi(c.req.raw, c.env);
} });
// Metrics API - Circuit Breaker 상태 조회 (관리자 전용) // POST /contact - 문의 폼 API (웹사이트용)
if (url.pathname === '/api/metrics' && request.method === 'GET') { api.post('/contact', async (c) => {
return handleMetrics(request, env); return await handleContactForm(c.req.raw, c.env);
} });
return new Response('Not Found', { status: 404 }); // GET /metrics - Circuit Breaker 상태 조회 (관리자 전용)
} api.get('/metrics', async (c) => {
return await handleMetrics(c.req.raw, c.env);
});
export { api as apiRouter };