## Security Fixes
- Fix XSS vulnerability in report.ts with escapeHtml()
- Add cache data integrity validation
- Add region_preference input validation (max 10 items, 50 chars each)
- Replace `any` types with `unknown` + type guards
## Architecture Refactoring
- Split utils.ts (801 lines) into 6 modules: http, validation, bandwidth, cache, ai, exchange-rate
- Extract AI logic to src/services/ai-service.ts (recommend.ts 49% reduction)
- Add Repository pattern: src/repositories/AnvilServerRepository.ts
- Reduce code duplication in DB queries
## New Features
- AI fallback: rule-based recommendations when OpenAI unavailable
- Vitest testing: 55 tests (utils.test.ts, bandwidth.test.ts)
- 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
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
165 lines
5.8 KiB
TypeScript
165 lines
5.8 KiB
TypeScript
/**
|
|
* Configuration constants and use case mappings
|
|
*/
|
|
|
|
import type { UseCaseConfig } from './types';
|
|
|
|
/**
|
|
* System limits and configuration constants
|
|
*/
|
|
export const LIMITS = {
|
|
MAX_REQUEST_BODY_BYTES: 10240, // 10KB
|
|
CACHE_TTL_SECONDS: 300, // 5 minutes
|
|
RATE_LIMIT_MAX_REQUESTS: 60, // per minute
|
|
RATE_LIMIT_WINDOW_MS: 60000, // 1 minute
|
|
VPS_BENCHMARK_LIMIT: 20,
|
|
MAX_AI_CANDIDATES: 15, // Reduce from 50 to save tokens
|
|
MAX_TECH_STACK: 20,
|
|
MAX_USE_CASE_LENGTH: 500,
|
|
MAX_REGION_PREFERENCE: 10,
|
|
} as const;
|
|
|
|
export const USE_CASE_CONFIGS: UseCaseConfig[] = [
|
|
{
|
|
category: 'video',
|
|
patterns: /video|stream|media|youtube|netflix|vod|동영상|스트리밍|미디어/i,
|
|
dauMultiplier: { min: 8, max: 12 },
|
|
activeRatio: 0.3
|
|
},
|
|
{
|
|
category: 'file',
|
|
patterns: /download|file|storage|cdn|파일|다운로드|저장소/i,
|
|
dauMultiplier: { min: 10, max: 14 },
|
|
activeRatio: 0.5
|
|
},
|
|
{
|
|
category: 'gaming',
|
|
patterns: /game|gaming|minecraft|게임/i,
|
|
dauMultiplier: { min: 10, max: 20 },
|
|
activeRatio: 0.5
|
|
},
|
|
{
|
|
category: 'api',
|
|
patterns: /api|saas|backend|서비스|백엔드/i,
|
|
dauMultiplier: { min: 5, max: 10 },
|
|
activeRatio: 0.6
|
|
},
|
|
{
|
|
category: 'ecommerce',
|
|
patterns: /e-?commerce|shop|store|쇼핑|커머스|온라인몰/i,
|
|
dauMultiplier: { min: 20, max: 30 },
|
|
activeRatio: 0.4
|
|
},
|
|
{
|
|
category: 'forum',
|
|
patterns: /forum|community|board|게시판|커뮤니티|포럼/i,
|
|
dauMultiplier: { min: 15, max: 25 },
|
|
activeRatio: 0.5
|
|
},
|
|
{
|
|
category: 'blog',
|
|
patterns: /blog|news|static|portfolio|블로그|뉴스|포트폴리오|landing/i,
|
|
dauMultiplier: { min: 30, max: 50 },
|
|
activeRatio: 0.3
|
|
},
|
|
{
|
|
category: 'chat',
|
|
patterns: /chat|messaging|slack|discord|채팅|메신저/i,
|
|
dauMultiplier: { min: 10, max: 14 },
|
|
activeRatio: 0.7
|
|
}
|
|
];
|
|
|
|
/**
|
|
* i18n Messages for multi-language support
|
|
*/
|
|
export const i18n: Record<string, {
|
|
missingFields: string;
|
|
invalidFields: string;
|
|
techStackItemLength: string;
|
|
schema: Record<string, string>;
|
|
example: Record<string, unknown>;
|
|
aiLanguageInstruction: string;
|
|
}> = {
|
|
en: {
|
|
missingFields: 'Missing required fields',
|
|
invalidFields: 'Invalid field values',
|
|
techStackItemLength: 'all items must be strings with max 50 characters',
|
|
schema: {
|
|
tech_stack: "(required) string[] - e.g. ['nginx', 'nodejs']",
|
|
expected_users: "(required) number - expected concurrent users, e.g. 1000",
|
|
use_case: "(required) string - e.g. 'e-commerce website'",
|
|
traffic_pattern: "(optional) 'steady' | 'spiky' | 'growing'",
|
|
region_preference: "(optional) string[] - e.g. ['korea', 'japan']",
|
|
budget_limit: "(optional) number - max monthly USD",
|
|
lang: "(optional) 'en' | 'zh' | 'ja' | 'ko' - response language"
|
|
},
|
|
example: {
|
|
tech_stack: ["nginx", "nodejs", "postgresql"],
|
|
expected_users: 5000,
|
|
use_case: "SaaS application"
|
|
},
|
|
aiLanguageInstruction: 'Respond in English.'
|
|
},
|
|
zh: {
|
|
missingFields: '缺少必填字段',
|
|
invalidFields: '字段值无效',
|
|
techStackItemLength: '所有项目必须是最长50个字符的字符串',
|
|
schema: {
|
|
tech_stack: "(必填) string[] - 例如 ['nginx', 'nodejs']",
|
|
expected_users: "(必填) number - 预计同时在线用户数,例如 1000",
|
|
use_case: "(必填) string - 例如 '电商网站'",
|
|
traffic_pattern: "(可选) 'steady' | 'spiky' | 'growing'",
|
|
region_preference: "(可选) string[] - 例如 ['korea', 'japan']",
|
|
budget_limit: "(可选) number - 每月最高预算(美元)",
|
|
lang: "(可选) 'en' | 'zh' | 'ja' | 'ko' - 响应语言"
|
|
},
|
|
example: {
|
|
tech_stack: ["nginx", "nodejs", "postgresql"],
|
|
expected_users: 5000,
|
|
use_case: "SaaS应用程序"
|
|
},
|
|
aiLanguageInstruction: 'Respond in Chinese (Simplified). All analysis text must be in Chinese.'
|
|
},
|
|
ja: {
|
|
missingFields: '必須フィールドがありません',
|
|
invalidFields: 'フィールド値が無効です',
|
|
techStackItemLength: 'すべての項目は最大50文字の文字列でなければなりません',
|
|
schema: {
|
|
tech_stack: "(必須) string[] - 例: ['nginx', 'nodejs']",
|
|
expected_users: "(必須) number - 予想同時接続ユーザー数、例: 1000",
|
|
use_case: "(必須) string - 例: 'ECサイト'",
|
|
traffic_pattern: "(任意) 'steady' | 'spiky' | 'growing'",
|
|
region_preference: "(任意) string[] - 例: ['korea', 'japan']",
|
|
budget_limit: "(任意) number - 月額予算上限(USD)",
|
|
lang: "(任意) 'en' | 'zh' | 'ja' | 'ko' - 応答言語"
|
|
},
|
|
example: {
|
|
tech_stack: ["nginx", "nodejs", "postgresql"],
|
|
expected_users: 5000,
|
|
use_case: "SaaSアプリケーション"
|
|
},
|
|
aiLanguageInstruction: 'Respond in Japanese. All analysis text must be in Japanese.'
|
|
},
|
|
ko: {
|
|
missingFields: '필수 필드가 누락되었습니다',
|
|
invalidFields: '필드 값이 잘못되었습니다',
|
|
techStackItemLength: '모든 항목은 50자 이하의 문자열이어야 합니다',
|
|
schema: {
|
|
tech_stack: "(필수) string[] - 예: ['nginx', 'nodejs']",
|
|
expected_users: "(필수) number - 예상 동시 접속자 수, 예: 1000",
|
|
use_case: "(필수) string - 예: '이커머스 웹사이트'",
|
|
traffic_pattern: "(선택) 'steady' | 'spiky' | 'growing'",
|
|
region_preference: "(선택) string[] - 예: ['korea', 'japan']",
|
|
budget_limit: "(선택) number - 월 예산 한도(원화, KRW)",
|
|
lang: "(선택) 'en' | 'zh' | 'ja' | 'ko' - 응답 언어"
|
|
},
|
|
example: {
|
|
tech_stack: ["nginx", "nodejs", "postgresql"],
|
|
expected_users: 5000,
|
|
use_case: "SaaS 애플리케이션"
|
|
},
|
|
aiLanguageInstruction: 'Respond in Korean. All analysis text must be in Korean.'
|
|
}
|
|
};
|