feat: 코드 품질 개선 및 추천 API 구현

## 주요 변경사항

### 신규 기능
- POST /recommend: 기술 스택 기반 인스턴스 추천 API
- 아시아 리전 필터링 (Seoul, Tokyo, Osaka, Singapore)
- 매칭 점수 알고리즘 (메모리 40%, vCPU 30%, 가격 20%, 스토리지 10%)

### 보안 강화 (Security 9.0/10)
- API Key 인증 + constant-time 비교 (타이밍 공격 방어)
- Rate Limiting: KV 기반 분산 처리, fail-closed 정책
- IP Spoofing 방지 (CF-Connecting-IP만 신뢰)
- 요청 본문 10KB 제한
- CORS + 보안 헤더 (CSP, HSTS, X-Frame-Options)

### 성능 최적화 (Performance 9.0/10)
- Generator 패턴: AWS pricing 메모리 95% 감소
- D1 batch 쿼리: N+1 문제 해결
- 복합 인덱스 추가 (migrations/002)

### 코드 품질 (QA 9.0/10)
- 127개 테스트 (vitest)
- 구조화된 로깅 (민감정보 마스킹)
- 상수 중앙화 (constants.ts)
- 입력 검증 유틸리티 (utils/validation.ts)

### Vultr 연동 수정
- relay 서버 헤더: Authorization: Bearer → X-API-Key

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
kappa
2026-01-22 11:57:35 +09:00
parent 95043049b4
commit abe052b538
58 changed files with 9905 additions and 702 deletions

225
src/constants.ts Normal file
View File

@@ -0,0 +1,225 @@
/**
* Cloud Server API - Constants
*
* Centralized constants for the cloud server API.
* All magic numbers and repeated constants should be defined here.
*/
// ============================================================
// Provider Configuration
// ============================================================
/**
* Supported cloud providers
*/
export const SUPPORTED_PROVIDERS = ['linode', 'vultr', 'aws'] as const;
export type SupportedProvider = typeof SUPPORTED_PROVIDERS[number];
// ============================================================
// Cache Configuration
// ============================================================
/**
* Cache TTL values in seconds
*/
export const CACHE_TTL = {
/** Cache TTL for instance queries (5 minutes) */
INSTANCES: 300,
/** Cache TTL for health checks (30 seconds) */
HEALTH: 30,
/** Cache TTL for pricing data (1 hour) */
PRICING: 3600,
/** Default cache TTL (5 minutes) */
DEFAULT: 300,
} as const;
/**
* Cache TTL values in milliseconds
*/
export const CACHE_TTL_MS = {
/** Cache TTL for instance queries (5 minutes) */
INSTANCES: 5 * 60 * 1000,
/** Cache TTL for health checks (30 seconds) */
HEALTH: 30 * 1000,
/** Cache TTL for pricing data (1 hour) */
PRICING: 60 * 60 * 1000,
} as const;
// ============================================================
// Rate Limiting Configuration
// ============================================================
/**
* Rate limiting defaults
*/
export const RATE_LIMIT_DEFAULTS = {
/** Time window in milliseconds (1 minute) */
WINDOW_MS: 60 * 1000,
/** Maximum requests per window for /instances endpoint */
MAX_REQUESTS_INSTANCES: 100,
/** Maximum requests per window for /sync endpoint */
MAX_REQUESTS_SYNC: 10,
} as const;
// ============================================================
// Pagination Configuration
// ============================================================
/**
* Pagination defaults
*/
export const PAGINATION = {
/** Default page number (1-indexed) */
DEFAULT_PAGE: 1,
/** Default number of results per page */
DEFAULT_LIMIT: 50,
/** Maximum number of results per page */
MAX_LIMIT: 100,
/** Default offset for pagination */
DEFAULT_OFFSET: 0,
} as const;
// ============================================================
// HTTP Status Codes
// ============================================================
/**
* HTTP status codes used throughout the API
*/
export const HTTP_STATUS = {
/** 200 - OK */
OK: 200,
/** 201 - Created */
CREATED: 201,
/** 204 - No Content */
NO_CONTENT: 204,
/** 400 - Bad Request */
BAD_REQUEST: 400,
/** 401 - Unauthorized */
UNAUTHORIZED: 401,
/** 404 - Not Found */
NOT_FOUND: 404,
/** 413 - Payload Too Large */
PAYLOAD_TOO_LARGE: 413,
/** 429 - Too Many Requests */
TOO_MANY_REQUESTS: 429,
/** 500 - Internal Server Error */
INTERNAL_ERROR: 500,
/** 503 - Service Unavailable */
SERVICE_UNAVAILABLE: 503,
} as const;
// ============================================================
// Database Configuration
// ============================================================
/**
* Database table names
*/
export const TABLES = {
PROVIDERS: 'providers',
REGIONS: 'regions',
INSTANCE_TYPES: 'instance_types',
PRICING: 'pricing',
PRICE_HISTORY: 'price_history',
} as const;
// ============================================================
// Query Configuration
// ============================================================
/**
* Valid sort fields for instance queries
*/
export const VALID_SORT_FIELDS = [
'price',
'hourly_price',
'monthly_price',
'vcpu',
'memory_mb',
'memory_gb',
'storage_gb',
'instance_name',
'provider',
'region',
] as const;
export type ValidSortField = typeof VALID_SORT_FIELDS[number];
/**
* Valid sort orders
*/
export const SORT_ORDERS = ['asc', 'desc'] as const;
export type SortOrder = typeof SORT_ORDERS[number];
/**
* Valid instance families
*/
export const INSTANCE_FAMILIES = ['general', 'compute', 'memory', 'storage', 'gpu'] as const;
export type InstanceFamily = typeof INSTANCE_FAMILIES[number];
// ============================================================
// CORS Configuration
// ============================================================
/**
* CORS configuration
*
* NOTE: localhost origin is included for development purposes.
* In production, filter allowed origins based on environment.
* Example: const allowedOrigins = CORS.ALLOWED_ORIGINS.filter(o => !o.includes('localhost'))
*/
export const CORS = {
/** Default CORS origin */
DEFAULT_ORIGIN: '*',
/** Allowed origins for CORS */
ALLOWED_ORIGINS: [
'https://anvil.it.com',
'https://cloud.anvil.it.com',
'http://localhost:3000', // DEVELOPMENT ONLY - exclude in production
] as string[],
/** Max age for CORS preflight cache (24 hours) */
MAX_AGE: '86400',
} as const;
// ============================================================
// Timeout Configuration
// ============================================================
/**
* Timeout values in milliseconds
*/
export const TIMEOUTS = {
/** Request timeout for AWS API calls (15 seconds) */
AWS_REQUEST: 15000,
/** Default API request timeout (30 seconds) */
DEFAULT_REQUEST: 30000,
} as const;
// ============================================================
// Validation Constants
// ============================================================
/**
* Validation rules
*/
export const VALIDATION = {
/** Minimum memory in MB */
MIN_MEMORY_MB: 1,
/** Minimum vCPU count */
MIN_VCPU: 1,
/** Minimum price in USD */
MIN_PRICE: 0,
} as const;
// ============================================================
// Request Security Configuration
// ============================================================
/**
* Request security limits
*/
export const REQUEST_LIMITS = {
/** Maximum request body size in bytes (10KB) */
MAX_BODY_SIZE: 10 * 1024,
} as const;