From 00ac17da70cd20168ce46367383c8d946c8d3a6b Mon Sep 17 00:00:00 2001 From: Michael SCHAL Date: Sun, 23 Nov 2025 16:31:47 +0100 Subject: [PATCH] feat: add admin service for user management and migration, and routes for database cleanup. --- routes/admin.routes.js | 41 +++++++++++++++++++++++++++ src/pages/Index.tsx | 26 +++++++++++++++++ src/services/AdminService.ts | 54 +++++++++++++++++++++++++----------- 3 files changed, 105 insertions(+), 16 deletions(-) diff --git a/routes/admin.routes.js b/routes/admin.routes.js index bda71db..40d5eed 100644 --- a/routes/admin.routes.js +++ b/routes/admin.routes.js @@ -362,4 +362,45 @@ router.get('/stats', async (req, res) => { } }); +/** + * POST /api/admin/migrate/in-progress + * Exécuter la migration pour ajouter la colonne in_progress + */ +router.post('/migrate/in-progress', async (req, res) => { + const pool = new Pool({ connectionString: DATABASE_URL }); + + try { + const client = await pool.connect(); + try { + logger.info('Lancement manuel de la migration in_progress par admin'); + + // Check if column exists + const checkRes = await client.query(` + SELECT column_name + FROM information_schema.columns + WHERE table_name='global_todos' AND column_name='in_progress' + `); + + if (checkRes.rows.length === 0) { + await client.query('ALTER TABLE global_todos ADD COLUMN in_progress INTEGER DEFAULT 0'); + logger.info('✓ Colonne in_progress ajoutée avec succès'); + res.json({ success: true, message: 'Migration réussie : colonne ajoutée' }); + } else { + logger.info('⚠ Colonne in_progress existe déjà'); + res.json({ success: true, message: 'Migration non nécessaire : la colonne existe déjà' }); + } + } finally { + client.release(); + } + await pool.end(); + } catch (error) { + logger.error('Erreur lors de la migration manuelle:', error); + res.status(500).json({ + success: false, + error: 'Erreur lors de la migration', + message: error.message + }); + } +}); + module.exports = router; diff --git a/src/pages/Index.tsx b/src/pages/Index.tsx index 3af1816..0e4eb3e 100644 --- a/src/pages/Index.tsx +++ b/src/pages/Index.tsx @@ -2115,6 +2115,32 @@ const Index = () => { + +
+

Zone de danger / Maintenance

+

+ Utilisez ces options uniquement si vous rencontrez des problèmes spécifiques. +

+ +
diff --git a/src/services/AdminService.ts b/src/services/AdminService.ts index 1c77c9f..9e18e66 100644 --- a/src/services/AdminService.ts +++ b/src/services/AdminService.ts @@ -14,25 +14,25 @@ class AdminService { const response = await fetch("/api/users", { headers: AuthService.getHeaders() }); - + if (!response.ok) { throw new Error("Erreur lors de la récupération des utilisateurs"); } - + const users = await response.json(); - + // Vérifier que les données sont un tableau if (!Array.isArray(users)) { return []; } - + return users; } catch (error) { showError(error instanceof Error ? error.message : "Erreur serveur"); return []; } } - + async createUser(username: string, password: string, isAdmin: boolean): Promise { try { const response = await fetch("/api/users", { @@ -40,12 +40,12 @@ class AdminService { headers: AuthService.getHeaders(), body: JSON.stringify({ username, password, is_admin: isAdmin }) }); - + if (!response.ok) { const error = await response.json(); throw new Error(error.message || "Erreur lors de la création de l'utilisateur"); } - + showSuccess("Utilisateur créé avec succès"); return await response.json(); } catch (error) { @@ -53,7 +53,7 @@ class AdminService { return null; } } - + async updateUserPassword(userId: number, password: string): Promise { try { const response = await fetch(`/api/users/${userId}`, { @@ -61,11 +61,11 @@ class AdminService { headers: AuthService.getHeaders(), body: JSON.stringify({ password }) }); - + if (!response.ok) { throw new Error("Erreur lors de la modification du mot de passe"); } - + showSuccess("Mot de passe modifié avec succès"); return true; } catch (error) { @@ -73,7 +73,7 @@ class AdminService { return false; } } - + async toggleAdminStatus(userId: number, currentStatus: boolean): Promise { try { const response = await fetch(`/api/users/${userId}`, { @@ -81,11 +81,11 @@ class AdminService { headers: AuthService.getHeaders(), body: JSON.stringify({ is_admin: !currentStatus }) }); - + if (!response.ok) { throw new Error("Erreur lors de la modification des droits"); } - + showSuccess("Droits modifiés avec succès"); return true; } catch (error) { @@ -93,18 +93,18 @@ class AdminService { return false; } } - + async deleteUser(userId: number): Promise { try { const response = await fetch(`/api/users/${userId}`, { method: "DELETE", headers: AuthService.getHeaders() }); - + if (!response.ok) { throw new Error("Erreur lors de la suppression de l'utilisateur"); } - + showSuccess("Utilisateur supprimé avec succès"); return true; } catch (error) { @@ -112,6 +112,28 @@ class AdminService { return false; } } + + async runInProgressMigration(): Promise<{ success: boolean; message: string }> { + try { + const response = await fetch("/api/admin/migrate/in-progress", { + method: "POST", + headers: AuthService.getHeaders() + }); + + const data = await response.json(); + + if (!response.ok) { + throw new Error(data.message || "Erreur lors de la migration"); + } + + return data; + } catch (error) { + return { + success: false, + message: error instanceof Error ? error.message : "Erreur serveur" + }; + } + } } // Créer une instance unique