Author SHA1 Message Date
Claude 944d99733c fix(meteo): afficher la réponse de l'API même si son enregistrement échoue
La température de l'an dernier disparaissait alors que Visual Crossing
répondait correctement (tableau de bord : requêtes réussies, 0 échec,
et l'URL d'historique testée dans le navigateur renvoie bien la journée).
Quand l'enregistrement de la réponse en cache échouait (par exemple une
contrainte d'unicité sur la date déjà occupée par la météo relevée ce
jour-là l'an dernier), la route relisait le cache, n'y trouvait rien et
jetait la donnée reçue : previousYear restait à null et l'API était
réinterrogée à chaque affichage.

La route garde désormais la réponse de l'API pour l'affichage quand
l'enregistrement échoue (année en cours et année précédente), et
journalise une réponse d'historique vide. Reproduit sur un Postgres local
avec une contrainte UNIQUE (date, location) et une API simulée renvoyant
la réponse réelle : N-1 vide avant, 14,1° après ; sans contrainte, le
comportement est inchangé.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MsDJjQrAggcJwbbBtKhgyb
2026-10-05 14:22:44 +00:00
Claude 8a27b310d2 fix(meteo): repli sur la météo relevée l'an dernier pour la comparaison N-1
La comparaison avec l'année dernière disparaît quand Visual Crossing
refuse durablement l'historique (plan, quota) : le repli précédent ne
cherchait que des lignes N-1 déjà en cache (is_current_year = false) à
7 jours près, qui n'existent plus si l'historique échoue depuis plus
d'une semaine.

La table weather_data garde pourtant la météo relevée chaque jour l'an
dernier, quand ces dates étaient « aujourd'hui » (is_current_year =
true). La route l'utilise désormais en second repli, au jour le plus
proche de la date cible (7 jours max), et journalise l'absence totale de
donnée N-1.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MsDJjQrAggcJwbbBtKhgyb
2026-10-05 14:10:06 +00:00
Claude 6bb61cdbcc fix(db): ne plus arrêter le serveur quand Postgres coupe une connexion
Quand la base redémarre (redéploiement, restart du conteneur) ou que le
réseau coupe, pg-pool émet 'error' pour chaque connexion inactive
interrompue. Aucun écouteur n'était branché sur le pool de server/db.ts :
Node levait l'événement non géré et arrêtait tout le serveur, avec un
dump géant du client pg dans les logs. Le pool écarte déjà la connexion
fautive et en ouvre une neuve à la requête suivante : on se contente de
journaliser le code et le message.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MsDJjQrAggcJwbbBtKhgyb
2026-10-05 14:07:23 +00:00
Claude d384271a30 fix(meteo): rétablir les appels séquentiels à l'API météo
La comparaison avec l'année dernière avait disparu du widget météo depuis
l'optimisation des chargements. La route /api/weather/current interrogeait
Visual Crossing pour aujourd'hui et pour l'année dernière en parallèle :
la seconde requête d'une même clé peut alors être refusée (limite de
requêtes simultanées), laissant previousYear à null. Les deux appels
redeviennent séquentiels comme avant ; seules les lectures du cache local
restent en parallèle.

Le widget retrouve son comportement d'origine (plus de cache de 30
minutes ni de retry désactivé), pour qu'un échec ponctuel ne masque plus
la comparaison pendant une demi-heure. Les erreurs de l'API journalisent
désormais la raison renvoyée par Visual Crossing (quota, plan, clé).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MsDJjQrAggcJwbbBtKhgyb
2026-10-04 18:13:03 +00:00
4 changed files with 70 additions and 50 deletions

No files matched your search

