Files
oikos/server/routes/contacts.js
T
ulsklyc 2e3e67baeb fix: Error Handling in Backend und Frontend verbessern (Phase 5, Schritt 31)
- Backend: JSON-Parse-Error + Payload-Too-Large Middleware in index.js
- Backend: Dashboard äußerer try/catch für db.get()-Fehler
- Backend: contacts/meta Route mit try/catch
- Frontend: try/catch + Toast-Fallback in loadMonth (budget), loadRange (calendar),
  loadWeek (meals), loadLists/switchList (shopping), initiales Laden (notes)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-26 00:35:16 +01:00

164 lines
6.0 KiB
JavaScript

/**
* Modul: Kontakte (Contacts)
* Zweck: REST-API-Routen für wichtige Familienkontakte
* Abhängigkeiten: express, server/db.js, server/auth.js
*/
'use strict';
const express = require('express');
const router = express.Router();
const db = require('../db');
const { str, oneOf, collectErrors, MAX_TITLE, MAX_TEXT, MAX_SHORT } = require('../middleware/validate');
const VALID_CATEGORIES = ['Arzt', 'Schule/Kita', 'Behörde', 'Versicherung',
'Handwerker', 'Notfall', 'Sonstiges'];
/**
* GET /api/v1/contacts
* Alle Kontakte, optional nach Kategorie gefiltert und nach Name gesucht.
* Query: ?category=<cat>&q=<search>
* Response: { data: Contact[] }
*/
router.get('/', (req, res) => {
try {
let sql = 'SELECT * FROM contacts';
const params = [];
const where = [];
if (req.query.category && VALID_CATEGORIES.includes(req.query.category)) {
where.push('category = ?');
params.push(req.query.category);
}
if (req.query.q) {
where.push('(name LIKE ? OR phone LIKE ? OR email LIKE ?)');
const like = `%${req.query.q}%`;
params.push(like, like, like);
}
if (where.length) sql += ' WHERE ' + where.join(' AND ');
sql += ' ORDER BY category ASC, name ASC';
const contacts = db.get().prepare(sql).all(...params);
res.json({ data: contacts });
} catch (err) {
console.error('[contacts/GET /]', err);
res.status(500).json({ error: 'Interner Fehler', code: 500 });
}
});
/**
* POST /api/v1/contacts
* Neuen Kontakt anlegen.
* Body: { name, category?, phone?, email?, address?, notes? }
* Response: { data: Contact }
*/
router.post('/', (req, res) => {
try {
const vName = str(req.body.name, 'Name', { max: MAX_TITLE });
const vCat = oneOf(req.body.category || 'Sonstiges', VALID_CATEGORIES, 'Kategorie');
const vPhone = str(req.body.phone, 'Telefon', { max: MAX_SHORT, required: false });
const vEmail = str(req.body.email, 'E-Mail', { max: MAX_TITLE, required: false });
const vAddress = str(req.body.address, 'Adresse', { max: MAX_TEXT, required: false });
const vNotes = str(req.body.notes, 'Notizen', { max: MAX_TEXT, required: false });
const errors = collectErrors([vName, vCat, vPhone, vEmail, vAddress, vNotes]);
if (errors.length) return res.status(400).json({ error: errors.join(' '), code: 400 });
const result = db.get().prepare(`
INSERT INTO contacts (name, category, phone, email, address, notes)
VALUES (?, ?, ?, ?, ?, ?)
`).run(vName.value, vCat.value || 'Sonstiges', vPhone.value, vEmail.value,
vAddress.value, vNotes.value);
const contact = db.get().prepare('SELECT * FROM contacts WHERE id = ?').get(result.lastInsertRowid);
res.status(201).json({ data: contact });
} catch (err) {
console.error('[contacts/POST /]', err);
res.status(500).json({ error: 'Interner Fehler', code: 500 });
}
});
/**
* PUT /api/v1/contacts/:id
* Kontakt bearbeiten.
* Body: alle Felder optional
* Response: { data: Contact }
*/
router.put('/:id', (req, res) => {
try {
const id = parseInt(req.params.id, 10);
const contact = db.get().prepare('SELECT * FROM contacts WHERE id = ?').get(id);
if (!contact) return res.status(404).json({ error: 'Kontakt nicht gefunden', code: 404 });
const checks = [];
if (req.body.name !== undefined) checks.push(str(req.body.name, 'Name', { max: MAX_TITLE, required: false }));
if (req.body.category !== undefined) checks.push(oneOf(req.body.category, VALID_CATEGORIES, 'Kategorie'));
if (req.body.phone !== undefined) checks.push(str(req.body.phone, 'Telefon', { max: MAX_SHORT, required: false }));
if (req.body.email !== undefined) checks.push(str(req.body.email, 'E-Mail', { max: MAX_TITLE, required: false }));
if (req.body.address !== undefined) checks.push(str(req.body.address, 'Adresse', { max: MAX_TEXT, required: false }));
if (req.body.notes !== undefined) checks.push(str(req.body.notes, 'Notizen', { max: MAX_TEXT, required: false }));
const errors = collectErrors(checks);
if (errors.length) return res.status(400).json({ error: errors.join(' '), code: 400 });
db.get().prepare(`
UPDATE contacts
SET name = COALESCE(?, name),
category = COALESCE(?, category),
phone = ?,
email = ?,
address = ?,
notes = ?
WHERE id = ?
`).run(
req.body.name?.trim() ?? null,
req.body.category ?? null,
req.body.phone !== undefined ? (req.body.phone?.trim() || null) : contact.phone,
req.body.email !== undefined ? (req.body.email?.trim() || null) : contact.email,
req.body.address !== undefined ? (req.body.address?.trim() || null) : contact.address,
req.body.notes !== undefined ? (req.body.notes?.trim() || null) : contact.notes,
id
);
const updated = db.get().prepare('SELECT * FROM contacts WHERE id = ?').get(id);
res.json({ data: updated });
} catch (err) {
console.error('[contacts/PUT /:id]', err);
res.status(500).json({ error: 'Interner Fehler', code: 500 });
}
});
/**
* DELETE /api/v1/contacts/:id
* Kontakt löschen.
* Response: 204 No Content
*/
router.delete('/:id', (req, res) => {
try {
const id = parseInt(req.params.id, 10);
const result = db.get().prepare('DELETE FROM contacts WHERE id = ?').run(id);
if (result.changes === 0)
return res.status(404).json({ error: 'Kontakt nicht gefunden', code: 404 });
res.status(204).end();
} catch (err) {
console.error('[contacts/DELETE /:id]', err);
res.status(500).json({ error: 'Interner Fehler', code: 500 });
}
});
/**
* GET /api/v1/contacts/meta
* Kategorien-Liste für Dropdowns.
* Response: { data: { categories } }
*/
router.get('/meta', (_req, res) => {
try {
res.json({ data: { categories: VALID_CATEGORIES } });
} catch (err) {
console.error('[contacts/GET /meta]', err);
res.status(500).json({ error: 'Interner Fehler', code: 500 });
}
});
module.exports = router;