From be4e9204a38fbe2f2a1dd7efc306f44ce4d6fe20 Mon Sep 17 00:00:00 2001 From: DanielS Date: Tue, 14 Jul 2026 17:24:00 +0200 Subject: [PATCH] feat(licserver): add partner API proxy routes and types - New: lib/types/licserver.ts (PartnerDto, paged result, problem) - New: app/api/licserver/partners/route.ts (GET search/list) - New: app/api/licserver/partners/[id]/route.ts (GET by UUID) - Updated: .env.example with LICSERVER_BASE_URL + LICSERVER_API_KEY Auth-guarded via Supabase session. API key is server-side only. Upstream: http://192.168.178.174:9980 (internal container network) --- shop/.env.example | 4 + shop/app/api/licserver/partners/[id]/route.ts | 73 +++++++++++++++++++ shop/app/api/licserver/partners/route.ts | 71 ++++++++++++++++++ shop/lib/types/licserver.ts | 31 ++++++++ 4 files changed, 179 insertions(+) create mode 100644 shop/app/api/licserver/partners/[id]/route.ts create mode 100644 shop/app/api/licserver/partners/route.ts create mode 100644 shop/lib/types/licserver.ts diff --git a/shop/.env.example b/shop/.env.example index 91cdb14..22e5715 100644 --- a/shop/.env.example +++ b/shop/.env.example @@ -2,3 +2,7 @@ # https://app.supabase.com/project/_/settings/api NEXT_PUBLIC_SUPABASE_URL=your-project-url NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY=your-publishable-or-anon-key + +# CASPOS LicServer (internal container network — server-side only, never expose to browser) +LICSERVER_BASE_URL=http://192.168.178.174:9980 +LICSERVER_API_KEY=your-licserver-api-key diff --git a/shop/app/api/licserver/partners/[id]/route.ts b/shop/app/api/licserver/partners/[id]/route.ts new file mode 100644 index 0000000..ecea03e --- /dev/null +++ b/shop/app/api/licserver/partners/[id]/route.ts @@ -0,0 +1,73 @@ +import { NextRequest, NextResponse } from 'next/server' +import { createClient } from '@/lib/supabase/server' +import type { PartnerDto, LicServerProblemDetails } from '@/lib/types/licserver' + +const LICSERVER_BASE_URL = process.env.LICSERVER_BASE_URL +const LICSERVER_API_KEY = process.env.LICSERVER_API_KEY + +/** + * GET /api/licserver/partners/[id] + * + * Proxy to LicServer GET /api-v1/partners/{id} + * [id] must be a valid UUID. + * + * Secured: requires a valid Supabase session. + */ +export async function GET( + _req: NextRequest, + { params }: { params: Promise<{ id: string }> } +) { + // ── Auth guard ───────────────────────────────────────────────────────── + const supabase = await createClient() + const { data: { user } } = await supabase.auth.getUser() + if (!user) { + return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }) + } + + // ── Env check ────────────────────────────────────────────────────────── + if (!LICSERVER_BASE_URL || !LICSERVER_API_KEY) { + console.error('[licserver] LICSERVER_BASE_URL or LICSERVER_API_KEY not set') + return NextResponse.json( + { error: 'LicServer not configured' }, + { status: 503 } + ) + } + + const { id } = await params + + // Basic UUID format guard + const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i + if (!UUID_RE.test(id)) { + return NextResponse.json({ error: 'Invalid partner ID' }, { status: 400 }) + } + + // ── Proxy request ────────────────────────────────────────────────────── + try { + const res = await fetch( + `${LICSERVER_BASE_URL}/api-v1/partners/${id}`, + { + method: 'GET', + headers: { + 'X-Api-Key': LICSERVER_API_KEY, + 'Accept': 'application/json', + }, + signal: AbortSignal.timeout(5_000), + } + ) + + if (!res.ok) { + const problem: LicServerProblemDetails = await res.json().catch(() => ({})) + return NextResponse.json(problem, { status: res.status }) + } + + const data: PartnerDto = await res.json() + return NextResponse.json(data) + + } catch (err) { + console.error('[licserver] partner/:id fetch error:', err) + return NextResponse.json( + { error: 'LicServer nicht erreichbar' }, + { status: 502 } + ) + } +} diff --git a/shop/app/api/licserver/partners/route.ts b/shop/app/api/licserver/partners/route.ts new file mode 100644 index 0000000..5190e01 --- /dev/null +++ b/shop/app/api/licserver/partners/route.ts @@ -0,0 +1,71 @@ +import { NextRequest, NextResponse } from 'next/server' +import { createClient } from '@/lib/supabase/server' +import type { PartnerDtoPagedResult, LicServerProblemDetails } from '@/lib/types/licserver' + +const LICSERVER_BASE_URL = process.env.LICSERVER_BASE_URL +const LICSERVER_API_KEY = process.env.LICSERVER_API_KEY + +/** + * GET /api/licserver/partners + * + * Proxy to LicServer GET /api-v1/partners + * Query params forwarded: search, page, pageSize + * + * Secured: requires a valid Supabase session. + * The X-Api-Key is injected server-side and never exposed to the browser. + */ +export async function GET(req: NextRequest) { + // ── Auth guard ───────────────────────────────────────────────────────── + const supabase = await createClient() + const { data: { user } } = await supabase.auth.getUser() + if (!user) { + return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }) + } + + // ── Env check ────────────────────────────────────────────────────────── + if (!LICSERVER_BASE_URL || !LICSERVER_API_KEY) { + console.error('[licserver] LICSERVER_BASE_URL or LICSERVER_API_KEY not set') + return NextResponse.json( + { error: 'LicServer not configured' }, + { status: 503 } + ) + } + + // ── Forward query params ─────────────────────────────────────────────── + const { searchParams } = req.nextUrl + const upstream = new URL(`${LICSERVER_BASE_URL}/api-v1/partners`) + const search = searchParams.get('search') + const page = searchParams.get('page') + const pageSize = searchParams.get('pageSize') + if (search) upstream.searchParams.set('search', search) + if (page) upstream.searchParams.set('page', page) + if (pageSize) upstream.searchParams.set('pageSize', pageSize) + + // ── Proxy request ────────────────────────────────────────────────────── + try { + const res = await fetch(upstream.toString(), { + method: 'GET', + headers: { + 'X-Api-Key': LICSERVER_API_KEY, + 'Accept': 'application/json', + }, + // Internal container network — short timeout + signal: AbortSignal.timeout(5_000), + }) + + if (!res.ok) { + const problem: LicServerProblemDetails = await res.json().catch(() => ({})) + return NextResponse.json(problem, { status: res.status }) + } + + const data: PartnerDtoPagedResult = await res.json() + return NextResponse.json(data) + + } catch (err) { + console.error('[licserver] partners fetch error:', err) + return NextResponse.json( + { error: 'LicServer nicht erreichbar' }, + { status: 502 } + ) + } +} diff --git a/shop/lib/types/licserver.ts b/shop/lib/types/licserver.ts new file mode 100644 index 0000000..eddb2c3 --- /dev/null +++ b/shop/lib/types/licserver.ts @@ -0,0 +1,31 @@ +/** + * TypeScript types generated from the CASPOS LicServer OpenAPI spec v1. + * Source: /api-v1/partners endpoints + */ + +export interface PartnerDto { + id: string // UUID + name: string | null + email: string | null + erpId: string | null +} + +export interface PartnerDtoPagedResult { + page: number + pageSize: number + totalCount: number + totalPages: number + hasNextPage: boolean + hasPreviousPage: boolean + items: PartnerDto[] | null +} + +/** Standard RFC 7807 problem response from LicServer */ +export interface LicServerProblemDetails { + type?: string | null + title?: string | null + status?: number | null + detail?: string | null + instance?: string | null + [key: string]: unknown +}