-5
View File
@@ -121,11 +121,6 @@ export default function WeatherWidget() {
const { data: weather, isLoading, error } = useQuery<WeatherResponse>({
queryKey: ['/api/weather/current'],
refetchInterval: 30 * 60 * 1000, // Refetch every 30 minutes
// Données valables jusqu'au prochain rafraîchissement : pas de rechargement au remontage
staleTime: 30 * 60 * 1000,
// Pas de nouvel essai : un 404 (météo non configurée) ne changera pas, et le
// serveur peut appeler l'API météo externe à chaque requête
retry: false,
});
+8
View File
@@ -30,6 +30,14 @@ if (isProduction && dbUrl && (dbUrl.includes('logiflow-db') || dbUrl.includes('l
db = drizzle({ client: pool, schema });
// Une connexion inactive coupée par Postgres (redémarrage ou redéploiement de
// la base, coupure réseau) fait émettre 'error' au pool. Sans écouteur, Node
// arrête tout le serveur ; le pool écarte déjà la connexion fautive et en
// ouvre une neuve à la requête suivante, il suffit donc de journaliser.
pool.on('error', (err: any) => {
console.error('❌ PostgreSQL idle connection error (connexion écartée):', err?.code || '', err?.message);
});
// Test connection
try {
await pool.connect().then((client: any) => {
+57 -43
View File
@@ -5421,58 +5421,59 @@ RÉSUMÉ DU SCAN
const today = new Date().toISOString().split('T')[0];
const previousYearDate = weatherService.getPreviousYearDate();
// Check if we already have today's data (deux lectures indépendantes)
// Lectures du cache indépendantes : en parallèle
let [currentYearData, previousYearData] = await Promise.all([
storage.getWeatherData(today, true),
storage.getWeatherData(previousYearDate, false),
]);
// Les appels à l'API pour l'année en cours et l'année précédente sont
// indépendants : ils sont lancés en parallèle
await Promise.all([
(async () => {
// Fetch current year data if not in cache
if (!currentYearData) {
console.log("🌤️ [FETCH] Fetching current weather data from API");
const apiData = await weatherService.fetchCurrentWeather(settings);
if (apiData) {
const weatherData = weatherService.convertApiDataToWeatherData(apiData, settings.location, true);
if (weatherData) {
try {
currentYearData = await storage.createWeatherData(weatherData);
console.log("✅ [CACHE] Current year data saved to cache");
} catch (error: any) {
console.warn("⚠️ [CACHE] Could not save current year data (may already exist):", error.message);
// Récupérer les données existantes au lieu de créer
currentYearData = await storage.getWeatherData(today, true);
}
}
// Les appels à l'API météo restent séquentiels : lancés en parallèle,
// Visual Crossing peut refuser la seconde requête d'une même clé (limite
// de requêtes simultanées) et la comparaison avec l'an dernier disparaît.
// Fetch current year data if not in cache
if (!currentYearData) {
console.log("🌤️ [FETCH] Fetching current weather data from API");
const apiData = await weatherService.fetchCurrentWeather(settings);
if (apiData) {
const weatherData = weatherService.convertApiDataToWeatherData(apiData, settings.location, true);
if (weatherData) {
try {
currentYearData = await storage.createWeatherData(weatherData);
console.log("✅ [CACHE] Current year data saved to cache");
} catch (error: any) {
console.warn("⚠️ [CACHE] Could not save current year data (may already exist):", error.message);
// Récupérer les données existantes au lieu de créer ; à défaut,
// afficher quand même la réponse de l'API
currentYearData = (await storage.getWeatherData(today, true)) ?? (weatherData as any);
}
}
})(),
(async () => {
// Fetch previous year data if not in cache
if (!previousYearData) {
console.log("🌤️ [FETCH] Fetching previous year weather data from API");
const apiData = await weatherService.fetchPreviousYearWeather(settings, previousYearDate);
if (apiData) {
const weatherData = weatherService.convertApiDataToWeatherData(apiData, settings.location, false);
if (weatherData) {
try {
previousYearData = await storage.createWeatherData(weatherData);
console.log("✅ [CACHE] Previous year data saved to cache");
} catch (error: any) {
console.warn("⚠️ [CACHE] Could not save previous year data (may already exist):", error.message);
// Récupérer les données existantes au lieu de créer
previousYearData = await storage.getWeatherData(previousYearDate, false);
}
}
} else {
console.warn("⚠️ [HISTORY] Could not fetch historical data - continuing with current year only");
}
}
// Fetch previous year data if not in cache
if (!previousYearData) {
console.log("🌤️ [FETCH] Fetching previous year weather data from API");
const apiData = await weatherService.fetchPreviousYearWeather(settings, previousYearDate);
if (apiData) {
const weatherData = weatherService.convertApiDataToWeatherData(apiData, settings.location, false);
if (weatherData) {
try {
previousYearData = await storage.createWeatherData(weatherData);
console.log("✅ [CACHE] Previous year data saved to cache");
} catch (error: any) {
console.warn("⚠️ [CACHE] Could not save previous year data (may already exist):", error.message);
// Récupérer les données existantes au lieu de créer ; à défaut,
// afficher quand même la réponse de l'API plutôt que de la perdre
previousYearData = (await storage.getWeatherData(previousYearDate, false)) ?? (weatherData as any);
}
} else {
console.warn(`⚠️ [HISTORY] Empty historical response for ${previousYearDate}`);
}
})(),
]);
} else {
console.warn("⚠️ [HISTORY] Could not fetch historical data - continuing with current year only");
}
}
// Repli : si l'API historique a échoué (quota épuisé, panne, plan sans
// accès à l'historique), reprendre la ligne en cache la plus proche de
@@ -5484,6 +5485,19 @@ RÉSUMÉ DU SCAN
}
}
// Second repli : la météo relevée l'an dernier, au jour le jour, quand ces
// dates étaient « aujourd'hui » (lignes is_current_year = true datées de
// l'année précédente). Elle reste disponible même si l'API refuse
// durablement l'historique (plan, quota).
if (!previousYearData) {
previousYearData = await storage.getNearestWeatherData(previousYearDate, true, 7);
if (previousYearData) {
console.log(`🌤️ [FALLBACK] Using weather recorded last year (${previousYearData.date}) for target ${previousYearDate}`);
} else {
console.warn(`⚠️ [FALLBACK] No previous-year weather available around ${previousYearDate}`);
}
}
// Toujours retourner une réponse même si seule l'année actuelle est disponible
const response = {
currentYear: currentYearData ? {
+5 -2
View File
@@ -27,7 +27,8 @@ export class WeatherService {
const response = await fetch(url);
if (!response.ok) {
console.error(`Weather API error: ${response.status} ${response.statusText}`);
const reason = (await response.text().catch(() => '')).slice(0, 300);
console.error(`Weather API error: ${response.status} ${response.statusText} ${reason}`);
return null;
}
@@ -51,7 +52,9 @@ export class WeatherService {
const response = await fetch(url);
if (!response.ok) {
console.error(`🌤️ [ERROR] Weather API error for previous year: ${response.status} ${response.statusText}`);
// Visual Crossing explique le refus dans le corps (quota, plan, clé...)
const reason = (await response.text().catch(() => '')).slice(0, 300);
console.error(`🌤️ [ERROR] Weather API error for previous year: ${response.status} ${response.statusText} ${reason}`);
// Pour les données historiques, certains comptes peuvent ne pas avoir accès
if (response.status === 401) {