mirror of
https://github.com/R0m1k3/noteflow.git
synced 2026-10-11 17:29:37 +02:00
Merge pull request #105 from R0m1k3/claude/fix-time-offset-bug-01FzrcjKh9o5emsU6hRMMLRL
Add: Système de logging complet pour débuguer le décalage horaire
This commit is contained in:
3 files changed
+329
-6
No files matched your search
@@ -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
|
||||
+15
-6
@@ -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
|
||||
|
||||
@@ -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',
|
||||
|
||||
Reference in new issue
Block a user