Fix: Correction du bug d'écrasement de notes lors de la création

Problème identifié :
- Quand une nouvelle note était créée, elle écrasait la première note existante
- La fonction runQuery() dans database-postgres.js retournait rowCount (1) au lieu de l'ID réel
- Les requêtes INSERT n'utilisaient pas RETURNING pour récupérer l'ID généré

Solution implémentée :
1. Ajout de RETURNING * à toutes les requêtes INSERT dans :
   - routes/notes.routes.js (notes, todos, images, fichiers, tags)
   - routes/todos.routes.js (todos globaux)
   - routes/users.routes.js (utilisateurs)
   - routes/rss.routes.js (flux RSS, résumés)
   - routes/settings.routes.js (paramètres)
   - routes/calendar.routes.js (événements, tokens OAuth)

2. Modification de runQuery() pour :
   - Retourner la ligne complète si RETURNING est présent
   - Inclure tous les champs de la ligne insérée (spread operator)
   - Ajouter des logs détaillés pour le debugging

3. Ajout de logs détaillés pour tracer :
   - Chaque création d'entité avec ses paramètres
   - L'ID retourné après insertion
   - Les erreurs potentielles

Impact :
- Les nouvelles notes ont maintenant leur vrai ID auto-incrémenté
- Plus d'écrasement des notes existantes
- Meilleure traçabilité avec les logs détaillés
This commit is contained in:
Claude committed 2025-11-17 07:34:12 +00:00
1 parent 81cf8d3100
commit 76b91788b7
7 files changed
+68 -15

No files matched your search

