feat: OG png + sentry + dept api + drom notice + registre canicule
Some checks are pending
Deploy info-canicule / deploy (push) Waiting to run

Quick wins :
- public/og-image.png (1200x630, via sharp depuis le SVG, build via pnpm build)
  SVG ne fonctionne pas pour Open Graph (Slack/Discord/X/FB).
- @sentry/astro intégré conditionnellement (skip si SENTRY_DSN absent → no-op).
  GIT_COMMIT_SHA en var pour le release tag dans GlitchTip si voulu.
- /api/vigilance/dept/[code] : JSON enrichi (phenomenon label + color name)
  pour J et J1, CORS *, Cache-Control 5min. 404 si code unknown.
- JSON-LD enrichi : @graph WebSite + Service avec isBasedOn Dataset + license LOv2.
- Lien retour vigilance.meteofrance.fr visible sous la carte.

DROM (97x / 976) :
- 5 entrées ajoutées dans departements.ts (région "DROM").
- /departement/[code] DROM : bannière "Vigilance Outre-mer non couverte par
  cette source open data" + bouton vers vigilance.meteofrance.fr.
- Home : ligne sous la carte listant les 5 DROM + lien retour.
- L'API /api/vigilance/dept/<DROM> retourne quand même un JSON 200 (arrays vides).

Registre canicule :
- Page /conseils/registre-canicule : qui, quoi, comment s'inscrire au CCAS.
- Numéro vert 0 800 06 66 66.
- Bannière mise en avant en haut de /conseils.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Florian 2026-05-25 20:59:11 +02:00
parent 89e48c18e4
commit 87d173684c
13 changed files with 1595 additions and 35 deletions

View file

@ -0,0 +1,62 @@
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' } },
);
}
};