diff --git a/admin-compliance/app/api/sdk/v1/banner/[[...path]]/route.ts b/admin-compliance/app/api/sdk/v1/banner/[[...path]]/route.ts new file mode 100644 index 0000000..9d460bf --- /dev/null +++ b/admin-compliance/app/api/sdk/v1/banner/[[...path]]/route.ts @@ -0,0 +1,74 @@ +/** + * Banner API Proxy — catch-all route for cookie banner endpoints. + * + * Maps: /api/sdk/v1/banner/ → backend-compliance:8002/api/compliance/banner/ + * + * Solves: Browser cannot call backend-compliance:8093 directly due to + * self-signed SSL certificates. This proxy runs server-side where + * certificate validation is not an issue. + */ + +import { NextRequest, NextResponse } from 'next/server' + +const BACKEND_URL = process.env.BACKEND_URL || 'http://backend-compliance:8002' +const DEFAULT_TENANT = process.env.DEFAULT_TENANT_ID || '9282a473-5c95-4b3a-bf78-0ecc0ec71d3e' + +async function proxyRequest( + request: NextRequest, + pathSegments: string[] | undefined, + method: string, +) { + const pathStr = pathSegments?.join('/') || '' + const qs = request.nextUrl.searchParams.toString() + const base = `${BACKEND_URL}/api/compliance/banner` + const url = pathStr + ? `${base}/${pathStr}${qs ? `?${qs}` : ''}` + : `${base}${qs ? `?${qs}` : ''}` + + try { + const headers: HeadersInit = { + 'X-Tenant-ID': request.headers.get('x-tenant-id') || DEFAULT_TENANT, + } + const ct = request.headers.get('Content-Type') + if (ct) headers['Content-Type'] = ct + + const opts: RequestInit = { method, headers, signal: AbortSignal.timeout(30000) } + + if (method === 'POST' || method === 'PUT') { + const body = await request.text() + if (body) opts.body = body + } + + const res = await fetch(url, opts) + const text = await res.text() + let data + try { data = JSON.parse(text) } catch { data = { raw: text } } + + if (!res.ok) { + return NextResponse.json( + { error: `Backend ${res.status}`, ...data }, + { status: res.status }, + ) + } + return NextResponse.json(data) + } catch (err: any) { + console.error('Banner proxy error:', err?.message) + return NextResponse.json( + { error: 'Backend nicht erreichbar' }, + { status: 503 }, + ) + } +} + +export async function GET(req: NextRequest, { params }: { params: Promise<{ path?: string[] }> }) { + return proxyRequest(req, (await params).path, 'GET') +} +export async function POST(req: NextRequest, { params }: { params: Promise<{ path?: string[] }> }) { + return proxyRequest(req, (await params).path, 'POST') +} +export async function PUT(req: NextRequest, { params }: { params: Promise<{ path?: string[] }> }) { + return proxyRequest(req, (await params).path, 'PUT') +} +export async function DELETE(req: NextRequest, { params }: { params: Promise<{ path?: string[] }> }) { + return proxyRequest(req, (await params).path, 'DELETE') +} diff --git a/admin-compliance/app/sdk/cmp/page.tsx b/admin-compliance/app/sdk/cmp/page.tsx index 0dfe429..9ee4f96 100644 --- a/admin-compliance/app/sdk/cmp/page.tsx +++ b/admin-compliance/app/sdk/cmp/page.tsx @@ -11,11 +11,10 @@ import Link from 'next/link' * but with EWR-Only as unique differentiator. */ -const API_BASE = typeof window !== 'undefined' - ? (process.env.NEXT_PUBLIC_SDK_URL || `${window.location.protocol}//${window.location.hostname}:8093`) - : '' +// Use Next.js API proxy to avoid SSL cert issues +const BANNER_API = '/api/sdk/v1/banner' const TENANT_ID = '9282a473-5c95-4b3a-bf78-0ecc0ec71d3e' -const HEADERS = { 'X-Tenant-ID': TENANT_ID } +const HEADERS = { 'x-tenant-id': TENANT_ID } interface BannerStats { total_consents: number; category_acceptance: Record } interface ConsentStats { total_consents: number; active_consents: number; revoked_consents: number; unique_users: number; conversion_rate: number } @@ -59,12 +58,13 @@ export default function CMPDashboardPage() { useEffect(() => { async function load() { - const f = (url: string) => fetch(`${API_BASE}${url}`, { headers: HEADERS }).then(r => r.ok ? r.json() : null).catch(() => null) + const fb = (path: string) => fetch(`${BANNER_API}/${path}`, { headers: HEADERS }).then(r => r.ok ? r.json() : null).catch(() => null) + const fa = (path: string) => fetch(`/api/sdk/v1/compliance/${path}`, { headers: HEADERS }).then(r => r.ok ? r.json() : null).catch(() => null) const [banner, consent, dsr, siteList] = await Promise.all([ - f('/banner/admin/stats/preview-test-site'), - f('/einwilligungen/consents/stats'), - f('/dsr/stats'), - f('/banner/admin/sites'), + fb('admin/stats/preview-test-site'), + fa('einwilligungen/consents/stats'), + fa('dsr/stats'), + fb('admin/sites'), ]) setBannerStats(banner) setConsentStats(consent) diff --git a/admin-compliance/app/sdk/cookie-banner/preview/page.tsx b/admin-compliance/app/sdk/cookie-banner/preview/page.tsx index f832afe..5f209c9 100644 --- a/admin-compliance/app/sdk/cookie-banner/preview/page.tsx +++ b/admin-compliance/app/sdk/cookie-banner/preview/page.tsx @@ -17,9 +17,8 @@ import { * This page runs OUTSIDE the SDK layout to simulate a real website experience. */ -const API_BASE = typeof window !== 'undefined' - ? (process.env.NEXT_PUBLIC_SDK_URL || `${window.location.protocol}//${window.location.hostname}:8093`) - : '' +// Use Next.js API proxy to avoid SSL cert issues with direct backend calls +const API_BASE = '/api/sdk/v1/banner' const SITE_ID = 'preview-test-site' const TENANT_ID = '9282a473-5c95-4b3a-bf78-0ecc0ec71d3e' @@ -63,7 +62,7 @@ export default function CookieBannerPreviewPage() { try { const res = await fetch( `${API_BASE}/banner/consent?site_id=${SITE_ID}&device_fingerprint=${fingerprint}`, - { headers: { 'X-Tenant-ID': TENANT_ID } }, + { headers: { 'x-tenant-id': TENANT_ID } }, ) if (res.ok) { const data = await res.json() @@ -91,7 +90,7 @@ export default function CookieBannerPreviewPage() { try { const res = await fetch(`${API_BASE}/banner/consent`, { method: 'POST', - headers: { 'Content-Type': 'application/json', 'X-Tenant-ID': TENANT_ID }, + headers: { 'Content-Type': 'application/json', 'x-tenant-id': TENANT_ID }, body: JSON.stringify({ site_id: SITE_ID, device_fingerprint: fingerprint,