CLAUDE.md: - Add server-agent.ts to Core Services table - Add Server Expert AI Flow architecture section - Document session management (KV-based, 1hr TTL) - Add search_trends/lookup_framework_docs tools - Update KV Namespace and Bindings tables README.md: - Add server recommendation AI consultation to features - Add SESSION_KV creation command Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1507 lines
53 KiB
Markdown
1507 lines
53 KiB
Markdown
# 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 |
|
||
|
||
**마이그레이션 작업 내용 (001):**
|
||
- `deposit_transactions.depositor_name_prefix` 컬럼 추가
|
||
- `bank_notifications.depositor_name_prefix` 컬럼 추가
|
||
- Partial Index 2개 생성 (pending 거래, unmatched 알림)
|
||
- 기존 데이터 backfill (SUBSTR 함수로 자동 채우기)
|
||
- 성능: Full Table Scan → Index Scan
|
||
|
||
**검증 명령:**
|
||
```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" 포함되어야 함
|
||
```
|
||
|
||
---
|
||
|
||
## 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 적용 |
|
||
| `migrations/002_add_version_columns.sql` | 스키마 마이그레이션 |
|
||
|
||
**적용 대상:**
|
||
- `request_deposit` (auto_matched case): 은행 알림 자동 매칭 시 잔액 증가
|
||
- `confirm_deposit`: 관리자 수동 확인 시 잔액 증가
|
||
|
||
**정합성 검증 (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"
|
||
```
|
||
|
||
---
|
||
|
||
## 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. 잔액 재확인 → 실제 등록 API 호출 → 결과 반환
|
||
```
|
||
|
||
**관련 코드:**
|
||
| 파일 | 역할 |
|
||
|------|------|
|
||
| `openai-service.ts:786-807` | `__KEYBOARD__` 마커 생성 |
|
||
| `telegram.ts:sendMessage()` | 마커 파싱 → inline_keyboard 변환 |
|
||
| `index.ts:callback_query` | 버튼 클릭 핸들링 |
|
||
| `domain-register.ts` | 실제 도메인 등록 실행 |
|
||
|
||
**버튼 콜백 데이터 형식:**
|
||
```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
|
||
```
|