Files
cloud-server/src/constants.ts
kappa de790988b4 refactor: code review 기반 품질 개선
- HonoVariables 타입 중앙화 (types.ts로 추출, 5개 파일 중복 제거)
- 6시간 pricing update cron 핸들러 추가 (syncPricingOnly 메서드)
- Response.json() → c.json() 패턴 통일 (Hono 표준)
- SORT_FIELD_MAP 중앙화 (constants.ts, 12개 필드 지원)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-29 10:58:27 +09:00

291 lines
8.2 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* 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
// ============================================================
/**
* Mapping of user-facing sort field names to database column names
*
* This is the single source of truth for sort field validation and mapping.
* Query aliases: it=instance_types, pr=pricing, p=providers, r=regions
*/
export const SORT_FIELD_MAP: Record<string, string> = {
price: 'pr.hourly_price',
hourly_price: 'pr.hourly_price',
monthly_price: 'pr.monthly_price',
vcpu: 'it.vcpu',
memory: 'it.memory_mb',
memory_mb: 'it.memory_mb',
memory_gb: 'it.memory_mb', // Note: memory_gb is converted to memory_mb at query level
storage_gb: 'it.storage_gb',
name: 'it.instance_name',
instance_name: 'it.instance_name',
provider: 'p.name',
region: 'r.region_code',
} as const;
/**
* Valid sort fields for instance queries (derived from SORT_FIELD_MAP)
*/
export const VALID_SORT_FIELDS = Object.keys(SORT_FIELD_MAP) as ReadonlyArray<string>;
export type ValidSortField = keyof typeof SORT_FIELD_MAP;
/**
* 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);
}