Files
telegram-bot-workers/CLAUDE.md
kappa 2b1bc6a371 feat: improve server management and refund display
Server Management:
- Fix /server command API auth (query param instead of header)
- Show server specs (vCPU/RAM/Bandwidth) in /server list
- Prevent AI from refusing server deletion based on expiration date
- Add explicit instructions in tool description and system prompt

Refund Display:
- Show before/after balance in server deletion refund message
- Format: 환불 전 잔액 → 환불 금액 → 환불 후 잔액

Other Changes:
- Add stopped status migration for server orders
- Clean up callback handler (remove deprecated code)
- Update constants and pattern utilities

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-30 05:30:59 +09:00

1576 lines
56 KiB
Markdown
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# CLAUDE.md
> 🔧 **개발자용 문서**: 기술 상세, 코드 패턴, 트러블슈팅
> 📖 **[README.md](./README.md)**: 기능 소개, 배포 가이드, 사용법
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
## Auto-Read on Start
**세션 시작 시 반드시 수행:**
1. `README.md`를 Read 도구로 읽어 프로젝트 전체 구조 파악
2. 작업 대상 파일의 기존 코드 먼저 확인
```
필수 읽기: README.md → 작업 대상 파일
```
---
## Agent Usage Policy
**🎯 목표: 컨텍스트 절약 - 대부분의 작업을 에이전트에 위임**
**프로젝트 특성:**
- 언어: TypeScript (strict mode)
- 런타임: Cloudflare Workers
- 프레임워크: Wrangler, Workers AI, D1
- 주요 디렉토리: `src/tools/`, `src/routes/`, `src/services/`
**사용 가능한 에이전트 타입:**
- `coder`: 20년 이상의 경험을 갖고 있는 시니어 코딩 전문가 (코드 작성/수정 최우선)
- TypeScript/Cloudflare Workers 구현 마스터
- 프로덕션 수준의 코드 품질 보장
- 엔터프라이즈급 에러 핸들링 및 타입 안정성
- 성능 최적화 및 베스트 프랙티스 적용
- 도구: Read, Write, Edit, Bash, Glob, Grep (쓰기 가능)
- `explorer`: 코드베이스 탐색/분석 전문가 (thorough 레벨)
- 프로젝트 구조 파악 및 의존성 분석
- 대량 파일 읽기 및 패턴 인식
- 도구: Read, Grep, Glob (읽기 전용)
- `planner`: 설계/계획 수립 전문가
- 아키텍처 설계 및 구현 계획 작성
- 시스템 분석 및 개선 방향 제시
- 도구: Read, Grep, Glob (읽기 전용)
- `reviewer`: 코드 리뷰 전문가
- 품질/보안/성능 검토
- 페르소나 조합으로 전문화 (qa/security/performance)
- 도구: Read, Grep, Glob (읽기 전용)
- `Bash`: 빌드/배포/테스트 실행
- 긴 로그 출력 분리 (컨텍스트 절약)
- 시스템 명령어 실행
**CRITICAL: 다음 작업은 반드시 Task tool (agent)를 사용하여 메인 세션 컨텍스트 절약:**
| 작업 유형 | 조건 | 에이전트 타입 | 이유 |
|-----------|------|---------------|------|
| **코드 작성/수정** | 모든 코드 변경 | `coder` | TS/Workers 전문, 타입 안정성, 프로덕션 품질 |
| **리팩토링** | 파일 수 무관 | `coder` (병렬) | 일관성, 컨텍스트 분리, TS 최적화 |
| **Function Calling 도구** | 추가/수정 | `coder` (병렬) | tools/ + openai-service.ts 동시 처리 |
| **스키마 작업** | D1 마이그레이션 | `coder` | 백업→마이그레이션→검증 전체 위임 |
| **프로젝트 분석** | 구조 파악 | `explorer` (thorough) | 대량 파일 읽기 분리 |
| **설계/계획** | 아키텍처 설계 | `planner` | 시스템 분석 및 개선 방향 제시 |
| **코드 리뷰** | 보안/성능 | `reviewer``coder` | 분석 후 개선 제안 (reviewer는 읽기 전용) |
| **빌드/배포** | npm run, wrangler | `Bash` | 긴 로그 출력 분리 |
| **테스트** | 로컬 테스트 실행 | `Bash` | 테스트 출력 분리 |
**에이전트 위임의 이점:**
- ✅ 각 에이전트가 독립 컨텍스트 사용 (메인 세션 부담 0)
- ✅ 요약만 메인 세션에 반환 (토큰 대폭 절약)
- ✅ 병렬 처리 가능 (시간 단축)
- ✅ 메인 세션은 조율/지시만 담당
**병렬 처리 필수:**
- 독립적인 파일 여러 개 → 병렬 `coder` 에이전트
- 다른 디렉토리 동시 작업 → 병렬 `coder` 에이전트
- Function Calling 도구 추가 → tools/{new}.ts + openai-service.ts 병렬
**예시:**
```typescript
// ❌ 직접 수정 (컨텍스트 소모)
Read src/tools/weather-tool.ts
Edit src/tools/weather-tool.ts
Read src/openai-service.ts
Edit src/openai-service.ts
// ✅ coder 에이전트 사용 (컨텍스트 절약 + 전문성)
Task (subagent_type: "coder", 2 )
TypeScript , Workers
```
**직접 처리 (최소화):**
- 간단한 문서 읽기 (README.md 확인)
- 사용자와의 대화/질문
- 에이전트 작업 조율/검토
---
## Critical Rules
**절대 지켜야 할 규칙:**
| 규칙 | 이유 |
|------|------|
| 배포 전 `npm run dev` 로컬 테스트 | 프로덕션 장애 방지 |
| D1 스키마 변경 시 마이그레이션 SQL 별도 작성 | 기존 데이터 보존 |
| Secrets(BOT_TOKEN, API_KEY 등) 코드에 하드코딩 금지 | 보안 |
| `wrangler.toml`의 ID 값 변경 금지 | 리소스 연결 유지 |
| Function Calling 도구 추가 시 `tools` 배열 + `executeFunctionCall()` 동시 수정 | 불일치 방지 |
**위험한 작업:**
- `wrangler d1 execute` 직접 실행 (production) → 반드시 확인 요청
- `user_deposits`, `deposit_transactions` 테이블 직접 수정 → 금전 관련, 주의
---
## Documentation Rules
**작업 완료 후 문서 자동 업데이트:**
### 트리거 조건
| 변경 유형 | 업데이트 대상 |
|-----------|---------------|
| `src/*.ts` 파일 수정 | CLAUDE.md (Core Services, Key Patterns) |
| 새 Function Calling 도구 추가 | 양쪽 (README: 지원 기능 테이블, CLAUDE: tools 목록) |
| `schema.sql` 변경 | 양쪽 (Data Layer 섹션) |
| `wrangler.toml` 환경변수 추가 | 양쪽 (Configuration 섹션) |
| 외부 API 연동 추가/변경 | 양쪽 (External Integrations) |
| 봇 명령어 추가 | 양쪽 (Commands 섹션) |
### 업데이트 체크리스트
```
[ ] CLAUDE.md - 기술 상세 (개발자용)
[ ] README.md - 사용자 가이드 (배포/운영자용)
[ ] 다이어그램/플로우 수정 필요 여부
[ ] wrangler.toml 주석 업데이트
```
---
## API Documentation
**OpenAPI Specification**: `openapi.yaml`
**문서 보기**:
```bash
# Swagger UI로 보기 (로컬)
npx swagger-ui-watcher openapi.yaml
# Redoc으로 HTML 생성
npx redoc-cli bundle openapi.yaml -o docs/api.html
# OpenAPI 스펙 검증
npx @apidevtools/swagger-cli validate openapi.yaml
```
**주요 엔드포인트**:
| 엔드포인트 | 메서드 | 인증 | 용도 |
|-----------|--------|------|------|
| `/health` | GET | None | Health check (최소 정보만) |
| `/webhook-info` | GET | Query Param | Telegram Webhook 상태 조회 |
| `/setup-webhook` | GET | Query Param | Telegram Webhook 설정 |
| `/api/contact` | POST | CORS | 웹사이트 문의 폼 |
| `/api/deposit/balance` | GET | API Key | 잔액 조회 (Internal) |
| `/api/deposit/deduct` | POST | API Key | 잔액 차감 (Internal) |
| `/api/metrics` | GET | Bearer | Circuit Breaker 상태 |
**인증 방식**:
- **API Key**: `X-API-Key: {DEPOSIT_API_SECRET}` (Deposit API)
- **Bearer**: `Authorization: Bearer {WEBHOOK_SECRET}` (Metrics)
- **Query Param**: `?token={BOT_TOKEN}&secret={WEBHOOK_SECRET}` (Webhook)
- **CORS**: `hosting.anvil.it.com`만 허용 (Contact Form)
**External Consumers**:
- **namecheap-api**: `/api/deposit/*` 호출 (도메인 등록 시 잔액 조회/차감)
- **hosting.anvil.it.com**: `/api/contact` 호출 (웹사이트 문의 폼)
- **Monitoring Tools**: `/api/metrics` 조회 (시스템 상태 모니터링)
**Rate Limiting**:
- 사용자별 30 requests / 60초 (Telegram 메시지)
- KV Namespace 기반 분산 Rate Limiting
---
## Commands
```bash
npm run dev # 로컬 개발 (wrangler dev)
npm run deploy # Cloudflare Workers 배포
npm run db:init # D1 스키마 초기화 (production) ⚠️ 주의
npm run db:init:local # D1 스키마 초기화 (local)
npm run tail # Workers 로그 스트리밍
npm test # 단위 테스트 실행 (Vitest)
npm run test:watch # Watch 모드
npm run test:coverage # 커버리지 리포트
```
**KV Namespace 생성 (최초 1회):**
```bash
# Rate Limiting용 KV Namespace 생성 (필수)
wrangler kv:namespace create RATE_LIMIT_KV
# 서버 상담 세션용 KV Namespace 생성 (서버 추천 기능 사용 시 필수)
wrangler kv:namespace create SESSION_KV
# 출력된 id를 wrangler.toml의 [[kv_namespaces]] 섹션에 입력
```
**Secrets 설정:**
```bash
wrangler secret put BOT_TOKEN # Telegram Bot Token
wrangler secret put WEBHOOK_SECRET # Webhook 검증용
wrangler secret put OPENAI_API_KEY # OpenAI API 키
wrangler secret put NAMECHEAP_API_KEY # namecheap-api 래퍼 인증 키
wrangler secret put NAMECHEAP_API_KEY_INTERNAL # Namecheap API 키 (내부용)
wrangler secret put BRAVE_API_KEY # Brave Search API 키
wrangler secret put DEPOSIT_API_SECRET # Deposit API 인증 키
```
**Webhook 설정:**
```bash
# Webhook 설정 (token + secret 필요)
curl "https://telegram-summary-bot.kappa-d8e.workers.dev/setup-webhook?token=${BOT_TOKEN}&secret=${WEBHOOK_SECRET}"
# Webhook 정보 조회 (token + secret 필요)
curl "https://telegram-summary-bot.kappa-d8e.workers.dev/webhook-info?token=${BOT_TOKEN}&secret=${WEBHOOK_SECRET}"
```
**Database Migrations:**
```bash
# 로컬 테스트
wrangler d1 execute telegram-conversations --local --file=migrations/001_optimize_prefix_indexes.sql
# 프로덕션 적용 ⚠️ 주의: 데이터 백업 권장
wrangler d1 execute telegram-conversations --file=migrations/001_optimize_prefix_indexes.sql
# 롤백 (필요 시)
wrangler d1 execute telegram-conversations --file=migrations/001_rollback.sql
```
**마이그레이션 목록:**
| 파일 | 설명 | 적용일 |
|------|------|--------|
| `001_optimize_prefix_indexes.sql` | 입금자명 prefix 인덱스 최적화 (99% 성능 향상) | 2026-01-19 |
| `002_add_version_columns.sql` | Optimistic Locking (user_deposits.version) | 2026-01-20 |
| `003_add_server_tables.sql` | server_orders, server_specs 테이블 추가 | 2026-01-28 |
| `004_add_terminated_at.sql` | server_orders.terminated_at 컬럼 추가 | 2026-01-28 |
| `005_add_stopped_status.sql` | server_orders 테이블에 'stopped' 상태 추가 | 2026-01-29 |
**마이그레이션 작업 내용 (001):**
- `deposit_transactions.depositor_name_prefix` 컬럼 추가
- `bank_notifications.depositor_name_prefix` 컬럼 추가
- Partial Index 2개 생성 (pending 거래, unmatched 알림)
- 기존 데이터 backfill (SUBSTR 함수로 자동 채우기)
- 성능: Full Table Scan → Index Scan
**마이그레이션 작업 내용 (005):**
- `server_orders` 테이블 CHECK constraint 수정 (SQLite 제약사항으로 테이블 재생성)
- status 값에 'stopped' 추가 (pending, provisioning, active, stopped, failed, cancelled, terminated)
- 모든 인덱스 재생성 (idx_server_orders_user, idx_server_orders_status, idx_server_orders_idempotency_unique)
- 기존 데이터 보존 (임시 테이블 사용)
**검증 명령 (001):**
```sql
-- 인덱스 사용 확인
EXPLAIN QUERY PLAN
SELECT * FROM deposit_transactions
WHERE status = 'pending' AND type = 'deposit'
AND depositor_name_prefix = '홍길동아버' AND amount = 10000;
-- 결과에 "USING INDEX idx_transactions_prefix_pending" 포함되어야 함
```
**검증 명령 (005):**
```sql
-- CHECK constraint 확인 (stopped 상태 포함 여부)
SELECT sql FROM sqlite_master WHERE name = 'server_orders';
-- 인덱스 존재 확인
SELECT name FROM sqlite_master
WHERE type = 'index' AND tbl_name = 'server_orders';
-- stopped 상태 테스트 (에러 없이 성공해야 함)
-- INSERT INTO server_orders (user_id, spec_id, status, region, price_paid)
-- VALUES (1, 1, 'stopped', 'Tokyo', 1000);
```
---
## Code Style & Conventions
### TypeScript
- **Strict mode**: `tsconfig.json`에서 strict 활성화
- **타입 정의**: `types.ts`에 인터페이스 집중 관리
- **any 사용 금지**: 불가피한 경우 주석으로 이유 명시
### 에러 핸들링
```typescript
// 패턴: try-catch + 사용자 친화적 메시지 + 구조화된 로깅
import { createLogger } from './utils/logger';
const logger = createLogger('service-name');
try {
// 작업
} catch (error) {
logger.error('작업 실패', error as Error, { context: 'data' });
return '죄송합니다. 일시적인 오류가 발생했습니다.';
}
```
### 로깅 규칙
```typescript
// 구조화된 로깅 (Phase 5-3에서 도입)
import { createLogger } from './utils/logger';
const logger = createLogger('service-name');
logger.info('동작 설명', { key: 'value' }); // 정상 동작
logger.error('에러 설명', error as Error); // 에러
logger.warn('경고 메시지', { context: 'data' }); // 경고
// 성능 측정
const end = logger.startTimer('작업 완료');
await doWork();
end(); // duration 자동 기록
// wrangler tail로 확인 가능
// 프로덕션: JSON 형식, 개발: 읽기 쉬운 형식 자동 전환
```
### 네이밍
- 파일: `kebab-case.ts` (예: `openai-service.ts`)
- 함수: `camelCase` (예: `executeFunctionCall`)
- 상수: `UPPER_SNAKE_CASE` (예: `SUMMARY_THRESHOLD`)
- 타입/인터페이스: `PascalCase` (예: `TelegramUpdate`)
---
## Testing
### 자동화된 단위 테스트 (Vitest)
**프레임워크**: Vitest + Miniflare (Cloudflare Workers 환경 시뮬레이션)
**실행 명령어**:
```bash
npm test # 모든 테스트 실행
npm run test:watch # Watch 모드 (개발 중)
npm run test:coverage # 커버리지 리포트
```
**테스트 파일 구조**:
```
tests/
├── setup.ts # D1 Database 초기화 및 헬퍼 함수
└── deposit-agent.test.ts # 예치금 시스템 테스트 (50+ test cases)
vitest.config.ts # Vitest 설정 (Miniflare 바인딩)
```
**테스트 범위**:
| 기능 | 테스트 케이스 | 상태 |
|------|--------------|------|
| **음수 금액 거부** | 음수/0원 입금 시도 | ✅ |
| **동시성 처리** | 동일 사용자 동시 입금, Race condition | ✅ |
| **Batch 실패 처리** | db.batch() 부분 실패 시뮬레이션 | ✅ |
| **7글자 매칭** | "홍길동아버지님" → "홍길동아버지" 자동 매칭 | ✅ |
| **관리자 권한** | 비관리자 confirm/reject/pending 차단 | ✅ |
| **거래 상태** | confirmed 거래 취소 차단 | ✅ |
| **Edge Cases** | 999,999,999원, 특수문자, 1글자 이름 | ✅ |
**Mock 전략**:
- **D1 Database**: Miniflare in-memory SQLite
- **Environment Variables**: `vitest.config.ts`에서 바인딩
- **KV Namespace**: Rate Limiting 모킹
**헬퍼 함수** (`tests/setup.ts`):
```typescript
createTestUser(telegramId, username) // 테스트용 사용자 생성
createBankNotification(depositorName, amount) // 은행 알림 생성
createDepositTransaction(userId, amount, status) // 거래 생성
getTestDB() // DB 바인딩 가져오기
```
**추가 예정**:
- `openai-service.ts` - Function Calling 도구 테스트
- `summary-service.ts` - 프로필 시스템 테스트
- Integration Tests - 전체 워크플로우 테스트
---
### 수동 테스트 (Webhook)
**로컬 테스트 절차**:
```bash
# 1. 로컬 D1 초기화 (최초 1회)
npm run db:init:local
# 2. 로컬 서버 실행
npm run dev
# 3. 다른 터미널에서 테스트 요청
curl -X POST http://localhost:8787/webhook \
-H "Content-Type: application/json" \
-H "X-Telegram-Bot-Api-Secret-Token: test-secret" \
-d '{"message":{"chat":{"id":123},"text":"테스트"}}'
```
**배포 후 확인**:
```bash
# 로그 스트리밍
npm run tail
# Webhook 상태 확인 (token + secret 필요)
curl "https://telegram-summary-bot.kappa-d8e.workers.dev/webhook-info?token=${BOT_TOKEN}&secret=${WEBHOOK_SECRET}"
```
**수동 테스트 예제** (자동화 예정):
- `src/services/__test__/notification.test.ts` - 관리자 알림
- `src/utils/__test__/logger.test.ts` - 구조화된 로깅
---
## Web Chat Testing (telegram-cli)
**목적:** Claude Code가 봇과 대화하여 기능을 테스트할 때 사용
### API 엔드포인트
```bash
# 봇과 대화
curl -s -X POST 'https://telegram-cli-web.kappa-d8e.workers.dev/api/chat' \
-H 'Content-Type: application/json' \
-d '{"message": "안녕"}'
```
**응답 형식:**
```json
{
"response": "봇 응답 텍스트...",
"time_ms": 1234
}
```
### Claude가 사용하는 경우
**테스트 시나리오:**
```bash
# 1. 기본 대화
curl -X POST 'https://telegram-cli-web.kappa-d8e.workers.dev/api/chat' \
-H 'Content-Type: application/json' \
-d '{"message": "안녕하세요"}'
# 2. 예치금 기능
curl -X POST 'https://telegram-cli-web.kappa-d8e.workers.dev/api/chat' \
-H 'Content-Type: application/json' \
-d '{"message": "잔액 조회"}'
# 3. 도메인 기능
curl -X POST 'https://telegram-cli-web.kappa-d8e.workers.dev/api/chat' \
-H 'Content-Type: application/json' \
-d '{"message": "example.com 조회"}'
# 4. Function Calling 도구
curl -X POST 'https://telegram-cli-web.kappa-d8e.workers.dev/api/chat' \
-H 'Content-Type: application/json' \
-d '{"message": "서울 날씨"}'
```
### 다른 엔드포인트
| 엔드포인트 | 메서드 | 설명 |
|-----------|--------|------|
| `/` | GET | 웹 채팅 UI (브라우저) |
| `/health` | GET | Health check |
### 배포 정보
**Worker URL**: https://telegram-cli-web.kappa-d8e.workers.dev
**별도 Worker**: 메인 봇과 독립적으로 배포됨
**상세 문서**: [telegram-cli/README.md](../telegram-cli/README.md)
**Secrets 필요:**
- `BOT_TOKEN`: Telegram Bot Token (Vault: telegram-bot)
- `WEBHOOK_SECRET`: Bot Worker /api/test 인증용 (Vault: telegram-bot)
**배포 명령:**
```bash
cd telegram-cli
npm install
wrangler secret put BOT_TOKEN
wrangler secret put WEBHOOK_SECRET
npm run deploy
```
### 아키텍처
```
Claude Code (또는 브라우저)
POST /api/chat → telegram-cli Worker
Bot Worker (/api/test)
- 메인 봇과 동일한 로직
- DB 저장/조회
- AI 응답 생성
- Function Calling
응답 반환 { response, time_ms }
```
**특징:**
- 별도 Worker로 분리되어 메인 봇에 영향 없음
- 동일한 Bot Worker의 /api/test 엔드포인트 호출
- 응답 시간 측정 자동 포함
- username: 'web-tester'로 자동 설정
**사용 사례:**
- Claude Code가 봇 기능 테스트
- 개발자가 브라우저에서 빠른 테스트
- CI/CD 파이프라인에서 자동 테스트
---
## Troubleshooting
### 자주 발생하는 에러
| 증상 | 원인 | 해결 |
|------|------|------|
| `D1_ERROR: no such table` | 스키마 미적용 | `npm run db:init` 실행 |
| `401 Unauthorized` (OpenAI) | API 키 만료/잘못됨 | `wrangler secret put OPENAI_API_KEY` |
| Webhook 응답 없음 | Secret Token 불일치 | `WEBHOOK_SECRET` 재설정 후 `/setup-webhook` |
| Function Calling 무한 루프 | tool_choice 설정 오류 | `tool_choice: "auto"` 확인 |
| 프로필 업데이트 안됨 | 메시지 20개 미만 | `/context`로 버퍼 수 확인 |
| Email Worker 파싱 실패 | SMS 형식 변경 | `index.ts`의 정규식 패턴 확인 |
| **AI가 도구 호출 안 함** | 키워드 미인식 | 시스템 프롬프트 + 도구 description에 키워드 추가 |
| **예치금 최소 금액 제한** | Agent 프롬프트 문제 | Deposit Agent 프롬프트 수정 (OpenAI API) |
| **다른 사용자 응답 없음** | DB 작업 try-catch 누락 | `index.ts:handleMessage` 전체 try-catch 적용 (2026-01 수정) |
| **CORS 오류 (웹사이트 문의)** | 허용된 Origin 아님 | `hosting.anvil.it.com`만 허용됨 |
### 디버깅 명령어
```bash
# D1 데이터 직접 조회 (로컬)
wrangler d1 execute telegram-conversations --local --command "SELECT * FROM users LIMIT 5"
# D1 데이터 직접 조회 (production) ⚠️ 주의
wrangler d1 execute telegram-conversations --command "SELECT * FROM users LIMIT 5"
```
---
## Security
### Endpoint Security
**공개 엔드포인트:**
| 엔드포인트 | 보안 수준 | 설명 |
|-----------|----------|------|
| `/health` | 최소 정보만 | status, timestamp만 반환 (DB 정보 미노출) |
| `/webhook-info` | BOT_TOKEN + WEBHOOK_SECRET 필요 | Telegram Webhook 상태 조회 |
| `/setup-webhook` | BOT_TOKEN + WEBHOOK_SECRET 필요 | Webhook 설정 |
**인증 필요 엔드포인트:**
| 엔드포인트 | 인증 방식 | 권한 |
|-----------|----------|------|
| `/webhook` | Telegram Secret Token | Telegram만 호출 가능 |
| `/api/deposit/*` | X-API-Key 헤더 | namecheap-api 전용 |
| `/api/test` | WEBHOOK_SECRET | 테스트 전용 |
| `/api/contact` | CORS | hosting.anvil.it.com만 |
| `/api/metrics` | Bearer Token (WEBHOOK_SECRET) | 관리자 전용 (Circuit Breaker 상태) |
**CORS 정책:**
```typescript
// 문의 폼 API (POST /api/contact)
'Access-Control-Allow-Origin': 'https://hosting.anvil.it.com' // 특정 도메인만 허용
'Access-Control-Allow-Methods': 'POST, OPTIONS'
'Access-Control-Allow-Headers': 'Content-Type'
```
**Rate Limiting (Cloudflare KV 기반):**
- 사용자별 메시지 제한 (30 requests / 60초)
- KV Namespace: `RATE_LIMIT_KV` (`wrangler.toml`)
- 인스턴스 간 공유, 재시작 후 유지
- 자동 만료 (TTL), 분산 환경 일관성 보장
- 과도한 요청 시 경고 메시지 + 차단
### Health Check 정책
**이전 (보안 취약):**
```json
{
"status": "ok",
"timestamp": "...",
"stats": {
"users": 123, // DB 테이블 정보 노출
"summaries": 456 // 민감한 통계 노출
}
}
```
**현재 (보안 강화):**
```json
{
"status": "ok",
"timestamp": "2026-01-19T12:34:56.789Z" // 최소 정보만
}
```
**상세 정보 필요 시:**
- 별도 인증된 Admin 엔드포인트 추가 검토 (미구현)
- 또는 Cloudflare Dashboard의 Analytics 활용
---
## Admin Notification System
**목적:** 심각한 시스템 에러 발생 시 관리자에게 실시간 Telegram 알림
**파일:** `src/services/notification.ts`
**알림 유형:**
| 유형 | 트리거 조건 | 심각도 |
|------|------------|--------|
| `circuit_breaker` | Circuit Breaker OPEN 상태 전환 | 🚨 HIGH |
| `retry_exhausted` | 모든 재시도 실패 (3회) | ⚠️ MEDIUM |
| `api_error` | 치명적 API 에러 (5xx, Rate Limit) | 🔴 CRITICAL |
**Rate Limiting:**
- 같은 유형의 알림은 1시간에 1회만 전송
- KV Namespace 사용 (`RATE_LIMIT_KV`)
- 키: `notification:{type}:{service}`
- TTL: 3600초 (1시간)
**사용 예시:**
```typescript
import { notifyAdmin } from './services/notification';
import { sendMessage } from './telegram';
// Circuit Breaker가 OPEN 상태가 되었을 때
await notifyAdmin(
'circuit_breaker',
{
service: 'OpenAI API',
error: 'Connection timeout after 30s',
context: 'User message processing failed'
},
{
telegram: {
sendMessage: (chatId: number, text: string) =>
sendMessage(env.BOT_TOKEN, chatId, text)
},
adminId: env.DEPOSIT_ADMIN_ID || '',
env
}
);
```
**알림 메시지 형식:**
```
🚨 시스템 알림 (Circuit Breaker)
서비스: OpenAI API
에러: Connection timeout
상태: OPEN
시간: 2026-01-19 15:30:45
자동 복구 시도: 30초 후
```
**환경 변수:**
- `DEPOSIT_ADMIN_ID`: 관리자 Telegram Chat ID (wrangler.toml)
**통합 지점:**
- `utils/circuit-breaker.ts`: Circuit 차단 시
- `utils/retry.ts`: 재시도 실패 시
- `openai-service.ts`: OpenAI API 에러 시
- `tools/*.ts`: 외부 API 에러 시
**에러 핸들링:**
- 알림 전송 실패 시 로그만 기록하고 무시
- 메인 로직에 영향 없음
**테스트:**
```bash
# 테스트 엔드포인트를 index.ts에 임시 추가
curl https://your-worker.workers.dev/test-notification
# 로그 확인
npm run tail
```
---
## Architecture
**Message Flow:**
```
Telegram Webhook → Security Validation → Command/Message Router
┌──────────────────────────┴──────────────────────────┐
↓ ↓
Command Handler AI Response Generator
(commands.ts) (openai-service.ts)
Function Calling (8개)
(weather, search, time, calc, docs,
domain, suggest_domains, deposit)
Profile System
(summary-service.ts)
```
**Core Services:**
| 파일 | 역할 | 주요 함수 |
|------|------|----------|
| `index.ts` | Worker 진입점, Email Handler | `fetch()`, `email()` |
| `openai-service.ts` | AI 응답 + Function Calling | `generateResponse()`, `executeFunctionCall()` |
| `summary-service.ts` | 프로필 시스템 | `updateSummary()`, `getConversationContext()` |
| `deposit-agent.ts` | 예치금 함수 (코드 직접 처리) | `executeDepositFunction()` |
| `server-agent.ts` | 서버 전문가 AI 상담 | `processServerConsultation()`, `callServerExpertAI()` |
| `security.ts` | Webhook 보안, Rate Limiting (KV) | `validateWebhook()`, `checkRateLimit()` |
| `services/notification.ts` | 관리자 알림 (Circuit Breaker, Retry 실패) | `notifyAdmin()` |
| `commands.ts` | 봇 명령어 | `handleCommand()` |
| `telegram.ts` | Telegram API | `sendMessage()`, `sendTypingAction()` |
**Logging & Monitoring (Phase 5-3):**
| 파일 | 역할 | 주요 기능 |
|------|------|----------|
| `utils/logger.ts` | 구조화된 로깅 | JSON 기반 로그, 환경별 전환 (개발/프로덕션) |
| `utils/metrics.ts` | 성능 메트릭 수집 | API 호출 시간, 에러율, Circuit Breaker 상태 |
| `utils/circuit-breaker.ts` | Circuit Breaker | OpenAI API 보호, 자동 복구 |
| `utils/retry.ts` | 재시도 로직 | 지수 백오프, 15개 API 지원 |
**Logger 사용 예시:**
```typescript
import { createLogger } from './utils/logger';
const logger = createLogger('openai');
logger.info('AI 응답 생성', { model: 'gpt-4o-mini' });
logger.error('API 호출 실패', error as Error, { retryCount: 3 });
const end = logger.startTimer('처리 완료');
await process();
end(); // duration 자동 기록
```
**Function Calling Tools (9개):**
| 도구 | 함수명 | 외부 API | 트리거 키워드 |
|------|--------|----------|---------------|
| 날씨 | `get_weather` | wttr.in | 날씨 |
| 검색 | `search_web` | Brave Search | ~란, ~뭐야 (한글→영문 자동 번역) |
| 시간 | `get_current_time` | 내장 | 몇 시, 시간 |
| 계산 | `calculate` | 내장 | 계산, +, -, *, / |
| 문서 | `lookup_docs` | Context7 | 문서, 사용법, API |
| 도메인 | `manage_domain` | 코드 직접 처리 → Namecheap | 도메인, 네임서버, WHOIS |
| 도메인 추천 | `suggest_domains` | GPT + Namecheap | **도메인 추천, 도메인 제안, 도메인 아이디어** |
| 예치금 | `manage_deposit` | 코드 직접 처리 | **입금, 충전, 잔액, 계좌, 송금** |
| 서버 | `manage_server` | Server Expert AI (세션 기반 상담) | **서버, VPS, 클라우드, 호스팅, 추천** |
**Data Layer (D1 SQLite):**
| 테이블 | 용도 | 주요 컬럼 |
|--------|------|----------|
| `users` | 사용자 | telegram_id, username |
| `message_buffer` | 대화 기록 | user_id, role, content |
| `summaries` | 프로필 | user_id, generation, summary |
| `user_deposits` | 예치금 계정 | user_id, balance |
| `deposit_transactions` | 거래 내역 | user_id, amount, status |
| `bank_notifications` | SMS 파싱 | depositor_name, amount, bank |
| `user_domains` | 도메인 소유권 | user_id, domain, verified (등록 시 자동 추가) |
**AI Fallback:** OpenAI 미설정 시 Workers AI (Llama 3.1 8B) 자동 전환
### 에러 핸들링 구조 (index.ts:handleMessage)
```
Webhook 수신
보안 검증 실패 → 401 반환 (로그 기록)
Rate Limit 초과 → 경고 메시지 전송 + return
사용자 DB 조회/생성 (try-catch)
↓ 실패 시 → "일시적인 오류" 메시지 전송 + return
메시지 처리 (전체 try-catch)
├── 명령어 처리 (handleCommand)
└── AI 응답 생성 (generateAIResponse)
↓ 실패 시 → "메시지 처리 오류" 메시지 전송
응답 전송 (sendMessage)
```
**중요:** 모든 DB 작업과 AI 호출은 try-catch로 감싸서 오류 시에도 사용자에게 메시지 전송
### 동적 도구 로딩
**목적:** 토큰 절약 + AI 선택 정확도 향상
```
사용자 메시지 → 키워드 패턴 매칭 → 관련 도구만 선택 → AI 호출
```
**카테고리 분류:**
| 카테고리 | 도구 | 감지 패턴 |
|----------|------|-----------|
| domain | manage_domain, suggest_domains | 도메인, 네임서버, whois, .com |
| deposit | manage_deposit | 입금, 충전, 잔액, 계좌 |
| weather | get_weather | 날씨, 기온, 비, 눈 |
| search | search_web, lookup_docs | 검색, 찾아, 뭐야, 가격 |
| utility | get_current_time, calculate | (항상 포함) |
**폴백:** 패턴 매칭 없으면 전체 도구 사용
**로그:** `[ToolSelector] 카테고리: domain, utility / 선택된 도구: manage_domain, ...`
### Server Expert AI Flow
**목적:** 30년 경력 시니어 아키텍트 페르소나 기반 대화형 서버 추천
```
사용자: "서버 추천해줘"
메인 AI → manage_server(action="start_consultation")
KV 세션 생성 (server_session:{userId}, TTL 1h)
Server Expert AI (gpt-4o-mini + Function Calling)
├── 페르소나: 30년 경력 클라우드 아키텍트
├── 용도/규모 파악 (최대 2번 질문)
├── [선택] search_trends 호출 (최신 트렌드)
├── [선택] lookup_framework_docs 호출 (공식 권장 스펙)
└── JSON 응답 {action, message, collectedInfo}
┌──────────────────┬──────────────────┐
│ action="question"│ action="recommend"|
├──────────────────┼──────────────────┤
│ 추가 정보 수집 │ 자동 스펙 추론 │
│ (용도, 규모) │ (tech_stack 등) │
│ 세션 유지 │ Cloud Orchestrator│
│ │ API 호출 │
│ │ 세션 삭제 │
└──────────────────┴──────────────────┘
서버 추천 결과 반환
```
**세션 관리:**
- KV Namespace: `SESSION_KV`
- Key: `server_session:{userId}`
- TTL: 1시간 (자동 만료)
- 상태: `gathering``recommending``completed`
**Function Calling 도구:**
| 도구 | 설명 | 용도 |
|------|------|------|
| `search_trends` | Brave Search API | 최신 기술 트렌드, 서버 요구사항 검색 |
| `lookup_framework_docs` | Context7 API | 프레임워크 공식 문서에서 권장 스펙 조회 |
**자동 추론 (30년 경험 기반):**
| 용도 | 추론된 tech_stack | 추론된 expected_users |
|------|-------------------|---------------------|
| 블로그 / WordPress | `['wordpress']` | 100명 (개인용) |
| 쇼핑몰 / 이커머스 | `['ecommerce']` | 500명 (사업용) |
| 커뮤니티 / 게시판 | `['php', 'mysql']` | - |
| API / 백엔드 | `['nodejs', 'express']` | - |
| 기본값 | `['web']` | 100명 |
**특징:**
- 경쟁사 (AWS, GCP, Azure, Vultr, Linode) 언급 금지
- Anvil 서버만 추천
- 최대 3회 도구 호출 (무한 루프 방지)
- "모르겠어요", "아무거나" → 즉시 추천 (기본값)
- 질문 최대 2번, 이후 자동 추천
**관련 파일:**
| 파일 | 역할 |
|------|------|
| `server-agent.ts` | 세션 관리 + Server Expert AI 호출 |
| `tools/server-tool.ts` | Cloud Orchestrator API 연동 |
| `tools/search-tool.ts` | search_trends, lookup_framework_docs 구현 |
---
## Key Patterns
### Function Calling 추가 방법
```typescript
// 1. openai-service.ts의 tools 배열에 추가
const tools = [
// ... 기존 도구들
{
type: "function",
function: {
name: "new_tool",
description: "도구 설명",
parameters: { /* JSON Schema */ }
}
}
];
// 2. executeFunctionCall()에 케이스 추가
case 'new_tool':
return await executeNewTool(args);
```
### 프로필 시스템 흐름 (3개 요약 통합 방식)
```
메시지 수신 → message_buffer 저장 (최대 19개)
↓ 20개 도달
┌──────────────┴──────────────┐
↓ ↓
기존 요약 3개 조회 사용자 발언만 추출
↓ ↓
└──────────→ OpenAI 통합 분석 ←┘
summaries 테이블 저장 (generation++)
구버전 삭제 (최근 3개만 유지)
```
**통합 분석 방식:**
- 기존: 최신 요약 1개만 참조하여 업데이트
- 변경: **모든 요약 (최대 3개) + 새 메시지 → AI가 통합 분석**
### Context Enrichment
```typescript
// getConversationContext() 반환값 구조
{
previousSummary: Summary | null, // 최신 요약 (호환성)
summaries: Summary[], // 전체 요약 (최대 3개, 최신순)
recentMessages: BufferedMessage[],
totalMessages: number,
}
```
**시스템 프롬프트에 통합:**
```
## 사용자 프로필 (3개 버전 통합)
[v1] 초기 프로필 내용...
[v2] 업데이트된 프로필...
[v3] 최신 프로필...
최신 버전을 우선시하되, 이전 버전 맥락도 고려
```
### AI 시스템 프롬프트 (`summary-service.ts`)
```
- 날씨, 시간, 계산 요청은 제공된 도구를 사용하세요.
- 최신 정보, 실시간 데이터, 현재 가격, 뉴스 등은 search_web 도구로 검색하세요.
- 예치금, 입금, 충전, 잔액, 계좌 관련 요청은 반드시 manage_deposit 도구를 사용하세요.
- 도메인 추천, 도메인 제안, 도메인 아이디어 요청은 반드시 suggest_domains 도구를 사용하세요.
- 기타 도메인 관련 요청(조회, 등록, 네임서버 등)은 manage_domain 도구를 사용하세요.
- manage_deposit, manage_domain, suggest_domains 도구 결과는 그대로 전달하세요.
```
**중요:** 메인 AI가 도구를 호출하지 않고 직접 답변하는 경우:
1. 시스템 프롬프트에 해당 키워드 추가 (`summary-service.ts:252-254`)
2. 도구 description에 키워드 명시 (`openai-service.ts` tools 배열)
### 검색 한글→영문 자동 번역
```
"판골린 VPN" → GPT-4o-mini 번역 → "Pangolin VPN" → Brave Search
```
**동작:**
- 한글 포함 검색어 감지 (`/[가-힣]/`)
- GPT-4o-mini로 영문 번역 (외래어/기술용어 원어 복원)
- 번역된 쿼리로 검색
- 결과에 원본+번역 표시: `검색 결과: 판골린 VPN (→ Pangolin VPN)`
**로그:** `[search_web] 번역: "판골린 VPN" → "Pangolin VPN"`
---
## Configuration
### wrangler.toml 환경변수
**기본 설정:**
| 변수 | 기본값 | 설명 |
|------|--------|------|
| `SUMMARY_THRESHOLD` | 20 | 프로필 업데이트 주기 (메시지 수) |
| `MAX_SUMMARIES_PER_USER` | 3 | 유지할 프로필 버전 수 |
| `DOMAIN_OWNER_ID` | - | 도메인 관리 권한 Telegram ID |
| `DEPOSIT_ADMIN_ID` | - | 예치금 관리 권한 Telegram ID |
**외부 API 엔드포인트 (커스터마이징 가능):**
| 변수 | 기본값 | 설명 |
|------|--------|------|
| `OPENAI_API_BASE` | `https://gateway.ai.cloudflare.com/v1/.../openai` | OpenAI API Gateway URL |
| `NAMECHEAP_API_URL` | `https://namecheap-api.anvil.it.com` | Namecheap API 래퍼 URL |
| `WHOIS_API_URL` | `https://whois-api-...vercel.app` | WHOIS 조회 API URL |
| `CONTEXT7_API_BASE` | `https://context7.com/api/v2` | Context7 문서 조회 API |
| `BRAVE_API_BASE` | `https://api.search.brave.com/res/v1` | Brave Search API |
| `WTTR_IN_URL` | `https://wttr.in` | 날씨 조회 API |
| `HOSTING_SITE_URL` | `https://hosting.anvil.it.com` | 공식 웹사이트 URL |
**환경별 설정:**
개발/스테이징/프로덕션 환경별로 다른 API 엔드포인트를 사용할 수 있습니다.
기본값이 설정되어 있으므로 생략 가능하며, 필요시에만 override하세요.
**Secrets (wrangler secret):**
| 변수 | 설명 | 저장 위치 |
|------|------|----------|
| `BOT_TOKEN` | Telegram Bot Token | Vault: telegram-bot |
| `WEBHOOK_SECRET` | Telegram Webhook 인증 | Vault: telegram-bot |
| `OPENAI_API_KEY` | OpenAI API 키 | - |
| `NAMECHEAP_API_KEY` | namecheap-api 래퍼 인증 | - |
| `NAMECHEAP_API_KEY_INTERNAL` | Namecheap API 키 (내부) | - |
| `BRAVE_API_KEY` | Brave Search API 키 | - |
| `DEPOSIT_API_SECRET` | Deposit API 인증 | - |
**KV Namespaces:**
| Binding | 설명 | 생성 명령 |
|---------|------|----------|
| `RATE_LIMIT_KV` | Rate Limiting 저장소 | `wrangler kv:namespace create RATE_LIMIT_KV` |
| `SESSION_KV` | 서버 상담 세션 저장소 | `wrangler kv:namespace create SESSION_KV` |
**Bindings:**
| Binding | 타입 | 용도 |
|---------|------|------|
| `DB` | D1 Database | 사용자/메시지/예치금 데이터 |
| `AI` | Workers AI | OpenAI 폴백용 |
| `RATE_LIMIT_KV` | KV Namespace | 사용자별 Rate Limiting (30 req/60s) |
| `SESSION_KV` | KV Namespace | 서버 상담 세션 저장 (1시간 TTL) |
---
## Performance Optimizations
### N+1 쿼리 제거
**Cron 스케줄러 (만료 거래 정리):**
- 이전: 반복문 내 N개 UPDATE 쿼리
- 개선: 단일 IN 절 쿼리 + 병렬 알림
- 성능: **99% 쿼리 감소** (100건 기준: 101 → 1 쿼리)
**Email Handler (SMS 매칭):**
- 이전: 순차적 2개 SELECT 쿼리
- 개선: JOIN으로 단일 쿼리
- 성능: **50% 응답 시간 단축**
### API 호출 최적화
**도메인 추천 (suggest_domains):**
- 이전: TLD별 순차 가격 조회
- 개선: Promise.all 병렬 처리
- 성능: **80% 시간 단축** (5개 TLD: 1초 → 0.2초)
### 캐싱 전략
**KV Namespace 활용:**
- TLD 가격: 1시간 TTL
- Rate Limiting: 사용자별 60초 윈도우
- 알림 Rate Limit: 1시간 (같은 타입)
- 서버 상담 세션: 1시간 TTL (자동 만료)
---
## External Integrations
**URL 커스터마이징:** 모든 외부 API URL은 `wrangler.toml`의 환경변수로 설정 가능합니다.
환경별로 다른 엔드포인트를 사용하거나, 자체 호스팅된 서비스로 교체할 수 있습니다.
| 서비스 | 용도 | 엔드포인트 | 주의사항 |
|--------|------|-----------|----------|
| **AI Gateway** | OpenAI 프록시 | gateway.ai.cloudflare.com | 지역 제한 우회, 로그/캐시 |
| Context7 | 문서 조회 | context7.com API | - |
| Namecheap API | 도메인 백엔드 | namecheap-api.anvil.it.com | 날짜: MM/DD/YYYY → ISO 변환 |
| WHOIS API | WHOIS 조회 | whois-api-eight.vercel.app | ccSLD 미지원 |
| wttr.in | 날씨 | wttr.in | - |
| Brave Search | 검색 | api.search.brave.com | Free AI 플랜 (2,000/월) |
| Vault | API 키 관리 | vault.anvil.it.com | - |
| Email Routing | 입금 SMS 수신 | Cloudflare Email Routing | Worker email handler로 직접 처리 |
### Cloudflare AI Gateway
OpenAI API 호출을 Cloudflare AI Gateway를 통해 프록시하여 지역 제한 우회
```
Gateway ID: telegram-bot
Account ID: d8e5997eb4040f8b489f09095c0f623c
URL: gateway.ai.cloudflare.com/v1/{account_id}/telegram-bot/openai/...
```
**적용 범위:**
- ✅ Chat Completions - AI Gateway 경유
- ✅ 예치금 관리 - 코드 직접 처리 (Assistants API 제거)
- ✅ 도메인 관리 - 코드 직접 처리
**대시보드:** https://dash.cloudflare.com → AI → AI Gateway → telegram-bot
---
## Deposit System
**자동 매칭 흐름:**
```
[시나리오 1: 사용자 먼저]
"홍길동 50000원 입금" → bank_notifications 검색
매칭 O → confirmed + 잔액↑ | 매칭 X → pending
[시나리오 2: SMS 먼저 - Email Routing]
은행 SMS → 메일 전달 → Cloudflare Email Routing → Worker (email handler)
파싱 → bank_notifications 저장
deposit_transactions 검색 (pending)
매칭 O → confirmed + 잔액↑ + 사용자/관리자 알림
매칭 X → 저장만 + 관리자 알림
```
**알림 시스템:**
| 이벤트 | 사용자 알림 | 관리자 알림 |
|--------|-------------|-------------|
| 자동 매칭 성공 | ✅ 입금액 + 현재 잔액 | ✅ 입금 정보 + 매칭 완료 |
| 매칭 대기 (SMS만) | - | ✅ 입금 정보 + 대기 상태 |
**매칭 로직 (입금자명 7글자 제한):**
```
은행 SMS는 입금자명을 7글자까지만 표시
사용자 입력: "홍길동아버지님" (8글자)
은행 SMS: "홍길동아버지" (7글자)
매칭 시 SUBSTR(depositor_name, 1, 7) 비교 → 매칭 성공
```
- `deposit-agent.ts:72`: 사용자 입력의 앞 7글자로 bank_notifications 검색
- `index.ts:908`: deposit_transactions의 앞 7글자와 SMS 입금자명 비교
**Email Routing 설정:**
- Cloudflare Dashboard → Email → Email Routing → Routes
- 수신 주소 → Worker: `telegram-summary-bot` 라우팅
**입금 계좌:** 하나은행 427-910018-27104 (주식회사 아이언클래드)
- Vault 경로: `secret/companies/ironclad-corp`
**아키텍처 변경 (2026-01):** Assistants API → 코드 직접 처리
| 구분 | 이전 (Agent) | 현재 (코드) |
|------|-------------|-------------|
| 의도 파악 | Deposit Agent | 메인 AI (action 파라미터) |
| API 호출 | Agent Function Calling | `executeDepositFunction()` |
| 응답 형식 | Agent 생성 (불안정) | 코드 고정 (100% 일관성) |
| 지역 제한 | ❌ Assistants API 403 | ✅ AI Gateway 경유 |
**manage_deposit 도구 파라미터:**
```typescript
{
action: 'balance' | 'account' | 'request' | 'history' | 'cancel' | 'pending' | 'confirm' | 'reject',
depositor_name?: string, // request용
amount?: number, // request용 (자연어→숫자 변환)
transaction_id?: number, // cancel, confirm, reject용
limit?: number // history용 (기본 10)
}
```
**응답 포맷 (고정):**
```
잔액 조회: "💰 현재 잔액: 10,000원"
입금 성공: "✅ 입금 확인 완료! • 입금액: 5,000원 • 현재 잔액: 15,000원"
입금 대기: "📋 입금 요청 등록 (#123) • 입금액: 5,000원"
거래 내역: "#5: 입금 10원 ✓ (01/17)" (✓확인, ⏳대기, ✗취소)
```
**action별 처리:**
| 함수 | 설명 | 권한 |
|------|------|------|
| `get_balance` | 잔액 조회 | 모든 사용자 |
| `get_account_info` | 입금 계좌 안내 | 모든 사용자 |
| `request_deposit` | 입금 신고 | 모든 사용자 |
| `get_transactions` | 거래 내역 | 모든 사용자 |
| `cancel_transaction` | 입금 취소 | 모든 사용자 |
| `get_pending_list` | 대기 목록 | 관리자 |
| `confirm_deposit` | 입금 확인 | 관리자 |
| `reject_deposit` | 입금 거절 | 관리자 |
### Cron 자동 취소 (24시간)
**목적:** 24시간 이상 대기 중인 입금 요청 자동 취소 + 사용자 알림
```
wrangler.toml:
crons = ["0 15 * * *"] # UTC 15:00 = KST 00:00 (매일 자정)
index.ts (scheduled 핸들러):
1. pending + created_at > 24시간 거래 조회
2. status → cancelled 업데이트
3. 사용자에게 Telegram 알림 전송
```
**wrangler.toml 설정:**
```toml
[triggers]
crons = ["0 15 * * *"] # KST 00:00
```
**사용자 알림 메시지:**
```
⏰ 입금 대기 자동 취소
거래 #123이 24시간 내 확인되지 않아 자동 취소되었습니다.
• 입금액: 10,000원
• 입금자: 홍길동
실제 입금하셨다면 다시 신고해주세요.
```
### 거래 내역 표시 형식
**응답 포맷 (`formatDepositResult`):**
```
#5: 입금 10,000원 ✓ (01/17)
#4: 출금 5,000원 ✓ (01/15) - 도메인 등록: example.com
#3: 입금 20,000원 ⏳ (01/14)
#2: 입금 5,000원 ✗ (01/10)
```
**상태 아이콘:**
| 상태 | 아이콘 | 설명 |
|------|--------|------|
| `confirmed` | ✓ | 확인 완료 |
| `pending` | ⏳ | 대기 중 |
| `cancelled` / `rejected` | ✗ | 취소/거절 |
**타입 라벨:**
| DB 값 | 표시 |
|-------|------|
| `deposit` | 입금 |
| `withdrawal` | 출금 |
| `refund` | 환불 |
**description 필드:** 거래 사유 (예: "도메인 등록: example.com")
### Transaction Isolation & Optimistic Locking
**문제:** D1 `batch()`는 진정한 트랜잭션이 아니므로 부분 실패 시 데이터 불일치 가능
**해결책:** Optimistic Locking 패턴 + 정합성 검증 Job
**구현:**
```
user_deposits 테이블에 version 컬럼 추가
잔액 변경 시마다 version 자동 증가
UPDATE 쿼리에서 WHERE version = ? 조건 검증
version 불일치 시 OptimisticLockError 발생
지수 백오프로 자동 재시도 (최대 3회)
재시도 실패 시 사용자 친화적 에러 메시지
```
**관련 파일:**
| 파일 | 역할 |
|------|------|
| `utils/optimistic-lock.ts` | Optimistic Locking 유틸리티 (재시도 로직) |
| `utils/reconciliation.ts` | 잔액 정합성 검증 (Cron 실행) |
| `deposit-agent.ts` | 입금 처리에 Optimistic Locking 적용 |
| `domain-register.ts` | 도메인 등록 결제에 Optimistic Locking 적용 |
| `migrations/002_add_version_columns.sql` | 스키마 마이그레이션 |
**적용 대상:**
- `request_deposit` (auto_matched case): 은행 알림 자동 매칭 시 잔액 증가
- `confirm_deposit`: 관리자 수동 확인 시 잔액 증가
- `executeDomainRegister`: 도메인 등록 시 잔액 차감 (Double-spending 방지)
**정합성 검증 (Reconciliation):**
```
매일 KST 00:00 Cron 실행
user_deposits.balance vs SUM(confirmed transactions) 비교
불일치 발견 시:
1. 로그에 상세 기록
2. 관리자에게 Telegram 알림
3. 검증 리포트 생성
```
**마이그레이션:**
```bash
# 로컬 테스트
wrangler d1 execute telegram-conversations --local --file=migrations/002_add_version_columns.sql
# 프로덕션 적용 (데이터 백업 권장)
wrangler d1 execute telegram-conversations --file=migrations/002_add_version_columns.sql
# 검증
wrangler d1 execute telegram-conversations --command "SELECT user_id, balance, version FROM user_deposits LIMIT 5"
```
**동시성 시나리오 예시:**
```
사용자 A: 잔액 10,000원 (version=1)
동시 요청:
입금 +5,000원 (요청1)
입금 +3,000원 (요청2)
요청1: version=1 읽음 → UPDATE (version=2) ✅ 성공
요청2: version=1 읽음 → UPDATE (version=1) ❌ 실패 (version 불일치)
요청2 재시도: version=2 읽음 → UPDATE (version=3) ✅ 성공
최종: 잔액 18,000원 (version=3) ✅ 정합성 보장
```
---
## Server System
**목적:** 클라우드 서버 추천 및 관리 (Cloud Orchestrator 연동)
**연결 방식:** Cloudflare Service Binding (Worker-to-Worker 직접 통신)
**manage_server 도구 파라미터:**
```typescript
{
action: 'recommend' | 'order' | 'start' | 'stop' | 'delete' | 'list',
tech_stack?: string[], // recommend용 (필수)
expected_users?: number, // recommend용 (필수)
use_case?: string, // recommend용 (필수)
traffic_pattern?: 'steady' | 'spiky' | 'growing',
region_preference?: string[],
budget_limit?: number,
lang?: 'ko' | 'ja' | 'zh' | 'en', // 자동 감지
server_id?: string, // order/start/stop/delete용
region_code?: string, // order용
label?: string, // order용
}
```
**action별 상태:**
| action | 설명 | 상태 |
|--------|------|------|
| `recommend` | 서버 추천 | ✅ 구현 완료 |
| `order` | 서버 신청 | 🚧 준비 중 |
| `start` | 서버 시작 | 🚧 준비 중 |
| `stop` | 서버 중지 | 🚧 준비 중 |
| `delete` | 서버 해지 | 🚧 준비 중 |
| `list` | 내 서버 목록 | 🚧 준비 중 |
**추천 결과 포맷:**
```
🖥️ 서버 추천 결과
1⃣ Standard 8GB (Anvil)
• 스펙: 4vCPU / 8GB / 160GB SSD
• 리전: Tokyo 3 (JP)
• 가격: ₩69,719/월 (대역폭 5TB)
• 예상 트래픽: 1.7TB (포함 범위 내) ← 항상 표시
• 점수: 95점 / 최대 7,500명
```
**대역폭 정보 표시 규칙:**
- 초과 없음: `예상 트래픽: X.XTB (포함 범위 내)`
- 초과 있음: `예상 트래픽: X.XTB → 초과 X.XTB (₩X,XXX)`
**CDN 캐시 히트율 추정:**
- tech_stack에 `cloudflare`, `cdn` 등 포함 시 자동 적용
- 비디오 스트리밍: 92%, 정적 사이트: 95%, API: 30%, 이커머스: 70%
**언어 자동 감지:**
- 한글 → `ko`, 히라가나/가타카나 → `ja`, 한자 → `zh`, 기본값 → `en`
**Service Binding 설정 (wrangler.toml):**
```toml
[[services]]
binding = "CLOUD_ORCHESTRATOR"
service = "cloud-orchestrator"
```
### 서버 주문 상태 전이
**상태 정의:**
| 상태 | 설정 주체 | UI 표시 | 설명 |
|------|----------|---------|------|
| `pending` | telegram-bot | ❌ 표시 안 함 | Queue 대기 중 (내부용) |
| `provisioning` | cloud-orchestrator | ✅ 🔄 생성 중... | Cloud Orchestrator 처리 중 |
| `active` | cloud-orchestrator | ✅ 🟢 가동 중 | 서버 준비 완료 |
| `stopped` | cloud-orchestrator | ✅ ⛔ 중지됨 | 서버 중지됨 |
| `failed` | cloud-orchestrator | ❌ 표시 안 함 | 프로비저닝 실패 |
| `terminated` | telegram-bot | ❌ 표시 안 함 | 서버 삭제 완료 |
**상태 전이 흐름:**
```
사용자: "서버 신청"
telegram-bot: POST /api/provision (status = 'pending')
↓ Queue 등록
cloud-orchestrator: Worker 처리
├─ Queue 가져오기 → status = 'provisioning'
├─ Incus 인스턴스 생성 (2-5분)
└─ 완료 → status = 'active'
"내 서버 목록":
- pending: 표시 안 함 (내부용)
- provisioning: "🔄 생성 중..." 표시
- active: "🟢 가동 중" 표시
```
**상태 변경 권한:**
- **telegram-bot**: `pending` 설정 (주문 생성), `terminated` 설정 (삭제 후)
- **cloud-orchestrator**: `provisioning`, `active`, `stopped`, `failed` 설정
**"내 서버 목록" 표시 규칙:**
-**표시**: `provisioning`, `active` (+ provider_instance_id 존재)
-**제외**: `pending`, `failed`, `terminated`, `deleted`
-**제외**: `active`인데 `provider_instance_id`가 없는 경우 (프로비저닝 실패)
---
## Domain System
**아키텍처 변경 (2025-01):** Agent 기반 → 코드 직접 처리
| 구분 | 이전 (Agent) | 현재 (코드) |
|------|-------------|-------------|
| 의도 파악 | Domain Agent | 메인 AI (action 파라미터) |
| API 호출 | Agent Function Calling | `executeDomainAction()` |
| 응답 형식 | Agent 생성 (불안정) | 코드 고정 (100% 일관성) |
| 비용 | Agent 호출당 ~$0.01 | 없음 |
**manage_domain 도구 파라미터:**
```typescript
{
action: 'register' | 'check' | 'whois' | 'list' | 'info' | 'get_ns' | 'set_ns' | 'price' | 'cheapest',
domain?: string, // 대상 도메인
nameservers?: string[], // set_ns용
tld?: string // price용
}
```
**action별 처리:**
| action | 설명 | 권한 |
|--------|------|------|
| `list` | 내 도메인 목록 | 소유자 |
| `info` | 도메인 상세정보 | 소유자 |
| `get_ns` | 네임서버 조회 | 공개 |
| `set_ns` | 네임서버 변경 | 소유자 |
| `check` | 가용성 확인 + 가격 | 공개 |
| `whois` | WHOIS 조회 | 공개 |
| `price` | TLD 가격 | 공개 |
| `cheapest` | 가장 저렴한 TLD 목록 (TOP 15) | 공개 |
| `register` | 등록 확인 페이지 | 사용자 |
### 도메인 등록 흐름
```
사용자: "example.com 등록해줘"
메인 AI: manage_domain(action="register", domain="example.com")
executeDomainAction():
1. check_domains API → 가용성 확인
2. get_price API → 가격 조회
3. DB 조회 → 현재 잔액 확인
4. 고정 형식 응답 생성
┌─────────────────────┬─────────────────────┐
│ 잔액 충분 시 │ 잔액 부족 시 │
├─────────────────────┼─────────────────────┤
│ 📋 도메인 등록 확인 │ 📋 도메인 등록 확인 │
│ • 도메인: example.com│ • 도메인: example.com│
│ • 가격: 15,000원 │ • 가격: 15,000원 │
│ • 현재 잔액: ✓ │ • 현재 잔액: ⚠️ 부족 │
│ • 등록 기간: 1년 │ • 부족 금액: X원 │
│ 📌 등록자 정보 │ │
│ ⚠️ 취소/환불 불가 │ 💳 입금 계좌 │
│ │ 하나은행 427-... │
│ '확인' 입력 요청 │ 입금 안내 │
└─────────────────────┴─────────────────────┘
```
### 인라인 버튼 확인 플로우 (Callback Query)
**목적:** 사용자에게 "확인/취소" 버튼 표시 후 클릭으로 등록 진행
```
executeDomainAction(register):
1. __KEYBOARD__{type, domain, price}__END__ 마커 포함 응답 생성
telegram.ts (sendMessage):
2. __KEYBOARD__ 감지 → 마커 파싱 → inline_keyboard 생성
Telegram:
3. 사용자에게 "✅ 등록 확인 / ❌ 취소" 버튼 표시
index.ts (callback_query 핸들러):
4. 버튼 클릭 감지 → data 파싱 → domain-register.ts 호출
domain-register.ts:
5. 잔액 재확인 → Optimistic Locking으로 잔액 차감 → 실제 등록 API 호출 → 결과 반환
```
**관련 코드:**
| 파일 | 역할 |
|------|------|
| `openai-service.ts:786-807` | `__KEYBOARD__` 마커 생성 |
| `telegram.ts:sendMessage()` | 마커 파싱 → inline_keyboard 변환 |
| `index.ts:callback_query` | 버튼 클릭 핸들링 |
| `domain-register.ts` | 실제 도메인 등록 실행 (Optimistic Locking 적용) |
**보안 개선 (2026-01):**
- Optimistic Locking 패턴 적용으로 Double-spending 방지
- version 컬럼 기반 동시성 제어
- 자동 재시도 (최대 3회, 지수 백오프)
- 동시성 충돌 시 사용자 친화적 에러 메시지
**버튼 콜백 데이터 형식:**
```typescript
// 확인: confirm_domain_register:example.com:15000
// 취소: cancel_domain_register:example.com
```
### 도메인 추천 기능 (`suggest_domains`)
**별도 구현된 코드 레벨 도구**
```
사용자: "커피숍 도메인 추천해줘"
1. GPT-4o-mini: 키워드 기반 창의적 도메인 15개 생성
2. Namecheap API: check_domains로 가용성 일괄 확인
3. 등록 가능 도메인 < 10개? → 1-2 반복 (최대 3회)
4. Namecheap API: TLD별 가격 조회
5. 결과 포맷팅 (등록 가능한 것만 표시, 10개 목표)
```
**특징:**
- 등록 가능 도메인만 표시 (이미 등록된 도메인 미표시)
- 10개 미만 시 자동 재시도 (최대 3회)
- 이전에 체크한 도메인은 제외하고 새로 생성
**Namecheap API:**
- 엔드포인트: `namecheap-api.anvil.it.com`
- 가격 정책: Namecheap 원가 + 13%, 매일 환율 업데이트
- 권한 체크: `user_domains` 테이블 `verified=1`
**Production/Sandbox 전환:**
```bash
# namecheap-api 서버의 .env 파일
NAMECHEAP_API_USER=your_username
NAMECHEAP_SANDBOX=false # true: 테스트 모드, false: 실제 등록
```
| 환경 | NAMECHEAP_SANDBOX | API 엔드포인트 |
|------|-------------------|----------------|
| Production | `false` | api.namecheap.com |
| Sandbox | `true` | api.sandbox.namecheap.com |
**⚠️ 주의:** Sandbox에서 등록한 도메인은 실제로 등록되지 않음
**등록자 정보:**
- 현재: 서비스 기본 정보만 지원 (일본 주소)
- WHOIS Guard 자동 적용 (개인정보 비공개)
- 추후: 사용자 본인 정보로 등록 옵션 추가 예정
**Deposit API (namecheap-api용):**
```
GET /api/deposit/balance?telegram_id=xxx # 잔액 조회
POST /api/deposit/deduct # 잔액 차감
{ telegram_id, amount, reason }
Header: X-API-Key: DEPOSIT_API_SECRET
```