/** * Sync Route Handler * * Endpoint for triggering synchronization with cloud providers. * Validates request parameters and orchestrates sync operations. */ import type { Env, SyncReport } from '../types'; /** * Request body interface for sync endpoint */ interface SyncRequestBody { providers?: string[]; force?: boolean; } /** * Supported cloud providers */ const SUPPORTED_PROVIDERS = ['linode', 'vultr', 'aws'] as const; type SupportedProvider = typeof SUPPORTED_PROVIDERS[number]; /** * Validate if provider is supported */ function isSupportedProvider(provider: string): provider is SupportedProvider { return SUPPORTED_PROVIDERS.includes(provider as SupportedProvider); } /** * Handle POST /sync endpoint * * @param request - HTTP request object * @param env - Cloudflare Worker environment bindings * @returns JSON response with sync results * * @example * POST /sync * { * "providers": ["linode"], * "force": false * } */ export async function handleSync( request: Request, _env: Env ): Promise { const startTime = Date.now(); const startedAt = new Date().toISOString(); console.log('[Sync] Request received', { timestamp: startedAt }); try { // Parse and validate request body let body: SyncRequestBody = {}; try { const contentType = request.headers.get('content-type'); if (contentType && contentType.includes('application/json')) { body = await request.json() as SyncRequestBody; } } catch (error) { console.error('[Sync] Invalid JSON in request body', { error }); return Response.json( { success: false, error: { code: 'INVALID_REQUEST', message: 'Invalid JSON in request body', details: error instanceof Error ? error.message : 'Unknown error' } }, { status: 400 } ); } // Validate providers array const providers = body.providers || ['linode']; if (!Array.isArray(providers)) { console.error('[Sync] Providers must be an array', { providers }); return Response.json( { success: false, error: { code: 'INVALID_PROVIDERS', message: 'Providers must be an array', details: { received: typeof providers } } }, { status: 400 } ); } if (providers.length === 0) { console.error('[Sync] Providers array is empty'); return Response.json( { success: false, error: { code: 'EMPTY_PROVIDERS', message: 'At least one provider must be specified', details: null } }, { status: 400 } ); } // Validate each provider const unsupportedProviders: string[] = []; for (const provider of providers) { if (typeof provider !== 'string') { console.error('[Sync] Provider must be a string', { provider }); return Response.json( { success: false, error: { code: 'INVALID_PROVIDER_TYPE', message: 'Each provider must be a string', details: { provider, type: typeof provider } } }, { status: 400 } ); } if (!isSupportedProvider(provider)) { unsupportedProviders.push(provider); } } if (unsupportedProviders.length > 0) { console.error('[Sync] Unsupported providers', { unsupportedProviders }); return Response.json( { success: false, error: { code: 'UNSUPPORTED_PROVIDERS', message: `Unsupported providers: ${unsupportedProviders.join(', ')}`, details: { unsupported: unsupportedProviders, supported: SUPPORTED_PROVIDERS } } }, { status: 400 } ); } const force = body.force === true; console.log('[Sync] Validation passed', { providers, force }); // TODO: Once SyncOrchestrator is implemented, use it here // For now, return a placeholder response // const syncOrchestrator = new SyncOrchestrator(env.DB, env.VAULT_URL, env.VAULT_TOKEN); // const syncReport = await syncOrchestrator.syncProviders(providers, force); // Placeholder sync report const completedAt = new Date().toISOString(); const totalDuration = Date.now() - startTime; const syncId = `sync_${Date.now()}`; console.log('[Sync] TODO: Implement actual sync logic'); console.log('[Sync] Placeholder response generated', { syncId, totalDuration }); // Return placeholder success response const placeholderReport: SyncReport = { success: true, started_at: startedAt, completed_at: completedAt, total_duration_ms: totalDuration, providers: providers.map(providerName => ({ provider: providerName, success: true, regions_synced: 0, instances_synced: 0, pricing_synced: 0, duration_ms: 0, })), summary: { total_providers: providers.length, successful_providers: providers.length, failed_providers: 0, total_regions: 0, total_instances: 0, total_pricing: 0, } }; return Response.json( { success: true, data: { sync_id: syncId, ...placeholderReport } }, { status: 200 } ); } catch (error) { console.error('[Sync] Unexpected error', { error }); const completedAt = new Date().toISOString(); const totalDuration = Date.now() - startTime; return Response.json( { success: false, error: { code: 'SYNC_FAILED', message: 'Sync operation failed', details: { error: error instanceof Error ? error.message : 'Unknown error', duration_ms: totalDuration, started_at: startedAt, completed_at: completedAt } } }, { status: 500 } ); } }