- 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
89 lines
3.1 KiB
TypeScript
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 }
|
|
);
|
|
}
|
|
}
|