Bisher gab es nur zwei fest im Code verdrahtete E-Mails (Einladung, Passwort-Reset) und die SMTP-Verbindung kam ausschließlich aus Umgebungsvariablen. Jetzt: - SMTP-Verbindung unter Einstellungen > E-Mail konfigurierbar (Host, Port, Verschlüsselung, Zugangsdaten verschlüsselt gespeichert, Absender), mit Testmail-Versand. Die Mail-Logik wandert dafür nach @kc/platform/mail, damit API und Worker sie gemeinsam nutzen. - Mailvorlagen-Verwaltung: jedes Ereignis hat eine feste "Definition" (Name, Auslöser, verfügbare Platzhalter/Daten), Betreff/Text sind editierbar, je Vorlage einzeln aktivierbar, mit Testversand anhand von Beispieldaten. Unbekannte Platzhalter werden beim Speichern abgelehnt. - Neue Ereignisse verdrahtet: Ticket erstellt (Bestätigung an Kunde), Personal antwortet auf Ticket (Benachrichtigung an Kunde), Rechnung ausgestellt, sowie die zuvor zurückgestellte Funktion "Panel- Zugangsdaten per E-Mail senden" nach einem Passwort-Reset. Diese laufen über die Jobqueue (mail.template), analog zu discord.notify. - Versandprotokoll (letzte 100 Versuche, ohne Inhalte) einsehbar unter Einstellungen > E-Mail. - Migration 022, Rechte email.read (Admin+) / email.write (Superadmin). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
38 lines
1.7 KiB
TypeScript
38 lines
1.7 KiB
TypeScript
import { readdirSync, readFileSync, existsSync } from 'node:fs';
|
|
import { join } from 'node:path';
|
|
|
|
/** Lädt *.env aus KC_ENV_DIR (Standard /etc/kundencenter), ohne bereits gesetzte Variablen zu überschreiben. */
|
|
function loadEnvDir(dir: string): void {
|
|
if (!existsSync(dir)) return;
|
|
for (const f of readdirSync(dir).filter((n) => n.endsWith('.env')).sort()) {
|
|
for (const line of readFileSync(join(dir, f), 'utf8').split('\n')) {
|
|
const m = /^\s*([A-Z0-9_]+)\s*=\s*(.*?)\s*$/.exec(line);
|
|
if (m && m[1] && process.env[m[1]] === undefined) process.env[m[1]] = m[2] ?? '';
|
|
}
|
|
}
|
|
}
|
|
loadEnvDir(process.env.KC_ENV_DIR ?? '/etc/kundencenter');
|
|
|
|
function req(name: string): string {
|
|
const v = process.env[name];
|
|
if (!v) throw new Error(`Konfiguration fehlt: ${name}`);
|
|
return v;
|
|
}
|
|
|
|
export const config = {
|
|
env: process.env.KC_NODE_ENV ?? 'development',
|
|
isProd: (process.env.KC_NODE_ENV ?? 'development') === 'production',
|
|
port: Number(process.env.KC_API_PORT ?? 4100),
|
|
baseUrl: process.env.KC_BASE_URL ?? 'http://localhost:4101',
|
|
/** Erlaubte Browser-Origins (CSRF/Origin-Prüfung): baseUrl plus optional KC_ALLOWED_ORIGINS (kommagetrennt). */
|
|
allowedOrigins: new Set([process.env.KC_BASE_URL ?? 'http://localhost:4101', ...(process.env.KC_ALLOWED_ORIGINS ?? '').split(',').map((o) => o.trim()).filter(Boolean)]),
|
|
secretKey: Buffer.from(req('KC_SECRET_KEY'), 'base64'),
|
|
db: {
|
|
host: process.env.DB_HOST ?? '127.0.0.1',
|
|
port: Number(process.env.DB_PORT ?? 3306),
|
|
database: req('DB_NAME'),
|
|
user: req('DB_USER'),
|
|
password: req('DB_PASSWORD'),
|
|
},
|
|
};
|
|
if (config.secretKey.length !== 32) throw new Error('KC_SECRET_KEY muss 32 Byte (base64) sein');
|