/** * Domains — API-Client (quick-261008-dts). Konsumiert `/modules/domains/*`. * `credentials: 'include'` fuer Cookie-Auth, `NEXT_PUBLIC_API_URL` als Basis * (Muster `nextcloud-status-api.ts`). Der Browser spricht nie mit AutoDNS; * Zugangsdaten kommen hier nie zurueck (nur `hasPassword`). */ const API_URL = process.env.NEXT_PUBLIC_API_URL || 'http://localhost:3001'; const BASE = '/modules/domains'; export type DomainsEnvironment = 'DEMO' | 'LIVE'; export interface DomainsStatus { environment: DomainsEnvironment; /** Ist das AKTIVE System eingerichtet? */ configured: boolean; demoConfigured: boolean; liveConfigured: boolean; } export interface DomainsEnvironmentAccess { user: string | null; hasPassword: boolean; context: number | null; } export interface DomainsSettings { environment: DomainsEnvironment; demo: DomainsEnvironmentAccess; live: DomainsEnvironmentAccess; configured: { demo: boolean; live: boolean }; } /** Teilspeichern: nur mitgeschickte Felder aendern sich; leeres Passwort = unveraendert. */ export interface SaveDomainsSettingsInput { environment?: DomainsEnvironment; confirmLive?: boolean; demoUser?: string; demoPassword?: string; demoContext?: number | null; liveUser?: string; livePassword?: string; liveContext?: number | null; } export interface ConnectionTestResult { ok: boolean; kind?: string; message: string; } /** Fehler mit HTTP-Status, maschinenlesbarer Kennung und deutscher Servermeldung. */ export class DomainsRequestError extends Error { constructor( readonly status: number, readonly code: string | null, message: string, ) { super(message); this.name = 'DomainsRequestError'; } } async function failure(res: Response): Promise { let message = `Request failed (${res.status})`; let code: string | null = null; try { const body = await res.json(); const raw = body?.message; if (Array.isArray(raw)) message = raw.join(' '); else if (typeof raw === 'string') message = raw; if (typeof body?.code === 'string') code = body.code; } catch { // Antwort ohne JSON-Koerper — Standardmeldung bleibt. } return new DomainsRequestError(res.status, code, message); } async function request( path: string, init: { method?: string; json?: unknown } = {}, ): Promise { const method = init.method ?? 'GET'; const headers: Record = {}; let body: string | undefined; if (init.json !== undefined) { headers['Content-Type'] = 'application/json'; body = JSON.stringify(init.json); } const res = await fetch(`${API_URL}${BASE}${path}`, { method, credentials: 'include', headers, body, ...(method === 'GET' ? { cache: 'no-store' as const } : {}), }); if (!res.ok) throw await failure(res); if (res.status === 204) return undefined as T; return (await res.json()) as T; } export function getDomainsStatus(): Promise { return request('/status'); } export function getDomainsSettings(): Promise { return request('/settings'); } export function saveDomainsSettings(input: SaveDomainsSettingsInput): Promise { return request('/settings', { method: 'PUT', json: input }); } export function testDomainsConnection( environment: DomainsEnvironment, ): Promise { return request('/connection-test', { method: 'POST', json: { environment }, }); } // --- Kunden, Kontakte und Domains (quick-261008-dts, Aufgabe 2) ---------------- export interface DomainsCustomer { id: string; name: string; isOwnCompany: boolean; /** Zugeordnete Kontakte im AKTIVEN System. */ contactCount: number; } export interface DomainsCustomerInput { name: string; isOwnCompany?: boolean; } export type DomainsContactType = 'PERSON' | 'ORG'; export interface DomainsContact { /** AutoDNS-Kontakt-Nummer. */ id: number; type: string | null; displayName: string; fname: string | null; lname: string | null; organization: string | null; address: string[]; pcode: string | null; city: string | null; country: string | null; email: string | null; phone: string | null; alias: string | null; customerId: string | null; customerName: string | null; } export interface DomainsContactList { environment: DomainsEnvironment; contacts: DomainsContact[]; /** Es werden nur die ersten 2000 Eintraege geliefert. */ truncated: boolean; fetchedAt: string; } export interface DomainsDomain { name: string; /** ISO-Zeitpunkt oder null. */ expire: string | null; /** Rohwert von AutoDNS (`registryStatus`). */ status: string | null; cancelationPending: boolean; ownerContactId: number | null; ownerName: string | null; customerId: string | null; customerName: string | null; } export interface DomainsDomainList { environment: DomainsEnvironment; domains: DomainsDomain[]; truncated: boolean; fetchedAt: string; } export interface CreateDomainsContactInput { type: DomainsContactType; organization?: string; fname: string; lname: string; /** Anschriftzeilen (Strasse und Hausnummer, optional eine zweite Zeile). */ street: string[]; pcode: string; city: string; /** Laendercode nach ISO 3166-1 (zwei Grossbuchstaben). */ country: string; email: string; phone: string; customerId?: string; } export function listCustomers(): Promise { return request('/customers'); } export function createCustomer(input: DomainsCustomerInput): Promise { return request('/customers', { method: 'POST', json: input }); } export function updateCustomer(id: string, input: DomainsCustomerInput): Promise { return request(`/customers/${encodeURIComponent(id)}`, { method: 'PUT', json: input, }); } export function deleteCustomer(id: string): Promise<{ deleted: true }> { return request<{ deleted: true }>(`/customers/${encodeURIComponent(id)}`, { method: 'DELETE' }); } export function listContacts(opts: { refresh?: boolean } = {}): Promise { return request(`/contacts${opts.refresh ? '?refresh=1' : ''}`); } export function createContact(input: CreateDomainsContactInput): Promise { return request('/contacts', { method: 'POST', json: input }); } /** `customerId: null` loest die Zuordnung. */ export function assignContacts( contactIds: number[], customerId: string | null, ): Promise<{ assigned: number; removed: number }> { return request<{ assigned: number; removed: number }>('/contacts/assign', { method: 'POST', json: { contactIds, customerId }, }); } export function listDomains(opts: { refresh?: boolean } = {}): Promise { return request(`/domains${opts.refresh ? '?refresh=1' : ''}`); } // --- Verfuegbarkeit, Registrierung und Auftraege (quick-261008-dts, Aufgabe 3) ---- export type DomainOrderStatus = | 'DRAFT' | 'SUBMITTING' | 'SUBMITTED' | 'SUCCESS' | 'FAILED' | 'UNKNOWN' | 'CANCELED'; export interface OrderPrice { amount: number; currency: string; } export interface AvailabilityResult { environment: DomainsEnvironment; /** ASCII-Schreibweise (Punycode) — so geht sie an AutoDNS. */ domain: string; domainUnicode: string; /** Nur `FREE` ist bestellbar. */ available: boolean; whoisStatus: string; /** Preis fuer ein Jahr; `null`, wenn AutoDNS keinen nennt. */ price: OrderPrice | null; } export interface DomainsOrder { id: string; environment: DomainsEnvironment; domainName: string; domainNameUnicode: string; status: DomainOrderStatus; jobStatus: string | null; errorText: string | null; confirmedByUsername: string | null; confirmedAt: string | null; createdAt: string; lastCheckedAt: string | null; price: OrderPrice | null; } export interface OrderContact { id: number; displayName: string; } /** Zusammenfassung eines Entwurfs — das, was der Benutzer verbindlich bestaetigt. */ export interface OrderSummary { order: DomainsOrder; contacts: { owner: OrderContact; admin: OrderContact; tech: OrderContact; zone: OrderContact; }; nameServers: string[]; periodYears: 1; /** Stand des Entwurfs; geht beim verbindlichen Bestellen unveraendert zurueck. */ version: string; } export interface CreateOrderInput { domain: string; ownerContactId: number; adminContactId: number; techContactId: number; zoneContactId: number; // Die Nameserver liest der Server selbst aus dem AutoDNS-Profil (h3t). } /** Standard-Nameserver aus dem AutoDNS-Benutzerprofil, in der Reihenfolge von AutoDNS. */ export interface DomainsNameServers { environment: DomainsEnvironment; nameServers: string[]; } /** Liest die Standard-Nameserver aus AutoDNS (nur zur Anzeige; der Server liest sie beim Entwurf neu). */ export function getNameServers(): Promise { return request('/name-servers'); } export function checkAvailability(domain: string): Promise { return request('/availability', { method: 'POST', json: { domain } }); } export function createOrder(input: CreateOrderInput): Promise { return request('/orders', { method: 'POST', json: input }); } /** * Verbindlich: schickt die Registrierung an AutoDNS (kostet im Live-System Geld). * `version` ist der Stand aus der Zusammenfassung; hat sich der Entwurf seither * geaendert, lehnt der Server mit 409 ab. */ export function submitOrder(id: string, version: string): Promise { return request(`/orders/${encodeURIComponent(id)}/submit`, { method: 'POST', json: { version }, }); } export function cancelOrder(id: string): Promise { return request(`/orders/${encodeURIComponent(id)}/cancel`, { method: 'POST' }); } export function listOrders(): Promise { return request('/orders'); } /** Gleicht offene Auftraege mit AutoDNS ab und liefert die aktuelle Liste. */ export function refreshOrders(): Promise { return request('/orders/refresh', { method: 'POST' }); }