Files
cloud-server/src/routes/sync.ts
kappa 95043049b4 Initial commit: Cloud Instances API
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>
2026-01-21 20:17:18 +09:00

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 }
);
}
}