docs: add provisioning API documentation to CLAUDE.md
- Add provisioning service files to Architecture section - Document telegram-conversations DB tables (users, user_deposits, server_orders) - Add Server Provisioning API section with endpoints and security features - Update Bindings with USER_DB and PROVISION_QUEUE - Add provisioning API test examples - Include schema-provisioning.sql for reference Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
119
CLAUDE.md
119
CLAUDE.md
@@ -32,12 +32,12 @@ npx wrangler tail
|
||||
|
||||
```
|
||||
src/
|
||||
├── index.ts # Main router, CORS, request handling
|
||||
├── index.ts # Main router, CORS, Queue consumer
|
||||
├── config.ts # Configuration constants
|
||||
├── types.ts # TypeScript type definitions
|
||||
├── region-utils.ts # Region matching utilities
|
||||
├── utils.ts # Re-exports from utils/ (backward compatibility)
|
||||
├── utils/ # Modular utilities (split from monolithic utils.ts)
|
||||
├── utils/ # Modular utilities
|
||||
│ ├── index.ts # Central export point
|
||||
│ ├── http.ts # HTTP responses, CORS, escapeHtml
|
||||
│ ├── validation.ts # Input validation, type guards
|
||||
@@ -46,14 +46,21 @@ src/
|
||||
│ ├── ai.ts # AI prompt sanitization
|
||||
│ └── exchange-rate.ts # Currency conversion
|
||||
├── repositories/
|
||||
│ └── AnvilServerRepository.ts # DB queries for Anvil servers
|
||||
│ ├── AnvilServerRepository.ts # DB queries for Anvil servers
|
||||
│ └── ProvisioningRepository.ts # Users, deposits, orders (telegram-conversations)
|
||||
├── services/
|
||||
│ └── ai-service.ts # AI recommendations & fallback logic
|
||||
│ ├── ai-service.ts # AI recommendations & fallback logic
|
||||
│ ├── provisioning-service.ts # Server provisioning workflow
|
||||
│ ├── vps-provider.ts # VPS provider abstract base class
|
||||
│ ├── linode-provider.ts # Linode API implementation
|
||||
│ └── vultr-provider.ts # Vultr API implementation
|
||||
├── handlers/
|
||||
│ ├── health.ts # GET /api/health
|
||||
│ ├── servers.ts # GET /api/servers
|
||||
│ ├── recommend.ts # POST /api/recommend
|
||||
│ └── report.ts # GET /api/recommend/report
|
||||
│ ├── report.ts # GET /api/recommend/report
|
||||
│ ├── provision.ts # POST/GET/DELETE /api/provision/*
|
||||
│ └── queue.ts # Queue consumer for async provisioning
|
||||
└── __tests__/
|
||||
├── utils.test.ts # Validation & security tests (27 tests)
|
||||
└── bandwidth.test.ts # Bandwidth estimation tests (32 tests, including CDN)
|
||||
@@ -84,6 +91,13 @@ src/
|
||||
**Legacy tables (no longer used)**:
|
||||
- `providers`, `instance_types`, `pricing`, `regions` - Old Linode/Vultr data
|
||||
|
||||
### D1 Database Tables (telegram-conversations)
|
||||
|
||||
**User & Payment tables**:
|
||||
- `users` - Telegram users (id, telegram_id, username)
|
||||
- `user_deposits` - User balance in KRW (user_id, balance)
|
||||
- `server_orders` - Server provisioning orders (status, price_paid, provider_instance_id, ip_address)
|
||||
|
||||
## Key Implementation Details
|
||||
|
||||
### DB Workload Multiplier (`recommend.ts`)
|
||||
@@ -254,6 +268,62 @@ When OpenAI API is unavailable, the system automatically falls back to rule-base
|
||||
- Provides basic capacity estimates based on vCPU count
|
||||
- Warns user that AI is temporarily unavailable
|
||||
|
||||
### Server Provisioning API (`handlers/provision.ts`)
|
||||
|
||||
Queue-based async server provisioning with automatic balance management.
|
||||
|
||||
**Endpoints** (require `X-API-Key` header):
|
||||
| Endpoint | Method | Description |
|
||||
|----------|--------|-------------|
|
||||
| `/api/provision` | POST | Create server order (sends to Queue) |
|
||||
| `/api/provision/orders` | GET | List user's orders |
|
||||
| `/api/provision/orders/:id` | GET | Get order details (includes root_password) |
|
||||
| `/api/provision/orders/:id` | DELETE | Terminate server |
|
||||
| `/api/provision/balance` | GET | Get user balance (KRW) |
|
||||
|
||||
**Provisioning Flow**:
|
||||
```
|
||||
POST /api/provision
|
||||
↓
|
||||
1. Validate user (telegram_id)
|
||||
2. Get pricing (anvil_pricing)
|
||||
3. Calculate KRW price (exchange rate × 500원 rounding)
|
||||
4. Deduct balance (atomic query)
|
||||
5. Create order (status: provisioning)
|
||||
6. Send to Queue → Return immediately
|
||||
↓
|
||||
[Queue Consumer]
|
||||
↓
|
||||
7. Fetch order (get root_password from DB)
|
||||
8. Call provider API (Linode/Vultr)
|
||||
9. Wait for IP assignment (polling, max 2min)
|
||||
10. Update order (status: active, ip_address)
|
||||
↓
|
||||
On failure: Refund balance + status: failed
|
||||
```
|
||||
|
||||
**Request Body**:
|
||||
```json
|
||||
{
|
||||
"user_id": "telegram_id",
|
||||
"pricing_id": 26,
|
||||
"label": "my-server",
|
||||
"image": "ubuntu_22_04",
|
||||
"dry_run": false
|
||||
}
|
||||
```
|
||||
|
||||
**Security Features**:
|
||||
- API key authentication (`X-API-Key` header)
|
||||
- Origin validation for browser requests
|
||||
- Atomic balance deduction (prevents race condition)
|
||||
- Root password stored in DB only (not in Queue message)
|
||||
- Automatic refund on any failure
|
||||
|
||||
**Supported Providers**:
|
||||
- Linode (`linode-provider.ts`)
|
||||
- Vultr (`vultr-provider.ts`)
|
||||
|
||||
### Configuration (`config.ts`)
|
||||
|
||||
Centralized limits and constants:
|
||||
@@ -280,8 +350,23 @@ binding = "DB"
|
||||
database_name = "cloud-instances-db"
|
||||
database_id = "bbcb472d-b25e-4e48-b6ea-112f9fffb4a8"
|
||||
|
||||
[vars]
|
||||
OPENAI_API_KEY = "sk-..." # Set via wrangler secret
|
||||
[[d1_databases]]
|
||||
binding = "USER_DB"
|
||||
database_name = "telegram-conversations"
|
||||
database_id = "c285bb5b-888b-405d-b36f-475ae5aed20e"
|
||||
|
||||
[[queues.producers]]
|
||||
queue = "provision-queue"
|
||||
binding = "PROVISION_QUEUE"
|
||||
|
||||
[[queues.consumers]]
|
||||
queue = "provision-queue"
|
||||
max_batch_size = 1
|
||||
max_retries = 3
|
||||
dead_letter_queue = "provision-queue-dlq"
|
||||
|
||||
# Secrets (via wrangler secret put)
|
||||
# OPENAI_API_KEY, LINODE_API_KEY, VULTR_API_KEY, PROVISION_API_KEY
|
||||
```
|
||||
|
||||
## Testing
|
||||
@@ -320,6 +405,26 @@ RESULT=$(curl -s -X POST https://cloud-orchestrator.kappa-d8e.workers.dev/api/re
|
||||
REPORT_URL="https://cloud-orchestrator.kappa-d8e.workers.dev/api/recommend/report?data=$(echo $RESULT | base64 | tr -d '\n')&lang=ko"
|
||||
# 3. Open in browser or fetch
|
||||
echo $REPORT_URL
|
||||
|
||||
# === Provisioning API (requires X-API-Key header) ===
|
||||
|
||||
# Dry run - validate without creating server
|
||||
curl -s -X POST https://cloud-orchestrator.kappa-d8e.workers.dev/api/provision -H "Content-Type: application/json" -H "X-API-Key: $PROVISION_API_KEY" -d '{"user_id":"821596605","pricing_id":26,"label":"test","dry_run":true}' | jq .
|
||||
|
||||
# Create server (async via Queue)
|
||||
curl -s -X POST https://cloud-orchestrator.kappa-d8e.workers.dev/api/provision -H "Content-Type: application/json" -H "X-API-Key: $PROVISION_API_KEY" -d '{"user_id":"821596605","pricing_id":26,"label":"my-server"}' | jq .
|
||||
|
||||
# Get user balance
|
||||
curl -s "https://cloud-orchestrator.kappa-d8e.workers.dev/api/provision/balance?user_id=821596605" -H "X-API-Key: $PROVISION_API_KEY" | jq .
|
||||
|
||||
# List user orders
|
||||
curl -s "https://cloud-orchestrator.kappa-d8e.workers.dev/api/provision/orders?user_id=821596605" -H "X-API-Key: $PROVISION_API_KEY" | jq .
|
||||
|
||||
# Get specific order (includes root_password)
|
||||
curl -s "https://cloud-orchestrator.kappa-d8e.workers.dev/api/provision/orders/15?user_id=821596605" -H "X-API-Key: $PROVISION_API_KEY" | jq .
|
||||
|
||||
# Terminate server
|
||||
curl -s -X DELETE "https://cloud-orchestrator.kappa-d8e.workers.dev/api/provision/orders/15?user_id=821596605" -H "X-API-Key: $PROVISION_API_KEY" | jq .
|
||||
```
|
||||
|
||||
## Recent Changes
|
||||
|
||||
70
schema-provisioning.sql
Normal file
70
schema-provisioning.sql
Normal file
@@ -0,0 +1,70 @@
|
||||
-- Provisioning System Schema
|
||||
-- Users, Payment Holds, Server Orders
|
||||
|
||||
-- 1. Users table with deposit balance
|
||||
CREATE TABLE IF NOT EXISTS users (
|
||||
id TEXT PRIMARY KEY, -- UUID
|
||||
email TEXT NOT NULL UNIQUE,
|
||||
name TEXT NOT NULL,
|
||||
balance_usd REAL NOT NULL DEFAULT 0.0 CHECK(balance_usd >= 0),
|
||||
status TEXT NOT NULL DEFAULT 'active' CHECK(status IN ('active', 'suspended', 'deleted')),
|
||||
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
||||
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_users_email ON users(email);
|
||||
CREATE INDEX IF NOT EXISTS idx_users_status ON users(status);
|
||||
|
||||
-- 2. Payment holds table (hold → capture/release)
|
||||
CREATE TABLE IF NOT EXISTS payment_holds (
|
||||
id TEXT PRIMARY KEY, -- UUID
|
||||
user_id TEXT NOT NULL,
|
||||
order_id TEXT NOT NULL, -- References server_orders.id
|
||||
amount_usd REAL NOT NULL CHECK(amount_usd > 0),
|
||||
status TEXT NOT NULL DEFAULT 'active' CHECK(status IN ('active', 'captured', 'released')),
|
||||
reason TEXT NOT NULL,
|
||||
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
||||
resolved_at TEXT, -- When captured or released
|
||||
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_payment_holds_user ON payment_holds(user_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_payment_holds_order ON payment_holds(order_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_payment_holds_status ON payment_holds(status);
|
||||
|
||||
-- 3. Server orders table
|
||||
CREATE TABLE IF NOT EXISTS server_orders (
|
||||
id TEXT PRIMARY KEY, -- UUID
|
||||
user_id TEXT NOT NULL,
|
||||
pricing_id INTEGER NOT NULL, -- References pricing.id (instance_type + region)
|
||||
provider_name TEXT NOT NULL, -- linode, vultr, etc.
|
||||
status TEXT NOT NULL DEFAULT 'pending' CHECK(status IN (
|
||||
'pending', -- Order created, hold placed
|
||||
'provisioning', -- API call in progress
|
||||
'active', -- Server running
|
||||
'failed', -- Provisioning failed
|
||||
'deleted' -- Server terminated
|
||||
)),
|
||||
-- Provider response data
|
||||
provider_instance_id TEXT, -- Linode/Vultr instance ID
|
||||
server_ip TEXT, -- IPv4 address
|
||||
server_ipv6 TEXT, -- IPv6 address
|
||||
root_password TEXT, -- Encrypted/hashed
|
||||
-- Cost tracking
|
||||
monthly_cost_usd REAL NOT NULL,
|
||||
-- Metadata
|
||||
label TEXT, -- User-defined server label
|
||||
os_image TEXT NOT NULL DEFAULT 'ubuntu_22_04',
|
||||
error_message TEXT,
|
||||
-- Timestamps
|
||||
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
||||
provisioned_at TEXT,
|
||||
deleted_at TEXT,
|
||||
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE,
|
||||
FOREIGN KEY (pricing_id) REFERENCES pricing(id)
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_server_orders_user ON server_orders(user_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_server_orders_status ON server_orders(status);
|
||||
CREATE INDEX IF NOT EXISTS idx_server_orders_provider ON server_orders(provider_name, provider_instance_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_server_orders_created ON server_orders(created_at);
|
||||
Reference in New Issue
Block a user