Fix 10 bugs found in general code review
Backend (app.py / database.py / ai_agent.py): - [Critique] Autoclose loop: add pg_try_advisory_lock so only one Gunicorn worker runs the check per 60s cycle; add random startup jitter - [Critique] admin_delete_idea: pass consultation_id to _update_synthesis_background so the right synthesis is regenerated - [Majeur] admin_login: return HMAC-signed session token instead of raw ADMIN_SECRET; require_admin verifies the signature (TTL 8h) - [Majeur] bulk_delete: replace str.isdigit() (Unicode-unsafe) with try/except int() to prevent crash on Unicode digit characters - [Majeur] create_consultation: force UTC timezone on naive datetime from fromisoformat() to prevent TypeError when comparing with UTC-aware now() - [Majeur] ai_agent.py: fix 'raw' in dir() -> 'raw' in locals() so the JSON parse error log actually shows the raw response - [Mineur] export print: use datetime.now(UTC) instead of datetime.now() Frontend (React): - [Majeur] consultation.tsx: show startsAt (not endsAt) for upcoming consultations; add startsAt variable - [Majeur] consultations-list.tsx: same fix for the list view - [Mineur] home.tsx: guard new Date(idea.createdAt) against null - [Mineur] admin.tsx: check HTTP status in exportCsv XHR before creating download link; show error toast on non-200 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -658,6 +658,10 @@ export default function Admin() {
|
||||
req.setRequestHeader("Authorization", `Bearer ${token}`);
|
||||
req.responseType = "blob";
|
||||
req.onload = () => {
|
||||
if (req.status !== 200) {
|
||||
toast({ title: "Erreur export", description: `Erreur ${req.status} lors de l'export CSV.`, variant: "destructive" });
|
||||
return;
|
||||
}
|
||||
const a = document.createElement("a");
|
||||
a.href = URL.createObjectURL(req.response);
|
||||
a.setAttribute("download", "contributions.csv");
|
||||
|
||||
@@ -235,6 +235,7 @@ export default function ConsultationPage() {
|
||||
const isOpen = consultation.isOpen;
|
||||
const isClosed = !!consultation.closedAt;
|
||||
const endsAt = consultation.endsAt ? new Date(consultation.endsAt) : null;
|
||||
const startsAt = consultation.startsAt ? new Date(consultation.startsAt) : null;
|
||||
const closedAt = consultation.closedAt ? new Date(consultation.closedAt) : null;
|
||||
|
||||
return (
|
||||
@@ -299,8 +300,8 @@ export default function ConsultationPage() {
|
||||
<span className="flex items-center gap-1.5">
|
||||
<Clock className="h-3 w-3" />
|
||||
{isOpen
|
||||
? `Fermeture ${formatDistanceToNow(endsAt, { locale: fr, addSuffix: true })}`
|
||||
: `Ouverture ${formatDistanceToNow(endsAt, { locale: fr, addSuffix: true })}`}
|
||||
? `Fermeture ${formatDistanceToNow(endsAt!, { locale: fr, addSuffix: true })}`
|
||||
: `Ouverture ${formatDistanceToNow(startsAt ?? endsAt!, { locale: fr, addSuffix: true })}`}
|
||||
{" · "}
|
||||
{format(endsAt, "d MMMM yyyy à HH:mm", { locale: fr })} UTC
|
||||
</span>
|
||||
|
||||
@@ -115,7 +115,7 @@ export default function ConsultationsList() {
|
||||
<Clock className="h-3 w-3" />
|
||||
{c.isOpen
|
||||
? `Ferme ${formatDistanceToNow(new Date(c.endsAt), { locale: fr, addSuffix: true })}`
|
||||
: `Ouvre ${formatDistanceToNow(new Date(c.endsAt), { locale: fr, addSuffix: true })}`}
|
||||
: `Ouvre ${formatDistanceToNow(new Date(c.startsAt ?? c.endsAt), { locale: fr, addSuffix: true })}`}
|
||||
</span>
|
||||
)}
|
||||
{c.closedAt && (
|
||||
|
||||
@@ -484,7 +484,7 @@ export default function Home() {
|
||||
</span>
|
||||
<span>•</span>
|
||||
<span>
|
||||
{format(new Date(idea.createdAt), "d MMM, HH:mm", { locale: fr })}
|
||||
{idea.createdAt ? format(new Date(idea.createdAt), "d MMM, HH:mm", { locale: fr }) : "—"}
|
||||
</span>
|
||||
<button
|
||||
onClick={() => handleFlag(idea.id)}
|
||||
|
||||
Reference in New Issue
Block a user