/** * 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=&q= * 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;