- Add middleware directory to architecture diagram - Document Admin SSH Key for server recovery - Update Security Features section (origin validation, timeouts, etc.) - Add TIMEOUTS and TECH_CATEGORY_WEIGHTS to config section - Update secrets list with SSH key variables - Add latest changes section for security hardening Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
27 KiB
CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
Project Overview
Cloudflare Worker-based AI server recommendation service. Uses OpenAI GPT-4o-mini (via AI Gateway), D1 database, KV cache, and VPS benchmark data to recommend cost-effective servers based on natural language requirements.
Production URL: https://cloud-orchestrator.kappa-d8e.workers.dev
Commands
# Development
npm run dev # Start local development server (wrangler dev)
npm run deploy # Deploy to Cloudflare Workers
npm run typecheck # TypeScript type checking
npm test # Run tests (Vitest)
npm run test:watch # Run tests in watch mode
# Database operations (D1)
npx wrangler d1 execute cloud-instances-db --file=schema.sql # Apply schema
npx wrangler d1 execute cloud-instances-db --file=seed.sql # Seed data
npx wrangler d1 execute cloud-instances-db --file=fix-tech-specs.sql # Update tech specs
npx wrangler d1 execute cloud-instances-db --command="SELECT ..." # Ad-hoc queries
# View logs
npx wrangler tail
Architecture
src/
├── index.ts # Main router, CORS, Queue consumer
├── config.ts # Configuration constants (LIMITS, TIMEOUTS, i18n)
├── types.ts # TypeScript type definitions
├── region-utils.ts # Region matching utilities
├── utils.ts # Re-exports from utils/ (backward compatibility)
├── utils/ # Modular utilities
│ ├── index.ts # Central export point
│ ├── http.ts # HTTP responses, CORS, escapeHtml
│ ├── validation.ts # Input validation, type guards, escapeLikePattern
│ ├── bandwidth.ts # Bandwidth estimation & cost calculation
│ ├── cache.ts # Caching, rate limiting (with time-based cleanup)
│ ├── ai.ts # AI prompt sanitization
│ └── exchange-rate.ts # Currency conversion
├── middleware/ # Request middleware
│ ├── index.ts # Central export point
│ ├── auth.ts # API key authentication
│ ├── origin.ts # Origin validation (restricts to .kappa-d8e.workers.dev)
│ ├── rate-limit.ts # Rate limiting middleware
│ ├── request-id.ts # Request ID generation
│ ├── security.ts # Security headers (CSP, HSTS, etc.)
│ └── user-id.ts # User ID extraction
├── repositories/
│ ├── AnvilServerRepository.ts # DB queries for Anvil servers
│ └── ProvisioningRepository.ts # Users, deposits, orders (telegram-conversations)
├── services/
│ ├── ai-service.ts # AI recommendations & fallback logic
│ ├── provisioning-service.ts # Server provisioning workflow + SSH key injection
│ ├── 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 (CSP: style-src 'unsafe-inline')
│ ├── 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)
Key Data Flow
- User sends request (
tech_stack,expected_users,use_case,region_preference) - Tech specs calculation with DB workload multiplier based on use_case
- Candidate filtering with flexible region matching
- VPS benchmarks retrieval (Geekbench 6), prioritizing same provider
- AI analysis returns 3 tiers: Budget, Balanced, Premium
- Results cached in KV (5 min TTL, empty results not cached)
D1 Database Tables (cloud-instances-db)
Primary tables (Anvil pricing):
anvil_instances- Anvil server specifications (vcpus, memory_gb, disk_gb, transfer_tb, etc.)anvil_regions- Anvil data center regions (name, display_name, country_code)anvil_pricing- Anvil pricing data (monthly_price in USD)anvil_transfer_pricing- Transfer/bandwidth overage pricing by region (price_per_gb in USD)
Support tables:
tech_specs- Resource requirements per technology (vcpu_per_users, min_memory_mb)vps_benchmarks- Geekbench 6 benchmark data (269 records)benchmark_results/benchmark_types/processors- Phoronix benchmark data
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)
Database resource requirements vary by workload type, not just user count:
| Workload Type | Multiplier | Example Use Cases |
|---|---|---|
| Heavy | 0.3x | analytics, log server, reporting, dashboard |
| Medium-Heavy | 0.5x | e-commerce, ERP, CRM, community forum |
| Medium | 0.7x | API, SaaS, app backend |
| Light | 1.0x | blog, portfolio, documentation, wiki |
Example: PostgreSQL (vcpu_per_users: 200) with 1000 users
- Analytics dashboard: 1000 / (200 × 0.3) = 17 vCPU
- E-commerce: 1000 / (200 × 0.5) = 10 vCPU
- Personal blog: 1000 / (200 × 1.0) = 5 vCPU
Bandwidth Estimation (bandwidth.ts)
Estimates monthly bandwidth based on use_case patterns:
| Pattern | Page Size | Pages/Day | Active Ratio |
|---|---|---|---|
| E-commerce | 2.5MB | 20 | 40% |
| Streaming | 50MB | 5 | 20% |
| Analytics | 0.7MB | 30 | 50% |
| Blog/Content | 1.5MB | 4 | 30% |
Heavy bandwidth (>1TB/month) triggers warning about overage costs.
CDN Cache Hit Rate (bandwidth.ts)
CDN 사용 시 원본 서버 트래픽을 자동 계산하여 실제 비용을 추정합니다.
API Parameters:
cdn_enabled: CDN 사용 여부 (기본:true)cdn_cache_hit_rate: 캐시 히트율 override (0.0-1.0, 기본: use_case별 자동)
Use Case별 기본 캐시 히트율:
| Use Case | 캐시 히트율 | 설명 |
|---|---|---|
| video/streaming | 92% | 동일 영상 반복 시청 |
| blog/static | 90% | 대부분 정적 콘텐츠 |
| file/download | 85% | 소프트웨어/에셋 다운로드 |
| ecommerce | 70% | 상품 이미지 캐시 가능 |
| forum | 60% | 정적/동적 혼합 |
| api/saas | 30% | 동적 응답 위주 |
| gaming | 20% | 실시간 통신 |
| chat | 10% | 실시간 메시지 |
응답 필드 (bandwidth_estimate):
cdn_enabled: CDN 사용 여부cdn_cache_hit_rate: 적용된 캐시 히트율gross_monthly_tb: CDN 적용 전 총 트래픽origin_monthly_tb: CDN 적용 후 원본 서버 트래픽
응답 필드 (bandwidth_info):
cdn_savings_tb: CDN으로 절감된 트래픽 (TB)cdn_savings_cost: CDN으로 절감된 비용 ($)
비용 절감 예시 (10,000 동시접속 video streaming):
CDN 없음: 총 2,966TB → 원본 2,966TB → 초과비용 $18,370
CDN 92%: 총 2,966TB → 원본 237TB → 초과비용 $1,464
절감: 2,729TB 절감, $16,906 절감 (92%)
Flexible Region Matching
Both /api/recommend and /api/servers use buildFlexibleRegionConditionsAnvil() for anvil_regions:
LOWER(ar.name) = ? OR
LOWER(ar.name) LIKE ? OR
LOWER(ar.display_name) LIKE ? OR
LOWER(ar.country_code) = ?
Valid inputs: "korea", "KR", "seoul", "tokyo", "japan", "ap-northeast-2", "icn"
Country names are auto-expanded via COUNTRY_NAME_TO_REGIONS mapping.
Exchange Rate Handling (utils.ts)
- Korean users (lang=ko) see prices in KRW
- Exchange rate fetched from open.er-api.com with 1-hour KV cache
- Fallback rate: 1450 KRW/USD if API unavailable
Transfer Pricing (bandwidth_info)
Each recommendation includes bandwidth_info with transfer/bandwidth cost details:
| Field | Description | KRW Rounding |
|---|---|---|
included_transfer_tb |
Free bandwidth included in plan (TB/month) | - |
overage_cost_per_gb |
Overage cost per GB | 1원 단위 |
overage_cost_per_tb |
Overage cost per TB | 100원 단위 |
estimated_monthly_tb |
Estimated monthly usage (TB) | - |
estimated_overage_tb |
Estimated overage (TB) | - |
estimated_overage_cost |
Estimated overage charges | 100원 단위 |
total_estimated_cost |
Server + overage total | 100원 단위 |
currency |
"USD" or "KRW" | - |
Data sources:
included_transfer_tb: Fromanvil_instances.transfer_tboverage_cost_per_gb: Fromanvil_transfer_pricing.price_per_gb
HTML Report Endpoint (handlers/report.ts)
GET /api/recommend/report?data={base64}&lang={en|ko}
Generates printable/PDF-friendly HTML report from recommendation results.
Parameters:
data: Base64-encoded JSON of recommendation responselang: Language (en, ko, ja, zh) - defaults to 'en'
Usage:
// Get recommendations
const result = await fetch('/api/recommend', {...});
const data = await result.json();
// Generate report URL
const reportUrl = `/api/recommend/report?data=${btoa(JSON.stringify(data))}&lang=ko`;
window.open(reportUrl); // Opens printable HTML
Region-Based Recommendation Strategy (recommend.ts)
When region IS specified (e.g., region_preference: ["seoul"]):
- Returns 3 spec tiers (Budget/Balanced/Premium) within that region
- Example: Seoul 1 - Standard 4GB, Standard 8GB, Pro 16GB
When NO region specified:
- Returns same/similar spec from 3 DIFFERENT regions for location comparison
- Example: Standard 4GB from Osaka 2, Seoul 1, Singapore 1
- Implemented by sending only 1 server per region to AI (forces diversity)
AI Prompt Strategy (recommend.ts)
- Uses OpenAI GPT-4o-mini via Cloudflare AI Gateway (bypasses regional restrictions)
- Server list format:
[server_id=XXXX] Provider Name...for accurate ID extraction - server_id uses
ap.id(pricing ID, unique per instance+region combination) - Scoring: Cost efficiency (40%) + Capacity fit (30%) + Scalability (30%)
- Capacity response in Korean for Korean users
- Prompt injection protection: User inputs sanitized via
sanitizeForAIPrompt() - Token optimization: Candidates limited to top 15 by price
Security Features
- XSS prevention:
escapeHtml()applied to all user data in HTML reports - Input validation: Comprehensive checks with length limits (tech_stack ≤20, use_case ≤500, region_preference ≤10 items)
- Cache integrity: Validates cached JSON structure before returning
- Rate limiting: 60 req/min per IP with in-memory fallback (time-based sorting for cleanup)
- SQL injection prevention: All queries use parameterized statements +
escapeLikePattern()for LIKE queries - Security headers: CSP, HSTS, X-Frame-Options, X-Content-Type-Options
- Origin validation: Restricts browser requests to
.kappa-d8e.workers.devdomain only - API key protection: Keys never logged, sanitized from error messages
- Prompt injection protection:
sanitizeForAIPrompt()filters malicious patterns - Password generation: Rejection sampling for unbiased random character selection
- Type safety: No
anytypes - usesunknown+ type guards - Request size limits: Base64 data parameter max 100KB for report endpoint
- Timeout protection: AbortController with configurable timeouts for all external APIs
AI Fallback System (services/ai-service.ts)
When OpenAI API is unavailable, the system automatically falls back to rule-based recommendations:
- Sorts candidates by price
- Selects 3 tiers: Budget (cheapest), Balanced (median), Premium (top 25%)
- 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:
{
"user_id": "telegram_id",
"pricing_id": 26,
"label": "my-server",
"image": "ubuntu_22_04",
"dry_run": false
}
Security Features:
- API key authentication (
X-API-Keyheader) - 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)
Admin SSH Key for Server Recovery
Admin SSH key is automatically added to all provisioned servers for emergency recovery access.
Environment Variables (set via wrangler secret put):
ADMIN_SSH_PUBLIC_KEY # Linode: public key string (ssh-rsa AAAA... or ssh-ed25519 AAAA...)
ADMIN_SSH_KEY_ID_VULTR # Vultr: pre-registered SSH key ID (from Vultr dashboard/API)
Provider Differences:
| Provider | Parameter | Value Type | How to Set |
|---|---|---|---|
| Linode | authorized_keys |
Public key string | Direct: ssh-ed25519 AAAA... |
| Vultr | sshkey_id |
Key ID | Register via dashboard/API first |
Vultr SSH Key Registration (one-time setup):
# Register key via Vultr API
curl -X POST "https://api.vultr.com/v2/ssh-keys" \
-H "Authorization: Bearer $VULTR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"name":"anvil-admin","ssh_key":"ssh-ed25519 AAAA..."}'
# Response: {"ssh_key":{"id":"cb676a46-66fd-4dfb-..."}}
# Use this ID for ADMIN_SSH_KEY_ID_VULTR
Password Recovery Options:
| Provider | API Reset | Console Access |
|---|---|---|
| Linode | ✅ POST /linode/instances/{id}/password |
✅ Lish via dashboard |
| Vultr | ❌ Not available | ✅ View Console in dashboard |
Configuration (config.ts)
Centralized limits and constants:
LIMITS = {
MAX_REQUEST_BODY_BYTES: 10240, // 10KB
CACHE_TTL_SECONDS: 300, // 5 minutes
RATE_LIMIT_MAX_REQUESTS: 60, // per minute
MAX_AI_CANDIDATES: 15, // reduce API cost
MAX_TECH_STACK: 20,
MAX_USE_CASE_LENGTH: 500,
MAX_REGION_PREFERENCE: 10,
MAX_REGION_LENGTH: 50,
MAX_BASE64_DATA_LENGTH: 100000, // ~75KB decoded
}
TIMEOUTS = {
AI_REQUEST_MS: 30000, // 30 seconds for OpenAI
VPS_PROVIDER_API_MS: 60000, // 60 seconds for Linode/Vultr
TELEGRAM_API_MS: 10000, // 10 seconds for Telegram notifications
}
TECH_CATEGORY_WEIGHTS = {
heavy_db: 0.3, // analytics, log server, reporting
medium_heavy_db: 0.5, // e-commerce, ERP, CRM
medium_db: 0.7, // API, SaaS, app backend
light_db: 1.0, // blog, portfolio, wiki
}
Bindings (wrangler.toml)
[[kv_namespaces]]
binding = "CACHE"
id = "c68cdb477022424cbe4594f491390c8a"
[[d1_databases]]
binding = "DB"
database_name = "cloud-instances-db"
database_id = "bbcb472d-b25e-4e48-b6ea-112f9fffb4a8"
[[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 - OpenAI API key for GPT-4o-mini
# LINODE_API_KEY - Linode API token
# VULTR_API_KEY - Vultr API key
# PROVISION_API_KEY - API key for /api/provision/* endpoints
# BOT_TOKEN - Telegram bot token for notifications (optional)
# ADMIN_SSH_PUBLIC_KEY - Admin SSH public key for Linode (optional)
# ADMIN_SSH_KEY_ID_VULTR - Admin SSH key ID for Vultr (optional)
Testing
Note: Use single-line curl commands. Backslash line continuation (\) may not work in some environments.
# Health check
curl -s https://cloud-orchestrator.kappa-d8e.workers.dev/api/health | jq .
# Recommendation - nodejs/redis real-time chat (Japan)
curl -s -X POST https://cloud-orchestrator.kappa-d8e.workers.dev/api/recommend -H "Content-Type: application/json" -d '{"tech_stack":["nodejs","redis"],"expected_users":1000,"use_case":"real-time chat","region_preference":["japan"]}' | jq .
# Recommendation - php/mysql community forum (Korea)
curl -s -X POST https://cloud-orchestrator.kappa-d8e.workers.dev/api/recommend -H "Content-Type: application/json" -d '{"tech_stack":["php","mysql"],"expected_users":800,"use_case":"community forum","region_preference":["korea"]}' | jq .
# Recommendation - analytics dashboard (heavier DB workload)
curl -s -X POST https://cloud-orchestrator.kappa-d8e.workers.dev/api/recommend -H "Content-Type: application/json" -d '{"tech_stack":["postgresql"],"expected_users":500,"use_case":"analytics dashboard","region_preference":["japan"]}' | jq .
# Server list with filters (supports flexible region: korea, seoul, tokyo, etc.)
curl -s "https://cloud-orchestrator.kappa-d8e.workers.dev/api/servers?region=korea&minCpu=4" | jq .
# CDN cache hit rate - default (use_case별 자동 적용)
curl -s -X POST https://cloud-orchestrator.kappa-d8e.workers.dev/api/recommend -H "Content-Type: application/json" -d '{"tech_stack":["nginx"],"expected_users":10000,"use_case":"video streaming","region_preference":["japan"]}' | jq '.bandwidth_estimate | {cdn_enabled, cdn_cache_hit_rate, gross_monthly_tb, origin_monthly_tb}'
# CDN disabled (원본 서버 전체 트래픽)
curl -s -X POST https://cloud-orchestrator.kappa-d8e.workers.dev/api/recommend -H "Content-Type: application/json" -d '{"tech_stack":["nginx"],"expected_users":10000,"use_case":"video streaming","region_preference":["japan"],"cdn_enabled":false}' | jq '.bandwidth_estimate'
# Custom CDN cache hit rate (80%)
curl -s -X POST https://cloud-orchestrator.kappa-d8e.workers.dev/api/recommend -H "Content-Type: application/json" -d '{"tech_stack":["nginx"],"expected_users":10000,"use_case":"video streaming","region_preference":["japan"],"cdn_cache_hit_rate":0.80}' | jq '.recommendations[0].bandwidth_info | {cdn_cache_hit_rate, cdn_savings_tb, cdn_savings_cost}'
# HTML Report (encode recommendation result as base64)
# 1. Get recommendation and save to variable
RESULT=$(curl -s -X POST https://cloud-orchestrator.kappa-d8e.workers.dev/api/recommend -H "Content-Type: application/json" -d '{"tech_stack":["nodejs"],"expected_users":500,"use_case":"simple api","lang":"ko"}')
# 2. Generate report URL with base64-encoded data
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
Security Hardening & Admin SSH Key (Latest)
Security Improvements:
- CSP headers for HTML reports (
style-src 'unsafe-inline') - Origin validation restricts to
.kappa-d8e.workers.devdomain - Base64 size limit (100KB) for report data parameter
- Rejection sampling for unbiased password generation
- SQL LIKE pattern escaping via
escapeLikePattern() - Rate limiter cleanup with time-based sorting
Performance Improvements:
- Telegram API timeout (10s) with AbortController
- Centralized TIMEOUTS config for all external APIs
- VPS provider timeout configurable (default 60s)
New Features:
- Admin SSH key support for server recovery
ADMIN_SSH_PUBLIC_KEYfor Linode (public key string)ADMIN_SSH_KEY_ID_VULTRfor Vultr (pre-registered key ID)
- Middleware layer: auth, origin, rate-limit, request-id, security, user-id
- Idempotency key for order deduplication
Code Quality:
- 404 status when no servers found (was 200 with empty array)
- Consolidated error logging to single JSON.stringify
- Import TECH_CATEGORY_WEIGHTS from config.ts
Files Added:
src/middleware/{auth,origin,rate-limit,request-id,security,user-id}.tsmigrations/004_add_idempotency_key.sql
CDN Cache Hit Rate
New Feature: CDN 캐시 히트율 기반 원본 서버 트래픽 및 비용 계산
API Parameters Added:
cdn_enabled: CDN 사용 여부 (기본: true)cdn_cache_hit_rate: 캐시 히트율 override (0.0-1.0)
Use Case별 기본 캐시 히트율:
- video/streaming: 92%, blog/static: 90%, file: 85%
- ecommerce: 70%, forum: 60%, api: 30%, gaming: 20%, chat: 10%
Response Fields Added:
bandwidth_estimate: cdn_enabled, cdn_cache_hit_rate, gross_monthly_tb, origin_monthly_tbbandwidth_info: cdn_savings_tb, cdn_savings_cost
Cost Impact Example (10K concurrent video streaming):
- Without CDN: $18,370 overage
- With CDN 92%: $1,464 overage (92% savings)
Tests Added: 4 new CDN-specific tests (total 59 tests)
Major Architecture Refactoring
Security Hardening:
- Fixed XSS vulnerability in HTML reports with
escapeHtml() - Added cache data integrity validation
- Added
region_preferenceinput validation (max 10 items, 50 chars each) - Replaced all
anytypes withunknown+ type guards
Architecture Improvements:
- Split
utils.ts(801 lines) → 6 modular files inutils/ - Extracted AI logic to
services/ai-service.ts(recommend.ts -49%) - Added Repository pattern:
repositories/AnvilServerRepository.ts - Reduced DB query duplication across handlers
New Features:
- AI fallback: Rule-based recommendations when OpenAI unavailable
- Vitest testing: 55 tests covering validation, security, bandwidth
- Duplicate server prevention in AI recommendations
Files Added:
src/utils/{index,http,validation,bandwidth,cache,ai,exchange-rate}.tssrc/services/ai-service.tssrc/repositories/AnvilServerRepository.tssrc/__tests__/{utils,bandwidth}.test.tsvitest.config.ts
Spec Diversity & Bandwidth-Based Filtering (Latest)
- Spec diversity: No region specified → 3 different spec tiers (Budget/Balanced/Premium) from different regions
- Bandwidth-based filtering: High bandwidth workloads (>2TB) automatically prioritize servers with adequate transfer allowance
- Bandwidth warning:
infrastructure_tipsincludes warning when estimated traffic exceeds included bandwidth by 2x+ - Previous issue fixed: Was recommending same Basic 1GB spec × 3 (only region different), now recommends diverse specs
Region Diversity & Bug Fixes
- Cache key fix:
region_preferencenow included in cache key - Server ID fix: Changed from
ai.id(instance) toap.id(pricing) for unique region+instance identification - Prompt cleanup: Removed obsolete Linode/Vultr/DigitalOcean references (Anvil only)
Transfer Pricing & Reporting
- Transfer pricing: Added
anvil_transfer_pricingtable data to recommendations - bandwidth_info: Each recommendation includes transfer costs (included_tb, overage costs)
- available_regions: Lists other regions where same server spec is available with prices
- HTML report: New
/api/recommend/reportendpoint for printable reports - KRW conversion: Bandwidth costs converted to KRW for Korean users (GB: 1원, TB/total: 100원 rounding)
Anvil Pricing Migration
- New tables: Migrated from
pricingtoanvil_pricingtables - Provider: Now uses "Anvil" as single provider (previously Linode/Vultr)
- Exchange rate: Real-time USD→KRW conversion via open.er-api.com
- Removed:
provider_filterparameter no longer supported - Currency handling: Korean users see KRW, others see USD
Architecture
- Modular architecture: Split from single 2370-line file into organized modules
- Centralized config: All magic numbers moved to
LIMITSin config.ts - Type safety:
parseAIResponse(unknown)with proper type guards
Features
- DB workload multiplier: Database resource calculation based on use_case
- Bandwidth estimation: Automatic bandwidth category detection for provider filtering
- Tech specs update: Realistic vcpu_per_users values for 150+ technologies
Security
- Prompt injection protection:
sanitizeForAIPrompt()filters malicious patterns - Rate limiting fallback: In-memory Map when KV unavailable
- Input sanitization: All user inputs validated and length-limited
Performance
- O(1) VPS lookup: Map-based benchmark matching (was O(n×m))
- AI token optimization: Candidates limited to 15 (was 50)
- KV caching: 5-minute TTL, empty results not cached
- Parallel queries: Promise.all for independent DB operations
Code Quality
- Dead code removed: Unused
queryVPSBenchmarksfunction deleted - Flexible region matching: Both endpoints support country/city/code inputs (korea, seoul, icn)