# 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 ํ…Œ์ŠคํŠธ ํด๋ผ์ด์–ธํŠธ npm test # ๋‹จ์œ„ ํ…Œ์ŠคํŠธ ์‹คํ–‰ (Vitest) npm run test:watch # Watch ๋ชจ๋“œ npm run test:coverage # ์ปค๋ฒ„๋ฆฌ์ง€ ๋ฆฌํฌํŠธ ``` **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 ### ์ž๋™ํ™”๋œ ๋‹จ์œ„ ํ…Œ์ŠคํŠธ (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 ์ƒํƒœ ํ™•์ธ curl https://telegram-summary-bot.kappa-d8e.workers.dev/webhook-info ``` **์ˆ˜๋™ ํ…Œ์ŠคํŠธ ์˜ˆ์ œ** (์ž๋™ํ™” ์˜ˆ์ •): - `src/services/__test__/notification.test.ts` - ๊ด€๋ฆฌ์ž ์•Œ๋ฆผ - `src/utils/__test__/logger.test.ts` - ๊ตฌ์กฐํ™”๋œ ๋กœ๊น… --- ## 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 --- ## 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) โœ… ์ •ํ•ฉ์„ฑ ๋ณด์žฅ ``` --- ## 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 ```