feat: retire API publique + dedupe load-balancing MF + maj pages
Some checks are pending
Deploy info-canicule / deploy (push) Waiting to run

API publique retirée :
- /api/vigilance et /api/vigilance/dept/[code] supprimés
- Mentions retirées dans footer, /mentions-legales, /embed
- /api/health garde, sans CORS (usage interne UptimeRobot + cron HC.io)
- Tests E2E mis à jour (vérifient 404 sur les endpoints retirés)

Pages :
- /dependances : entièrement mise à jour (Sentry, sharp, Playwright,
  sitemap, typography ajoutés ; API MF officielle DPObs+DPVigilance,
  normales 1991-2020 listées ; section Services tiers ajoutée pour
  Opendatasoft en fallback ; section Infrastructure complétée avec
  GlitchTip + CrowdSec).
- /soutenir : "~7€/mois mutualisés" → "~30€/mois", suppression du
  détail VPS OVH (juste "infra"), suppression du bloc "Réutiliser
  les données via API".

Vigilance Météo France load-balancing :
- lib/vigilance.ts : fetch parallèle x3 et garde la réponse au
  update_time le plus récent. Constat 2026-05-26 : le gateway MF
  load-balance entre instances désynchronisées (~60% renvoyaient
  bulletin J-1, ~40% bulletin J). Cette mitigation atteint >95%
  de probabilité d'avoir le bulletin frais.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Florian 2026-05-26 02:16:04 +02:00
parent 0a1f11aa00
commit 27441cdbb8
10 changed files with 139 additions and 148 deletions

View file

@ -12,6 +12,11 @@ export const GET: APIRoute = async () => {
};
return new Response(JSON.stringify(body), {
status: cacheOk ? 200 : 503,
headers: { 'Content-Type': 'application/json', 'Cache-Control': 'no-store' },
headers: {
'Content-Type': 'application/json',
'Cache-Control': 'no-store',
// Pas de CORS : endpoint d'usage interne (UptimeRobot + cron HC.io freshness).
// Pas destiné aux clients tiers.
},
});
};

View file

@ -1,24 +0,0 @@
import type { APIRoute } from 'astro';
import { getVigilanceSnapshot } from '../../lib/vigilance';
export const prerender = false;
// JSON public du snapshot Vigilance actuel — réutilisable sous Licence Ouverte 2.0.
export const GET: APIRoute = async () => {
try {
const snap = await getVigilanceSnapshot();
return new Response(JSON.stringify(snap), {
status: 200,
headers: {
'Content-Type': 'application/json; charset=utf-8',
'Cache-Control': 'public, max-age=300',
'Access-Control-Allow-Origin': '*',
},
});
} catch (e) {
return new Response(
JSON.stringify({ error: 'fetch_failed', detail: (e as Error).message }),
{ status: 502, headers: { 'Content-Type': 'application/json' } },
);
}
};

View file

@ -1,62 +0,0 @@
import type { APIRoute } from 'astro';
import { getVigilanceSnapshot, alertsForDepartement } from '../../../../lib/vigilance';
import { getDepartement } from '../../../../lib/departements';
import { PHENOMENA, COLORS } from '../../../../lib/phenomena';
export const prerender = false;
// JSON par département (J + J1) — CORS *, réutilisable sous Licence Ouverte 2.0.
// Ex : GET /api/vigilance/dept/75 → alertes Paris
export const GET: APIRoute = async ({ params }) => {
const codeRaw = params.code;
if (!codeRaw) {
return new Response(JSON.stringify({ error: 'missing_code' }), {
status: 400,
headers: { 'Content-Type': 'application/json' },
});
}
const code = codeRaw.toUpperCase();
const dept = getDepartement(code);
if (!dept) {
return new Response(JSON.stringify({ error: 'unknown_departement', code }), {
status: 404,
headers: { 'Content-Type': 'application/json' },
});
}
try {
const snap = await getVigilanceSnapshot();
const today = alertsForDepartement(snap, code, 'J');
const tomorrow = alertsForDepartement(snap, code, 'J1');
const enrich = (a: ReturnType<typeof alertsForDepartement>[0]) => ({
phenomenonId: a.phenomenonId,
phenomenon: PHENOMENA[a.phenomenonId].label,
colorId: a.colorId,
color: COLORS[a.colorId].name,
beginTime: a.beginTime,
endTime: a.endTime,
});
const body = {
departement: { code, name: dept.name, region: dept.region },
productDatetime: snap.productDatetime,
fetchedAt: snap.fetchedAt,
today: today.map(enrich),
tomorrow: tomorrow.map(enrich),
};
return new Response(JSON.stringify(body), {
status: 200,
headers: {
'Content-Type': 'application/json; charset=utf-8',
'Cache-Control': 'public, max-age=300',
'Access-Control-Allow-Origin': '*',
},
});
} catch (e) {
return new Response(
JSON.stringify({ error: 'fetch_failed', detail: (e as Error).message }),
{ status: 502, headers: { 'Content-Type': 'application/json' } },
);
}
};