feat: add telegram-cli web chat interface and /api/chat endpoint
- Add telegram-cli Worker with web chat UI for browser-based bot testing - Add POST /api/chat authenticated endpoint (Bearer token, production enabled) - Fix ENVIRONMENT to production in wrangler.toml (was blocking Service Binding) - Add Service Binding (BOT_WORKER) for Worker-to-Worker communication - Add cloud-db-schema.sql for local development telegram-cli features: - Web UI at GET / with dark theme - JSON API at POST /api/chat - Service Binding to telegram-summary-bot Worker Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -34,6 +34,13 @@ const ContactFormBodySchema = z.object({
|
||||
name: z.string().optional(),
|
||||
});
|
||||
|
||||
const ChatApiBodySchema = z.object({
|
||||
message: z.string(),
|
||||
chat_id: z.number().optional(),
|
||||
user_id: z.number().optional(),
|
||||
username: z.string().optional(),
|
||||
});
|
||||
|
||||
/**
|
||||
* API Key 인증 검증 (Timing-safe comparison으로 타이밍 공격 방지)
|
||||
* @returns 인증 실패 시 Response, 성공 시 null
|
||||
@@ -356,6 +363,102 @@ async function handleTestApi(request: Request, env: Env): Promise<Response> {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* POST /api/chat - 인증된 채팅 API (프로덕션 활성화)
|
||||
*
|
||||
* @param request - HTTP Request with body
|
||||
* @param env - Environment bindings
|
||||
* @returns JSON response with AI response
|
||||
*/
|
||||
async function handleChatApi(request: Request, env: Env): Promise<Response> {
|
||||
const startTime = Date.now();
|
||||
|
||||
try {
|
||||
// Bearer Token 인증
|
||||
const authHeader = request.headers.get('Authorization');
|
||||
if (!env.WEBHOOK_SECRET || authHeader !== `Bearer ${env.WEBHOOK_SECRET}`) {
|
||||
logger.warn('Chat API - Unauthorized access attempt', { hasAuthHeader: !!authHeader });
|
||||
return Response.json({ error: 'Unauthorized' }, { status: 401 });
|
||||
}
|
||||
|
||||
// JSON 파싱 (별도 에러 핸들링)
|
||||
let jsonData: unknown;
|
||||
try {
|
||||
jsonData = await request.json();
|
||||
} catch {
|
||||
return Response.json({ error: 'Invalid JSON format' }, { status: 400 });
|
||||
}
|
||||
|
||||
const parseResult = ChatApiBodySchema.safeParse(jsonData);
|
||||
|
||||
if (!parseResult.success) {
|
||||
logger.warn('Chat API - Invalid request body', { errors: parseResult.error.issues });
|
||||
return Response.json({
|
||||
error: 'Invalid request body',
|
||||
details: parseResult.error.issues
|
||||
}, { status: 400 });
|
||||
}
|
||||
|
||||
const body = parseResult.data;
|
||||
|
||||
if (!body.message) {
|
||||
return Response.json({ error: 'message required' }, { status: 400 });
|
||||
}
|
||||
|
||||
// 기본값 설정
|
||||
const telegramUserId = body.user_id?.toString() || '821596605';
|
||||
const chatId = body.chat_id || 821596605;
|
||||
const chatIdStr = chatId.toString();
|
||||
const username = body.username || 'web-tester';
|
||||
|
||||
// 사용자 조회/생성
|
||||
const userId = await getOrCreateUser(env.DB, telegramUserId, 'WebUser', username);
|
||||
|
||||
let responseText: string;
|
||||
|
||||
// 명령어 처리
|
||||
if (body.message.startsWith('/')) {
|
||||
const [command, ...argParts] = body.message.split(' ');
|
||||
const args = argParts.join(' ');
|
||||
responseText = await handleCommand(env, userId, chatIdStr, command, args);
|
||||
} else {
|
||||
// 1. 사용자 메시지 버퍼에 추가
|
||||
await addToBuffer(env.DB, userId, chatIdStr, 'user', body.message);
|
||||
|
||||
// 2. AI 응답 생성
|
||||
responseText = await generateAIResponse(env, userId, chatIdStr, body.message, telegramUserId);
|
||||
|
||||
// 3. 봇 응답 버퍼에 추가
|
||||
await addToBuffer(env.DB, userId, chatIdStr, 'bot', responseText);
|
||||
|
||||
// 4. 임계값 도달시 프로필 업데이트
|
||||
const { summarized } = await processAndSummarize(env, userId, chatIdStr);
|
||||
if (summarized) {
|
||||
responseText += '\n\n👤 프로필이 업데이트되었습니다.';
|
||||
}
|
||||
}
|
||||
|
||||
const processingTimeMs = Date.now() - startTime;
|
||||
|
||||
logger.info('Chat API request processed', {
|
||||
user_id: telegramUserId,
|
||||
username,
|
||||
message_length: body.message.length,
|
||||
processing_time_ms: processingTimeMs,
|
||||
});
|
||||
|
||||
return Response.json({
|
||||
success: true,
|
||||
response: responseText,
|
||||
processing_time_ms: processingTimeMs,
|
||||
});
|
||||
} catch (error) {
|
||||
const processingTimeMs = Date.now() - startTime;
|
||||
logger.error('Chat API error', toError(error), { processing_time_ms: processingTimeMs });
|
||||
return Response.json({ error: 'Internal server error' }, { status: 500 });
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* POST /api/contact - 문의 폼 API (웹사이트용)
|
||||
*
|
||||
@@ -529,16 +632,21 @@ async function handleMetrics(request: Request, env: Env): Promise<Response> {
|
||||
* -H "X-API-Key: your-secret" \
|
||||
* -H "Content-Type: application/json" \
|
||||
* -d '{"telegram_id":"123","amount":1000,"reason":"test"}'
|
||||
* 4. Test API:
|
||||
* 4. Test API (dev only):
|
||||
* curl -X POST http://localhost:8787/api/test \
|
||||
* -H "Content-Type: application/json" \
|
||||
* -d '{"text":"hello","secret":"your-secret"}'
|
||||
* 5. Test contact (from allowed origin):
|
||||
* 5. Chat API (production-ready):
|
||||
* curl -X POST http://localhost:8787/api/chat \
|
||||
* -H "Authorization: Bearer your-webhook-secret" \
|
||||
* -H "Content-Type: application/json" \
|
||||
* -d '{"message":"서버 추천해줘","chat_id":821596605,"user_id":821596605,"username":"web-tester"}'
|
||||
* 6. Test contact (from allowed origin):
|
||||
* curl -X POST http://localhost:8787/api/contact \
|
||||
* -H "Origin: https://hosting.anvil.it.com" \
|
||||
* -H "Content-Type: application/json" \
|
||||
* -d '{"email":"test@example.com","message":"test message"}'
|
||||
* 6. Test metrics (Circuit Breaker status):
|
||||
* 7. Test metrics (Circuit Breaker status):
|
||||
* curl http://localhost:8787/api/metrics \
|
||||
* -H "Authorization: Bearer your-webhook-secret"
|
||||
*/
|
||||
@@ -558,6 +666,11 @@ export async function handleApiRequest(request: Request, env: Env, url: URL): Pr
|
||||
return handleTestApi(request, env);
|
||||
}
|
||||
|
||||
// Chat API - 인증된 채팅 API (프로덕션 활성화)
|
||||
if (url.pathname === '/api/chat' && request.method === 'POST') {
|
||||
return handleChatApi(request, env);
|
||||
}
|
||||
|
||||
// 문의 폼 API (웹사이트용)
|
||||
if (url.pathname === '/api/contact' && request.method === 'POST') {
|
||||
return handleContactForm(request, env);
|
||||
|
||||
Reference in New Issue
Block a user