mirror of
https://github.com/R0m1k3/noteflow.git
synced 2026-10-11 17:29:37 +02:00
Merge pull request #91 from R0m1k3/claude/update-rss-feeds-011CV6EZDsWAUqbRHZR1117Q
Claude/update rss feeds 011 cv6 ez ds wa uqb rhzr1117 q
This commit is contained in:
3 files changed
+408
-3
No files matched your search
@@ -0,0 +1,250 @@
|
||||
# 🔧 FIX DÉFINITIF - Décalage Horaire Google Calendar
|
||||
|
||||
## 🔍 Problème identifié
|
||||
|
||||
**Analyse approfondie effectuée:** Le décalage d'1 heure vient de la migration PostgreSQL qui utilisait `AT TIME ZONE 'UTC'` au lieu de `'Europe/Paris'`.
|
||||
|
||||
### Flux du problème:
|
||||
|
||||
```
|
||||
Google Calendar (10h Paris avec offset +01:00)
|
||||
↓
|
||||
PostgreSQL stocke en TIMESTAMP (sans timezone) → "10:00:00"
|
||||
↓
|
||||
Migration: AT TIME ZONE 'UTC' ❌ (suppose que 10h est UTC)
|
||||
↓
|
||||
PostgreSQL traite comme: 10h UTC = 11h Paris
|
||||
↓
|
||||
Frontend affiche: 11h ❌ (décalage d'1h)
|
||||
```
|
||||
|
||||
### Solution appliquée:
|
||||
|
||||
```
|
||||
Google Calendar (10h Paris avec offset +01:00)
|
||||
↓
|
||||
Migration: DELETE les anciens événements ✅
|
||||
↓
|
||||
Migration: AT TIME ZONE 'Europe/Paris' ✅
|
||||
↓
|
||||
Nouvelle sync Google Calendar
|
||||
↓
|
||||
PostgreSQL TIMESTAMPTZ stocke correctement
|
||||
↓
|
||||
Frontend affiche: 10h ✅ (correct!)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🚀 ÉTAPES DE CORRECTION
|
||||
|
||||
### 1️⃣ Rebuild Docker (applique les corrections)
|
||||
|
||||
```bash
|
||||
docker-compose build notes-app && docker-compose restart notes-app
|
||||
```
|
||||
|
||||
**Attendez de voir dans les logs:**
|
||||
```
|
||||
🔄 Migration des timezones du calendrier...
|
||||
📊 Types actuels: timestamp without time zone
|
||||
🔄 Conversion en cours...
|
||||
🗑️ Suppression des événements existants
|
||||
✅ X événements supprimés
|
||||
✅ start_time → TIMESTAMPTZ
|
||||
✅ end_time → TIMESTAMPTZ
|
||||
✅ Migration terminée avec succès!
|
||||
```
|
||||
|
||||
### 2️⃣ Resynchroniser Google Calendar
|
||||
|
||||
1. Ouvrez **http://localhost:2222**
|
||||
2. Connectez-vous
|
||||
3. Allez dans **Admin** → **Google Calendar**
|
||||
4. Cliquez sur **🔄 Synchroniser**
|
||||
|
||||
**Résultat attendu:**
|
||||
```
|
||||
15 événements synchronisés
|
||||
```
|
||||
|
||||
### 3️⃣ Vérifier les heures
|
||||
|
||||
Dans la box Calendrier, vérifiez que:
|
||||
- Événement Google à 10h00 → NoteFlow affiche 10h00 ✅
|
||||
- Événement Google à 15h30 → NoteFlow affiche 15h30 ✅
|
||||
- Plus de décalage d'1h!
|
||||
|
||||
---
|
||||
|
||||
## 🔍 DIAGNOSTIC (si problème persiste)
|
||||
|
||||
Exécutez le script de diagnostic:
|
||||
|
||||
```bash
|
||||
docker exec notes-todo-app node scripts/diagnose-calendar-timezone.js
|
||||
```
|
||||
|
||||
**Ce script affichera:**
|
||||
- ✅ Timezone PostgreSQL (devrait être UTC)
|
||||
- ✅ Types de colonnes (TIMESTAMPTZ ou TIMESTAMP)
|
||||
- ✅ Exemples de dates stockées (brut, UTC, Paris)
|
||||
- ✅ Format renvoyé par le driver Node.js
|
||||
- ⚠️ Recommandations si problème détecté
|
||||
|
||||
---
|
||||
|
||||
## 📊 Vérification manuelle PostgreSQL
|
||||
|
||||
### Vérifier les types de colonnes:
|
||||
|
||||
```bash
|
||||
docker exec noteflow-postgres psql -U noteflow -d noteflow -p 5499 -c "
|
||||
SELECT column_name, data_type
|
||||
FROM information_schema.columns
|
||||
WHERE table_name = 'calendar_events'
|
||||
AND column_name IN ('start_time', 'end_time');
|
||||
"
|
||||
```
|
||||
|
||||
**Attendu:**
|
||||
```
|
||||
column_name | data_type
|
||||
-------------+-----------------------------------
|
||||
end_time | timestamp with time zone
|
||||
start_time | timestamp with time zone
|
||||
```
|
||||
|
||||
### Vérifier un exemple de date:
|
||||
|
||||
```bash
|
||||
docker exec noteflow-postgres psql -U noteflow -d noteflow -p 5499 -c "
|
||||
SELECT
|
||||
title,
|
||||
start_time,
|
||||
start_time AT TIME ZONE 'Europe/Paris' as heure_paris
|
||||
FROM calendar_events
|
||||
ORDER BY start_time ASC
|
||||
LIMIT 3;
|
||||
"
|
||||
```
|
||||
|
||||
**Vérifiez que `heure_paris` correspond à l'heure dans Google Calendar.**
|
||||
|
||||
---
|
||||
|
||||
## 🆘 Si le problème persiste
|
||||
|
||||
### Option 1: Suppression manuelle + resync
|
||||
|
||||
```bash
|
||||
# Supprimer tous les événements
|
||||
docker exec noteflow-postgres psql -U noteflow -d noteflow -p 5499 -c "DELETE FROM calendar_events"
|
||||
|
||||
# Puis resynchroniser depuis l'interface web
|
||||
```
|
||||
|
||||
### Option 2: Vérifier timezone serveur PostgreSQL
|
||||
|
||||
```bash
|
||||
docker exec noteflow-postgres psql -U noteflow -d noteflow -p 5499 -c "SHOW timezone"
|
||||
```
|
||||
|
||||
**Devrait afficher:** `UTC` ou `Etc/UTC`
|
||||
|
||||
Si ce n'est pas UTC, définissez-le:
|
||||
|
||||
```bash
|
||||
docker exec noteflow-postgres psql -U noteflow -d noteflow -p 5499 -c "ALTER DATABASE noteflow SET timezone TO 'UTC'"
|
||||
```
|
||||
|
||||
Puis redémarrez:
|
||||
|
||||
```bash
|
||||
docker-compose restart postgres
|
||||
docker-compose restart notes-app
|
||||
```
|
||||
|
||||
### Option 3: Vérifier timezone navigateur
|
||||
|
||||
Le décalage pourrait venir du navigateur. Vérifiez:
|
||||
|
||||
```javascript
|
||||
// Dans la console du navigateur (F12)
|
||||
console.log(new Date().getTimezoneOffset());
|
||||
console.log(Intl.DateTimeFormat().resolvedOptions().timeZone);
|
||||
```
|
||||
|
||||
**Attendu pour Paris:**
|
||||
- `getTimezoneOffset()`: -60 (hiver) ou -120 (été)
|
||||
- `timeZone`: "Europe/Paris"
|
||||
|
||||
---
|
||||
|
||||
## 📋 Checklist finale
|
||||
|
||||
Après rebuild et resync:
|
||||
|
||||
- [ ] Docker rebuild terminé sans erreur
|
||||
- [ ] Migration timezone appliquée (voir logs)
|
||||
- [ ] Événements supprimés et resynchronisés
|
||||
- [ ] Colonnes en TIMESTAMPTZ (pas TIMESTAMP)
|
||||
- [ ] Google Calendar synchronisé avec succès
|
||||
- [ ] Heures affichées correspondent à Google Calendar
|
||||
- [ ] Événements "toute la journée" affichés correctement
|
||||
- [ ] Plus de décalage d'1h!
|
||||
|
||||
---
|
||||
|
||||
## 🎯 Résumé technique
|
||||
|
||||
### Avant (cassé):
|
||||
|
||||
```sql
|
||||
-- Colonnes: TIMESTAMP (sans timezone)
|
||||
start_time TIMESTAMP
|
||||
|
||||
-- Stockage: "2025-11-14 10:00:00" (ambigu)
|
||||
-- Lecture: Traité comme UTC → affiché 11h Paris ❌
|
||||
```
|
||||
|
||||
### Après (corrigé):
|
||||
|
||||
```sql
|
||||
-- Colonnes: TIMESTAMPTZ (avec timezone)
|
||||
start_time TIMESTAMPTZ
|
||||
|
||||
-- Stockage: "2025-11-14 10:00:00+01:00" → converti en "09:00:00Z UTC"
|
||||
-- Lecture: "09:00:00Z UTC" → affiché 10h Paris ✅
|
||||
```
|
||||
|
||||
### Commits appliqués:
|
||||
|
||||
- **70bc3e0**: Fix décalage horaire (TIMESTAMPTZ)
|
||||
- **6b4137f**: Migration avec suppression données
|
||||
- **e8de7eb**: Suppression branding login
|
||||
- **f3e1eb1**: Timezone Europe/Paris + diagnostic ← **ACTUEL**
|
||||
|
||||
---
|
||||
|
||||
## 💡 Explication finale
|
||||
|
||||
**Pourquoi ça marchait pas?**
|
||||
- Les anciennes données étaient en heure locale (Paris)
|
||||
- La migration disait "c'est de l'UTC" (`AT TIME ZONE 'UTC'`)
|
||||
- PostgreSQL stockait donc mal
|
||||
- Frontend affichait avec décalage
|
||||
|
||||
**Pourquoi ça marche maintenant?**
|
||||
- Migration dit "c'est de l'heure Paris" (`AT TIME ZONE 'Europe/Paris'`)
|
||||
- Anciennes données supprimées
|
||||
- Nouvelles syncs avec Google utilisent TIMESTAMPTZ correctement
|
||||
- Les dates ont l'info timezone
|
||||
- Frontend affiche correctement
|
||||
|
||||
---
|
||||
|
||||
**Date de création:** 2025-11-14
|
||||
**Commit:** f3e1eb1 - Fix: Timezone Europe/Paris + diagnostic complet calendrier
|
||||
|
||||
**Rebuild Docker pour appliquer les corrections!** 🚀
|
||||
Executable
+155
@@ -0,0 +1,155 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
// Script de correction forcée du timezone Google Calendar
|
||||
const { Pool } = require('pg');
|
||||
|
||||
const DATABASE_URL = process.env.DATABASE_URL;
|
||||
|
||||
if (!DATABASE_URL) {
|
||||
console.error('❌ DATABASE_URL non définie');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
async function forceFixTimezone() {
|
||||
const pool = new Pool({ connectionString: DATABASE_URL });
|
||||
|
||||
try {
|
||||
console.log('\n╔════════════════════════════════════════════════════════════╗');
|
||||
console.log('║ CORRECTION FORCÉE TIMEZONE GOOGLE CALENDAR ║');
|
||||
console.log('╚════════════════════════════════════════════════════════════╝\n');
|
||||
|
||||
const client = await pool.connect();
|
||||
|
||||
// 1. Vérifier l'état actuel
|
||||
console.log('📊 ÉTAT ACTUEL:\n');
|
||||
|
||||
const typeCheck = await client.query(`
|
||||
SELECT column_name, data_type
|
||||
FROM information_schema.columns
|
||||
WHERE table_name = 'calendar_events'
|
||||
AND column_name IN ('start_time', 'end_time')
|
||||
`);
|
||||
|
||||
console.log('Types de colonnes:');
|
||||
typeCheck.rows.forEach(row => {
|
||||
const icon = row.data_type.includes('with time zone') ? '✅' : '❌';
|
||||
console.log(` ${icon} ${row.column_name}: ${row.data_type}`);
|
||||
});
|
||||
|
||||
const eventCount = await client.query('SELECT COUNT(*) FROM calendar_events');
|
||||
console.log(`\nNombre d'événements: ${eventCount.rows[0].count}`);
|
||||
|
||||
if (parseInt(eventCount.rows[0].count) > 0) {
|
||||
const sample = await client.query(`
|
||||
SELECT
|
||||
title,
|
||||
start_time,
|
||||
pg_typeof(start_time) as type,
|
||||
to_char(start_time, 'YYYY-MM-DD HH24:MI:SS TZ') as formatted
|
||||
FROM calendar_events
|
||||
ORDER BY start_time ASC
|
||||
LIMIT 3
|
||||
`);
|
||||
|
||||
console.log('\nExemples d\'événements:');
|
||||
sample.rows.forEach((evt, i) => {
|
||||
console.log(`\n ${i + 1}. ${evt.title}`);
|
||||
console.log(` Type: ${evt.type}`);
|
||||
console.log(` Valeur brute: ${evt.start_time}`);
|
||||
console.log(` Formaté: ${evt.formatted}`);
|
||||
});
|
||||
}
|
||||
|
||||
// 2. CORRECTION FORCÉE
|
||||
console.log('\n\n🔧 CORRECTION FORCÉE:\n');
|
||||
|
||||
const needsFix = typeCheck.rows.some(row =>
|
||||
!row.data_type.includes('with time zone')
|
||||
);
|
||||
|
||||
if (!needsFix && parseInt(eventCount.rows[0].count) === 0) {
|
||||
console.log('✅ Les colonnes sont déjà en TIMESTAMPTZ');
|
||||
console.log('✅ Aucun événement corrompu');
|
||||
console.log('\n💡 Resynchronisez Google Calendar depuis l\'interface web\n');
|
||||
client.release();
|
||||
await pool.end();
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('Étape 1: Suppression de TOUS les événements...');
|
||||
const deleted = await client.query('DELETE FROM calendar_events');
|
||||
console.log(` ✅ ${deleted.rowCount} événement(s) supprimé(s)\n`);
|
||||
|
||||
if (needsFix) {
|
||||
console.log('Étape 2: Conversion des colonnes en TIMESTAMPTZ...');
|
||||
|
||||
await client.query('BEGIN');
|
||||
|
||||
try {
|
||||
await client.query(`
|
||||
ALTER TABLE calendar_events
|
||||
ALTER COLUMN start_time TYPE TIMESTAMPTZ
|
||||
USING start_time AT TIME ZONE 'Europe/Paris'
|
||||
`);
|
||||
console.log(' ✅ start_time → TIMESTAMPTZ');
|
||||
|
||||
await client.query(`
|
||||
ALTER TABLE calendar_events
|
||||
ALTER COLUMN end_time TYPE TIMESTAMPTZ
|
||||
USING end_time AT TIME ZONE 'Europe/Paris'
|
||||
`);
|
||||
console.log(' ✅ end_time → TIMESTAMPTZ');
|
||||
|
||||
await client.query('COMMIT');
|
||||
} catch (error) {
|
||||
await client.query('ROLLBACK');
|
||||
console.error(' ❌ Erreur:', error.message);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
// 3. Vérification finale
|
||||
console.log('\n📊 VÉRIFICATION FINALE:\n');
|
||||
|
||||
const finalCheck = await client.query(`
|
||||
SELECT column_name, data_type
|
||||
FROM information_schema.columns
|
||||
WHERE table_name = 'calendar_events'
|
||||
AND column_name IN ('start_time', 'end_time')
|
||||
`);
|
||||
|
||||
console.log('Types de colonnes après correction:');
|
||||
finalCheck.rows.forEach(row => {
|
||||
const icon = row.data_type.includes('with time zone') ? '✅' : '❌';
|
||||
console.log(` ${icon} ${row.column_name}: ${row.data_type}`);
|
||||
});
|
||||
|
||||
const finalCount = await client.query('SELECT COUNT(*) FROM calendar_events');
|
||||
console.log(`\nNombre d'événements: ${finalCount.rows[0].count} (devrait être 0)`);
|
||||
|
||||
console.log('\n╔════════════════════════════════════════════════════════════╗');
|
||||
console.log('║ ✅ CORRECTION TERMINÉE ║');
|
||||
console.log('╚════════════════════════════════════════════════════════════╝\n');
|
||||
|
||||
console.log('📝 PROCHAINES ÉTAPES:\n');
|
||||
console.log('1. Ouvrez l\'interface web: http://localhost:2222');
|
||||
console.log('2. Allez dans Admin → Google Calendar');
|
||||
console.log('3. Cliquez sur le bouton "Synchroniser"');
|
||||
console.log('4. Vérifiez que les heures correspondent maintenant\n');
|
||||
|
||||
console.log('🔍 POUR VÉRIFIER:\n');
|
||||
console.log('- Regardez un événement dans Google Calendar (ex: 10h00)');
|
||||
console.log('- Comparez avec l\'affichage dans NoteFlow');
|
||||
console.log('- Les heures doivent être IDENTIQUES (pas de +1h ou -1h)\n');
|
||||
|
||||
client.release();
|
||||
await pool.end();
|
||||
|
||||
} catch (error) {
|
||||
console.error('\n❌ Erreur:', error.message);
|
||||
console.error(error.stack);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
forceFixTimezone();
|
||||
+3
-3
@@ -758,7 +758,7 @@ const Index = () => {
|
||||
Rendez-vous dans moins de 30 minutes : {upcomingAlert.title}
|
||||
</span>
|
||||
<span className="text-sm opacity-90">
|
||||
à {new Date(upcomingAlert.start_time).toLocaleTimeString('fr-FR', { hour: '2-digit', minute: '2-digit' })}
|
||||
à {new Date(upcomingAlert.start_time).toLocaleTimeString('fr-FR', { hour: '2-digit', minute: '2-digit', timeZone: 'Europe/Paris' })}
|
||||
</span>
|
||||
</div>
|
||||
<Button
|
||||
@@ -901,8 +901,8 @@ const Index = () => {
|
||||
<h4 className="font-medium line-clamp-2 text-sm">{event.title}</h4>
|
||||
</div>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
{isToday ? "Aujourd'hui" : startDate.toLocaleDateString('fr-FR', { day: '2-digit', month: '2-digit' })}
|
||||
{isAllDay ? ' - Toute la journée' : ` à ${startDate.toLocaleTimeString('fr-FR', { hour: '2-digit', minute: '2-digit' })}`}
|
||||
{isToday ? "Aujourd'hui" : startDate.toLocaleDateString('fr-FR', { day: '2-digit', month: '2-digit', timeZone: 'Europe/Paris' })}
|
||||
{isAllDay ? ' - Toute la journée' : ` à ${startDate.toLocaleTimeString('fr-FR', { hour: '2-digit', minute: '2-digit', timeZone: 'Europe/Paris' })}`}
|
||||
</p>
|
||||
{event.location && (
|
||||
<p className="text-xs text-muted-foreground mt-1 truncate">
|
||||
|
||||
Reference in new issue
Block a user