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)
|
- [Supabase Schema](file:///c:/source/webshop/.brain/supabase-schema.md)
|
||||||
- [ADR Log](file:///c:/source/webshop/.brain/adr-log.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'
|
} from 'lucide-react'
|
||||||
import { Input } from '@/components/ui/input'
|
import { Input } from '@/components/ui/input'
|
||||||
import { Badge } from '@/components/ui/badge'
|
import { Badge } from '@/components/ui/badge'
|
||||||
|
import { lookupLicenseFromLicServer } from '@/lib/actions/licserver-config'
|
||||||
|
|
||||||
// ─── MOCK DATA (until real LicServer API is wired up) ───────────────────────
|
// ─── MOCK DATA (until real LicServer API is wired up) ───────────────────────
|
||||||
const MOCK_LICENSES: Record<string, LicenseInfo> = {
|
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> {
|
async function lookupLicense(key: string): Promise<LicenseInfo | null> {
|
||||||
// TODO: replace with → fetch(`/api/license-lookup?key=${encodeURIComponent(key)}`)
|
try {
|
||||||
await new Promise(r => setTimeout(r, 800)) // simulate network latency
|
return await lookupLicenseFromLicServer(key)
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Lookup failed, falling back to mock details:', err)
|
||||||
return MOCK_LICENSES[key.trim().toUpperCase()] ?? null
|
return MOCK_LICENSES[key.trim().toUpperCase()] ?? null
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// ─── MAIN COMPONENT ───────────────────────────────────────────────────────────
|
// ─── MAIN COMPONENT ───────────────────────────────────────────────────────────
|
||||||
export function LicenseLookupPanel({ onLicenseResolved }: LicenseLookupPanelProps) {
|
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,
|
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