From ef6a8c7b4a08b485d6985bff36fc2f3707e62db8 Mon Sep 17 00:00:00 2001 From: Michael SCHAL Date: Wed, 25 Mar 2026 09:09:53 +0100 Subject: [PATCH] feat: Implement core application dashboard, add todo and task management API, and include database testing utilities. --- README.md | 25 +++++++++++++++++++------ routes/todos.routes.js | 32 +++++++++++++++++++++----------- server.js | 1 + src/pages/Index.tsx | 40 +++++++++++++++++++++++++++++++++------- test-db.js | 10 ++++++++++ 5 files changed, 84 insertions(+), 24 deletions(-) create mode 100644 test-db.js diff --git a/README.md b/README.md index 44d164e..ff70c78 100644 --- a/README.md +++ b/README.md @@ -29,10 +29,16 @@ Application web moderne de gestion de notes et de tâches, Dockerisée, avec aut - Attribution des droits administrateur - Interface dédiée pour les admins +## Architecture + +- **Frontend**: React 18, Vite, Tailwind CSS, Shadcn UI +- **Backend**: Node.js, Express +- **Base de Données**: PostgreSQL 16 (recommandé) + ## 🛠️ Stack Technique - **Backend** : Node.js 20 + Express -- **Base de données** : SQLite3 +- **Base de données** : PostgreSQL 16 - **Authentification** : JWT + bcrypt - **Frontend** : React 19 + TypeScript + Vite - **UI Components** : shadcn/ui + Tailwind CSS @@ -224,12 +230,19 @@ PUT /api/notes/todos/:todoId DELETE /api/notes/todos/:todoId ``` -### Todos Globaux +### Tâches (Todos Globaux) ``` -GET /api/todos -POST /api/todos -PUT /api/todos/:id -DELETE /api/todos/:id +GET /api/todos # Liste toutes les tâches +POST /api/todos # Créer une tâche (text, priority, due_date, parent_id) +PUT /api/todos/:id # Modifier une tâche +PATCH /api/todos/:id/toggle # Basculer l'état complété +PATCH /api/todos/:id/priority # Basculer la priorité +PATCH /api/todos/:id/in-progress # Basculer l'état "en cours" +DELETE /api/todos/:id # Supprimer une tâche + +# Alias sémantique (recommandé pour les intégrations externes) +POST /api/tasks # Identique à POST /api/todos +GET /api/tasks # Identique à GET /api/todos ``` ## 🔒 Sécurité diff --git a/routes/todos.routes.js b/routes/todos.routes.js index 726c63d..2198952 100644 --- a/routes/todos.routes.js +++ b/routes/todos.routes.js @@ -19,10 +19,10 @@ router.get('/', async (req, res) => { // Essayer d'abord avec le champ priority (nouvelle version) try { const todos = await getAll(` - SELECT id, text, completed, priority, in_progress, parent_id, level, created_at + SELECT id, text, completed, priority, in_progress, parent_id, level, due_date, created_at FROM global_todos WHERE user_id = $1 - ORDER BY priority DESC, created_at DESC + ORDER BY priority DESC, due_date ASC NULLS LAST, created_at DESC `, [req.user.id]); res.json(todos); @@ -54,7 +54,10 @@ router.get('/', async (req, res) => { router.post('/', [ body('text').trim().notEmpty().withMessage('Le texte est requis'), - body('parent_id').optional().isInt() + body('parent_id').optional().isInt(), + body('priority').optional().isBoolean(), + body('in_progress').optional().isBoolean(), + body('due_date').optional().isISO8601() ], async (req, res) => { try { @@ -63,7 +66,7 @@ router.post('/', return res.status(400).json({ error: 'Données invalides', details: errors.array() }); } - const { text, parent_id } = req.body; + const { text, parent_id, priority = false, in_progress = false, due_date = null } = req.body; let level = 0; // Si c'est un subtask, vérifier que le parent existe et calculer le niveau @@ -78,10 +81,10 @@ router.post('/', logger.info(`[CREATE GLOBAL TODO] Début création - user_id: ${req.user.id}, text: "${text}"${parent_id ? ` (subtask of ${parent_id})` : ''}`); const result = await runQuery(` - INSERT INTO global_todos (user_id, text, parent_id, level) - VALUES ($1, $2, $3, $4) + INSERT INTO global_todos (user_id, text, parent_id, level, priority, in_progress, due_date) + VALUES ($1, $2, $3, $4, $5, $6, $7) RETURNING * - `, [req.user.id, text, parent_id || null, level]); + `, [req.user.id, text, parent_id || null, level, priority, in_progress ? 1 : 0, due_date]); logger.info(`[CREATE GLOBAL TODO] Todo global créé avec succès - ID: ${result.id}, user: ${req.user.username}, level: ${level}`); @@ -89,10 +92,11 @@ router.post('/', id: result.id, text, completed: false, - priority: false, - in_progress: false, + priority: priority, + in_progress: !!in_progress, parent_id: parent_id || null, level: level, + due_date: due_date, created_at: new Date().toISOString() }); } catch (error) { @@ -111,7 +115,8 @@ router.put('/:id', body('text').optional().trim().notEmpty(), body('completed').optional().isBoolean(), body('priority').optional().isBoolean(), - body('in_progress').optional().isBoolean() + body('in_progress').optional().isBoolean(), + body('due_date').optional().isISO8601().nullable() ], async (req, res) => { try { @@ -120,7 +125,7 @@ router.put('/:id', return res.status(400).json({ error: 'Données invalides', details: errors.array() }); } - const { text, completed, priority, in_progress } = req.body; + const { text, completed, priority, in_progress, due_date } = req.body; // Vérifier que le todo appartient à l'utilisateur const todo = await getOne('SELECT id FROM global_todos WHERE id = $1 AND user_id = $2', [req.params.id, req.user.id]); @@ -152,6 +157,11 @@ router.put('/:id', updates.push(`in_progress = $${paramCount}`); params.push(in_progress ? 1 : 0); // global_todos.in_progress is INTEGER not BOOLEAN } + if (due_date !== undefined) { + paramCount++; + updates.push(`due_date = $${paramCount}`); + params.push(due_date); + } if (updates.length > 0) { paramCount++; diff --git a/server.js b/server.js index 228fd94..4ab5200 100644 --- a/server.js +++ b/server.js @@ -94,6 +94,7 @@ app.use('/api/auth', require('./routes/auth.routes')); app.use('/api/users', require('./routes/users.routes')); app.use('/api/notes', require('./routes/notes.routes')); app.use('/api/todos', require('./routes/todos.routes')); +app.use('/api/tasks', require('./routes/todos.routes')); app.use('/api/recurring-todos', require('./routes/recurring-todos.routes')); app.use('/api/settings', require('./routes/settings.routes')); app.use('/api/rss', require('./routes/rss.routes')); diff --git a/src/pages/Index.tsx b/src/pages/Index.tsx index 73780d3..663bb32 100644 --- a/src/pages/Index.tsx +++ b/src/pages/Index.tsx @@ -2809,6 +2809,27 @@ const Index = () => { { "text": "Tâche 1" }, { "text": "Tâche 2", "priority": true } ] +}`} + + + + + + +
+ POST + /api/tasks + (Alias de /api/todos) +
+

