Files
nexarch/web/connect-setup/lib/api.ts
patrickandClaude Sonnet 5 1ee268f25f API-01: Connect-Konfigurationsoberflaeche (Connectoren einrichten)
Neue Oberflaeche web/connect-setup (Next.js/React, @nexarch/shl):
Connector-Typ waehlen, Zugangsdaten hinterlegen (API-Key/OAuth2),
Feld-Mapping je Verbindung bearbeiten — vollstaendig ohne
Konfigurationsdatei. Zugangsdaten werden nie im Klartext angezeigt,
nur als feste Maske plus letzte 4 Zeichen.

connect/cmd/setup-a11y-fixture + web/a11y-audit/connect-setup-run.sh
fuehren einen echten Browserlauf (Chrome/puppeteer-core) gegen den
echten Dienst und eine echte Postgres-Wegwerf-DB durch: Formular
tatsaechlich ausgefuellt und abgesendet, Maskierung im DOM geprueft,
Mapping-Aenderung nach Neuladen bestaetigt. axe: 0 Befunde in hell/dunkel.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-10-16 13:25:59 +00:00

114 lines
3.3 KiB
TypeScript

// Duenner Client fuer API-01 (connect/internal/connectorsetupapi) — enthaelt
// keine eigene Geschaeftslogik, nur Datenabruf/-versand. Identitaet kommt
// uebergangsweise (wie bei web/connect-status vor der zentralen
// Core-JWT-Anbindung) aus Umgebungsvariablen, die als Header durchgereicht
// werden.
export class UnauthorizedError extends Error {}
export class ApiError extends Error {}
function apiBase(): string {
const base = process.env.NEXT_PUBLIC_CONNECT_API_URL;
if (!base) {
throw new Error("NEXT_PUBLIC_CONNECT_API_URL ist nicht gesetzt (Umgebungsvariable erforderlich)");
}
return base;
}
function identityHeaders(): Record<string, string> {
const userID = process.env.NEXT_PUBLIC_USER_ID;
const tenant = process.env.NEXT_PUBLIC_TENANT_SLUG;
const headers: Record<string, string> = {};
if (userID) headers["X-User-Id"] = userID;
if (tenant) headers["X-Tenant-Slug"] = tenant;
return headers;
}
async function req<T>(path: string, init?: RequestInit): Promise<T> {
const res = await fetch(`${apiBase()}${path}`, {
...init,
headers: {
...identityHeaders(),
...(init?.body ? { "Content-Type": "application/json" } : {}),
...init?.headers,
},
});
if (res.status === 401) {
throw new UnauthorizedError("Anmeldung erforderlich, um Connectoren einzurichten.");
}
if (!res.ok) {
const body = await res.json().catch(() => ({}));
throw new ApiError(body.error ?? `Anfrage fehlgeschlagen (${res.status})`);
}
return res.json().catch(() => undefined as T);
}
export type CredentialKind = "oauth2" | "api_key";
export interface FieldDef {
nexarch_field: string;
required: boolean;
}
export interface Entry {
nexarch_field: string;
external_field: string;
}
export interface ConnectorType {
id: string;
display_name: string;
credential_kind: CredentialKind;
schema: FieldDef[];
}
export interface Connection {
connector_type: string;
connector_type_label: string;
connector_instance_id: string;
credential_kind: CredentialKind;
masked_credential: string;
expires_at?: string;
updated_at: string;
}
export interface MappingView {
schema: FieldDef[];
fields: Entry[];
version: number;
}
export function fetchCatalog(): Promise<ConnectorType[]> {
return req("/connectors/catalog");
}
export function fetchConnections(): Promise<Connection[]> {
return req("/connectors/connections");
}
export interface SetConnectionInput {
kind: CredentialKind;
api_key?: string;
access_token?: string;
refresh_token?: string;
expires_at?: string;
}
export function setConnection(connectorType: string, instanceID: string, input: SetConnectionInput): Promise<Connection> {
return req(`/connectors/connections/${encodeURIComponent(connectorType)}/${encodeURIComponent(instanceID)}`, {
method: "PUT",
body: JSON.stringify(input),
});
}
export function fetchMapping(connectorType: string, instanceID: string): Promise<MappingView> {
return req(`/connectors/connections/${encodeURIComponent(connectorType)}/${encodeURIComponent(instanceID)}/mapping`);
}
export function saveMapping(connectorType: string, instanceID: string, fields: Entry[]): Promise<MappingView> {
return req(`/connectors/connections/${encodeURIComponent(connectorType)}/${encodeURIComponent(instanceID)}/mapping`, {
method: "PUT",
body: JSON.stringify({ fields }),
});
}