mirror of
https://github.com/R0m1k3/noteflow.git
synced 2026-10-11 17:29:37 +02:00
feat: Implement core application dashboard, add todo and task management API, and include database testing utilities.
This commit is contained in:
1 parent
b1f58a2f13
commit
ef6a8c7b4a
5 files changed
+84
-24
No files matched your search
@@ -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é
|
||||
|
||||
+21
-11
@@ -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++;
|
||||
|
||||
@@ -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'));
|
||||
|
||||
+33
-7
@@ -2809,6 +2809,27 @@ const Index = () => {
|
||||
{ "text": "Tâche 1" },
|
||||
{ "text": "Tâche 2", "priority": true }
|
||||
]
|
||||
}`}</pre>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card>
|
||||
<CardContent className="p-4">
|
||||
<div className="flex items-center gap-2 mb-2">
|
||||
<Badge className="bg-green-600">POST</Badge>
|
||||
<code className="text-sm">/api/tasks</code>
|
||||
<span className="text-xs text-muted-foreground ml-2">(Alias de /api/todos)</span>
|
||||
</div>
|
||||
<p className="text-sm text-muted-foreground mb-2">
|
||||
Créer une tâche générale avec priorité, progression et date d'échéance
|
||||
</p>
|
||||
<div className="bg-muted p-2 rounded text-xs font-mono overflow-x-auto">
|
||||
<pre>{`{
|
||||
"text": "Ma tâche importante",
|
||||
"priority": true,
|
||||
"in_progress": false,
|
||||
"due_date": "2024-12-31T23:59:59Z"
|
||||
}`}</pre>
|
||||
</div>
|
||||
</CardContent>
|
||||
@@ -2820,19 +2841,24 @@ const Index = () => {
|
||||
<div className="space-y-2">
|
||||
<h3 className="font-semibold text-lg">3. Exemple complet (curl)</h3>
|
||||
<div className="bg-muted p-3 rounded-lg font-mono text-sm overflow-x-auto">
|
||||
<pre>{`# Créer une note avec tâches (clé API permanente)
|
||||
curl -X POST ${window.location.origin}/api/notes/full \\
|
||||
<p className="text-xs text-muted-foreground mb-2 font-sans"># Créer une note avec tâches :</p>
|
||||
<pre className="mb-4">{`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"]
|
||||
}'`}</pre>
|
||||
<p className="text-xs text-muted-foreground mb-2 font-sans"># Créer une tâche générale prioritaire :</p>
|
||||
<pre>{`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"
|
||||
}'`}</pre>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
+10
@@ -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);
|
||||
}
|
||||
});
|
||||
Reference in new issue
Block a user