Files
apex/server/routes/layoutPresets.js
T
lejayandCursor 30921aade6 v2.1
Ship configurator updates with catalog DB included for server deploy.

Co-authored-by: Cursor <[email protected]>
2026-10-07 17:13:03 +02:00

126 lines
4.3 KiB
JavaScript

import pool, { run } from '../db/index.js';
import { DEFAULT_LAYOUT_PRESETS } from '../db/defaultLayouts.js';
const MAX_NAME_LENGTH = 100;
const PRESET_COLUMNS = 'id, seed_key, name, description, config, sort_order, created_at, updated_at';
let seeded = null;
/**
* Synchronise les dispositions intégrées avec leur définition en code (insertion ou mise à jour).
* Une disposition intégrée supprimée reste masquée (active = 0).
*/
function ensureDefaultLayouts() {
if (!seeded) {
seeded = (async () => {
const { rows } = await pool.query(
'SELECT seed_key, name, description, config, sort_order FROM layout_presets WHERE seed_key IS NOT NULL'
);
const existing = new Map(rows.map((r) => [r.seed_key, r]));
for (const [index, preset] of DEFAULT_LAYOUT_PRESETS.entries()) {
const config = JSON.stringify(preset.config);
const row = existing.get(preset.seedKey);
if (row) {
const unchanged = row.name === preset.name && row.description === preset.description
&& row.config === config && Number(row.sort_order) === index;
if (!unchanged) {
await run(
`UPDATE layout_presets
SET name = $1, description = $2, config = $3, sort_order = $4, updated_at = datetime('now')
WHERE seed_key = $5`,
[preset.name, preset.description, config, index, preset.seedKey]
);
}
continue;
}
await run(
`INSERT INTO layout_presets (seed_key, name, description, config, sort_order)
VALUES ($1, $2, $3, $4, $5)`,
[preset.seedKey, preset.name, preset.description, config, index]
);
}
})().catch((err) => {
seeded = null;
throw err;
});
}
return seeded;
}
function parsePreset(row) {
if (!row) return null;
let config = null;
try {
config = JSON.parse(row.config);
} catch { /* config illisible → null */ }
return { ...row, builtin: row.seed_key != null, config };
}
export async function getLayoutPresets(req, res) {
try {
await ensureDefaultLayouts();
const { rows } = await pool.query(
`SELECT ${PRESET_COLUMNS}
FROM layout_presets
WHERE active = 1
ORDER BY sort_order ASC, id ASC`
);
res.json(rows.map(parsePreset));
} catch (err) {
res.status(500).json({ error: err.message });
}
}
export async function createLayoutPreset(req, res) {
try {
const name = String(req.body?.name || '').trim();
const description = String(req.body?.description || '').trim() || null;
const config = req.body?.config;
if (!name) return res.status(400).json({ error: 'Le nom est requis.' });
if (name.length > MAX_NAME_LENGTH) {
return res.status(400).json({ error: `Le nom ne peut pas dépasser ${MAX_NAME_LENGTH} caractères.` });
}
if (!config || typeof config !== 'object' || Array.isArray(config) || !Array.isArray(config.modules) || config.modules.length === 0) {
return res.status(400).json({ error: 'Disposition invalide (au moins une colonne requise).' });
}
await ensureDefaultLayouts();
await run(
`INSERT INTO layout_presets (name, description, config, sort_order)
VALUES ($1, $2, $3, 1000)`,
[name, description, JSON.stringify(config)]
);
const { rows } = await pool.query(
`SELECT ${PRESET_COLUMNS}
FROM layout_presets
ORDER BY id DESC
LIMIT 1`
);
res.status(201).json(parsePreset(rows[0]));
} catch (err) {
res.status(500).json({ error: err.message });
}
}
export async function deleteLayoutPreset(req, res) {
try {
const id = Number(req.params.id);
if (!Number.isInteger(id) || id < 1) {
return res.status(400).json({ error: 'Identifiant invalide.' });
}
const { rows } = await pool.query('SELECT id, seed_key FROM layout_presets WHERE id = $1 LIMIT 1', [id]);
if (rows.length === 0) return res.status(404).json({ error: 'Disposition introuvable.' });
if (rows[0].seed_key != null) {
await run(`UPDATE layout_presets SET active = 0, updated_at = datetime('now') WHERE id = $1`, [id]);
} else {
await run('DELETE FROM layout_presets WHERE id = $1', [id]);
}
res.status(204).end();
} catch (err) {
res.status(500).json({ error: err.message });
}
}