feat(licserver): add partner API proxy routes and types
All checks were successful
Staging Build / build (push) Successful in 2m54s
All checks were successful
Staging Build / build (push) Successful in 2m54s
- 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)
This commit is contained in:
@@ -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
|
||||
|
||||
73
shop/app/api/licserver/partners/[id]/route.ts
Normal file
73
shop/app/api/licserver/partners/[id]/route.ts
Normal file
@@ -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 }
|
||||
)
|
||||
}
|
||||
}
|
||||
71
shop/app/api/licserver/partners/route.ts
Normal file
71
shop/app/api/licserver/partners/route.ts
Normal file
@@ -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 }
|
||||
)
|
||||
}
|
||||
}
|
||||
31
shop/lib/types/licserver.ts
Normal file
31
shop/lib/types/licserver.ts
Normal file
@@ -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
|
||||
}
|
||||
Reference in New Issue
Block a user