wulf-pulse/app/api/route53/sync/route.ts
lorentz 53ec51c5e0 feat(24-05): read routes for zones, records, history, and sync status
- app/api/route53/sync/route.ts: POST (requireAdmin, fire-and-forget) + GET (requireAuth, status/history)
- app/api/route53/zones/route.ts: GET (requireAuth) list mirrored hosted zones
- app/api/route53/zones/[zoneId]/records/route.ts: GET (requireAuth) list records with type/search filters
- app/api/route53/zones/[zoneId]/records/[recordId]/history/route.ts: GET (requireAuth) append-only change ledger
- None gated on integration_settings disable toggle (D-10 — route53 is not a PAX8-style exception)
- /api/route53 confirmed absent from middleware.ts public-route list
- tsc clean
2026-08-05 20:33:58 -04:00

89 lines
3.1 KiB
TypeScript

/**
* POST /api/route53/sync — trigger a manual full or incremental Route 53 sync.
* Body: { syncType?: 'full' | 'incremental' } (default 'full')
* requireAdmin() gated. Fire-and-forget — returns immediately, sync runs in
* the background.
*
* GET /api/route53/sync — sync status + recent history.
* requireAuth() gated.
*
* D-10: Route 53 is NOT gated by the admin-integrations disable toggle —
* that disable-blocks-sync behavior is a PAX8-only exception. Every other
* integration's toggle (including this one) is display-only.
*/
import { NextRequest, NextResponse } from 'next/server';
import { requireAuth, requireAdmin } from '@/lib/auth-utils';
import postgresClient from '@/lib/services/postgres-client';
import { isRoute53Configured } from '@/lib/services/route53-factory';
import { getRoute53SyncService } from '@/lib/services/route53-sync-service';
import { sanitizeAwsError } from '@/lib/services/route53-record-validation';
export async function POST(req: NextRequest) {
const { session, error } = await requireAdmin();
if (error) return error;
if (!isRoute53Configured()) {
return NextResponse.json(
{
error: 'Route 53 not configured',
message: 'AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY must be set to sync Route 53',
},
{ status: 503 }
);
}
const body = await req.json().catch(() => ({}));
const syncType = body.syncType === 'incremental' ? 'incremental' : 'full';
const svc = getRoute53SyncService();
if (svc.isSyncInProgress()) {
return NextResponse.json({ error: 'Sync already in progress' }, { status: 409 });
}
const triggeredBy = session!.user.email;
const runSync = syncType === 'incremental' ? svc.incrementalSync(triggeredBy) : svc.fullSync(triggeredBy);
runSync.catch((err) =>
console.error('[ROUTE53-SYNC] Background sync error:', sanitizeAwsError(err))
);
return NextResponse.json({ ok: true, message: 'Route 53 sync started' });
}
export async function GET() {
const { error } = await requireAuth();
if (error) return error;
try {
const svc = getRoute53SyncService();
const inProgress = svc.isSyncInProgress();
const counts = await postgresClient.query(`
SELECT
(SELECT COUNT(*) FROM route53_zones WHERE is_deleted = false) AS zones,
(SELECT COUNT(*) FROM route53_records WHERE is_deleted = false) AS records,
(SELECT COUNT(*) FROM route53_record_history) AS "historyRows"
`);
const history = await postgresClient.query(
`SELECT id, sync_type, status, started_at, completed_at,
records_added, records_updated, records_deleted, error_message, triggered_by
FROM sync_history
WHERE entity_type = 'route53'
ORDER BY started_at DESC
LIMIT 10`
);
return NextResponse.json({
inProgress,
counts: counts.rows[0],
history: history.rows,
});
} catch (err) {
console.error('[ROUTE53-SYNC] Failed to get sync status:', err);
return NextResponse.json(
{ error: err instanceof Error ? err.message : 'Failed to get Route 53 sync status' },
{ status: 500 }
);
}
}