Correctif Contrainte de suppression de compte
This commit is contained in:
@@ -2086,4 +2086,69 @@ console.log('[DB] Migrations 2FA OK');
|
||||
}
|
||||
}
|
||||
|
||||
// ── Migration : plateforme_id en CASCADE (au lieu de RESTRICT) sur
|
||||
// depots_retraits et investissements ────────────────────────────────────
|
||||
// RESTRICT empêchait la suppression complète d'un compte (DELETE FROM users) :
|
||||
// la cascade users→plateformes (user_id CASCADE) et users→investisseurs→
|
||||
// depots_retraits/investissements (CASCADE) sont deux branches indépendantes
|
||||
// de l'arbre de suppression ; SQLite peut supprimer une plateforme avant les
|
||||
// lignes qui la référencent encore sur l'autre branche, ce qui déclenche le
|
||||
// RESTRICT (erreur SQLITE_CONSTRAINT_TRIGGER). La protection "impossible de
|
||||
// supprimer une plateforme qui a encore des données" est déplacée dans la
|
||||
// route DELETE /api/plateformes/:id (vérification explicite avec message clair).
|
||||
{
|
||||
const fixPlateformeCascade = (tableName, indexStatements) => {
|
||||
const row = db.prepare(
|
||||
`SELECT sql FROM sqlite_master WHERE type='table' AND name=?`
|
||||
).get(tableName);
|
||||
if (!row || !/REFERENCES\s+plateformes\(id\)\s+ON DELETE RESTRICT/i.test(row.sql)) return;
|
||||
|
||||
const tempName = `__repair_${tableName}`;
|
||||
const nameRe = new RegExp(
|
||||
`CREATE TABLE\\s+(?:IF NOT EXISTS\\s+)?["'\`\\[]?${tableName}["'\`\\]]?`, 'i'
|
||||
);
|
||||
if (!nameRe.test(row.sql)) {
|
||||
console.error(`[DB] migration plateforme_id CASCADE : nom de table non reconnu dans le DDL de "${tableName}", migration ignorée.`);
|
||||
return;
|
||||
}
|
||||
const fixedDdl = row.sql
|
||||
.replace(nameRe, `CREATE TABLE "${tempName}"`)
|
||||
.replace(/REFERENCES\s+plateformes\(id\)\s+ON DELETE RESTRICT/i, 'REFERENCES plateformes(id) ON DELETE CASCADE');
|
||||
|
||||
const colDefs = db.prepare(`PRAGMA table_info("${tableName}")`).all();
|
||||
const colNames = colDefs.map(c => `"${c.name}"`).join(', ');
|
||||
|
||||
const idxs = db.prepare(
|
||||
`SELECT name FROM sqlite_master WHERE type='index' AND tbl_name=? AND sql IS NOT NULL`
|
||||
).all(tableName);
|
||||
|
||||
db.exec('PRAGMA foreign_keys = OFF');
|
||||
db.exec(`DROP TABLE IF EXISTS "${tempName}"`);
|
||||
db.exec(fixedDdl);
|
||||
db.exec(`INSERT INTO "${tempName}" (${colNames}) SELECT ${colNames} FROM "${tableName}"`);
|
||||
for (const idx of idxs) db.exec(`DROP INDEX IF EXISTS "${idx.name}"`);
|
||||
db.exec(`DROP TABLE "${tableName}"`);
|
||||
db.exec('PRAGMA legacy_alter_table = ON');
|
||||
db.exec(`ALTER TABLE "${tempName}" RENAME TO "${tableName}"`);
|
||||
db.exec('PRAGMA legacy_alter_table = OFF');
|
||||
db.exec('PRAGMA foreign_keys = ON');
|
||||
|
||||
for (const stmt of indexStatements) db.exec(stmt);
|
||||
console.log(`[DB] migration : plateforme_id passé en ON DELETE CASCADE sur "${tableName}".`);
|
||||
};
|
||||
|
||||
fixPlateformeCascade('depots_retraits', [
|
||||
'CREATE INDEX IF NOT EXISTS idx_depret_inv ON depots_retraits(investisseur_id)',
|
||||
'CREATE INDEX IF NOT EXISTS idx_depret_plat ON depots_retraits(plateforme_id)',
|
||||
'CREATE INDEX IF NOT EXISTS idx_depret_date ON depots_retraits(date_operation)',
|
||||
]);
|
||||
|
||||
fixPlateformeCascade('investissements', [
|
||||
'CREATE INDEX IF NOT EXISTS idx_inv_inv ON investissements(investisseur_id)',
|
||||
'CREATE INDEX IF NOT EXISTS idx_inv_plat ON investissements(plateforme_id)',
|
||||
'CREATE INDEX IF NOT EXISTS idx_inv_statut ON investissements(statut)',
|
||||
'CREATE INDEX IF NOT EXISTS idx_inv_date ON investissements(date_souscription)',
|
||||
]);
|
||||
}
|
||||
|
||||
export default db;
|
||||
|
||||
@@ -70,7 +70,7 @@ CREATE INDEX IF NOT EXISTS idx_plateformes_user ON plateformes(user_id);
|
||||
CREATE TABLE IF NOT EXISTS depots_retraits (
|
||||
id INTEGER PRIMARY KEY,
|
||||
investisseur_id INTEGER NOT NULL REFERENCES investisseurs(id) ON DELETE CASCADE,
|
||||
plateforme_id INTEGER NOT NULL REFERENCES plateformes(id) ON DELETE RESTRICT,
|
||||
plateforme_id INTEGER NOT NULL REFERENCES plateformes(id) ON DELETE CASCADE,
|
||||
date_operation TEXT NOT NULL, -- ISO YYYY-MM-DD
|
||||
type TEXT NOT NULL CHECK(type IN ('depot','retrait')),
|
||||
montant REAL NOT NULL CHECK(montant >= 0),
|
||||
@@ -92,7 +92,7 @@ CREATE INDEX IF NOT EXISTS idx_depret_date ON depots_retraits(date_operation);
|
||||
CREATE TABLE IF NOT EXISTS investissements (
|
||||
id INTEGER PRIMARY KEY,
|
||||
investisseur_id INTEGER NOT NULL REFERENCES investisseurs(id) ON DELETE CASCADE,
|
||||
plateforme_id INTEGER NOT NULL REFERENCES plateformes(id) ON DELETE RESTRICT,
|
||||
plateforme_id INTEGER NOT NULL REFERENCES plateformes(id) ON DELETE CASCADE,
|
||||
nom_projet TEXT NOT NULL,
|
||||
emetteur TEXT, -- nom de la société emprunteuse
|
||||
date_souscription TEXT NOT NULL,
|
||||
|
||||
@@ -679,6 +679,16 @@ router.delete('/:id', (req, res, next) => {
|
||||
const plat = db.prepare('SELECT logo_filename FROM plateformes WHERE id=? AND user_id=?')
|
||||
.get(req.params.id, req.user.id);
|
||||
if (!plat) throw new HttpError(404, 'Not found');
|
||||
|
||||
// plateforme_id est en ON DELETE CASCADE sur investissements/depots_retraits (nécessaire pour
|
||||
// permettre la suppression complète d'un compte) — on protège donc ici, explicitement, contre
|
||||
// la suppression accidentelle d'une plateforme qui a encore des données rattachées.
|
||||
const { n: nbInv } = db.prepare('SELECT COUNT(*) AS n FROM investissements WHERE plateforme_id = ?').get(req.params.id);
|
||||
const { n: nbDr } = db.prepare('SELECT COUNT(*) AS n FROM depots_retraits WHERE plateforme_id = ?').get(req.params.id);
|
||||
if (nbInv > 0 || nbDr > 0) {
|
||||
throw new HttpError(400, `Impossible de supprimer cette plateforme : elle a encore ${nbInv} investissement(s) et ${nbDr} mouvement(s) de dépôt/retrait enregistrés. Supprimez-les d'abord.`);
|
||||
}
|
||||
|
||||
const r = db.prepare('DELETE FROM plateformes WHERE id=? AND user_id=?')
|
||||
.run(req.params.id, req.user.id);
|
||||
if (r.changes === 0) throw new HttpError(404, 'Not found');
|
||||
|
||||
Reference in New Issue
Block a user