refactor: modularize codebase and add DB workload multiplier
- Split monolithic index.ts (2370 lines) into modular structure: - src/handlers/ for route handlers - src/utils.ts for shared utilities - src/config.ts for configuration - src/types.ts for TypeScript definitions - Add DB workload multiplier for smarter database resource calculation: - Heavy (analytics, logs): 0.3x multiplier - Medium-heavy (e-commerce, transactional): 0.5x - Medium (API, SaaS): 0.7x - Light (blog, portfolio): 1.0x - Fix tech_specs with realistic vcpu_per_users values (150+ technologies) - Fix "blog" matching "log" regex bug - Update documentation to reflect new architecture Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
20
src/handlers/health.ts
Normal file
20
src/handlers/health.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
/**
|
||||
* Health check endpoint handler
|
||||
*/
|
||||
|
||||
import { jsonResponse } from '../utils';
|
||||
|
||||
/**
|
||||
* Health check endpoint
|
||||
*/
|
||||
export function handleHealth(corsHeaders: Record<string, string>): Response {
|
||||
return jsonResponse(
|
||||
{
|
||||
status: 'ok',
|
||||
timestamp: new Date().toISOString(),
|
||||
service: 'server-recommend',
|
||||
},
|
||||
200,
|
||||
corsHeaders
|
||||
);
|
||||
}
|
||||
1240
src/handlers/recommend.ts
Normal file
1240
src/handlers/recommend.ts
Normal file
File diff suppressed because it is too large
Load Diff
139
src/handlers/servers.ts
Normal file
139
src/handlers/servers.ts
Normal file
@@ -0,0 +1,139 @@
|
||||
/**
|
||||
* GET /api/servers - Server list with filtering handler
|
||||
*/
|
||||
|
||||
import type { Env } from '../types';
|
||||
import { jsonResponse, isValidServer } from '../utils';
|
||||
|
||||
/**
|
||||
* GET /api/servers - Server list with filtering
|
||||
*/
|
||||
export async function handleGetServers(
|
||||
request: Request,
|
||||
env: Env,
|
||||
corsHeaders: Record<string, string>
|
||||
): Promise<Response> {
|
||||
try {
|
||||
const url = new URL(request.url);
|
||||
const provider = url.searchParams.get('provider');
|
||||
const minCpu = url.searchParams.get('minCpu');
|
||||
const minMemory = url.searchParams.get('minMemory');
|
||||
const region = url.searchParams.get('region');
|
||||
|
||||
console.log('[GetServers] Query params:', {
|
||||
provider,
|
||||
minCpu,
|
||||
minMemory,
|
||||
region,
|
||||
});
|
||||
|
||||
// Build SQL query dynamically
|
||||
let query = `
|
||||
SELECT
|
||||
it.id,
|
||||
p.display_name as provider_name,
|
||||
it.instance_id,
|
||||
it.instance_name,
|
||||
it.vcpu,
|
||||
it.memory_mb,
|
||||
ROUND(it.memory_mb / 1024.0, 1) as memory_gb,
|
||||
it.storage_gb,
|
||||
it.network_speed_gbps,
|
||||
it.instance_family,
|
||||
it.gpu_count,
|
||||
it.gpu_type,
|
||||
MIN(pr.monthly_price) as monthly_price,
|
||||
MIN(r.region_name) as region_name,
|
||||
MIN(r.region_code) as region_code
|
||||
FROM instance_types it
|
||||
JOIN providers p ON it.provider_id = p.id
|
||||
JOIN pricing pr ON pr.instance_type_id = it.id
|
||||
JOIN regions r ON pr.region_id = r.id
|
||||
WHERE p.id IN (1, 2) -- Linode, Vultr only
|
||||
AND (
|
||||
-- Korea (Seoul)
|
||||
r.region_code IN ('icn', 'ap-northeast-2') OR
|
||||
LOWER(r.region_name) LIKE '%seoul%' OR
|
||||
-- Japan (Tokyo, Osaka)
|
||||
r.region_code IN ('nrt', 'itm', 'ap-northeast-1', 'ap-northeast-3') OR
|
||||
LOWER(r.region_code) LIKE '%tyo%' OR
|
||||
LOWER(r.region_code) LIKE '%osa%' OR
|
||||
LOWER(r.region_name) LIKE '%tokyo%' OR
|
||||
LOWER(r.region_name) LIKE '%osaka%' OR
|
||||
-- Singapore
|
||||
r.region_code IN ('sgp', 'ap-southeast-1') OR
|
||||
LOWER(r.region_code) LIKE '%sin%' OR
|
||||
LOWER(r.region_code) LIKE '%sgp%' OR
|
||||
LOWER(r.region_name) LIKE '%singapore%'
|
||||
)
|
||||
`;
|
||||
|
||||
const params: (string | number)[] = [];
|
||||
|
||||
if (provider) {
|
||||
query += ` AND p.name = ?`;
|
||||
params.push(provider);
|
||||
}
|
||||
|
||||
if (minCpu) {
|
||||
const parsedCpu = parseInt(minCpu, 10);
|
||||
if (isNaN(parsedCpu)) {
|
||||
return jsonResponse({ error: 'Invalid minCpu parameter' }, 400, corsHeaders);
|
||||
}
|
||||
query += ` AND it.vcpu >= ?`;
|
||||
params.push(parsedCpu);
|
||||
}
|
||||
|
||||
if (minMemory) {
|
||||
const parsedMemory = parseInt(minMemory, 10);
|
||||
if (isNaN(parsedMemory)) {
|
||||
return jsonResponse({ error: 'Invalid minMemory parameter' }, 400, corsHeaders);
|
||||
}
|
||||
query += ` AND it.memory_mb >= ?`;
|
||||
params.push(parsedMemory * 1024);
|
||||
}
|
||||
|
||||
if (region) {
|
||||
query += ` AND r.region_code = ?`;
|
||||
params.push(region);
|
||||
}
|
||||
|
||||
query += ` GROUP BY it.id ORDER BY MIN(pr.monthly_price) ASC LIMIT 100`;
|
||||
|
||||
const result = await env.DB.prepare(query).bind(...params).all();
|
||||
|
||||
if (!result.success) {
|
||||
throw new Error('Database query failed');
|
||||
}
|
||||
|
||||
// Validate each result with type guard
|
||||
const servers = (result.results as unknown[]).filter(isValidServer);
|
||||
const invalidCount = result.results.length - servers.length;
|
||||
if (invalidCount > 0) {
|
||||
console.warn(`[GetServers] Filtered out ${invalidCount} invalid server records`);
|
||||
}
|
||||
|
||||
console.log('[GetServers] Found servers:', servers.length);
|
||||
|
||||
return jsonResponse(
|
||||
{
|
||||
servers,
|
||||
count: servers.length,
|
||||
filters: { provider, minCpu, minMemory, region },
|
||||
},
|
||||
200,
|
||||
corsHeaders
|
||||
);
|
||||
} catch (error) {
|
||||
console.error('[GetServers] Error:', error);
|
||||
const requestId = crypto.randomUUID();
|
||||
return jsonResponse(
|
||||
{
|
||||
error: 'Failed to retrieve servers',
|
||||
request_id: requestId,
|
||||
},
|
||||
500,
|
||||
corsHeaders
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user