mirror of
https://github.com/R0m1k3/noteflow.git
synced 2026-10-22 06:38:17 +02:00
feat: add backend support for hierarchical subtasks
- Create migration 005 to add parent_id and level columns to todos tables - Fix migration 003 to use correct table name (note_todos instead of todos) - Update POST /api/notes/:id/todos to accept parent_id parameter - Update POST /api/todos to accept parent_id for global todos - Add automatic level calculation based on parent depth - Update GET endpoints to return parent_id and level fields - Add validation to ensure parent todo exists before creating subtask - Support CASCADE DELETE for subtasks when parent is deleted
This commit is contained in:
4 files changed
+71
-25
No files matched your search
+25
-10
@@ -61,7 +61,7 @@ router.get('/', async (req, res) => {
|
||||
// Charger les todos, images, fichiers et tags pour chaque note
|
||||
for (const note of notes) {
|
||||
const todos = await getAll(`
|
||||
SELECT id, text, completed, position
|
||||
SELECT id, text, completed, position, parent_id, level
|
||||
FROM note_todos
|
||||
WHERE note_id = $1
|
||||
ORDER BY position ASC, id ASC
|
||||
@@ -391,10 +391,13 @@ router.delete('/:id/image', async (req, res) => {
|
||||
|
||||
/**
|
||||
* POST /api/notes/:id/todos
|
||||
* Ajouter un todo à une note
|
||||
* Ajouter un todo à une note (supports subtasks via parent_id)
|
||||
*/
|
||||
router.post('/:id/todos',
|
||||
[body('text').trim().notEmpty().withMessage('Le texte est requis')],
|
||||
[
|
||||
body('text').trim().notEmpty().withMessage('Le texte est requis'),
|
||||
body('parent_id').optional().isInt()
|
||||
],
|
||||
async (req, res) => {
|
||||
try {
|
||||
const errors = validationResult(req);
|
||||
@@ -408,17 +411,27 @@ router.post('/:id/todos',
|
||||
return res.status(404).json({ error: 'Note non trouvée' });
|
||||
}
|
||||
|
||||
const { text } = req.body;
|
||||
const { text, parent_id } = req.body;
|
||||
let level = 0;
|
||||
|
||||
logger.info(`[CREATE TODO] Ajout todo à note ${req.params.id}: "${text}"`);
|
||||
// Si c'est un subtask, vérifier que le parent existe et calculer le niveau
|
||||
if (parent_id) {
|
||||
const parent = await getOne('SELECT id, level FROM note_todos WHERE id = $1 AND note_id = $2', [parent_id, req.params.id]);
|
||||
if (!parent) {
|
||||
return res.status(404).json({ error: 'Todo parent non trouvé' });
|
||||
}
|
||||
level = (parent.level || 0) + 1;
|
||||
}
|
||||
|
||||
logger.info(`[CREATE TODO] Ajout todo à note ${req.params.id}: "${text}"${parent_id ? ` (subtask of ${parent_id})` : ''}`);
|
||||
|
||||
const result = await runQuery(`
|
||||
INSERT INTO note_todos (note_id, text, position)
|
||||
VALUES ($1, $2, (SELECT COALESCE(MAX(position), 0) + 1 FROM note_todos WHERE note_id = $3))
|
||||
INSERT INTO note_todos (note_id, text, parent_id, level, position)
|
||||
VALUES ($1, $2, $3, $4, (SELECT COALESCE(MAX(position), 0) + 1 FROM note_todos WHERE note_id = $5 AND COALESCE(parent_id, 0) = COALESCE($6, 0)))
|
||||
RETURNING *
|
||||
`, [req.params.id, text, req.params.id]);
|
||||
`, [req.params.id, text, parent_id || null, level, req.params.id, parent_id || null]);
|
||||
|
||||
logger.info(`[CREATE TODO] Todo créé - ID: ${result.id}, note_id: ${req.params.id}`);
|
||||
logger.info(`[CREATE TODO] Todo créé - ID: ${result.id}, note_id: ${req.params.id}, level: ${level}`);
|
||||
|
||||
await runQuery('UPDATE notes SET updated_at = CURRENT_TIMESTAMP WHERE id = $1', [req.params.id]);
|
||||
|
||||
@@ -426,7 +439,9 @@ router.post('/:id/todos',
|
||||
id: result.id,
|
||||
text,
|
||||
completed: false,
|
||||
position: 0
|
||||
position: result.position || 0,
|
||||
parent_id: parent_id || null,
|
||||
level: level
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error('Erreur lors de l\'ajout du todo:', error);
|
||||
|
||||
+25
-10
@@ -19,7 +19,7 @@ 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, created_at
|
||||
SELECT id, text, completed, priority, in_progress, parent_id, level, created_at
|
||||
FROM global_todos
|
||||
WHERE user_id = $1
|
||||
ORDER BY priority DESC, created_at DESC
|
||||
@@ -38,7 +38,7 @@ router.get('/', async (req, res) => {
|
||||
`, [req.user.id]);
|
||||
|
||||
// Ajouter priority=false et in_progress=false par défaut pour compatibilité frontend
|
||||
const todosWithPriority = todos.map(t => ({ ...t, priority: false, in_progress: false }));
|
||||
const todosWithPriority = todos.map(t => ({ ...t, priority: false, in_progress: false, parent_id: null, level: 0 }));
|
||||
res.json(todosWithPriority);
|
||||
}
|
||||
} catch (error) {
|
||||
@@ -49,10 +49,13 @@ router.get('/', async (req, res) => {
|
||||
|
||||
/**
|
||||
* POST /api/todos
|
||||
* Créer un nouveau todo global
|
||||
* Créer un nouveau todo global (supports subtasks via parent_id)
|
||||
*/
|
||||
router.post('/',
|
||||
[body('text').trim().notEmpty().withMessage('Le texte est requis')],
|
||||
[
|
||||
body('text').trim().notEmpty().withMessage('Le texte est requis'),
|
||||
body('parent_id').optional().isInt()
|
||||
],
|
||||
async (req, res) => {
|
||||
try {
|
||||
const errors = validationResult(req);
|
||||
@@ -60,17 +63,27 @@ router.post('/',
|
||||
return res.status(400).json({ error: 'Données invalides', details: errors.array() });
|
||||
}
|
||||
|
||||
const { text } = req.body;
|
||||
const { text, parent_id } = req.body;
|
||||
let level = 0;
|
||||
|
||||
logger.info(`[CREATE GLOBAL TODO] Début création - user_id: ${req.user.id}, text: "${text}"`);
|
||||
// Si c'est un subtask, vérifier que le parent existe et calculer le niveau
|
||||
if (parent_id) {
|
||||
const parent = await getOne('SELECT id, level FROM global_todos WHERE id = $1 AND user_id = $2', [parent_id, req.user.id]);
|
||||
if (!parent) {
|
||||
return res.status(404).json({ error: 'Todo parent non trouvé' });
|
||||
}
|
||||
level = (parent.level || 0) + 1;
|
||||
}
|
||||
|
||||
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)
|
||||
VALUES ($1, $2)
|
||||
INSERT INTO global_todos (user_id, text, parent_id, level)
|
||||
VALUES ($1, $2, $3, $4)
|
||||
RETURNING *
|
||||
`, [req.user.id, text]);
|
||||
`, [req.user.id, text, parent_id || null, level]);
|
||||
|
||||
logger.info(`[CREATE GLOBAL TODO] Todo global créé avec succès - ID: ${result.id}, user: ${req.user.username}`);
|
||||
logger.info(`[CREATE GLOBAL TODO] Todo global créé avec succès - ID: ${result.id}, user: ${req.user.username}, level: ${level}`);
|
||||
|
||||
res.status(201).json({
|
||||
id: result.id,
|
||||
@@ -78,6 +91,8 @@ router.post('/',
|
||||
completed: false,
|
||||
priority: false,
|
||||
in_progress: false,
|
||||
parent_id: parent_id || null,
|
||||
level: level,
|
||||
created_at: new Date().toISOString()
|
||||
});
|
||||
} catch (error) {
|
||||
|
||||
Reference in new issue
Block a user