import { NextRequest, NextResponse } from 'next/server' const BACKEND_URL = process.env.BACKEND_URL || 'http://backend-compliance:8002' /** * Proxy: GET /api/sdk/v1/canonical?endpoint=... * * Routes to backend canonical control endpoints: * endpoint=frameworks → GET /api/compliance/v1/canonical/frameworks * endpoint=controls → GET /api/compliance/v1/canonical/controls(?severity=...&domain=...) * endpoint=control&id= → GET /api/compliance/v1/canonical/controls/{id} * endpoint=sources → GET /api/compliance/v1/canonical/sources * endpoint=licenses → GET /api/compliance/v1/canonical/licenses */ export async function GET(request: NextRequest) { try { const { searchParams } = new URL(request.url) const endpoint = searchParams.get('endpoint') || 'frameworks' let backendPath: string switch (endpoint) { case 'frameworks': backendPath = '/api/compliance/v1/canonical/frameworks' break case 'controls': { const controlParams = new URLSearchParams() const passthrough = ['severity', 'domain', 'release_state', 'verification_method', 'category', 'target_audience', 'source', 'search', 'control_type', 'exclude_duplicates', 'sort', 'order', 'limit', 'offset'] for (const key of passthrough) { const val = searchParams.get(key) if (val) controlParams.set(key, val) } const qs = controlParams.toString() backendPath = `/api/compliance/v1/canonical/controls${qs ? `?${qs}` : ''}` break } case 'controls-count': { const countParams = new URLSearchParams() const countPassthrough = ['severity', 'domain', 'release_state', 'verification_method', 'category', 'target_audience', 'source', 'search', 'control_type', 'exclude_duplicates'] for (const key of countPassthrough) { const val = searchParams.get(key) if (val) countParams.set(key, val) } const countQs = countParams.toString() backendPath = `/api/compliance/v1/canonical/controls-count${countQs ? `?${countQs}` : ''}` break } case 'controls-meta': backendPath = '/api/compliance/v1/canonical/controls-meta' break case 'control': { const controlId = searchParams.get('id') if (!controlId) { return NextResponse.json({ error: 'Missing control id' }, { status: 400 }) } backendPath = `/api/compliance/v1/canonical/controls/${encodeURIComponent(controlId)}` break } case 'sources': backendPath = '/api/compliance/v1/canonical/sources' break case 'licenses': backendPath = '/api/compliance/v1/canonical/licenses' break // Generator endpoints case 'generate-jobs': backendPath = '/api/compliance/v1/canonical/generate/jobs' break case 'generate-status': { const jobId = searchParams.get('jobId') if (!jobId) { return NextResponse.json({ error: 'Missing jobId' }, { status: 400 }) } backendPath = `/api/compliance/v1/canonical/generate/status/${encodeURIComponent(jobId)}` break } case 'review-queue': { const state = searchParams.get('release_state') || 'needs_review' backendPath = `/api/compliance/v1/canonical/generate/review-queue?release_state=${encodeURIComponent(state)}` break } case 'processed-stats': backendPath = '/api/compliance/v1/canonical/generate/processed-stats' break case 'categories': backendPath = '/api/compliance/v1/canonical/categories' break case 'traceability': { const traceId = searchParams.get('id') if (!traceId) { return NextResponse.json({ error: 'Missing control id' }, { status: 400 }) } backendPath = `/api/compliance/v1/canonical/controls/${encodeURIComponent(traceId)}/traceability` break } case 'provenance': { const provId = searchParams.get('id') if (!provId) { return NextResponse.json({ error: 'Missing control id' }, { status: 400 }) } backendPath = `/api/compliance/v1/canonical/controls/${encodeURIComponent(provId)}/provenance` break } case 'atomic-stats': backendPath = '/api/compliance/v1/canonical/controls/atomic-stats' break case 'similar': { const simControlId = searchParams.get('id') if (!simControlId) { return NextResponse.json({ error: 'Missing control id' }, { status: 400 }) } const simThreshold = searchParams.get('threshold') || '0.85' backendPath = `/api/compliance/v1/canonical/controls/${encodeURIComponent(simControlId)}/similar?threshold=${simThreshold}` break } case 'blocked-sources': backendPath = '/api/compliance/v1/canonical/blocked-sources' break case 'controls-customer': { const custSeverity = searchParams.get('severity') const custDomain = searchParams.get('domain') const custParams = new URLSearchParams() if (custSeverity) custParams.set('severity', custSeverity) if (custDomain) custParams.set('domain', custDomain) const custQs = custParams.toString() backendPath = `/api/compliance/v1/canonical/controls-customer${custQs ? `?${custQs}` : ''}` break } default: return NextResponse.json({ error: `Unknown endpoint: ${endpoint}` }, { status: 400 }) } const response = await fetch(`${BACKEND_URL}${backendPath}`) if (!response.ok) { if (response.status === 404) { return NextResponse.json(null, { status: 404 }) } const errorText = await response.text() return NextResponse.json( { error: 'Backend error', details: errorText }, { status: response.status } ) } return NextResponse.json(await response.json()) } catch (error) { console.error('Canonical control proxy error:', error) return NextResponse.json( { error: 'Failed to connect to backend' }, { status: 503 } ) } } /** * Proxy: POST /api/sdk/v1/canonical?endpoint=... * * endpoint=create-control → POST /api/compliance/v1/canonical/controls * endpoint=similarity-check&id= → POST /api/compliance/v1/canonical/controls/{id}/similarity-check */ export async function POST(request: NextRequest) { try { const { searchParams } = new URL(request.url) const endpoint = searchParams.get('endpoint') const body = await request.json() let backendPath: string if (endpoint === 'create-control') { backendPath = '/api/compliance/v1/canonical/controls' } else if (endpoint === 'generate') { backendPath = '/api/compliance/v1/canonical/generate' } else if (endpoint === 'review') { const controlId = searchParams.get('id') if (!controlId) { return NextResponse.json({ error: 'Missing control id' }, { status: 400 }) } backendPath = `/api/compliance/v1/canonical/generate/review/${encodeURIComponent(controlId)}` } else if (endpoint === 'bulk-review') { backendPath = '/api/compliance/v1/canonical/generate/bulk-review' } else if (endpoint === 'blocked-sources-cleanup') { backendPath = '/api/compliance/v1/canonical/blocked-sources/cleanup' } else if (endpoint === 'similarity-check') { const controlId = searchParams.get('id') if (!controlId) { return NextResponse.json({ error: 'Missing control id' }, { status: 400 }) } backendPath = `/api/compliance/v1/canonical/controls/${encodeURIComponent(controlId)}/similarity-check` } else { return NextResponse.json({ error: `Unknown POST endpoint: ${endpoint}` }, { status: 400 }) } const response = await fetch(`${BACKEND_URL}${backendPath}`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body), }) if (!response.ok) { const errorText = await response.text() return NextResponse.json( { error: 'Backend error', details: errorText }, { status: response.status } ) } return NextResponse.json(await response.json(), { status: response.status }) } catch (error) { console.error('Canonical control POST proxy error:', error) return NextResponse.json( { error: 'Failed to connect to backend' }, { status: 503 } ) } } /** * Proxy: PUT /api/sdk/v1/canonical?endpoint=update-control&id=AUTH-001 * * Routes to: PUT /api/compliance/v1/canonical/controls/{id} */ export async function PUT(request: NextRequest) { try { const { searchParams } = new URL(request.url) const controlId = searchParams.get('id') if (!controlId) { return NextResponse.json({ error: 'Missing control id' }, { status: 400 }) } const body = await request.json() const response = await fetch( `${BACKEND_URL}/api/compliance/v1/canonical/controls/${encodeURIComponent(controlId)}`, { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body), } ) if (!response.ok) { const errorText = await response.text() return NextResponse.json( { error: 'Backend error', details: errorText }, { status: response.status } ) } return NextResponse.json(await response.json()) } catch (error) { console.error('Canonical control PUT proxy error:', error) return NextResponse.json( { error: 'Failed to connect to backend' }, { status: 503 } ) } } /** * Proxy: DELETE /api/sdk/v1/canonical?id=AUTH-001 * * Routes to: DELETE /api/compliance/v1/canonical/controls/{id} */ export async function DELETE(request: NextRequest) { try { const { searchParams } = new URL(request.url) const controlId = searchParams.get('id') if (!controlId) { return NextResponse.json({ error: 'Missing control id' }, { status: 400 }) } const response = await fetch( `${BACKEND_URL}/api/compliance/v1/canonical/controls/${encodeURIComponent(controlId)}`, { method: 'DELETE' } ) if (!response.ok && response.status !== 204) { const errorText = await response.text() return NextResponse.json( { error: 'Backend error', details: errorText }, { status: response.status } ) } return new NextResponse(null, { status: 204 }) } catch (error) { console.error('Canonical control DELETE proxy error:', error) return NextResponse.json( { error: 'Failed to connect to backend' }, { status: 503 } ) } }