All checks were successful
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-ai-compliance (push) Successful in 39s
CI / test-python-backend-compliance (push) Successful in 37s
CI / test-python-document-crawler (push) Successful in 28s
CI / test-python-dsms-gateway (push) Successful in 23s
Both academy and training proxy routes now set default identity headers so the Go backend RBAC middleware can set the tenant context. Without these defaults, the browser doesn't send X-User-ID and modules/courses return empty. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
139 lines
3.9 KiB
TypeScript
139 lines
3.9 KiB
TypeScript
/**
|
|
* Academy API Proxy - Catch-all route
|
|
* Proxies all /api/sdk/v1/academy/* requests to ai-compliance-sdk backend
|
|
*/
|
|
|
|
import { NextRequest, NextResponse } from 'next/server'
|
|
|
|
const SDK_BACKEND_URL = process.env.SDK_API_URL || 'http://ai-compliance-sdk:8090'
|
|
|
|
async function proxyRequest(
|
|
request: NextRequest,
|
|
pathSegments: string[] | undefined,
|
|
method: string
|
|
) {
|
|
const pathStr = pathSegments?.join('/') || ''
|
|
const searchParams = request.nextUrl.searchParams.toString()
|
|
const basePath = `${SDK_BACKEND_URL}/sdk/v1/academy`
|
|
const url = pathStr
|
|
? `${basePath}/${pathStr}${searchParams ? `?${searchParams}` : ''}`
|
|
: `${basePath}${searchParams ? `?${searchParams}` : ''}`
|
|
|
|
try {
|
|
const headers: HeadersInit = {
|
|
'Content-Type': 'application/json',
|
|
}
|
|
|
|
const authHeader = request.headers.get('authorization')
|
|
if (authHeader) {
|
|
headers['Authorization'] = authHeader
|
|
}
|
|
|
|
// Forward identity headers for RBAC context
|
|
const userHeader = request.headers.get('x-user-id')
|
|
headers['X-User-ID'] = userHeader || '00000000-0000-0000-0000-000000000001'
|
|
|
|
const tenantHeader = request.headers.get('x-tenant-id')
|
|
headers['X-Tenant-ID'] = tenantHeader || (process.env.DEFAULT_TENANT_ID || '9282a473-5c95-4b3a-bf78-0ecc0ec71d3e')
|
|
|
|
const fetchOptions: RequestInit = {
|
|
method,
|
|
headers,
|
|
signal: AbortSignal.timeout(30000),
|
|
}
|
|
|
|
if (['POST', 'PUT', 'PATCH'].includes(method)) {
|
|
const contentType = request.headers.get('content-type')
|
|
if (contentType?.includes('application/json')) {
|
|
try {
|
|
const text = await request.text()
|
|
if (text && text.trim()) {
|
|
fetchOptions.body = text
|
|
}
|
|
} catch {
|
|
// Empty or invalid body - continue without
|
|
}
|
|
}
|
|
}
|
|
|
|
const response = await fetch(url, fetchOptions)
|
|
|
|
// Handle non-JSON responses (e.g., PDF certificates)
|
|
const responseContentType = response.headers.get('content-type')
|
|
if (responseContentType?.includes('application/pdf') ||
|
|
responseContentType?.includes('application/octet-stream')) {
|
|
const blob = await response.blob()
|
|
return new NextResponse(blob, {
|
|
status: response.status,
|
|
headers: {
|
|
'Content-Type': responseContentType,
|
|
'Content-Disposition': response.headers.get('content-disposition') || '',
|
|
},
|
|
})
|
|
}
|
|
|
|
if (!response.ok) {
|
|
const errorText = await response.text()
|
|
let errorJson
|
|
try {
|
|
errorJson = JSON.parse(errorText)
|
|
} catch {
|
|
errorJson = { error: errorText }
|
|
}
|
|
return NextResponse.json(
|
|
{ error: `Backend Error: ${response.status}`, ...errorJson },
|
|
{ status: response.status }
|
|
)
|
|
}
|
|
|
|
const data = await response.json()
|
|
return NextResponse.json(data)
|
|
} catch (error) {
|
|
console.error('Academy API proxy error:', error)
|
|
return NextResponse.json(
|
|
{ error: 'Verbindung zum SDK Backend fehlgeschlagen' },
|
|
{ status: 503 }
|
|
)
|
|
}
|
|
}
|
|
|
|
export async function GET(
|
|
request: NextRequest,
|
|
{ params }: { params: Promise<{ path?: string[] }> }
|
|
) {
|
|
const { path } = await params
|
|
return proxyRequest(request, path, 'GET')
|
|
}
|
|
|
|
export async function POST(
|
|
request: NextRequest,
|
|
{ params }: { params: Promise<{ path?: string[] }> }
|
|
) {
|
|
const { path } = await params
|
|
return proxyRequest(request, path, 'POST')
|
|
}
|
|
|
|
export async function PUT(
|
|
request: NextRequest,
|
|
{ params }: { params: Promise<{ path?: string[] }> }
|
|
) {
|
|
const { path } = await params
|
|
return proxyRequest(request, path, 'PUT')
|
|
}
|
|
|
|
export async function PATCH(
|
|
request: NextRequest,
|
|
{ params }: { params: Promise<{ path?: string[] }> }
|
|
) {
|
|
const { path } = await params
|
|
return proxyRequest(request, path, 'PATCH')
|
|
}
|
|
|
|
export async function DELETE(
|
|
request: NextRequest,
|
|
{ params }: { params: Promise<{ path?: string[] }> }
|
|
) {
|
|
const { path } = await params
|
|
return proxyRequest(request, path, 'DELETE')
|
|
}
|