# 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 κ΅¬ν˜„ λ§ˆμŠ€ν„° - ν”„λ‘œλ•μ…˜ μˆ˜μ€€μ˜ μ½”λ“œ ν’ˆμ§ˆ 보μž₯ - μ—”ν„°ν”„λΌμ΄μ¦ˆκΈ‰ μ—λŸ¬ 핸듀링 및 νƒ€μž… μ•ˆμ •μ„± - μ„±λŠ₯ μ΅œμ ν™” 및 베슀트 ν”„λž™ν‹°μŠ€ 적용 - `Explore`: ν”„λ‘œμ νŠΈ ꡬ쑰 뢄석 (thorough 레벨) - `Bash`: λΉŒλ“œ/배포/ν…ŒμŠ€νŠΈ μ‹€ν–‰ **CRITICAL: λ‹€μŒ μž‘μ—…μ€ λ°˜λ“œμ‹œ Task tool (agent)λ₯Ό μ‚¬μš©ν•˜μ—¬ 메인 μ„Έμ…˜ μ»¨ν…μŠ€νŠΈ μ ˆμ•½:** | μž‘μ—… μœ ν˜• | 쑰건 | μ—μ΄μ „νŠΈ νƒ€μž… | 이유 | |-----------|------|---------------|------| | **μ½”λ“œ μž‘μ„±/μˆ˜μ •** | λͺ¨λ“  μ½”λ“œ λ³€κ²½ | `coder` | TS/Workers μ „λ¬Έ, νƒ€μž… μ•ˆμ •μ„±, ν”„λ‘œλ•μ…˜ ν’ˆμ§ˆ | | **λ¦¬νŒ©ν† λ§** | 파일 수 무관 | `coder` (병렬) | 일관성, μ»¨ν…μŠ€νŠΈ 뢄리, TS μ΅œμ ν™” | | **Function Calling 도ꡬ** | μΆ”κ°€/μˆ˜μ • | `coder` (병렬) | tools/ + openai-service.ts λ™μ‹œ 처리 | | **μŠ€ν‚€λ§ˆ μž‘μ—…** | D1 λ§ˆμ΄κ·Έλ ˆμ΄μ…˜ | `coder` | λ°±μ—…β†’λ§ˆμ΄κ·Έλ ˆμ΄μ…˜β†’κ²€μ¦ 전체 μœ„μž„ | | **ν”„λ‘œμ νŠΈ 뢄석** | ꡬ쑰 νŒŒμ•… | `Explore` (thorough) | λŒ€λŸ‰ 파일 읽기 뢄리 | | **μ½”λ“œ 리뷰** | λ³΄μ•ˆ/μ„±λŠ₯ | `Explore` + `coder` | 뢄석 ν›„ κ°œμ„  μ œμ•ˆ | | **λΉŒλ“œ/배포** | 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 run chat # CLI ν…ŒμŠ€νŠΈ ν΄λΌμ΄μ–ΈνŠΈ ``` **CLI ν…ŒμŠ€νŠΈ ν΄λΌμ΄μ–ΈνŠΈ:** ```bash # .env 파일 생성 (졜초 1회) echo 'WEBHOOK_SECRET=...' > .env # Vault: secret/data/telegram-bot # λŒ€ν™”ν˜• λͺ¨λ“œ npm run chat # 단일 λ©”μ‹œμ§€ λͺ¨λ“œ npm run chat "날씨 μ•Œλ €μ€˜" ``` **KV Namespace 생성 (졜초 1회):** ```bash # Rate Limiting용 KV Namespace 생성 wrangler kv:namespace create RATE_LIMIT_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 curl https://telegram-summary-bot.kappa-d8e.workers.dev/setup-webhook curl https://telegram-summary-bot.kappa-d8e.workers.dev/webhook-info ``` **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 | **λ§ˆμ΄κ·Έλ ˆμ΄μ…˜ μž‘μ—… λ‚΄μš© (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 **ν˜„μž¬ ν…ŒμŠ€νŠΈ 슀크립트 μ—†μŒ** - μˆ˜λ™ ν…ŒμŠ€νŠΈ ν•„μˆ˜ ### 둜컬 ν…ŒμŠ€νŠΈ 절차 ```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 μƒνƒœ 확인 curl https://telegram-summary-bot.kappa-d8e.workers.dev/webhook-info ``` --- ## 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 ν•„μš” | 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()` | | `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 (8개):** | 도ꡬ | ν•¨μˆ˜λͺ… | μ™ΈλΆ€ 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` | μ½”λ“œ 직접 처리 | **μž…κΈˆ, μΆ©μ „, μž”μ•‘, κ³„μ’Œ, μ†‘κΈˆ** | **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, ...` --- ## 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` | **Bindings:** | Binding | νƒ€μž… | μš©λ„ | |---------|------|------| | `DB` | D1 Database | μ‚¬μš©μž/λ©”μ‹œμ§€/예치금 데이터 | | `AI` | Workers AI | OpenAI 폴백용 | | `RATE_LIMIT_KV` | KV Namespace | μ‚¬μš©μžλ³„ Rate Limiting (30 req/60s) | --- ## 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μ‹œκ°„ (같은 νƒ€μž…) --- ## 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 ### Web Page (Cloudflare Pages) **URL:** https://hosting.anvil.it.com **ν”„λ‘œμ νŠΈ:** `anvil-hosting` (Cloudflare Pages) **μ†ŒμŠ€:** `web/index.html` **배포:** ```bash wrangler pages deploy web --project-name anvil-hosting ``` **ꡬ성:** - 둜고: 3D λ©”νƒˆλ¦­ λͺ¨λ£¨ + λŒ€μž₯κ°„ 망치 + λΆˆκ½ƒ (SVG) - μ„œλΉ„μŠ€ μˆœμ„œ: 도메인 β†’ DDoS β†’ ν•΄μ™Έμ„œλ²„ β†’ μ›Ήν˜ΈμŠ€νŒ… - ν‘Έν„° μ‚¬μ—…μž 정보: namecheap-api λ“±λ‘μž 정보와 동일 **λ“±λ‘μž 정보 μ†ŒμŠ€:** ``` ssh npm-linode-1 cat /home/admin/namecheap_api/.env # REGISTRANT_* λ³€μˆ˜λ“€ ``` **ν‘Έν„° 정보:** - LIBEHAIM Inc. | Taro Tanaka - #202 K-Flat, 3-1-13 Higashioi, Shinagawa-ku, Tokyo 140-0011, Japan --- ## 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") --- ## 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 ```