+ Créer une tâche générale avec priorité, progression et date d'échéance +

+
+
{`{
+  "text": "Ma tâche importante",
+  "priority": true,
+  "in_progress": false,
+  "due_date": "2024-12-31T23:59:59Z"
 }`}
@@ -2820,19 +2841,24 @@ const Index = () => {

3. Exemple complet (curl)

-
{`# Créer une note avec tâches (clé API permanente)
-curl -X POST ${window.location.origin}/api/notes/full \\
+                

# Créer une note avec tâches :

+
{`curl -X POST ${window.location.origin}/api/notes/full \\
   -H "Content-Type: application/json" \\
   -H "X-API-Key: ${apiInfoModal.apiKey || 'VOTRE_CLE_API'}" \\
   -d '{
     "title": "Liste de courses",
     "content": "Pour le weekend",
-    "todos": [
-      { "text": "Pain" },
-      { "text": "Lait" },
-      { "text": "Fruits" }
-    ],
+    "todos": [{ "text": "Pain" }, { "text": "Lait" }],
     "tags": ["courses"]
+  }'`}
+

# Créer une tâche générale prioritaire :

+
{`curl -X POST ${window.location.origin}/api/tasks \\
+  -H "Content-Type: application/json" \\
+  -H "X-API-Key: ${apiInfoModal.apiKey || 'VOTRE_CLE_API'}" \\
+  -d '{
+    "text": "Finaliser le rapport",
+    "priority": true,
+    "due_date": "2024-10-25T17:00:00Z"
   }'`}
diff --git a/test-db.js b/test-db.js new file mode 100644 index 0000000..64abdb7 --- /dev/null +++ b/test-db.js @@ -0,0 +1,10 @@ +const { pool } = require('./config/database'); +pool.query('SELECT 1', (err, res) => { + if (err) { + console.error('DB Connection Error:', err); + process.exit(1); + } else { + console.log('DB Connection Success'); + process.exit(0); + } +});