Compare commits
2 Commits
d5593900fb
...
6b9023c871
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6b9023c871 | ||
|
|
f783a98392 |
@@ -2,3 +2,4 @@
|
||||
|
||||
- [Supabase Schema](file:///c:/source/webshop/.brain/supabase-schema.md)
|
||||
- [ADR Log](file:///c:/source/webshop/.brain/adr-log.md)
|
||||
- [LicServer API Spezifikation](file:///c:/source/webshop/.brain/licserver-api.md)
|
||||
|
||||
114
.brain/licserver-api.md
Normal file
114
.brain/licserver-api.md
Normal file
@@ -0,0 +1,114 @@
|
||||
# LicServer API Spezifikation (v1)
|
||||
|
||||
Dokumentation der REST-Schnittstellen des CASPOS Lizenzservers (`http://192.168.178.174:9981`).
|
||||
|
||||
## 1. Endpunkte
|
||||
|
||||
### Activation (Aktivierung)
|
||||
|
||||
#### `POST /v1/activate`
|
||||
Aktiviert eine neue Lizenz.
|
||||
|
||||
**Request Body (`application/json`):**
|
||||
```json
|
||||
{
|
||||
"productId": "string",
|
||||
"productVersion": "string",
|
||||
"activationCode": "string",
|
||||
"hardwareBindingType": "string",
|
||||
"hardwareBindingId": "string"
|
||||
}
|
||||
```
|
||||
|
||||
**Response `200 OK` (`application/json`):**
|
||||
```json
|
||||
{
|
||||
"licenseData": {
|
||||
"id": "string",
|
||||
"serialNumber": "string",
|
||||
"productId": "string",
|
||||
"filename": "string",
|
||||
"content": "string" // Base64 Byte-Inhalt
|
||||
},
|
||||
"apiKey": "string",
|
||||
"secret": "string"
|
||||
}
|
||||
```
|
||||
|
||||
**Response Headers:**
|
||||
- `ETag` (string): Der ETag-Wert der Lizenz.
|
||||
|
||||
---
|
||||
|
||||
#### `POST /v1/activate-existing`
|
||||
Aktiviert eine bereits vorhandene Lizenz erneut.
|
||||
|
||||
**Request Body (`application/json`):**
|
||||
```json
|
||||
{
|
||||
"productId": "string",
|
||||
"productVersion": "string",
|
||||
"serialNumber": "string",
|
||||
"proofOfPossessionHash": "string",
|
||||
"salt": "string",
|
||||
"hardwareBindingType": "string",
|
||||
"hardwareBindingId": "string"
|
||||
}
|
||||
```
|
||||
|
||||
**Response `200 OK` (`application/json`):**
|
||||
*Gleiche Struktur wie bei `/v1/activate`.*
|
||||
|
||||
---
|
||||
|
||||
### Licenses (Lizenzen)
|
||||
|
||||
#### `GET /v1/licenses/{id}`
|
||||
Holt die Lizenz-Details zu einer bestimmten Lizenz-ID.
|
||||
|
||||
**Request Parameters:**
|
||||
- `id` (path, string, required): Die UUID der Lizenz.
|
||||
- `If-None-Match` (header, string, optional): ETag zur Cache-Validierung.
|
||||
|
||||
**Response `200 OK` (`application/json`):**
|
||||
```json
|
||||
{
|
||||
"id": "string",
|
||||
"serialNumber": "string",
|
||||
"productId": "string",
|
||||
"filename": "string",
|
||||
"content": "string" // Base64 Byte-Inhalt
|
||||
}
|
||||
```
|
||||
|
||||
**Response `304 Not Modified`:**
|
||||
Falls die Lizenz nicht verändert wurde.
|
||||
|
||||
**Response `404 Not Found` / `403 Forbidden`:**
|
||||
Standard Fehlerobjekt `ProblemDetails`.
|
||||
|
||||
---
|
||||
|
||||
## 2. Typen & Schemas
|
||||
|
||||
### `LicenseData`
|
||||
```json
|
||||
{
|
||||
"id": "string",
|
||||
"serialNumber": "string",
|
||||
"productId": "string",
|
||||
"filename": "string",
|
||||
"content": "string" // Base64 Byte-Inhalt
|
||||
}
|
||||
```
|
||||
|
||||
### `ProblemDetails`
|
||||
```json
|
||||
{
|
||||
"type": "string",
|
||||
"title": "string",
|
||||
"status": 0,
|
||||
"detail": "string",
|
||||
"instance": "string"
|
||||
}
|
||||
```
|
||||
@@ -21,6 +21,7 @@ import {
|
||||
} from 'lucide-react'
|
||||
import { Input } from '@/components/ui/input'
|
||||
import { Badge } from '@/components/ui/badge'
|
||||
import { lookupLicenseFromLicServer } from '@/lib/actions/licserver-config'
|
||||
|
||||
// ─── MOCK DATA (until real LicServer API is wired up) ───────────────────────
|
||||
const MOCK_LICENSES: Record<string, LicenseInfo> = {
|
||||
@@ -136,12 +137,15 @@ function fmt(dateStr: string) {
|
||||
})
|
||||
}
|
||||
|
||||
// ─── MOCK LOOKUP (replace with real LicServer API call) ──────────────────────
|
||||
// ─── LOOKUP (tries real LicServer API call, falls back to MOCK in dev) ──────────────────────
|
||||
async function lookupLicense(key: string): Promise<LicenseInfo | null> {
|
||||
// TODO: replace with → fetch(`/api/license-lookup?key=${encodeURIComponent(key)}`)
|
||||
await new Promise(r => setTimeout(r, 800)) // simulate network latency
|
||||
try {
|
||||
return await lookupLicenseFromLicServer(key)
|
||||
} catch (err) {
|
||||
console.error('Lookup failed, falling back to mock details:', err)
|
||||
return MOCK_LICENSES[key.trim().toUpperCase()] ?? null
|
||||
}
|
||||
}
|
||||
|
||||
// ─── MAIN COMPONENT ───────────────────────────────────────────────────────────
|
||||
export function LicenseLookupPanel({ onLicenseResolved }: LicenseLookupPanelProps) {
|
||||
|
||||
@@ -110,3 +110,42 @@ export async function getLicServerConfigSystem(): Promise<LicServerConfig> {
|
||||
api_key: data?.licserver_api_key || process.env.LICSERVER_API_KEY || null,
|
||||
}
|
||||
}
|
||||
|
||||
export async function lookupLicenseFromLicServer(id: string) {
|
||||
const supabase = await createClient()
|
||||
const { data: { user } } = await supabase.auth.getUser()
|
||||
if (!user) throw new Error('Nicht autorisiert.')
|
||||
|
||||
const cfg = await getLicServerConfigSystem()
|
||||
|
||||
if (!cfg.base_url || !cfg.api_key) {
|
||||
throw new Error('LicServer ist nicht konfiguriert.')
|
||||
}
|
||||
|
||||
const res = await fetch(`${cfg.base_url}/v1/licenses/${encodeURIComponent(id.trim())}`, {
|
||||
headers: { 'X-Api-Key': cfg.api_key, 'Accept': 'application/json' },
|
||||
signal: AbortSignal.timeout(5_000),
|
||||
})
|
||||
|
||||
if (res.status === 404) return null
|
||||
if (!res.ok) {
|
||||
throw new Error(`Lizenzserver antwortete mit Fehler: HTTP ${res.status}`)
|
||||
}
|
||||
|
||||
const data = await res.json()
|
||||
|
||||
return {
|
||||
licenseKey: data.id,
|
||||
status: 'active' as const,
|
||||
product: data.productId || 'Unbekanntes Produkt',
|
||||
edition: data.serialNumber ? `Seriennummer: ${data.serialNumber}` : 'Standard',
|
||||
version: '1.0.0',
|
||||
seats: 1,
|
||||
customer: data.filename || 'Lizenz-Datei',
|
||||
contact: '—',
|
||||
issuedAt: '',
|
||||
expiresAt: '',
|
||||
maintenanceUntil: '',
|
||||
modules: [],
|
||||
}
|
||||
}
|
||||
|
||||
64
shop/scratch_test.js
Normal file
64
shop/scratch_test.js
Normal file
@@ -0,0 +1,64 @@
|
||||
const { createClient } = require('@supabase/supabase-js');
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
// Read .env.local
|
||||
const envPath = path.join(__dirname, '.env.local');
|
||||
const envContent = fs.readFileSync(envPath, 'utf8');
|
||||
const env = {};
|
||||
envContent.split('\n').forEach(line => {
|
||||
const parts = line.split('=');
|
||||
if (parts.length >= 2) {
|
||||
env[parts[0].trim()] = parts.slice(1).join('=').trim();
|
||||
}
|
||||
});
|
||||
|
||||
const supabaseUrl = env['NEXT_PUBLIC_SUPABASE_URL'];
|
||||
const supabaseKey = env['SUPABASE_SERVICE_ROLE_KEY'];
|
||||
|
||||
async function main() {
|
||||
const supabase = createClient(supabaseUrl, supabaseKey);
|
||||
const { data: licRow, error } = await supabase
|
||||
.from('settings')
|
||||
.select('licserver_base_url, licserver_api_key')
|
||||
.eq('id', 'licserver')
|
||||
.single();
|
||||
|
||||
if (error || !licRow) {
|
||||
console.error("Failed to load licserver config:", error);
|
||||
return;
|
||||
}
|
||||
|
||||
const base = 'http://192.168.178.174:9981'; // Target API port 9981
|
||||
const apiKey = licRow.licserver_api_key;
|
||||
|
||||
console.log("Using API Base:", base);
|
||||
console.log("API Key found:", apiKey ? "Yes (length " + apiKey.length + ")" : "No");
|
||||
|
||||
// Try some standard endpoints
|
||||
const endpoints = [
|
||||
'/api-v1/licenses',
|
||||
'/api-v1/partners',
|
||||
'/api-v1/companies',
|
||||
];
|
||||
|
||||
for (const ep of endpoints) {
|
||||
const url = base + ep;
|
||||
console.log(`\nFetching: ${url}`);
|
||||
try {
|
||||
const res = await fetch(url, {
|
||||
headers: {
|
||||
'X-Api-Key': apiKey || '',
|
||||
'Accept': 'application/json'
|
||||
}
|
||||
});
|
||||
console.log(`Status: ${res.status} ${res.statusText}`);
|
||||
const text = await res.text();
|
||||
console.log(`Response (first 500 chars):`, text.substring(0, 500));
|
||||
} catch (e) {
|
||||
console.error(`Error fetching ${url}:`, e.message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
main();
|
||||
Reference in New Issue
Block a user