fix(ui): echte Umlaute in allen sichtbaren Texten

Menüs, Dialoge, Schaltflächen, Platzhalter, Hinweise und die
Fehlermeldungen der API standen in Ersatzschreibweise (oe/ae/ue). Sie
erscheinen als Toast oder Beschriftung direkt in der Oberfläche und
gehören damit richtig geschrieben; sechs sichtbare Texte mit ss statt ß
sind gleich mit erledigt ("Schließen", "Straße", "zu groß").

Unangetastet bleibt bewusst:

- Kommentare im Quelltext - die sind durchgängig ASCII, und der Diff wäre
  dreimal so groß ohne Nutzen für den Benutzer
- Protokollzeilen (console.*) - die landen im journal, nicht in der App
- Datenbankwerte und interne Kennungen: 'uebernommen', 'empfaenger',
  'vorschlaege', 'laeuft-bereits' stecken in Enums, SQL und
  Zustandsvergleichen. Ein Umlaut darin hätte still falsche Ergebnisse
  geliefert
- server/src/mail/extract.js - die Muster decken absichtlich beide
  Schreibweisen ab: /gr(ü|ue)(ß|ss)e/
- scanner/utils/crypto.js - der Ersatzschlüssel darf sich nicht ändern,
  sonst sind gespeicherte Portal-Passwörter nicht mehr lesbar

