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/
|
src/
|
||||||
├── index.ts # Main router, CORS, request handling
|
├── index.ts # Main router, CORS, Queue consumer
|
||||||
├── config.ts # Configuration constants
|
├── config.ts # Configuration constants
|
||||||
├── types.ts # TypeScript type definitions
|
├── types.ts # TypeScript type definitions
|
||||||
├── region-utils.ts # Region matching utilities
|
├── region-utils.ts # Region matching utilities
|
||||||
├── utils.ts # Re-exports from utils/ (backward compatibility)
|
├── utils.ts # Re-exports from utils/ (backward compatibility)
|
||||||
├── utils/ # Modular utilities (split from monolithic utils.ts)
|
├── utils/ # Modular utilities
|
||||||
│ ├── index.ts # Central export point
|
│ ├── index.ts # Central export point
|
||||||
│ ├── http.ts # HTTP responses, CORS, escapeHtml
|
│ ├── http.ts # HTTP responses, CORS, escapeHtml
|
||||||
│ ├── validation.ts # Input validation, type guards
|
│ ├── validation.ts # Input validation, type guards
|
||||||
@@ -46,14 +46,21 @@ src/
|
|||||||
│ ├── ai.ts # AI prompt sanitization
|
│ ├── ai.ts # AI prompt sanitization
|
||||||
│ └── exchange-rate.ts # Currency conversion
|
│ └── exchange-rate.ts # Currency conversion
|
||||||
├── repositories/
|
├── repositories/
|
||||||
│ └── AnvilServerRepository.ts # DB queries for Anvil servers
|
│ ├── AnvilServerRepository.ts # DB queries for Anvil servers
|
||||||
|
│ └── ProvisioningRepository.ts # Users, deposits, orders (telegram-conversations)
|
||||||
├── services/
|
├── 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/
|
├── handlers/
|
||||||
│ ├── health.ts # GET /api/health
|
│ ├── health.ts # GET /api/health
|
||||||
│ ├── servers.ts # GET /api/servers
|
│ ├── servers.ts # GET /api/servers
|
||||||
│ ├── recommend.ts # POST /api/recommend
|
│ ├── 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__/
|
└── __tests__/
|
||||||
├── utils.test.ts # Validation & security tests (27 tests)
|
├── utils.test.ts # Validation & security tests (27 tests)
|
||||||
└── bandwidth.test.ts # Bandwidth estimation tests (32 tests, including CDN)
|
└── bandwidth.test.ts # Bandwidth estimation tests (32 tests, including CDN)
|
||||||
@@ -84,6 +91,13 @@ src/
|
|||||||
**Legacy tables (no longer used)**:
|
**Legacy tables (no longer used)**:
|
||||||
- `providers`, `instance_types`, `pricing`, `regions` - Old Linode/Vultr data
|
- `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
|
## Key Implementation Details
|
||||||
|
|
||||||
### DB Workload Multiplier (`recommend.ts`)
|
### 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
|
- Provides basic capacity estimates based on vCPU count
|
||||||
- Warns user that AI is temporarily unavailable
|
- 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`)
|
### Configuration (`config.ts`)
|
||||||
|
|
||||||
Centralized limits and constants:
|
Centralized limits and constants:
|
||||||
@@ -280,8 +350,23 @@ binding = "DB"
|
|||||||
database_name = "cloud-instances-db"
|
database_name = "cloud-instances-db"
|
||||||
database_id = "bbcb472d-b25e-4e48-b6ea-112f9fffb4a8"
|
database_id = "bbcb472d-b25e-4e48-b6ea-112f9fffb4a8"
|
||||||
|
|
||||||
[vars]
|
[[d1_databases]]
|
||||||
OPENAI_API_KEY = "sk-..." # Set via wrangler secret
|
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
|
## 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"
|
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
|
# 3. Open in browser or fetch
|
||||||
echo $REPORT_URL
|
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
|
## 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