152 lines
4.5 KiB
TypeScript
152 lines
4.5 KiB
TypeScript
'use server'
|
|
|
|
import { createClient } from '@/lib/supabase/server'
|
|
import { createAdminClient } from '@/lib/supabase/admin'
|
|
|
|
export interface LicServerConfig {
|
|
base_url: string | null
|
|
api_key: string | null
|
|
}
|
|
|
|
/**
|
|
* Read LicServer config from the settings table.
|
|
* Falls back to environment variables if DB values are empty.
|
|
* Returns null config for unauthenticated callers.
|
|
*/
|
|
export async function getLicServerConfig(): Promise<LicServerConfig> {
|
|
const supabase = await createClient()
|
|
|
|
// Auth guard — never expose config to unauthenticated callers
|
|
const { data: { user } } = await supabase.auth.getUser()
|
|
if (!user) return { base_url: null, api_key: null }
|
|
|
|
const { data } = await supabase
|
|
.from('settings')
|
|
.select('licserver_base_url, licserver_api_key')
|
|
.eq('id', 'licserver')
|
|
.single()
|
|
|
|
return {
|
|
base_url: data?.licserver_base_url || process.env.LICSERVER_BASE_URL || null,
|
|
api_key: data?.licserver_api_key || process.env.LICSERVER_API_KEY || null,
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Save LicServer config. Admin-only — checks role before writing.
|
|
*/
|
|
export async function saveLicServerConfig(
|
|
base_url: string,
|
|
api_key: string
|
|
): Promise<{ success: boolean; error?: string }> {
|
|
const supabase = await createClient()
|
|
|
|
// Auth + role check
|
|
const { data: { user } } = await supabase.auth.getUser()
|
|
if (!user) return { success: false, error: 'Nicht angemeldet' }
|
|
|
|
const { data: userData } = await supabase
|
|
.from('users')
|
|
.select('role')
|
|
.eq('id', user.id)
|
|
.single()
|
|
|
|
// Only 'admin' exists in schema — 'superadmin' removed
|
|
if (!userData || userData.role !== 'admin') {
|
|
return { success: false, error: 'Keine Berechtigung (nur Admins)' }
|
|
}
|
|
|
|
const { error } = await supabase
|
|
.from('settings')
|
|
.upsert({
|
|
id: 'licserver',
|
|
licserver_base_url: base_url.trim() || null,
|
|
licserver_api_key: api_key.trim() || null,
|
|
updated_at: new Date().toISOString(),
|
|
})
|
|
|
|
if (error) return { success: false, error: error.message }
|
|
return { success: true }
|
|
}
|
|
|
|
/**
|
|
* Test the connection with current config.
|
|
* Calls GET /api-v1/partners?pageSize=1 and returns true if 200.
|
|
*/
|
|
export async function testLicServerConnection(): Promise<{ ok: boolean; message: string }> {
|
|
const cfg = await getLicServerConfig()
|
|
|
|
if (!cfg.base_url || !cfg.api_key) {
|
|
return { ok: false, message: 'URL oder API-Key nicht konfiguriert' }
|
|
}
|
|
|
|
try {
|
|
const res = await fetch(`${cfg.base_url}/api-v1/partners?pageSize=1`, {
|
|
headers: { 'X-Api-Key': cfg.api_key, Accept: 'application/json' },
|
|
signal: AbortSignal.timeout(5_000),
|
|
})
|
|
if (res.ok) return { ok: true, message: `Verbindung OK (HTTP ${res.status})` }
|
|
return { ok: false, message: `LicServer antwortete mit HTTP ${res.status}` }
|
|
} catch (err: any) {
|
|
return { ok: false, message: `Nicht erreichbar: ${err.message}` }
|
|
}
|
|
}
|
|
|
|
/**
|
|
* System version of getLicServerConfig that bypasses client auth check.
|
|
* Used internally by automated background jobs / cron routes.
|
|
*/
|
|
export async function getLicServerConfigSystem(): Promise<LicServerConfig> {
|
|
const admin = createAdminClient()
|
|
|
|
const { data } = await admin
|
|
.from('settings')
|
|
.select('licserver_base_url, licserver_api_key')
|
|
.eq('id', 'licserver')
|
|
.single()
|
|
|
|
return {
|
|
base_url: data?.licserver_base_url || process.env.LICSERVER_BASE_URL || 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: [],
|
|
}
|
|
}
|