Security fixes: - migrate.ts: SQL/Command Injection 방지 (spawnSync 사용) - migrate.ts: Path Traversal 검증 추가 - api-tester.ts: API 키 마스킹 (4자만 노출) - api-tester.ts: 최소 16자 키 길이 검증 - cache.ts: ReDoS 방지 (패턴 길이/와일드카드 제한) Performance improvements: - cache.ts: 순차 삭제 → 병렬 배치 처리 (50개씩) - cache.ts: KV 등록 fire-and-forget (non-blocking) - cache.ts: 메모리 제한 (5000키) - cache.ts: 25초 실행 시간 가드 - cache.ts: 패턴 매칭 prefix 최적화 New features: - 마이그레이션 자동화 시스템 (scripts/migrate.ts) - KV 기반 캐시 인덱스 (invalidatePattern, clearAll) - 글로벌 CacheService 싱글톤 Other: - .env.example 추가, API 키 환경변수 처리 - CACHE_TTL.RECOMMENDATIONS (10분) 분리 - e2e-tester.ts JSON 파싱 에러 핸들링 개선 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
285 lines
7.7 KiB
TypeScript
285 lines
7.7 KiB
TypeScript
/**
|
||
* 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,
|
||
/** Cache TTL for recommendation results (10 minutes) */
|
||
RECOMMENDATIONS: 600,
|
||
/** 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,
|
||
/** Maximum requests per window for /recommend endpoint */
|
||
MAX_REQUESTS_RECOMMEND: 50,
|
||
} 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
|
||
*
|
||
* Security: Explicit allowed origins only. No wildcard fallback.
|
||
* Development origins are separated and only used in development environment.
|
||
*/
|
||
export const CORS = {
|
||
/** Default CORS origin - explicit production origin */
|
||
DEFAULT_ORIGIN: 'https://anvil.it.com',
|
||
/** Allowed production origins for CORS */
|
||
ALLOWED_ORIGINS: [
|
||
'https://anvil.it.com',
|
||
'https://cloud.anvil.it.com',
|
||
'https://hosting.anvil.it.com',
|
||
] as string[],
|
||
/** Development origins - only used when ENVIRONMENT === 'development' */
|
||
DEVELOPMENT_ORIGINS: [
|
||
'http://localhost:3000',
|
||
'http://127.0.0.1:3000',
|
||
] 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;
|
||
|
||
// ============================================================
|
||
// USD Retail Pricing Configuration
|
||
// ============================================================
|
||
|
||
/**
|
||
* Default USD retail pricing configuration
|
||
*
|
||
* These defaults are used to calculate retail prices from wholesale prices.
|
||
* Calculation formula:
|
||
* Retail = Wholesale × Margin (1.1) × VAT (1.1)
|
||
* Retail = Wholesale × 1.21
|
||
*/
|
||
export const USD_RETAIL_DEFAULTS = {
|
||
/** Margin multiplier (10% margin) */
|
||
MARGIN_MULTIPLIER: 1.1,
|
||
/** VAT multiplier (10% VAT) */
|
||
VAT_MULTIPLIER: 1.1,
|
||
/** Total multiplier (margin × VAT) */
|
||
TOTAL_MULTIPLIER: 1.21,
|
||
} as const;
|
||
|
||
/**
|
||
* Calculate USD retail hourly price from wholesale price
|
||
* Applies margin and VAT
|
||
*
|
||
* @param wholesale - Wholesale hourly price in USD
|
||
* @returns Retail price in USD, rounded to 4 decimal places
|
||
*
|
||
* @example
|
||
* calculateRetailHourly(0.0075) // Returns 0.0091 (with defaults)
|
||
* calculateRetailHourly(0.144) // Returns 0.1742 (with defaults)
|
||
*/
|
||
export function calculateRetailHourly(wholesale: number): number {
|
||
return Math.round(wholesale * USD_RETAIL_DEFAULTS.TOTAL_MULTIPLIER * 10000) / 10000;
|
||
}
|
||
|
||
/**
|
||
* Calculate USD retail monthly price from wholesale price
|
||
* Applies margin and VAT
|
||
*
|
||
* @param wholesale - Wholesale monthly price in USD
|
||
* @returns Retail price in USD, rounded to nearest $1
|
||
*
|
||
* @example
|
||
* calculateRetailMonthly(5) // Returns 6 (with defaults)
|
||
* calculateRetailMonthly(96) // Returns 116 (with defaults)
|
||
*/
|
||
export function calculateRetailMonthly(wholesale: number): number {
|
||
return Math.round(wholesale * USD_RETAIL_DEFAULTS.TOTAL_MULTIPLIER);
|
||
}
|