diff --git a/DEBUG-TIMEZONE-LOGS.md b/DEBUG-TIMEZONE-LOGS.md new file mode 100644 index 0000000..402307e --- /dev/null +++ b/DEBUG-TIMEZONE-LOGS.md @@ -0,0 +1,211 @@ +# 🔍 SYSTÈME DE LOGGING POUR DÉBUGUER LE TIMEZONE + +Ce document explique comment utiliser le systĂšme de logging complet pour identifier le problĂšme de dĂ©calage horaire. + +## 📋 Logs ajoutĂ©s + +### 1. **Parser PostgreSQL** (`config/database.js`) +Logs chaque conversion TIMESTAMPTZ → ISO string + +### 2. **Synchronisation Google Calendar** (`routes/calendar.routes.js`) +Logs ce que Google renvoie et ce qui est stockĂ© + +### 3. **RĂ©cupĂ©ration des Ă©vĂ©nements** (`routes/calendar.routes.js`) +Logs ce qui est lu de la DB et envoyĂ© au frontend + +### 4. **Endpoint de diagnostic** (`GET /api/calendar/debug`) +Affiche un rapport complet du traitement + +--- + +## 🚀 PROCÉDURE COMPLÈTE + +### **ÉTAPE 1 : Rebuild avec les logs** + +```bash +docker-compose down +docker-compose build --no-cache notes-app +docker-compose up -d +``` + +### **ÉTAPE 2 : Supprimer les anciens Ă©vĂ©nements** + +```bash +docker exec noteflow-postgres psql -U noteflow -d noteflow -p 5499 \ + -c "DELETE FROM calendar_events" +``` + +### **ÉTAPE 3 : Activer les logs debug** + +Suivre les logs en temps rĂ©el : + +```bash +docker-compose logs -f notes-app +``` + +### **ÉTAPE 4 : Synchroniser avec Google Calendar** + +1. Ouvrez http://localhost:2222 +2. Allez dans Admin → Google Calendar +3. Cliquez sur "🔄 Synchroniser" + +**Observez les logs** dans votre terminal : + +``` +[SYNC CALENDAR] ÉvĂ©nement: "Dr Julian Wlodarczak" +[SYNC CALENDAR] - Google startTime brut: {"dateTime":"2024-11-17T10:20:00+01:00"} +[SYNC CALENDAR] - startTime extrait: "2024-11-17T10:20:00+01:00" +[SYNC CALENDAR] - Type: string +[SYNC CALENDAR] - new Date(startTime): 2024-11-17T09:20:00.000Z +[SYNC CALENDAR] - Affichage Paris: 17/11/2024 10:20:00 +``` + +**Puis le parser PostgreSQL** : + +``` +[PARSER TIMESTAMPTZ] Input avec TZ: "2024-11-17 09:20:00+00" → Output: "2024-11-17T09:20:00.000Z" +``` + +**OU** + +``` +[PARSER TIMESTAMPTZ] Input sans TZ: "2024-11-17 09:20:00" → ISO+Z: "2024-11-17T09:20:00Z" → Output: "2024-11-17T09:20:00.000Z" +``` + +### **ÉTAPE 5 : RĂ©cupĂ©rer les Ă©vĂ©nements** + +Rechargez la page NoteFlow et observez les logs : + +``` +[GET /events] "Dr Julian Wlodarczak" +[GET /events] - start_time de la DB: "2024-11-17T09:20:00.000Z" (type: string) +[GET /events] - new Date(): 2024-11-17T09:20:00.000Z +[GET /events] - Affichage Paris: 10:20 +``` + +### **ÉTAPE 6 : Utiliser l'endpoint de diagnostic** + +Appelez l'endpoint de diagnostic pour voir un rapport complet : + +```bash +curl -H "Authorization: Bearer VOTRE_TOKEN" \ + http://localhost:2222/api/calendar/debug | jq +``` + +**OU** avec le navigateur : + +1. Ouvrez http://localhost:2222 +2. Ouvrez la console dĂ©veloppeur (F12) +3. ExĂ©cutez : +```javascript +fetch('/api/calendar/debug', { + headers: { 'Authorization': 'Bearer ' + localStorage.getItem('token') } +}) +.then(r => r.json()) +.then(console.log) +``` + +**RĂ©sultat attendu** : + +```json +{ + "timestamp": "2024-11-17T...", + "serverTimezone": "UTC", + "postgresTimezone": "UTC", + "sampleEvent": { + "title": "Dr Julian Wlodarczak", + "type_col": "timestamp with time zone", + "start_time_raw": "2024-11-17T09:20:00.000Z", + "start_time_type": "string", + "start_text": "2024-11-17 09:20:00+00", + "start_utc_formatted": "2024-11-17T09:20:00Z", + "start_paris_formatted": "2024-11-17 10:20:00" + }, + "parsing": { + "input": "2024-11-17T09:20:00.000Z", + "inputType": "string", + "dateObject": "2024-11-17T09:20:00.000Z", + "displayUTC": "Sun, 17 Nov 2024 09:20:00 GMT", + "displayParis": "17/11/2024 10:20:00", + "displayParisTime": "10:20" + } +} +``` + +--- + +## 🔍 ANALYSE DES LOGS + +### **Si tout est correct** : + +1. Google renvoie : `"2024-11-17T10:20:00+01:00"` (10:20 Paris) +2. Conversion en UTC : `2024-11-17T09:20:00.000Z` (09:20 UTC) +3. PostgreSQL stocke : `2024-11-17 09:20:00+00` (09:20 UTC) +4. Parser renvoie : `"2024-11-17T09:20:00.000Z"` +5. Frontend affiche : `10:20` ✅ + +### **Si le dĂ©calage persiste** : + +Cherchez dans les logs oĂč la conversion Ă©choue : + +- ❌ **Google renvoie une heure incorrecte** → ProblĂšme API Google +- ❌ **Conversion UTC incorrecte** → ProblĂšme dans `new Date()` +- ❌ **PostgreSQL stocke mal** → ProblĂšme de timezone PostgreSQL +- ❌ **Parser renvoie mal** → ProblĂšme dans le parser +- ❌ **Frontend affiche mal** → ProblĂšme dans `toLocaleTimeString` + +--- + +## 📊 COMMANDES UTILES + +### Voir les logs en temps rĂ©el : +```bash +docker-compose logs -f notes-app +``` + +### Voir seulement les logs de timezone : +```bash +docker-compose logs -f notes-app | grep -E "\[SYNC CALENDAR\]|\[PARSER TIMESTAMPTZ\]|\[GET /events\]" +``` + +### VĂ©rifier le timezone PostgreSQL : +```bash +docker exec noteflow-postgres psql -U noteflow -d noteflow -p 5499 -c "SHOW timezone" +``` + +### Voir les Ă©vĂ©nements bruts dans PostgreSQL : +```bash +docker exec noteflow-postgres psql -U noteflow -d noteflow -p 5499 -c " + SELECT + title, + start_time, + start_time::text, + start_time AT TIME ZONE 'Europe/Paris' as paris_time + FROM calendar_events + ORDER BY start_time DESC + LIMIT 5 +" +``` + +--- + +## 📝 RAPPORT À ENVOYER + +Si le problĂšme persiste, envoyez : + +1. **Les logs de synchronisation** (ÉTAPE 4) +2. **Les logs de rĂ©cupĂ©ration** (ÉTAPE 5) +3. **Le rĂ©sultat de l'endpoint debug** (ÉTAPE 6) +4. **Capture d'Ă©cran de NoteFlow** montrant le dĂ©calage +5. **Capture d'Ă©cran de Google Calendar** montrant l'heure correcte + +Avec ces informations, je pourrai identifier EXACTEMENT oĂč se produit le dĂ©calage. + +--- + +## ⚠ IMPORTANT + +Les logs sont en mode `debug` et peuvent ĂȘtre volumineux. Pour production, pensez Ă  : + +1. RĂ©duire le niveau de log +2. Ou dĂ©sactiver les logs une fois le problĂšme rĂ©solu diff --git a/config/database.js b/config/database.js index b23b4c5..b823e3a 100644 --- a/config/database.js +++ b/config/database.js @@ -11,19 +11,28 @@ types.setTypeParser(1184, function(stringValue) { // 1184 = TIMESTAMPTZ if (!stringValue) return null; + // LOG pour debug + const originalValue = stringValue; + // PostgreSQL avec timezone=UTC renvoie: "2024-11-17 09:20:00" ou "2024-11-17 09:20:00+00" // On doit toujours renvoyer une ISO string UTC propre avec 'Z' + let result; + // Si dĂ©jĂ  au format ISO avec Z ou timezone (+HH:MM ou +HH) if (stringValue.includes('Z') || stringValue.match(/[+-]\d{2}(:\d{2})?$/)) { - return new Date(stringValue).toISOString(); + result = new Date(stringValue).toISOString(); + logger.debug(`[PARSER TIMESTAMPTZ] Input avec TZ: "${originalValue}" → Output: "${result}"`); + } else { + // Si format "YYYY-MM-DD HH:MM:SS" sans timezone + // Comme timezone=UTC, on sait que c'est en UTC + // On ajoute 'Z' pour forcer JavaScript Ă  l'interprĂ©ter comme UTC + const isoString = stringValue.replace(' ', 'T') + 'Z'; + result = new Date(isoString).toISOString(); + logger.debug(`[PARSER TIMESTAMPTZ] Input sans TZ: "${originalValue}" → ISO+Z: "${isoString}" → Output: "${result}"`); } - // Si format "YYYY-MM-DD HH:MM:SS" sans timezone - // Comme timezone=UTC, on sait que c'est en UTC - // On ajoute 'Z' pour forcer JavaScript Ă  l'interprĂ©ter comme UTC - const isoString = stringValue.replace(' ', 'T') + 'Z'; - return new Date(isoString).toISOString(); + return result; }); // Configuration PostgreSQL depuis DATABASE_URL ou variables d'environnement diff --git a/routes/calendar.routes.js b/routes/calendar.routes.js index 7b51c67..fc45ac0 100644 --- a/routes/calendar.routes.js +++ b/routes/calendar.routes.js @@ -352,6 +352,17 @@ router.get('/events', authenticateToken, async (req, res) => { LIMIT $3 `, [req.user.id, now, limit]); + // LOG pour chaque Ă©vĂ©nement rĂ©cupĂ©rĂ© + events.forEach(event => { + logger.debug(`[GET /events] "${event.title}"`); + logger.debug(`[GET /events] - start_time de la DB: "${event.start_time}" (type: ${typeof event.start_time})`); + if (event.start_time) { + const date = new Date(event.start_time); + logger.debug(`[GET /events] - new Date(): ${date.toISOString()}`); + logger.debug(`[GET /events] - Affichage Paris: ${date.toLocaleString('fr-FR', { timeZone: 'Europe/Paris', hour: '2-digit', minute: '2-digit' })}`); + } + }); + res.json(events || []); } catch (error) { logger.error('Erreur lors de la rĂ©cupĂ©ration des Ă©vĂ©nements:', error); @@ -359,6 +370,87 @@ router.get('/events', authenticateToken, async (req, res) => { } }); +/** + * GET /api/calendar/debug + * Endpoint de diagnostic dĂ©taillĂ© pour le timezone + */ +router.get('/debug', authenticateToken, async (req, res) => { + try { + const diagnostics = { + timestamp: new Date().toISOString(), + serverTimezone: Intl.DateTimeFormat().resolvedOptions().timeZone, + postgresTimezone: null, + sampleEvent: null, + parsing: { + input: null, + output: null, + display: null + } + }; + + // RĂ©cupĂ©rer le timezone de PostgreSQL + const tzResult = await getOne('SHOW timezone'); + diagnostics.postgresTimezone = tzResult?.timezone || tzResult?.TimeZone; + + // RĂ©cupĂ©rer un Ă©vĂ©nement pour test + const event = await getOne(` + SELECT + title, + start_time, + pg_typeof(start_time) as type_col, + start_time::text as start_text, + to_char(start_time AT TIME ZONE 'UTC', 'YYYY-MM-DD"T"HH24:MI:SS"Z"') as start_utc_formatted, + to_char(start_time AT TIME ZONE 'Europe/Paris', 'YYYY-MM-DD HH24:MI:SS') as start_paris_formatted + FROM calendar_events + WHERE user_id = $1 + ORDER BY start_time DESC + LIMIT 1 + `, [req.user.id]); + + if (event) { + diagnostics.sampleEvent = { + title: event.title, + type_col: event.type_col, + start_time_raw: event.start_time, + start_time_type: typeof event.start_time, + start_text: event.start_text, + start_utc_formatted: event.start_utc_formatted, + start_paris_formatted: event.start_paris_formatted + }; + + // Test de parsing + if (event.start_time) { + const date = new Date(event.start_time); + diagnostics.parsing = { + input: event.start_time, + inputType: typeof event.start_time, + dateObject: date.toISOString(), + displayUTC: date.toUTCString(), + displayParis: date.toLocaleString('fr-FR', { + timeZone: 'Europe/Paris', + year: 'numeric', + month: '2-digit', + day: '2-digit', + hour: '2-digit', + minute: '2-digit', + second: '2-digit' + }), + displayParisTime: date.toLocaleTimeString('fr-FR', { + timeZone: 'Europe/Paris', + hour: '2-digit', + minute: '2-digit' + }) + }; + } + } + + res.json(diagnostics); + } catch (error) { + logger.error('Erreur lors du diagnostic:', error); + res.status(500).json({ error: error.message }); + } +}); + /** * POST /api/calendar/sync * Synchroniser avec Google Calendar (OAuth2, Service Account ou API Key) @@ -410,6 +502,17 @@ router.post('/sync', authenticateToken, async (req, res) => { const endTime = event.end.dateTime || event.end.date; const isAllDay = !event.start.dateTime; // Si pas de dateTime, c'est un Ă©vĂ©nement toute la journĂ©e + // LOG DÉTAILLÉ pour debug timezone + logger.info(`[SYNC CALENDAR] ÉvĂ©nement: "${event.summary}"`); + logger.info(`[SYNC CALENDAR] - Google startTime brut: ${JSON.stringify(event.start)}`); + logger.info(`[SYNC CALENDAR] - startTime extrait: "${startTime}"`); + logger.info(`[SYNC CALENDAR] - Type: ${typeof startTime}`); + if (startTime) { + const testDate = new Date(startTime); + logger.info(`[SYNC CALENDAR] - new Date(startTime): ${testDate.toISOString()}`); + logger.info(`[SYNC CALENDAR] - Affichage Paris: ${testDate.toLocaleString('fr-FR', { timeZone: 'Europe/Paris' })}`); + } + // VĂ©rifier si l'Ă©vĂ©nement existe dĂ©jĂ  const existing = await getOne( 'SELECT id FROM calendar_events WHERE google_event_id = $1 AND user_id = $2',