Geprüft mit einem Rundgang durch die Ansichten und Dialoge: sichtbarer
Text plus title/aria-label/placeholder enthalten keine
Ersatzschreibweisen mehr.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Sascha Wolf
2026-08-11 09:37:24 +02:00
co-authored by Claude Opus 5
parent ea55acdb5c
commit 8b7ce448a6
42 changed files with 168 additions and 168 deletions
+4 -4
View File
@@ -71,7 +71,7 @@ async function fetchAccessToken() {
}
if (!data?.access_token) {
throw new GraphError('Anmeldung lieferte kein Token zurueck.', { status: response.status });
throw new GraphError('Anmeldung lieferte kein Token zurück.', { status: response.status });
}
return data;
@@ -189,7 +189,7 @@ export async function testConnection() {
ok: false,
stage: 'token',
error: err.message,
hint: 'Tenant-ID, Client-ID und Client-Secret in der .env pruefen.',
hint: 'Tenant-ID, Client-ID und Client-Secret in der .env prüfen.',
};
}
@@ -211,8 +211,8 @@ export async function testConnection() {
err.code === 'ErrorAccessDenied' || err.status === 403
? 'Zugriff verweigert: Anwendungsberechtigung Mail.Read erteilt und Administratorzustimmung gegeben? Erlaubt die ApplicationAccessPolicy dieses Postfach?'
: err.code === 'ResourceNotFound' || err.status === 404
? `Postfach ${MAILBOX} wurde nicht gefunden - Adresse pruefen.`
: 'Berechtigungen und Zugriffsrichtlinie in Exchange Online pruefen.';
? `Postfach ${MAILBOX} wurde nicht gefunden - Adresse prüfen.`
: 'Berechtigungen und Zugriffsrichtlinie in Exchange Online prüfen.';
return { ok: false, stage: 'mailbox', error: err.message, code: err.code ?? null, hint };
}
}
+1 -1
View File
@@ -81,7 +81,7 @@ router.patch('/me', requireAuth, async (req, res, next) => {
}
if (fields.length === 0) {
return res.status(400).json({ error: 'Keine Aenderungen uebergeben.' });
return res.status(400).json({ error: 'Keine Änderungen übergeben.' });
}
try {
+6 -6
View File
@@ -51,7 +51,7 @@ router.post('/', async (req, res, next) => {
const toId = Number(req.body?.to_lead_id);
if (!Number.isInteger(fromId) || !Number.isInteger(toId)) {
return res.status(400).json({ error: 'Beide Kontakte muessen angegeben werden.' });
return res.status(400).json({ error: 'Beide Kontakte müssen angegeben werden.' });
}
if (fromId === toId) {
return res.status(400).json({ error: 'Ein Kontakt kann nicht mit sich selbst verbunden werden.' });
@@ -90,13 +90,13 @@ router.post('/', async (req, res, next) => {
// PATCH /api/connections/:id
router.patch('/:id', async (req, res, next) => {
const id = Number(req.params.id);
if (!Number.isInteger(id)) return res.status(400).json({ error: 'Ungueltige Verbindungs-ID.' });
if (!Number.isInteger(id)) return res.status(400).json({ error: 'Ungültige Verbindungs-ID.' });
const fields = [];
const values = [];
if (req.body?.type !== undefined) {
if (!TYPES.has(req.body.type)) return res.status(400).json({ error: 'Ungueltiger Verbindungstyp.' });
if (!TYPES.has(req.body.type)) return res.status(400).json({ error: 'Ungültiger Verbindungstyp.' });
fields.push('type = ?');
values.push(req.body.type);
}
@@ -107,13 +107,13 @@ router.patch('/:id', async (req, res, next) => {
if (req.body?.strength !== undefined) {
const strength = Number(req.body.strength);
if (![1, 2, 3].includes(strength)) {
return res.status(400).json({ error: 'Verbindungsstaerke muss 1, 2 oder 3 sein.' });
return res.status(400).json({ error: 'Verbindungsstärke muss 1, 2 oder 3 sein.' });
}
fields.push('strength = ?');
values.push(strength);
}
if (fields.length === 0) return res.status(400).json({ error: 'Keine Aenderungen uebergeben.' });
if (fields.length === 0) return res.status(400).json({ error: 'Keine Änderungen übergeben.' });
try {
values.push(id);
@@ -138,7 +138,7 @@ router.patch('/:id', async (req, res, next) => {
// DELETE /api/connections/:id
router.delete('/:id', async (req, res, next) => {
const id = Number(req.params.id);
if (!Number.isInteger(id)) return res.status(400).json({ error: 'Ungueltige Verbindungs-ID.' });
if (!Number.isInteger(id)) return res.status(400).json({ error: 'Ungültige Verbindungs-ID.' });
try {
const [result] = await pool.query('DELETE FROM crm_lead_connections WHERE id = ?', [id]);
+10 -10
View File
@@ -47,11 +47,11 @@ function emptyToNull(value) {
/** Teilnehmer-IDs aus dem Request pruefen und deduplizieren. */
async function normalizeParticipants(raw) {
if (raw === undefined) return undefined;
if (!Array.isArray(raw)) return { error: 'Teilnehmer muessen als Liste uebergeben werden.' };
if (!Array.isArray(raw)) return { error: 'Teilnehmer müssen als Liste übergeben werden.' };
const ids = [...new Set(raw.map((value) => Number(value)))];
if (ids.some((id) => !Number.isInteger(id) || id <= 0)) {
return { error: 'Ungueltige Teilnehmer-ID.' };
return { error: 'Ungültige Teilnehmer-ID.' };
}
if (ids.length === 0) return { ids: [] };
@@ -142,7 +142,7 @@ router.post('/', async (req, res, next) => {
const startsAt = normalizeDateTime(req.body?.starts_at);
const endsAt = normalizeDateTime(req.body?.ends_at);
if (!startsAt || !endsAt) {
return res.status(400).json({ error: 'Start und Ende muessen gueltige Zeitpunkte sein.' });
return res.status(400).json({ error: 'Start und Ende müssen gültige Zeitpunkte sein.' });
}
if (endsAt < startsAt) {
return res.status(400).json({ error: 'Das Ende darf nicht vor dem Start liegen.' });
@@ -186,7 +186,7 @@ function mayModify(user, event) {
// PATCH /api/events/:id
router.patch('/:id', async (req, res, next) => {
const id = Number(req.params.id);
if (!Number.isInteger(id)) return res.status(400).json({ error: 'Ungueltige Termin-ID.' });
if (!Number.isInteger(id)) return res.status(400).json({ error: 'Ungültige Termin-ID.' });
const conn = await pool.getConnection();
try {
@@ -196,7 +196,7 @@ router.patch('/:id', async (req, res, next) => {
if (!mayModify(req.user, existing)) {
return res
.status(403)
.json({ error: 'Diesen Termin darf nur der Ersteller oder ein Administrator aendern.' });
.json({ error: 'Diesen Termin darf nur der Ersteller oder ein Administrator ändern.' });
}
const fields = [];
@@ -217,7 +217,7 @@ router.patch('/:id', async (req, res, next) => {
let startsAt = existing.starts_at;
if (req.body?.starts_at !== undefined) {
startsAt = normalizeDateTime(req.body.starts_at);
if (!startsAt) return res.status(400).json({ error: 'Ungueltiger Startzeitpunkt.' });
if (!startsAt) return res.status(400).json({ error: 'Ungültiger Startzeitpunkt.' });
fields.push('starts_at = ?');
values.push(startsAt);
}
@@ -225,7 +225,7 @@ router.patch('/:id', async (req, res, next) => {
let endsAt = existing.ends_at;
if (req.body?.ends_at !== undefined) {
endsAt = normalizeDateTime(req.body.ends_at);
if (!endsAt) return res.status(400).json({ error: 'Ungueltiger Endzeitpunkt.' });
if (!endsAt) return res.status(400).json({ error: 'Ungültiger Endzeitpunkt.' });
fields.push('ends_at = ?');
values.push(endsAt);
}
@@ -243,7 +243,7 @@ router.patch('/:id', async (req, res, next) => {
if (participants?.error) return res.status(400).json({ error: participants.error });
if (fields.length === 0 && !participants) {
return res.status(400).json({ error: 'Keine Aenderungen uebergeben.' });
return res.status(400).json({ error: 'Keine Änderungen übergeben.' });
}
await conn.beginTransaction();
@@ -265,7 +265,7 @@ router.patch('/:id', async (req, res, next) => {
// DELETE /api/events/:id - Teilnehmer entfernt die DB per ON DELETE CASCADE
router.delete('/:id', async (req, res, next) => {
const id = Number(req.params.id);
if (!Number.isInteger(id)) return res.status(400).json({ error: 'Ungueltige Termin-ID.' });
if (!Number.isInteger(id)) return res.status(400).json({ error: 'Ungültige Termin-ID.' });
try {
const [rows] = await pool.query('SELECT created_by FROM crm_events WHERE id = ? LIMIT 1', [id]);
@@ -274,7 +274,7 @@ router.delete('/:id', async (req, res, next) => {
if (!mayModify(req.user, existing)) {
return res
.status(403)
.json({ error: 'Diesen Termin darf nur der Ersteller oder ein Administrator loeschen.' });
.json({ error: 'Diesen Termin darf nur der Ersteller oder ein Administrator löschen.' });
}
await pool.query('DELETE FROM crm_events WHERE id = ?', [id]);
+11 -11
View File
@@ -115,7 +115,7 @@ router.post('/', async (req, res, next) => {
: Number(req.body.owner_id);
if (Number.isNaN(ownerId) || !(await userExists(ownerId))) {
return res.status(400).json({ error: 'Zustaendiger Benutzer existiert nicht.' });
return res.status(400).json({ error: 'Zuständiger Benutzer existiert nicht.' });
}
const columns = [...TEXT_FIELDS, 'status', 'tags', 'owner_id', 'created_by'];
@@ -283,7 +283,7 @@ router.get('/mail-pool/sources', async (req, res, next) => {
*/
router.post('/mail-pool/promote', async (req, res, next) => {
const ids = Array.isArray(req.body?.ids) ? req.body.ids.map(Number).filter(Number.isInteger) : [];
if (ids.length === 0) return res.status(400).json({ error: 'Keine Eintraege uebergeben.' });
if (ids.length === 0) return res.status(400).json({ error: 'Keine Einträge übergeben.' });
const keepPool = req.body?.keep_pool === true;
@@ -323,7 +323,7 @@ router.post('/mail-pool/promote', async (req, res, next) => {
*/
router.post('/:id/promote', async (req, res, next) => {
const id = Number(req.params.id);
if (!Number.isInteger(id)) return res.status(400).json({ error: 'Ungueltige Lead-ID.' });
if (!Number.isInteger(id)) return res.status(400).json({ error: 'Ungültige Lead-ID.' });
const keepPool = req.body?.keep_pool === true;
const targetLeadId =
@@ -331,7 +331,7 @@ router.post('/:id/promote', async (req, res, next) => {
? null
: Number(req.body.target_lead_id);
if (targetLeadId !== null && !Number.isInteger(targetLeadId)) {
return res.status(400).json({ error: 'Ungueltige Ziel-Kontakt-ID.' });
return res.status(400).json({ error: 'Ungültige Ziel-Kontakt-ID.' });
}
try {
@@ -345,7 +345,7 @@ router.post('/:id/promote', async (req, res, next) => {
? poolLead.owner_id ?? req.user.id
: Number(req.body.owner_id);
if (!Number.isInteger(ownerId) || !(await userExists(ownerId))) {
return res.status(400).json({ error: 'Zustaendiger Benutzer existiert nicht.' });
return res.status(400).json({ error: 'Zuständiger Benutzer existiert nicht.' });
}
const leadId = await copyPoolLeadToContacts(poolLead, { userId: req.user.id, ownerId });
@@ -403,7 +403,7 @@ router.post('/:id/promote', async (req, res, next) => {
// PATCH /api/leads/:id
router.patch('/:id', async (req, res, next) => {
const id = Number(req.params.id);
if (!Number.isInteger(id)) return res.status(400).json({ error: 'Ungueltige Lead-ID.' });
if (!Number.isInteger(id)) return res.status(400).json({ error: 'Ungültige Lead-ID.' });
const fields = [];
const values = [];
@@ -412,14 +412,14 @@ router.patch('/:id', async (req, res, next) => {
if (req.body?.[field] === undefined) continue;
const value = emptyToNull(req.body[field]);
if ((field === 'first_name' || field === 'last_name') && !value) {
return res.status(400).json({ error: 'Vorname und Nachname duerfen nicht leer sein.' });
return res.status(400).json({ error: 'Vorname und Nachname dürfen nicht leer sein.' });
}
fields.push(`${field} = ?`);
values.push(value);
}
if (req.body?.status !== undefined) {
if (!STATUSES.has(req.body.status)) return res.status(400).json({ error: 'Ungueltiger Status.' });
if (!STATUSES.has(req.body.status)) return res.status(400).json({ error: 'Ungültiger Status.' });
fields.push('status = ?');
values.push(req.body.status);
}
@@ -433,13 +433,13 @@ router.patch('/:id', async (req, res, next) => {
const ownerId =
req.body.owner_id === null || req.body.owner_id === '' ? null : Number(req.body.owner_id);
if (ownerId !== null && (Number.isNaN(ownerId) || !(await userExists(ownerId)))) {
return res.status(400).json({ error: 'Zustaendiger Benutzer existiert nicht.' });
return res.status(400).json({ error: 'Zuständiger Benutzer existiert nicht.' });
}
fields.push('owner_id = ?');
values.push(ownerId);
}
if (fields.length === 0) return res.status(400).json({ error: 'Keine Aenderungen uebergeben.' });
if (fields.length === 0) return res.status(400).json({ error: 'Keine Änderungen übergeben.' });
try {
values.push(id);
@@ -457,7 +457,7 @@ router.patch('/:id', async (req, res, next) => {
// DELETE /api/leads/:id - Verbindungen entfernt die DB per ON DELETE CASCADE
router.delete('/:id', async (req, res, next) => {
const id = Number(req.params.id);
if (!Number.isInteger(id)) return res.status(400).json({ error: 'Ungueltige Lead-ID.' });
if (!Number.isInteger(id)) return res.status(400).json({ error: 'Ungültige Lead-ID.' });
try {
const [result] = await pool.query('DELETE FROM crm_leads WHERE id = ?', [id]);
+14 -14
View File
@@ -128,7 +128,7 @@ router.post('/settings', requireAdmin, async (req, res, next) => {
const allowed = Object.keys(MAIL_SETTING_DEFAULTS);
const incoming = Object.entries(req.body ?? {}).filter(([key]) => allowed.includes(key));
if (incoming.length === 0) {
return res.status(400).json({ error: 'Keine bekannten Einstellungen uebergeben.' });
return res.status(400).json({ error: 'Keine bekannten Einstellungen übergeben.' });
}
for (const [key, value] of incoming) {
@@ -180,7 +180,7 @@ router.post('/sync', requireAdmin, async (req, res, next) => {
return res.status(409).json({ error: `Zugangsdaten fehlen: ${missingCredentials().join(', ')}` });
}
if (isMailSyncRunning()) {
return res.status(409).json({ error: 'Ein Abgleich laeuft bereits. Bitte kurz warten.' });
return res.status(409).json({ error: 'Ein Abgleich läuft bereits. Bitte kurz warten.' });
}
// Vollstaendiger Neuaufbau auf Wunsch: Delta-Verweis verwerfen. Vorhandene
@@ -188,7 +188,7 @@ router.post('/sync', requireAdmin, async (req, res, next) => {
if (req.body?.reset === true) await resetDelta();
const result = await runMailSync({ trigger: 'manuell' });
if (result.skipped) return res.status(409).json({ error: `Abgleich uebersprungen: ${result.skipped}` });
if (result.skipped) return res.status(409).json({ error: `Abgleich übersprungen: ${result.skipped}` });
if (!result.ok) return res.status(502).json({ error: result.error, result });
res.json(result);
} catch (err) {
@@ -259,7 +259,7 @@ router.get('/messages', async (req, res, next) => {
// GET /api/mail/messages/:id
router.get('/messages/:id', async (req, res, next) => {
const id = Number(req.params.id);
if (!Number.isInteger(id)) return res.status(400).json({ error: 'Ungueltige Mail-ID.' });
if (!Number.isInteger(id)) return res.status(400).json({ error: 'Ungültige Mail-ID.' });
try {
const [rows] = await pool.query('SELECT * FROM crm_mail_messages WHERE id = ? LIMIT 1', [id]);
@@ -323,7 +323,7 @@ router.get('/messages/:id', async (req, res, next) => {
// POST /api/mail/messages/:id/links - Mail an Kontakt und/oder Opportunity haengen
router.post('/messages/:id/links', async (req, res, next) => {
const id = Number(req.params.id);
if (!Number.isInteger(id)) return res.status(400).json({ error: 'Ungueltige Mail-ID.' });
if (!Number.isInteger(id)) return res.status(400).json({ error: 'Ungültige Mail-ID.' });
const leadId = req.body?.lead_id === undefined || req.body.lead_id === null ? null : Number(req.body.lead_id);
const opportunityId =
@@ -335,10 +335,10 @@ router.post('/messages/:id/links', async (req, res, next) => {
return res.status(400).json({ error: 'Kontakt oder Opportunity angeben.' });
}
if (leadId !== null && !Number.isInteger(leadId)) {
return res.status(400).json({ error: 'Ungueltige Kontakt-ID.' });
return res.status(400).json({ error: 'Ungültige Kontakt-ID.' });
}
if (opportunityId !== null && !Number.isInteger(opportunityId)) {
return res.status(400).json({ error: 'Ungueltige Opportunity-ID.' });
return res.status(400).json({ error: 'Ungültige Opportunity-ID.' });
}
try {
@@ -378,7 +378,7 @@ router.post('/messages/:id/links', async (req, res, next) => {
}
if (created.length === 0) {
return res.status(409).json({ error: 'Diese Verknuepfung besteht bereits.' });
return res.status(409).json({ error: 'Diese Verknüpfung besteht bereits.' });
}
res.status(201).json({ ok: true, created });
@@ -390,11 +390,11 @@ router.post('/messages/:id/links', async (req, res, next) => {
// DELETE /api/mail/links/:id
router.delete('/links/:id', async (req, res, next) => {
const id = Number(req.params.id);
if (!Number.isInteger(id)) return res.status(400).json({ error: 'Ungueltige Verknuepfungs-ID.' });
if (!Number.isInteger(id)) return res.status(400).json({ error: 'Ungültige Verknüpfungs-ID.' });
try {
const [result] = await pool.query('DELETE FROM crm_mail_links WHERE id = ?', [id]);
if (result.affectedRows === 0) return res.status(404).json({ error: 'Verknuepfung nicht gefunden.' });
if (result.affectedRows === 0) return res.status(404).json({ error: 'Verknüpfung nicht gefunden.' });
res.json({ ok: true });
} catch (err) {
next(err);
@@ -416,7 +416,7 @@ router.get('/candidates', async (req, res, next) => {
const where = [];
const params = [];
if (status !== 'alle') {
if (!CANDIDATE_STATUSES.has(status)) return res.status(400).json({ error: 'Ungueltiger Status.' });
if (!CANDIDATE_STATUSES.has(status)) return res.status(400).json({ error: 'Ungültiger Status.' });
where.push('c.status = ?');
params.push(status);
}
@@ -465,7 +465,7 @@ async function loadCandidate(id) {
// POST /api/mail/candidates/:id/adopt - Vorschlag zum Kontakt machen
router.post('/candidates/:id/adopt', async (req, res, next) => {
const id = Number(req.params.id);
if (!Number.isInteger(id)) return res.status(400).json({ error: 'Ungueltige Vorschlags-ID.' });
if (!Number.isInteger(id)) return res.status(400).json({ error: 'Ungültige Vorschlags-ID.' });
try {
const candidate = await loadCandidate(id);
@@ -479,7 +479,7 @@ router.post('/candidates/:id/adopt', async (req, res, next) => {
req.body?.owner_id === undefined || req.body.owner_id === null
? req.user.id
: Number(req.body.owner_id);
if (!Number.isInteger(ownerId)) return res.status(400).json({ error: 'Ungueltige Benutzer-ID.' });
if (!Number.isInteger(ownerId)) return res.status(400).json({ error: 'Ungültige Benutzer-ID.' });
/*
* Von Hand uebernommen heisst: richtiger Kontakt. Der Vorbereich "Aus Mails"
@@ -505,7 +505,7 @@ router.post('/candidates/:id/adopt', async (req, res, next) => {
// POST /api/mail/candidates/:id/reject
router.post('/candidates/:id/reject', async (req, res, next) => {
const id = Number(req.params.id);
if (!Number.isInteger(id)) return res.status(400).json({ error: 'Ungueltige Vorschlags-ID.' });
if (!Number.isInteger(id)) return res.status(400).json({ error: 'Ungültige Vorschlags-ID.' });
try {
const candidate = await loadCandidate(id);
+12 -12
View File
@@ -28,7 +28,7 @@ function emptyToNull(value) {
function normalizeDate(value) {
if (value === null || value === undefined || String(value).trim() === '') return { value: null };
const match = /^(\d{4})-(\d{2})-(\d{2})/.exec(String(value).trim());
if (!match) return { error: 'Ungueltiges Datum (erwartet JJJJ-MM-TT).' };
if (!match) return { error: 'Ungültiges Datum (erwartet JJJJ-MM-TT).' };
const [, year, month, day] = match;
const probe = new Date(Number(year), Number(month) - 1, Number(day));
@@ -140,13 +140,13 @@ async function userExists(id) {
/** Prueft die Ansprechpartner-Liste `[{ lead_id, role }]`. */
async function normalizeContacts(raw) {
if (raw === undefined) return undefined;
if (!Array.isArray(raw)) return { error: 'Ansprechpartner muessen als Liste uebergeben werden.' };
if (!Array.isArray(raw)) return { error: 'Ansprechpartner müssen als Liste übergeben werden.' };
const seen = new Map();
for (const entry of raw) {
const leadId = Number(entry?.lead_id ?? entry);
if (!Number.isInteger(leadId) || leadId <= 0) {
return { error: 'Ungueltige Kontakt-ID bei den Ansprechpartnern.' };
return { error: 'Ungültige Kontakt-ID bei den Ansprechpartnern.' };
}
const role = emptyToNull(entry?.role);
if (role !== null && role.length > 120) {
@@ -209,7 +209,7 @@ router.post('/', async (req, res, next) => {
// etwas anderes gesetzt.
const stageGiven = req.body?.stage !== undefined && req.body.stage !== null && req.body.stage !== '';
if (stageGiven && !STAGES.has(req.body.stage)) {
return res.status(400).json({ error: 'Ungueltige Phase.' });
return res.status(400).json({ error: 'Ungültige Phase.' });
}
const stage = stageGiven ? req.body.stage : 'Neu';
@@ -241,7 +241,7 @@ router.post('/', async (req, res, next) => {
? req.user.id
: Number(req.body.owner_id);
if (Number.isNaN(ownerId) || !(await userExists(ownerId))) {
return res.status(400).json({ error: 'Zustaendiger Benutzer existiert nicht.' });
return res.status(400).json({ error: 'Zuständiger Benutzer existiert nicht.' });
}
const contacts = await normalizeContacts(req.body?.contacts ?? []);
@@ -289,7 +289,7 @@ router.post('/', async (req, res, next) => {
// PATCH /api/opportunities/:id
router.patch('/:id', async (req, res, next) => {
const id = Number(req.params.id);
if (!Number.isInteger(id)) return res.status(400).json({ error: 'Ungueltige Opportunity-ID.' });
if (!Number.isInteger(id)) return res.status(400).json({ error: 'Ungültige Opportunity-ID.' });
const conn = await pool.getConnection();
try {
@@ -301,7 +301,7 @@ router.patch('/:id', async (req, res, next) => {
if (!existing) return res.status(404).json({ error: 'Opportunity nicht gefunden.' });
if (!mayModify(req.user, existing)) {
return res.status(403).json({
error: 'Diese Opportunity darf nur der Ersteller, der Zustaendige oder ein Administrator aendern.',
error: 'Diese Opportunity darf nur der Ersteller, der Zuständige oder ein Administrator ändern.',
});
}
@@ -319,7 +319,7 @@ router.patch('/:id', async (req, res, next) => {
}
if (req.body?.stage !== undefined) {
if (!STAGES.has(req.body.stage)) return res.status(400).json({ error: 'Ungueltige Phase.' });
if (!STAGES.has(req.body.stage)) return res.status(400).json({ error: 'Ungültige Phase.' });
fields.push('stage = ?');
values.push(req.body.stage);
}
@@ -367,7 +367,7 @@ router.patch('/:id', async (req, res, next) => {
const ownerId =
req.body.owner_id === null || req.body.owner_id === '' ? null : Number(req.body.owner_id);
if (ownerId !== null && (Number.isNaN(ownerId) || !(await userExists(ownerId)))) {
return res.status(400).json({ error: 'Zustaendiger Benutzer existiert nicht.' });
return res.status(400).json({ error: 'Zuständiger Benutzer existiert nicht.' });
}
fields.push('owner_id = ?');
values.push(ownerId);
@@ -377,7 +377,7 @@ router.patch('/:id', async (req, res, next) => {
if (contacts?.error) return res.status(400).json({ error: contacts.error });
if (fields.length === 0 && !contacts) {
return res.status(400).json({ error: 'Keine Aenderungen uebergeben.' });
return res.status(400).json({ error: 'Keine Änderungen übergeben.' });
}
await conn.beginTransaction();
@@ -402,7 +402,7 @@ router.patch('/:id', async (req, res, next) => {
// DELETE /api/opportunities/:id - Ansprechpartner raeumt die DB per CASCADE
router.delete('/:id', async (req, res, next) => {
const id = Number(req.params.id);
if (!Number.isInteger(id)) return res.status(400).json({ error: 'Ungueltige Opportunity-ID.' });
if (!Number.isInteger(id)) return res.status(400).json({ error: 'Ungültige Opportunity-ID.' });
try {
const [rows] = await pool.query(
@@ -413,7 +413,7 @@ router.delete('/:id', async (req, res, next) => {
if (!existing) return res.status(404).json({ error: 'Opportunity nicht gefunden.' });
if (!mayModify(req.user, existing)) {
return res.status(403).json({
error: 'Diese Opportunity darf nur der Ersteller, der Zustaendige oder ein Administrator loeschen.',
error: 'Diese Opportunity darf nur der Ersteller, der Zuständige oder ein Administrator löschen.',
});
}
+2 -2
View File
@@ -273,7 +273,7 @@ router.post('/portals/sync-session', async (req, res) => {
try {
parsedState = typeof sessionState === 'string' ? JSON.parse(sessionState) : sessionState;
} catch {
return res.status(400).json({ error: 'Ungueltiges Format - es muss JSON sein.' });
return res.status(400).json({ error: 'Ungültiges Format - es muss JSON sein.' });
}
// Drei Formen sind erlaubt: Playwright storageState, reines Cookie-Array
@@ -289,7 +289,7 @@ router.post('/portals/sync-session', async (req, res) => {
const cookies = rawCookies.map(normalizeCookie).filter(Boolean);
if (cookies.length === 0) {
return res.status(400).json({ error: 'Keine gueltigen Cookies gefunden.' });
return res.status(400).json({ error: 'Keine gültigen Cookies gefunden.' });
}
portal.sessionState = JSON.stringify({ cookies, origins });
+6 -6
View File
@@ -120,7 +120,7 @@ router.post('/', async (req, res, next) => {
// PATCH /api/users/:id - bearbeitet Benutzer inkl. Admin
router.patch('/:id', async (req, res, next) => {
const id = Number(req.params.id);
if (!Number.isInteger(id)) return res.status(400).json({ error: 'Ungueltige Benutzer-ID.' });
if (!Number.isInteger(id)) return res.status(400).json({ error: 'Ungültige Benutzer-ID.' });
try {
const [existingRows] = await pool.query('SELECT * FROM crm_users WHERE id = ? LIMIT 1', [id]);
@@ -156,7 +156,7 @@ router.patch('/:id', async (req, res, next) => {
const nextRole = req.body?.role !== undefined ? String(req.body.role) : existing.role;
if (req.body?.role !== undefined) {
if (!ROLES.has(nextRole)) return res.status(400).json({ error: 'Ungueltige Rolle.' });
if (!ROLES.has(nextRole)) return res.status(400).json({ error: 'Ungültige Rolle.' });
fields.push('role = ?');
values.push(nextRole);
}
@@ -187,7 +187,7 @@ router.patch('/:id', async (req, res, next) => {
}
if (fields.length === 0) {
return res.status(400).json({ error: 'Keine Aenderungen uebergeben.' });
return res.status(400).json({ error: 'Keine Änderungen übergeben.' });
}
values.push(id);
@@ -209,9 +209,9 @@ router.patch('/:id', async (req, res, next) => {
// DELETE /api/users/:id
router.delete('/:id', async (req, res, next) => {
const id = Number(req.params.id);
if (!Number.isInteger(id)) return res.status(400).json({ error: 'Ungueltige Benutzer-ID.' });
if (!Number.isInteger(id)) return res.status(400).json({ error: 'Ungültige Benutzer-ID.' });
if (id === req.user.id) {
return res.status(400).json({ error: 'Das eigene Konto kann nicht geloescht werden.' });
return res.status(400).json({ error: 'Das eigene Konto kann nicht gelöscht werden.' });
}
try {
@@ -224,7 +224,7 @@ router.delete('/:id', async (req, res, next) => {
if (target.role === 'admin' && target.is_active && (await countOtherActiveAdmins(id)) === 0) {
return res
.status(409)
.json({ error: 'Der letzte aktive Administrator kann nicht geloescht werden.' });
.json({ error: 'Der letzte aktive Administrator kann nicht gelöscht werden.' });
}
await pool.query('DELETE FROM crm_users WHERE id = ?', [id]);
+1 -1
View File
@@ -69,7 +69,7 @@ app.use((err, req, res, next) => {
// nicht als unbehandelter Serverfehler im Log landen.
const status = err?.status || err?.statusCode;
if (err?.type === 'entity.parse.failed' || status === 400) {
return res.status(400).json({ error: 'Ungueltige Anfrage (JSON konnte nicht gelesen werden).' });
return res.status(400).json({ error: 'Ungültige Anfrage (JSON konnte nicht gelesen werden).' });
}
if (err?.type === 'entity.too.large' || status === 413) {
return res.status(413).json({ error: 'Die Anfrage ist zu gross (max. 6 MB).' });
+2 -2
View File
@@ -18,7 +18,7 @@ const AVATAR_MAX_LENGTH = 3_000_000;
export function normalizeColor(value) {
if (value === null || value === undefined || String(value).trim() === '') return { value: null };
const color = String(value).trim().toLowerCase();
if (!COLOR_RE.test(color)) return { error: 'Ungueltige Farbe (erwartet z. B. #6ba04a).' };
if (!COLOR_RE.test(color)) return { error: 'Ungültige Farbe (erwartet z. B. #6ba04a).' };
return { value: color };
}
@@ -30,7 +30,7 @@ export function normalizeAvatar(value) {
return { error: 'Das Foto ist zu gross. Bitte ein kleineres Bild verwenden.' };
}
if (!AVATAR_RE.test(avatar)) {
return { error: 'Ungueltiges Foto (erwartet eine Bild-Data-URL oder eine http(s)-Adresse).' };
return { error: 'Ungültiges Foto (erwartet eine Bild-Data-URL oder eine http(s)-Adresse).' };
}
return { value: avatar };
}