Gestion des Thèmes

This commit is contained in:
ocroguennec committed 2026-09-06 18:59:09 +02:00
1 parent bab7a79b63
commit be3c93897b
24 files changed
+1041 -141

No files matched your search

+10 -2
View File
@@ -27,9 +27,11 @@ function ensureRow() {
// colonne applique automatiquement son DEFAULT (5 Go / 500 documents,
// cf. migration dans db/index.js) quand elle est omise d'un INSERT.
}
// parametres_environnement : normalement déjà créée avec sa ligne par défaut
// par la migration dans db/index.js (INSERT OR IGNORE) — filet de sécurité ici.
// parametres_environnement / parametres_theme : normalement déjà créées avec
// leur ligne par défaut par la migration dans db/index.js (INSERT OR IGNORE)
// — filet de sécurité ici.
db.prepare('INSERT OR IGNORE INTO parametres_environnement (id) VALUES (1)').run();
db.prepare('INSERT OR IGNORE INTO parametres_theme (id) VALUES (1)').run();
}
// 1 Go = 1024^3 octets (convention déjà utilisée par fmtOctets côté frontend).
@@ -40,10 +42,12 @@ router.get('/', (_req, res, next) => {
ensureRow();
const row = db.prepare('SELECT app_name, app_url, mcp_url, allow_registration, min_password_length, documents_quota_bytes, documents_quota_count FROM smtp_config WHERE id = 1').get();
const envRow = db.prepare('SELECT environnement FROM parametres_environnement WHERE id = 1').get();
const themeRow = db.prepare('SELECT color_theme FROM parametres_theme WHERE id = 1').get();
res.json({
appName: row.app_name || 'Crowdlending Tracker',
appUrl: row.app_url || '',
environnement: envRow?.environnement || 'developpement',
colorTheme: themeRow?.color_theme || 'classique',
mcpUrl: row.mcp_url || '',
allowRegistration: row.allow_registration !== 0,
minPasswordLength: row.min_password_length || 8,
@@ -57,6 +61,7 @@ const PatchSchema = z.object({
appName: z.string().min(1).max(100).optional(),
appUrl: z.string().max(500).optional(),
environnement: z.enum(['production', 'qualification', 'developpement']).optional(),
colorTheme: z.enum(['classique', 'indigo_teal', 'nuit', 'nature', 'coucher_de_soleil', 'violet', 'frais', 'contraste', 'pastel', 'moderne']).optional(),
mcpUrl: z.string().max(500).optional(),
allowRegistration: z.boolean().optional(),
minPasswordLength: z.number().int().min(6).max(64).optional(),
@@ -93,6 +98,9 @@ router.patch('/', (req, res, next) => {
if (body.environnement !== undefined) {
db.prepare('UPDATE parametres_environnement SET environnement = ? WHERE id = 1').run(body.environnement);
}
if (body.colorTheme !== undefined) {
db.prepare('UPDATE parametres_theme SET color_theme = ? WHERE id = 1').run(body.colorTheme);
}
res.json({ ok: true });
} catch (e) { next(e); }
+33 -1
View File
@@ -10,7 +10,18 @@ const ALLOWED_KEYS = new Set([
'chart_cashback',
'pfo_assujetti',
'objectifs_actifs',
// Extensible : ajouter ici les futures prefs (theme, font_scale, langue, devise, display_mode…)
'color_theme',
// Extensible : ajouter ici les futures prefs (font_scale, langue, devise, display_mode…)
]);
// Thème de couleur (Admin > Général, cf. project_theme_interface.md) — même
// énumération que la colonne parametres_theme.color_theme côté admin.
// Contrairement aux autres clés de ALLOWED_KEYS, la valeur est validée ici
// (pas seulement la clé) car une valeur invalide casserait silencieusement
// le rendu CSS ([data-color-theme] côté frontend n'a pas de fallback).
const COLOR_THEME_VALUES = new Set([
'classique', 'indigo_teal', 'nuit', 'nature', 'coucher_de_soleil',
'violet', 'frais', 'contraste', 'pastel', 'moderne',
]);
/**
@@ -51,6 +62,10 @@ router.patch('/', (req, res) => {
return res.status(400).json({ error: `Clés inconnues : ${unknown.join(', ')}` });
}
if ('color_theme' in payload && !COLOR_THEME_VALUES.has(payload.color_theme)) {
return res.status(400).json({ error: `Valeur invalide pour color_theme : ${payload.color_theme}` });
}
const persist = db.transaction(() => {
for (const [key, value] of Object.entries(payload)) {
upsert.run(req.user.id, key, String(value));
@@ -61,4 +76,21 @@ router.patch('/', (req, res) => {
res.json({ ok: true });
});
/**
* DELETE /api/preferences/:key
* Supprime la préférence de l'utilisateur connecté pour une clé donnée —
* utile pour revenir à un comportement "par défaut" (ex. "Automatique"
* pour le thème de couleur, cf. Settings > Apparence) plutôt que de
* stocker une valeur sentinelle : une clé absente n'est simplement plus
* retournée par GET (le frontend retombe alors sur sa valeur par défaut).
*/
router.delete('/:key', (req, res) => {
const { key } = req.params;
if (!ALLOWED_KEYS.has(key)) {
return res.status(400).json({ error: `Clé inconnue : ${key}` });
}
db.prepare('DELETE FROM user_preferences WHERE user_id = ? AND key = ?').run(req.user.id, key);
res.json({ ok: true });
});
export default router;