Files
cloud-orchestrator/CLAUDE.md
kappa f62712af37 docs: update CLAUDE.md with security hardening and admin SSH key
- 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>
2026-01-29 11:39:26 +09:00

27 KiB
Raw Permalink Blame History

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

  1. User sends request (tech_stack, expected_users, use_case, region_preference)
  2. Tech specs calculation with DB workload multiplier based on use_case
  3. Candidate filtering with flexible region matching
  4. VPS benchmarks retrieval (Geekbench 6), prioritizing same provider
  5. AI analysis returns 3 tiers: Budget, Balanced, Premium
  6. 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: From anvil_instances.transfer_tb
  • overage_cost_per_gb: From anvil_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 response
  • lang: 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.dev domain 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 any types - uses unknown + 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-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)

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.dev domain
  • 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_KEY for Linode (public key string)
    • ADMIN_SSH_KEY_ID_VULTR for 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}.ts
  • migrations/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_tb
  • bandwidth_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_preference input validation (max 10 items, 50 chars each)
  • Replaced all any types with unknown + type guards

Architecture Improvements:

  • Split utils.ts (801 lines) → 6 modular files in utils/
  • 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}.ts
  • src/services/ai-service.ts
  • src/repositories/AnvilServerRepository.ts
  • src/__tests__/{utils,bandwidth}.test.ts
  • vitest.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_tips includes 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_preference now included in cache key
  • Server ID fix: Changed from ai.id (instance) to ap.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_pricing table 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/report endpoint 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 pricing to anvil_pricing tables
  • Provider: Now uses "Anvil" as single provider (previously Linode/Vultr)
  • Exchange rate: Real-time USD→KRW conversion via open.er-api.com
  • Removed: provider_filter parameter 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 LIMITS in 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 queryVPSBenchmarks function deleted
  • Flexible region matching: Both endpoints support country/city/code inputs (korea, seoul, icn)