+13 -1
View File
@@ -256,8 +256,20 @@ async function runQuery(sql, params = []) {
const client = await pool.connect();
try {
const result = await client.query(sql, params);
// Si c'est un INSERT/UPDATE avec RETURNING, retourner la ligne complète
if (result.rows && result.rows.length > 0) {
logger.info(`[runQuery] Requête avec RETURNING - ID: ${result.rows[0].id}, rowCount: ${result.rowCount}`);
return {
...result.rows[0], // Retourne toutes les colonnes de la première ligne
changes: result.rowCount
};
}
// Sinon, retourner juste le nombre de lignes affectées
logger.info(`[runQuery] Requête sans RETURNING - rowCount: ${result.rowCount}`);
return {
id: result.rows[0]?.id || result.rowCount,
id: result.rowCount,
changes: result.rowCount
};
} catch (error) {
+8
View File
@@ -190,10 +190,12 @@ router.get('/oauth-callback', async (req, res) => {
userId
]);
} else {
logger.info(`[CREATE OAUTH TOKEN] Ajout nouveau token OAuth - user_id: ${userId}`);
await runQuery(`
INSERT INTO google_oauth_tokens
(user_id, access_token, refresh_token, token_type, expiry_date, scope)
VALUES ($1, $2, $3, $4, $5, $6)
RETURNING *
`, [
userId,
tokens.access_token,
@@ -202,6 +204,7 @@ router.get('/oauth-callback', async (req, res) => {
tokens.expiry_date || null,
tokens.scope || SCOPES.join(' ')
]);
logger.info(`[CREATE OAUTH TOKEN] Token OAuth créé avec succès pour user_id: ${userId}`);
}
logger.info(`Tokens OAuth sauvegardés pour l'utilisateur ${userId}`);
@@ -562,10 +565,12 @@ router.post('/sync', authenticateToken, async (req, res) => {
]);
} else {
// Créer un nouvel événement
logger.info(`[CREATE CALENDAR EVENT] Ajout événement - event_id: "${event.id}", title: "${event.summary || 'Sans titre'}"`);
await runQuery(`
INSERT INTO calendar_events
(google_event_id, user_id, title, description, start_time, end_time, location, html_link, all_day)
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)
RETURNING *
`, [
event.id,
req.user.id,
@@ -577,6 +582,7 @@ router.post('/sync', authenticateToken, async (req, res) => {
event.htmlLink || '',
isAllDay
]);
logger.info(`[CREATE CALENDAR EVENT] Événement créé avec succès - event_id: "${event.id}"`);
}
syncedCount++;
}
@@ -707,6 +713,7 @@ router.post('/events', authenticateToken, async (req, res) => {
const itemEndUTC = itemIsAllDay ? itemEndRaw : new Date(itemEndRaw).toISOString();
// PostgreSQL : INSERT ... ON CONFLICT DO UPDATE (pas INSERT OR REPLACE)
logger.info(`[SYNC CALENDAR EVENT] Sync événement - event_id: "${item.id}"`);
await runQuery(`
INSERT INTO calendar_events
(google_event_id, user_id, title, description, start_time, end_time, location, html_link, all_day, synced_at)
@@ -720,6 +727,7 @@ router.post('/events', authenticateToken, async (req, res) => {
html_link = EXCLUDED.html_link,
all_day = EXCLUDED.all_day,
synced_at = CURRENT_TIMESTAMP
RETURNING *
`, [
item.id,
req.user.id,
+23 -4
View File
@@ -172,12 +172,15 @@ router.post('/',
const { title, content } = req.body;
logger.info(`[CREATE NOTE] Début création - user_id: ${req.user.id}, title: "${title}"`);
const result = await runQuery(`
INSERT INTO notes (user_id, title, content)
VALUES ($1, $2, $3)
RETURNING *
`, [req.user.id, title, content || '']);
logger.info(`Note créée: ${title} (ID: ${result.id}) par ${req.user.username}`);
logger.info(`[CREATE NOTE] Note créée avec succès - ID: ${result.id}, title: "${title}", user: ${req.user.username}`);
res.status(201).json({
id: result.id,
@@ -395,11 +398,16 @@ router.post('/:id/todos',
const { text } = req.body;
logger.info(`[CREATE TODO] Ajout todo à note ${req.params.id}: "${text}"`);
const result = await runQuery(`
INSERT INTO note_todos (note_id, text, position)
VALUES ($1, $2, (SELECT COALESCE(MAX(position), FALSE) + 1 FROM note_todos WHERE note_id = $3))
RETURNING *
`, [req.params.id, text, req.params.id]);
logger.info(`[CREATE TODO] Todo créé - ID: ${result.id}, note_id: ${req.params.id}`);
await runQuery('UPDATE notes SET updated_at = CURRENT_TIMESTAMP WHERE id = $1', [req.params.id]);
res.status(201).json({
@@ -582,15 +590,18 @@ router.post('/:id/images', imageUpload.single('image'), async (req, res) => {
}
// Enregistrer l'image dans la base de données
logger.info(`[CREATE IMAGE] Ajout image à note ${req.params.id}: ${req.file.originalname}`);
const result = await runQuery(`
INSERT INTO note_images (note_id, filename, original_name)
VALUES ($1, $2, $3)
RETURNING *
`, [req.params.id, req.file.filename, req.file.originalname]);
// Mettre à jour la date de modification de la note
await runQuery('UPDATE notes SET updated_at = CURRENT_TIMESTAMP WHERE id = $1', [req.params.id]);
logger.info(`Image ajoutée à la note ${req.params.id}: ${req.file.filename}`);
logger.info(`[CREATE IMAGE] Image créée - ID: ${result.id}, filename: ${req.file.filename}, note_id: ${req.params.id}`);
res.json({
message: 'Image ajoutée avec succès',
@@ -716,15 +727,18 @@ router.post('/:id/files', fileUpload.single('file'), async (req, res) => {
}
// Enregistrer le fichier dans la base de données
logger.info(`[CREATE FILE] Ajout fichier à note ${req.params.id}: ${req.file.originalname}`);
const result = await runQuery(`
INSERT INTO note_files (note_id, filename, original_name, file_size, mime_type)
VALUES ($1, $2, $3, $4, $5)
RETURNING *
`, [req.params.id, req.file.filename, req.file.originalname, req.file.size, req.file.mimetype]);
// Mettre à jour la date de modification de la note
await runQuery('UPDATE notes SET updated_at = CURRENT_TIMESTAMP WHERE id = $1', [req.params.id]);
logger.info(`Fichier ajouté à la note ${req.params.id}: ${req.file.originalname}`);
logger.info(`[CREATE FILE] Fichier créé - ID: ${result.id}, filename: ${req.file.filename}, note_id: ${req.params.id}`);
res.json({
message: 'Fichier ajouté avec succès',
@@ -877,7 +891,12 @@ router.post('/:id/tags', async (req, res) => {
return res.status(404).json({ error: 'Note non trouvée' });
}
const result = await runQuery('INSERT INTO note_tags (note_id, tag) VALUES ($1, $2)', [req.params.id, tag.trim().toLowerCase()]);
logger.info(`[CREATE TAG] Ajout tag à note ${req.params.id}: "${tag.trim().toLowerCase()}"`);
const result = await runQuery('INSERT INTO note_tags (note_id, tag) VALUES ($1, $2) RETURNING *', [req.params.id, tag.trim().toLowerCase()]);
logger.info(`[CREATE TAG] Tag créé - ID: ${result.id}, tag: "${result.tag}", note_id: ${req.params.id}`);
res.json({ id: result.id, tag: tag.trim().toLowerCase() });
} catch (error) {
if (error.message && error.message.includes('UNIQUE constraint')) {
+9 -3
View File
@@ -47,13 +47,15 @@ router.post('/feeds', requireAdmin, async (req, res) => {
try {
const feed = await parser.parseURL(url);
logger.info(`[CREATE RSS FEED] Ajout flux - url: "${url}", title: "${feed.title || url}"`);
// Ajouter le flux
const result = await runQuery(
'INSERT INTO rss_feeds (url, title, description, enabled) VALUES ($1, $2, $3, TRUE)',
'INSERT INTO rss_feeds (url, title, description, enabled) VALUES ($1, $2, $3, TRUE) RETURNING *',
[url, feed.title || url, feed.description || '']
);
logger.info(`✅ Flux ajouté: ${feed.title}`);
logger.info(`[CREATE RSS FEED] Flux créé avec succès - ID: ${result.id}, title: "${feed.title}"`);
// Déclencher un fetch immédiat
setTimeout(() => {
@@ -250,11 +252,15 @@ Résumé (100 mots max):`;
const summary = response.data.choices[0].message.content;
logger.info(`[CREATE RSS SUMMARY] Ajout résumé - article: "${article.title}", model: "${selectedModel}"`);
await runQuery(
'INSERT INTO rss_summaries (summary, model, articles_count, feed_title, created_at) VALUES (?, ?, ?, ?, datetime("now"))',
'INSERT INTO rss_summaries (summary, model, articles_count, feed_title, created_at) VALUES ($1, $2, $3, $4, CURRENT_TIMESTAMP) RETURNING *',
[`**${article.title}**\n\n${summary}\n\n🔗 [Lire l'article](${article.link})`, selectedModel, 1, article.feed_title]
);
logger.info(`[CREATE RSS SUMMARY] Résumé créé avec succès`);
return {
article_id: article.id,
title: article.title,
+7 -4
View File
@@ -52,10 +52,12 @@ router.put('/', async (req, res) => {
[value, key]
);
} else {
logger.info(`[CREATE SETTING] Ajout nouveau paramètre - key: "${key}"`);
await runQuery(
'INSERT INTO settings (key, value) VALUES ($1, $2)',
'INSERT INTO settings (key, value) VALUES ($1, $2) RETURNING *',
[key, value]
);
logger.info(`[CREATE SETTING] Paramètre créé avec succès - key: "${key}"`);
}
}
@@ -102,14 +104,15 @@ router.put('/:key', async (req, res) => {
'UPDATE settings SET value = $1, updated_at = CURRENT_TIMESTAMP WHERE key = $2',
[value, req.params.key]
);
logger.info(`Paramètre mis à jour: ${req.params.key}`);
} else {
logger.info(`[CREATE SETTING] Ajout nouveau paramètre - key: "${req.params.key}"`);
await runQuery(
'INSERT INTO settings (key, value) VALUES ($1, $2)',
'INSERT INTO settings (key, value) VALUES ($1, $2) RETURNING *',
[req.params.key, value]
);
logger.info(`[CREATE SETTING] Paramètre créé avec succès - key: "${req.params.key}"`);
}
logger.info(`Paramètre mis à jour: ${req.params.key}`);
res.json({ key: req.params.key, value });
} catch (error) {
logger.error('Erreur lors de la mise à jour du paramètre:', error);
+4 -1
View File
@@ -45,12 +45,15 @@ router.post('/',
const { text } = req.body;
logger.info(`[CREATE GLOBAL TODO] Début création - user_id: ${req.user.id}, text: "${text}"`);
const result = await runQuery(`
INSERT INTO global_todos (user_id, text)
VALUES ($1, $2)
RETURNING *
`, [req.user.id, text]);
logger.info(`Todo global créé (ID: ${result.id}) par ${req.user.username}`);
logger.info(`[CREATE GLOBAL TODO] Todo global créé avec succès - ID: ${result.id}, user: ${req.user.username}`);
res.status(201).json({
id: result.id,
+4 -2
View File
@@ -56,13 +56,15 @@ router.post('/',
// Hasher le mot de passe
const passwordHash = await bcrypt.hash(password, 12);
logger.info(`[CREATE USER] Début création - username: "${username}", is_admin: ${is_admin ? 1 : 0}`);
// Créer l'utilisateur
const result = await runQuery(
'INSERT INTO users (username, password_hash, is_admin) VALUES ($1, $2, $3)',
'INSERT INTO users (username, password_hash, is_admin) VALUES ($1, $2, $3) RETURNING *',
[username, passwordHash, is_admin ? 1 : 0]
);
logger.info(`Utilisateur créé: ${username} (ID: ${result.id})`);
logger.info(`[CREATE USER] Utilisateur créé avec succès - ID: ${result.id}, username: "${username}"`);
res.status(201).json({
id: result.id,