Multi-cloud VM instance database with Cloudflare Workers - Linode, Vultr, AWS connector integration - D1 database with regions, instances, pricing - Query API with filtering, caching, pagination - Cron-based auto-sync (daily + 6-hourly) - Health monitoring endpoint Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
229 lines
6.0 KiB
TypeScript
229 lines
6.0 KiB
TypeScript
/**
|
|
* 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<Response> {
|
|
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 }
|
|
);
|
|
}
|
|
}
|