From 98bce1b0da675715b2e5482c0ad61ba21135f62f Mon Sep 17 00:00:00 2001
From: Server-Bibliothek <server-bibliothek@lokal>
Date: Di, 04 Aug 2026 07:00:04 +0200
Subject: [PATCH] Add a desktop client that knows what is installed locally

---
 packages/bibliothek-client/src/api-client.ts |  130 +++++++++++++++++++++++++++++++++++++++++++
 1 files changed, 130 insertions(+), 0 deletions(-)

diff --git a/packages/bibliothek-client/src/api-client.ts b/packages/bibliothek-client/src/api-client.ts
new file mode 100644
index 0000000..11f7bec
--- /dev/null
+++ b/packages/bibliothek-client/src/api-client.ts
@@ -0,0 +1,130 @@
+import type { Einstellungen, EinstellungenAnsicht, GeheimnisUebersicht, Software, SoftwareEingabe } from 'bibliothek-types';
+
+/** Wertet eine Antwort aus, wirft bei Fehler mit der Server-Meldung. */
+async function jsonOderFehler<T>(antwort: Response): Promise<T> {
+  if (!antwort.ok) {
+    const daten = (await antwort.json().catch(() => ({}))) as { fehler?: string };
+    throw new Error(daten.fehler ?? `Fehler ${antwort.status}`);
+  }
+  return antwort.json() as Promise<T>;
+}
+
+/** Lädt den kompletten Software-Katalog vom Server. */
+export async function ladeKatalog(): Promise<Software[]> {
+  return jsonOderFehler(await fetch('/api/software'));
+}
+
+/** Fragt, ob der Zugriff direkt am Server (lokal) erfolgt — steuert die Sichtbarkeit der Verwaltung. */
+export async function ladeUmgebung(): Promise<{ lokal: boolean }> {
+  return jsonOderFehler(await fetch('/api/umgebung'));
+}
+
+export async function softwareAnlegen(eingabe: SoftwareEingabe): Promise<Software> {
+  return jsonOderFehler(
+    await fetch('/api/software', {
+      method: 'POST',
+      headers: { 'Content-Type': 'application/json' },
+      body: JSON.stringify(eingabe),
+    })
+  );
+}
+
+export async function softwareAktualisieren(id: number, eingabe: SoftwareEingabe): Promise<Software> {
+  return jsonOderFehler(
+    await fetch(`/api/software/${id}`, {
+      method: 'PUT',
+      headers: { 'Content-Type': 'application/json' },
+      body: JSON.stringify(eingabe),
+    })
+  );
+}
+
+export async function softwareLoeschen(id: number): Promise<void> {
+  const antwort = await fetch(`/api/software/${id}`, { method: 'DELETE' });
+  if (!antwort.ok) {
+    throw new Error(`Löschen fehlgeschlagen (Status ${antwort.status})`);
+  }
+}
+
+/** Hinterlegt eine Datei von Hand (Quellentyp „manuell"). */
+export async function dateiHochladen(id: number, datei: File): Promise<Software> {
+  return jsonOderFehler(
+    await fetch(`/api/software/${id}/datei?dateiname=${encodeURIComponent(datei.name)}`, {
+      method: 'POST',
+      headers: { 'Content-Type': 'application/octet-stream' },
+      body: datei,
+    })
+  );
+}
+
+/** Löst einen sofortigen Abgleich der Bezugsquelle einer Software aus. */
+export async function abgleichen(id: number): Promise<Software> {
+  return jsonOderFehler(await fetch(`/api/software/${id}/abgleichen`, { method: 'POST' }));
+}
+
+/** Gleicht alle Einträge mit gesetzter Quelle ab. */
+export async function alleAbgleichen(): Promise<Software[]> {
+  return jsonOderFehler(await fetch('/api/abgleich/alle', { method: 'POST' }));
+}
+
+/** Stand der Bau-Geheimnisse einer Software (nur Namen, nie die Werte). */
+export async function ladeGeheimnisse(id: number): Promise<GeheimnisUebersicht[]> {
+  return jsonOderFehler(await fetch(`/api/software/${id}/geheimnisse`));
+}
+
+/** Speichert Bau-Geheimnisse. Ein leerer Wert lässt den bisherigen unverändert. */
+export async function speichereGeheimnisse(id: number, werte: Array<{ name: string; wert: string }>): Promise<GeheimnisUebersicht[]> {
+  return jsonOderFehler(
+    await fetch(`/api/software/${id}/geheimnisse`, {
+      method: 'PUT',
+      headers: { 'Content-Type': 'application/json' },
+      body: JSON.stringify(werte),
+    })
+  );
+}
+
+/** URL für den Umzugs-Export (Katalog + Einstellungen, optional mit Geheimnissen). */
+export function exportUrl(mitGeheimnissen: boolean): string {
+  return `/api/export${mitGeheimnissen ? '?geheimnisse=ja' : ''}`;
+}
+
+/** Liest ein Umzugs-Paket ein und ersetzt damit den bisherigen Katalog. */
+export async function importiereUmzugsPaket(paket: unknown): Promise<{ uebernommen: number; mitGeheimnissen: number; hinweis: string }> {
+  return jsonOderFehler(
+    await fetch('/api/import', {
+      method: 'POST',
+      headers: { 'Content-Type': 'application/json' },
+      body: JSON.stringify(paket),
+    })
+  );
+}
+
+/** Lädt die Einstellungen samt der daraus abgeleiteten Pfade (nur lokal erlaubt). */
+export async function ladeEinstellungen(): Promise<EinstellungenAnsicht> {
+  return jsonOderFehler(await fetch('/api/einstellungen'));
+}
+
+export async function speichereEinstellungen(einstellungen: Einstellungen): Promise<EinstellungenAnsicht> {
+  return jsonOderFehler(
+    await fetch('/api/einstellungen', {
+      method: 'PUT',
+      headers: { 'Content-Type': 'application/json' },
+      body: JSON.stringify(einstellungen),
+    })
+  );
+}
+
+/** URL zum Herunterladen mehrerer Programme als ZIP. */
+export function downloadMehrereUrl(ids: number[], angegebenerName?: string): string {
+  const parameter = new URLSearchParams({ ids: ids.join(',') });
+  if (angegebenerName) {
+    parameter.set('name', angegebenerName);
+  }
+  return `/api/download-mehrere?${parameter.toString()}`;
+}
+
+/** URL zum Herunterladen der Datei einer Software (optional mit angegebenem Namen fürs Protokoll). */
+export function downloadUrl(id: number, angegebenerName?: string): string {
+  const basis = `/api/software/${id}/download`;
+  return angegebenerName ? `${basis}?name=${encodeURIComponent(angegebenerName)}` : basis;
+}

--
Gitblit v1.9.3