Compare commits
2 Commits
6b9023c871
...
3a090b7664
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3a090b7664 | ||
|
|
e2d92a031f |
@@ -1,8 +1,8 @@
|
|||||||
# LicServer API Spezifikation (v1)
|
# LicServer API Spezifikation (v1)
|
||||||
|
|
||||||
Dokumentation der REST-Schnittstellen des CASPOS Lizenzservers (`http://192.168.178.174:9981`).
|
Dokumentation der REST-Schnittstellen des CASPOS Lizenzservers.
|
||||||
|
|
||||||
## 1. Endpunkte
|
## 1. Endpunkte (Port 9981 - Activation API)
|
||||||
|
|
||||||
### Activation (Aktivierung)
|
### Activation (Aktivierung)
|
||||||
|
|
||||||
@@ -89,7 +89,119 @@ Standard Fehlerobjekt `ProblemDetails`.
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 2. Typen & Schemas
|
## 2. Endpunkte (Port 9980 - Management API)
|
||||||
|
|
||||||
|
Alle Anfragen an die Management-API müssen authentifiziert sein.
|
||||||
|
- **Header-Format:** `X-Api-Key: <Schlüssel>`
|
||||||
|
|
||||||
|
### Partners (Partner)
|
||||||
|
|
||||||
|
#### `GET /api-v1/partners`
|
||||||
|
Gibt eine paginierte Liste aller Partner zurück.
|
||||||
|
|
||||||
|
**Request Parameters (Query):**
|
||||||
|
- `search` (string, optional): Filtert nach Name/E-Mail.
|
||||||
|
- `page` (integer, default: 1)
|
||||||
|
- `pageSize` (integer, default: 20)
|
||||||
|
|
||||||
|
**Response `200 OK` (`application/json`):**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"page": 1,
|
||||||
|
"pageSize": 20,
|
||||||
|
"totalCount": 1,
|
||||||
|
"totalPages": 1,
|
||||||
|
"hasNextPage": false,
|
||||||
|
"hasPreviousPage": false,
|
||||||
|
"items": [
|
||||||
|
{
|
||||||
|
"id": "string (UUID)",
|
||||||
|
"name": "string",
|
||||||
|
"email": "string",
|
||||||
|
"erpId": "string"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### `GET /api-v1/partners/{id}`
|
||||||
|
Holt Partner-Details zu einer bestimmten ID.
|
||||||
|
|
||||||
|
**Response `200 OK` (`application/json`):**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"id": "string (UUID)",
|
||||||
|
"name": "string",
|
||||||
|
"email": "string",
|
||||||
|
"erpId": "string"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Customers (Kunden von Partnern)
|
||||||
|
|
||||||
|
#### `GET /api-v1/partners/{partnerId}/customers`
|
||||||
|
Gibt eine paginierte Liste aller Kunden eines bestimmten Partners zurück.
|
||||||
|
|
||||||
|
**Request Parameters (Query):**
|
||||||
|
- `search` (string, optional): Filtert nach Name/E-Mail.
|
||||||
|
- `page` (integer, default: 1)
|
||||||
|
- `pageSize` (integer, default: 20)
|
||||||
|
|
||||||
|
**Response `200 OK` (`application/json`):**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"page": 1,
|
||||||
|
"pageSize": 20,
|
||||||
|
"totalCount": 1,
|
||||||
|
"totalPages": 1,
|
||||||
|
"hasNextPage": false,
|
||||||
|
"hasPreviousPage": false,
|
||||||
|
"items": [
|
||||||
|
{
|
||||||
|
"id": "string (UUID)",
|
||||||
|
"name": "string",
|
||||||
|
"street": "string",
|
||||||
|
"houseNumber": "string",
|
||||||
|
"houseNumberAddon": "string",
|
||||||
|
"zipCode": "string",
|
||||||
|
"city": "string",
|
||||||
|
"countryCode": "string",
|
||||||
|
"email": "string",
|
||||||
|
"partnerId": "string (UUID)",
|
||||||
|
"erpId": "string"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### `GET /api-v1/partners/{partnerId}/customers/{customerId}`
|
||||||
|
Holt detaillierte Kundeninformationen.
|
||||||
|
|
||||||
|
**Response `200 OK` (`application/json`):**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"id": "string (UUID)",
|
||||||
|
"name": "string",
|
||||||
|
"street": "string",
|
||||||
|
"houseNumber": "string",
|
||||||
|
"houseNumberAddon": "string",
|
||||||
|
"zipCode": "string",
|
||||||
|
"city": "string",
|
||||||
|
"countryCode": "string",
|
||||||
|
"email": "string",
|
||||||
|
"partnerId": "string (UUID)",
|
||||||
|
"erpId": "string",
|
||||||
|
"partnerName": "string",
|
||||||
|
"taxNumber": "string",
|
||||||
|
"vatIdNumber": "string",
|
||||||
|
"notes": "string",
|
||||||
|
"locations": []
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. Typen & Schemas
|
||||||
|
|
||||||
### `LicenseData`
|
### `LicenseData`
|
||||||
```json
|
```json
|
||||||
@@ -112,3 +224,4 @@ Standard Fehlerobjekt `ProblemDetails`.
|
|||||||
"instance": "string"
|
"instance": "string"
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -184,8 +184,7 @@ export default function AdminSettings() {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{statusMsg && (
|
{statusMsg && (
|
||||||
<div className={`p-4 rounded-lg text-sm border ${
|
<div className={`p-4 rounded-lg text-sm border ${statusType === 'success' ? 'bg-green-500/10 border-green-500/20 text-green-600 dark:text-green-400' :
|
||||||
statusType === 'success' ? 'bg-green-500/10 border-green-500/20 text-green-600 dark:text-green-400' :
|
|
||||||
statusType === 'error' ? 'bg-destructive/10 border-destructive/20 text-destructive' :
|
statusType === 'error' ? 'bg-destructive/10 border-destructive/20 text-destructive' :
|
||||||
'bg-blue-500/10 border-blue-500/20 text-blue-600 dark:text-blue-400'
|
'bg-blue-500/10 border-blue-500/20 text-blue-600 dark:text-blue-400'
|
||||||
}`}>
|
}`}>
|
||||||
@@ -275,15 +274,13 @@ export default function AdminSettings() {
|
|||||||
LicServer Konfiguration
|
LicServer Konfiguration
|
||||||
</h2>
|
</h2>
|
||||||
<p className="text-slate-500 dark:text-slate-400 text-xs mt-1">
|
<p className="text-slate-500 dark:text-slate-400 text-xs mt-1">
|
||||||
Verbindungseinstellungen zum CASPOS Lizenzserver (internes Container-Netz).
|
Verbindungseinstellungen zum CASPOS Lizenzserver.
|
||||||
Der API-Key wird verschlüsselt in der Datenbank gespeichert und nie an den Browser übertragen.
|
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Status Message */}
|
{/* Status Message */}
|
||||||
{licMsg && (
|
{licMsg && (
|
||||||
<div className={`p-3 rounded-lg text-sm border ${
|
<div className={`p-3 rounded-lg text-sm border ${licMsgType === 'success'
|
||||||
licMsgType === 'success'
|
|
||||||
? 'bg-green-500/10 border-green-500/20 text-green-600 dark:text-green-400'
|
? 'bg-green-500/10 border-green-500/20 text-green-600 dark:text-green-400'
|
||||||
: 'bg-destructive/10 border-destructive/20 text-destructive'
|
: 'bg-destructive/10 border-destructive/20 text-destructive'
|
||||||
}`}>
|
}`}>
|
||||||
@@ -293,8 +290,7 @@ export default function AdminSettings() {
|
|||||||
|
|
||||||
{/* Connection Test Result */}
|
{/* Connection Test Result */}
|
||||||
{licStatus && (
|
{licStatus && (
|
||||||
<div className={`flex items-center gap-2 p-3 rounded-lg text-sm border ${
|
<div className={`flex items-center gap-2 p-3 rounded-lg text-sm border ${licStatus.ok
|
||||||
licStatus.ok
|
|
||||||
? 'bg-emerald-500/10 border-emerald-500/20 text-emerald-600 dark:text-emerald-400'
|
? 'bg-emerald-500/10 border-emerald-500/20 text-emerald-600 dark:text-emerald-400'
|
||||||
: 'bg-amber-500/10 border-amber-500/20 text-amber-600 dark:text-amber-400'
|
: 'bg-amber-500/10 border-amber-500/20 text-amber-600 dark:text-amber-400'
|
||||||
}`}>
|
}`}>
|
||||||
@@ -318,7 +314,7 @@ export default function AdminSettings() {
|
|||||||
placeholder="http://192.168.178.174:9980"
|
placeholder="http://192.168.178.174:9980"
|
||||||
className="text-sm font-mono"
|
className="text-sm font-mono"
|
||||||
/>
|
/>
|
||||||
<p className="text-[10px] text-slate-400">Interne Container-URL ohne trailing slash</p>
|
<p className="text-[10px] text-slate-400">URL des CASPOS Lizenzservers</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* API Key */}
|
{/* API Key */}
|
||||||
@@ -344,7 +340,7 @@ export default function AdminSettings() {
|
|||||||
{showKey ? <EyeOff className="w-4 h-4" /> : <Eye className="w-4 h-4" />}
|
{showKey ? <EyeOff className="w-4 h-4" /> : <Eye className="w-4 h-4" />}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<p className="text-[10px] text-slate-400">Wird als X-Api-Key Header an den LicServer gesendet</p>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -388,7 +384,7 @@ export default function AdminSettings() {
|
|||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
85
shop/app/api/licserver/partners/[id]/customers/route.ts
Normal file
85
shop/app/api/licserver/partners/[id]/customers/route.ts
Normal file
@@ -0,0 +1,85 @@
|
|||||||
|
import { NextRequest, NextResponse } from 'next/server'
|
||||||
|
import { createClient } from '@/lib/supabase/server'
|
||||||
|
import { getLicServerConfig } from '@/lib/actions/licserver-config'
|
||||||
|
import type { CustomerDtoPagedResult, LicServerProblemDetails } from '@/lib/types/licserver'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* GET /api/licserver/partners/[id]/customers
|
||||||
|
*
|
||||||
|
* Proxy to LicServer GET /api-v1/partners/{id}/customers
|
||||||
|
* [id] must be a valid UUID.
|
||||||
|
* Query params forwarded: search, page, pageSize
|
||||||
|
*
|
||||||
|
* Secured: requires a valid Supabase session.
|
||||||
|
* base_url + api_key are read from DB settings (admin-configurable),
|
||||||
|
* with fallback to LICSERVER_BASE_URL / LICSERVER_API_KEY env vars.
|
||||||
|
*/
|
||||||
|
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 })
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Load config from DB (falls back to env vars) ──────────────────────
|
||||||
|
const cfg = await getLicServerConfig()
|
||||||
|
if (!cfg.base_url || !cfg.api_key) {
|
||||||
|
console.error('[licserver] base_url or api_key not configured (DB or env)')
|
||||||
|
return NextResponse.json(
|
||||||
|
{ error: 'LicServer nicht konfiguriert. Bitte in den Admin-Einstellungen hinterlegen.' },
|
||||||
|
{ 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 })
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Forward query params ───────────────────────────────────────────────
|
||||||
|
const { searchParams } = req.nextUrl
|
||||||
|
const upstream = new URL(`${cfg.base_url}/api-v1/partners/${id}/customers`)
|
||||||
|
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': cfg.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: CustomerDtoPagedResult = await res.json()
|
||||||
|
return NextResponse.json(data)
|
||||||
|
|
||||||
|
} catch (err) {
|
||||||
|
console.error('[licserver] partner/:id/customers fetch error:', err)
|
||||||
|
return NextResponse.json(
|
||||||
|
{ error: 'LicServer nicht erreichbar' },
|
||||||
|
{ status: 502 }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -29,3 +29,60 @@ export interface LicServerProblemDetails {
|
|||||||
instance?: string | null
|
instance?: string | null
|
||||||
[key: string]: unknown
|
[key: string]: unknown
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface CustomerDto {
|
||||||
|
id: string // UUID
|
||||||
|
name: string | null
|
||||||
|
street: string | null
|
||||||
|
houseNumber: string | null
|
||||||
|
houseNumberAddon: string | null
|
||||||
|
zipCode: string | null
|
||||||
|
city: string | null
|
||||||
|
countryCode: string | null
|
||||||
|
email: string | null
|
||||||
|
partnerId: string | null
|
||||||
|
erpId: string | null
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CustomerDtoPagedResult {
|
||||||
|
page: number
|
||||||
|
pageSize: number
|
||||||
|
totalCount: number
|
||||||
|
totalPages: number
|
||||||
|
hasNextPage: boolean
|
||||||
|
hasPreviousPage: boolean
|
||||||
|
items: CustomerDto[] | null
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface LocationDto {
|
||||||
|
id: string // UUID
|
||||||
|
name: string | null
|
||||||
|
street: string | null
|
||||||
|
houseNumber: string | null
|
||||||
|
houseNumberAddon: string | null
|
||||||
|
countryCode: string | null
|
||||||
|
postalCode: string | null
|
||||||
|
city: string | null
|
||||||
|
taxNumber: string | null
|
||||||
|
vatIdNumber: string | null
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CustomerDetailsDto {
|
||||||
|
id: string // UUID
|
||||||
|
name: string | null
|
||||||
|
street: string | null
|
||||||
|
houseNumber: string | null
|
||||||
|
houseNumberAddon: string | null
|
||||||
|
zipCode: string | null
|
||||||
|
city: string | null
|
||||||
|
countryCode: string | null
|
||||||
|
email: string | null
|
||||||
|
partnerId: string | null
|
||||||
|
erpId: string | null
|
||||||
|
partnerName: string | null
|
||||||
|
taxNumber: string | null
|
||||||
|
vatIdNumber: string | null
|
||||||
|
notes: string | null
|
||||||
|
locations: LocationDto[] | null
|
||||||
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user