Files
tessera-ctl/apps/web/src/lib/settings-api.ts
T
schalli b41be21190 feat(quick-260914-m97): Feld Fehlermeldungen an im SMTP-Formular — settings-api, Formular, i18n settings.smtp
- SmtpConfig.bugReportRecipient (string | null) und SaveSmtpPayload.bugReportRecipient (null loescht, fehlend bewahrt)
- Eingabefeld type=email hinter der Absenderadresse mit Hinweistext; Payload traegt immer trim() || null
- Zwei Komponententests: Vorbelegung aus GET, PUT-Payload mit Wert bzw. null
- i18n settings.smtp.bugReportRecipient / bugReportRecipientHelp in de und en

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018N9CD3ebPKm1b32bPpBknY
2026-09-14 16:51:36 +02:00

100 lines
3.0 KiB
TypeScript

/**
* Settings API client — SMTP configuration.
* Consumes the SettingsController built in 07-03.
* All calls use credentials: 'include' for cookie-based auth.
*
* T-07-17: SmtpConfig exposes only hasPassword, never the secret.
* The password field is never pre-filled from server data.
*/
const API_URL = process.env.NEXT_PUBLIC_API_URL || 'http://localhost:3001';
// --- Types mirroring the 07-03 backend contract ---
/**
* SMTP configuration as returned by GET /settings/smtp.
* encryptedPassword is NEVER returned (T-07-17).
*/
export interface SmtpConfig {
host: string;
port: number;
encryption: 'none' | 'starttls' | 'ssl-tls';
username?: string;
fromAddress: string;
/** True when an encrypted password is stored; the password value is never exposed. */
hasPassword: boolean;
/** Postfach fuer den Fehler-melden-Knopf (quick-260914-m97); null = nicht gesetzt. */
bugReportRecipient: string | null;
}
/**
* Payload for PUT /settings/smtp and POST /settings/smtp/test.
* password is optional — omit to preserve the existing stored password.
*/
export interface SaveSmtpPayload {
host: string;
port: number;
encryption: 'none' | 'starttls' | 'ssl-tls';
username?: string;
/** Only include when the user has typed a new password. */
password?: string;
fromAddress: string;
/** If set, backend sends a real test email to this address instead of just verify(). */
testTo?: string;
/**
* Postfach fuer den Fehler-melden-Knopf (quick-260914-m97). Vertrag mit
* `saveSmtpConfig`: `null` loescht den gespeicherten Wert, ein fehlendes
* Feld bewahrt ihn.
*/
bugReportRecipient?: string | null;
}
// --- API functions ---
/**
* Fetch the SMTP configuration for the current tenant.
* GET /settings/smtp
* Returns null when no configuration has been saved yet (404).
*/
export async function fetchSmtp(): Promise<SmtpConfig | null> {
const res = await fetch(`${API_URL}/settings/smtp`, {
credentials: 'include',
});
if (res.status === 404) return null;
if (!res.ok) throw new Error('Failed to fetch SMTP config');
return res.json();
}
/**
* Save the SMTP configuration.
* PUT /settings/smtp
*/
export async function saveSmtp(payload: SaveSmtpPayload): Promise<SmtpConfig> {
const res = await fetch(`${API_URL}/settings/smtp`, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
credentials: 'include',
body: JSON.stringify(payload),
});
if (!res.ok) throw new Error('Failed to save SMTP config');
return res.json();
}
/**
* Test the SMTP connection with the given settings.
* POST /settings/smtp/test
* Returns { success: boolean } — backend runs nodemailer.verify().
*/
export async function testSmtp(
payload: SaveSmtpPayload,
): Promise<{ success: boolean; warning?: string }> {
const res = await fetch(`${API_URL}/settings/smtp/test`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
credentials: 'include',
body: JSON.stringify(payload),
});
if (!res.ok) throw new Error('Failed to test SMTP config');
return res.json();
}