Mise en place des workspaces

This commit is contained in:
ocroguennec committed 2026-09-08 12:33:04 +02:00
1 parent be3c93897b
commit 6b6cad7cd6
19 files changed
+1273 -56

No files matched your search

+75
View File
@@ -3028,4 +3028,79 @@ db.exec('CREATE INDEX IF NOT EXISTS idx_ai_messages_thread ON ai_messages(user_i
}
}
// ── Migration : Workspaces (extension multi-workspace, 08/09/26) ─────────
// Transforme l'app en socle commun + "workspaces" activables/désactivables
// par utilisateur, gouvernés par l'admin — cf. project_workspaces_transformation.md
// (mémoire projet) pour le plan complet. Phase 0 : catalogue global
// (`workspaces`) + octroi (admin) / activation (utilisateur) par compte
// (`user_workspaces`). Le workspace "crowdlending" représente l'app
// actuelle et est accordé + activé automatiquement à tous les comptes
// existants (backfill ci-dessous) et à tout nouveau compte (cf.
// grantDefaultWorkspace, appelée depuis auth.js /register, admin.js POST
// /users et invitations.js /:token/register — mêmes 3 points que la
// création auto de l'investisseur principal). Volontairement PAS ajouté à
// ENV_LOCAL_TABLES : ce sont des données métier normales (comme
// investissements), pas des réglages propres à un environnement — une
// restauration doit les écraser comme le reste.
db.exec(`
CREATE TABLE IF NOT EXISTS workspaces (
id INTEGER PRIMARY KEY AUTOINCREMENT,
slug TEXT NOT NULL UNIQUE,
nom TEXT NOT NULL,
libelle_menu TEXT NOT NULL,
description TEXT,
actif_global INTEGER NOT NULL DEFAULT 1,
ordre INTEGER NOT NULL DEFAULT 0,
created_at TEXT NOT NULL DEFAULT (datetime('now')),
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
)
`);
db.exec(`
CREATE TABLE IF NOT EXISTS user_workspaces (
user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE,
workspace_id INTEGER NOT NULL REFERENCES workspaces(id) ON DELETE CASCADE,
granted_by_admin INTEGER NOT NULL DEFAULT 0,
active_by_user INTEGER NOT NULL DEFAULT 1,
granted_at TEXT,
PRIMARY KEY (user_id, workspace_id)
)
`);
db.exec('CREATE INDEX IF NOT EXISTS idx_user_workspaces_user ON user_workspaces(user_id)');
// Seed : workspace "crowdlending" = l'app actuelle. Idempotent (slug UNIQUE).
db.prepare(`
INSERT OR IGNORE INTO workspaces (slug, nom, libelle_menu, description, actif_global, ordre)
VALUES ('crowdlending', 'Crowdlending', 'Crowdlending', 'Suivi de portefeuille crowdlending', 1, 0)
`).run();
/** Accorde + active le workspace par défaut (crowdlending) à un utilisateur.
* Appelée à la création d'un compte (auth.js /register, admin.js POST
* /users, invitations.js /:token/register) — même logique que la création
* auto du profil investisseur principal à ces 3 mêmes endroits, pour
* qu'aucun nouveau compte ne se retrouve sans accès à l'app actuelle. */
export function grantDefaultWorkspace(userId) {
const ws = db.prepare("SELECT id FROM workspaces WHERE slug = 'crowdlending'").get();
if (!ws) return;
db.prepare(`
INSERT OR IGNORE INTO user_workspaces (user_id, workspace_id, granted_by_admin, active_by_user, granted_at)
VALUES (?, ?, 1, 1, datetime('now'))
`).run(userId, ws.id);
}
// Backfill : tout utilisateur existant sans accès au workspace crowdlending
// (comptes créés avant cette migration) le reçoit automatiquement — ne doit
// rien casser du fonctionnement actuel. Idempotent (INSERT OR IGNORE sur la
// PK composite user_id/workspace_id).
{
const wsBackfill = db.prepare("SELECT id FROM workspaces WHERE slug = 'crowdlending'").get();
if (wsBackfill) {
db.prepare(`
INSERT OR IGNORE INTO user_workspaces (user_id, workspace_id, granted_by_admin, active_by_user, granted_at)
SELECT id, ?, 1, 1, datetime('now') FROM users
`).run(wsBackfill.id);
}
}
export default db;