feat(meals): customizable meal type visibility in Settings (#14)
Users can now toggle which meal types (breakfast, lunch, dinner, snack) are displayed in the meal planner via a new Settings section. Preference is stored household-wide in sync_config and applied as a filter on the meals page. Includes preferences API, i18n (DE/EN/IT), and Settings UI.
This commit is contained in:
@@ -0,0 +1,91 @@
|
||||
/**
|
||||
* Modul: Haushalt-Einstellungen (Preferences)
|
||||
* Zweck: REST-API fuer haushaltweite Praeferenzen (via sync_config-Tabelle)
|
||||
* Abhängigkeiten: express, server/db.js
|
||||
*/
|
||||
|
||||
import { createLogger } from '../logger.js';
|
||||
import express from 'express';
|
||||
import * as db from '../db.js';
|
||||
|
||||
const log = createLogger('Preferences');
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
const VALID_MEAL_TYPES = ['breakfast', 'lunch', 'dinner', 'snack'];
|
||||
const DEFAULT_MEAL_TYPES = VALID_MEAL_TYPES.join(',');
|
||||
|
||||
// --------------------------------------------------------
|
||||
// Hilfsfunktionen
|
||||
// --------------------------------------------------------
|
||||
|
||||
function cfgGet(key) {
|
||||
const row = db.get().prepare('SELECT value FROM sync_config WHERE key = ?').get(key);
|
||||
return row ? row.value : null;
|
||||
}
|
||||
|
||||
function cfgSet(key, value) {
|
||||
db.get().prepare(`
|
||||
INSERT INTO sync_config (key, value)
|
||||
VALUES (?, ?)
|
||||
ON CONFLICT(key) DO UPDATE SET value = excluded.value,
|
||||
updated_at = strftime('%Y-%m-%dT%H:%M:%SZ', 'now')
|
||||
`).run(key, value);
|
||||
}
|
||||
|
||||
// --------------------------------------------------------
|
||||
// GET /api/v1/preferences
|
||||
// Alle Haushalt-Praeferenzen lesen.
|
||||
// Response: { data: { visible_meal_types: string[] } }
|
||||
// --------------------------------------------------------
|
||||
|
||||
router.get('/', (req, res) => {
|
||||
try {
|
||||
const raw = cfgGet('visible_meal_types') ?? DEFAULT_MEAL_TYPES;
|
||||
const visibleMealTypes = raw.split(',').filter((t) => VALID_MEAL_TYPES.includes(t));
|
||||
|
||||
res.json({
|
||||
data: {
|
||||
visible_meal_types: visibleMealTypes,
|
||||
},
|
||||
});
|
||||
} catch (err) {
|
||||
log.error('GET /', err);
|
||||
res.status(500).json({ error: 'Interner Fehler', code: 500 });
|
||||
}
|
||||
});
|
||||
|
||||
// --------------------------------------------------------
|
||||
// PUT /api/v1/preferences
|
||||
// Haushalt-Praeferenzen aktualisieren.
|
||||
// Body: { visible_meal_types: string[] }
|
||||
// Response: { data: { visible_meal_types: string[] } }
|
||||
// --------------------------------------------------------
|
||||
|
||||
router.put('/', (req, res) => {
|
||||
try {
|
||||
const { visible_meal_types } = req.body;
|
||||
|
||||
if (!Array.isArray(visible_meal_types)) {
|
||||
return res.status(400).json({ error: 'visible_meal_types muss ein Array sein', code: 400 });
|
||||
}
|
||||
|
||||
const filtered = visible_meal_types.filter((t) => VALID_MEAL_TYPES.includes(t));
|
||||
if (filtered.length === 0) {
|
||||
return res.status(400).json({ error: 'Mindestens ein Mahlzeit-Typ muss aktiv sein', code: 400 });
|
||||
}
|
||||
|
||||
cfgSet('visible_meal_types', filtered.join(','));
|
||||
|
||||
res.json({
|
||||
data: {
|
||||
visible_meal_types: filtered,
|
||||
},
|
||||
});
|
||||
} catch (err) {
|
||||
log.error('PUT /', err);
|
||||
res.status(500).json({ error: 'Interner Fehler', code: 500 });
|
||||
}
|
||||
});
|
||||
|
||||
export default router;
|
||||
Reference in New Issue
Block a user