From cf81ffa35e85160641de0973aeb3363f4af385bd Mon Sep 17 00:00:00 2001 From: billisdead Date: Sun, 24 May 2026 10:02:00 +0200 Subject: [PATCH] Suggestions : .env.example complet, page /consultations, fix set-domain.sh MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - .env.example : variables à jour (Mistral, SECRET_KEY, ADMIN_SECRET, Redis, hCaptcha, anti-abus) — l'ancienne version référençait encore OpenAI uniquement - Nouveau set-domain.sh : supprime la référence à vite.config.selfhost.ts supprimé - Nouvelle page /consultations : index public des consultations actives/clôturées, toggle "afficher les clôturées", lien dans le footer - App.tsx : route /consultations + lien footer Consultations Co-Authored-By: Claude Sonnet 4.6 --- .env.example | 48 ++++-- artifacts/voix-du-peuple/src/App.tsx | 5 + .../src/pages/consultations-list.tsx | 147 ++++++++++++++++++ scripts/set-domain.sh | 3 +- 4 files changed, 190 insertions(+), 13 deletions(-) create mode 100644 artifacts/voix-du-peuple/src/pages/consultations-list.tsx diff --git a/.env.example b/.env.example index b99c222..d2be06e 100644 --- a/.env.example +++ b/.env.example @@ -1,22 +1,46 @@ +# La Voix du Peuple — Variables d'environnement +# Copiez ce fichier en .env et remplissez les valeurs. +# Sécurisez le fichier : chmod 600 .env + # ─── Base de données PostgreSQL ────────────────────────────────────────────── # Format : postgresql://utilisateur:motdepasse@hote:port/nomdb DATABASE_URL=postgresql://voixdupeuple:CHANGEME@localhost:5432/voixdupeuple -# ─── Clé API OpenAI ────────────────────────────────────────────────────────── -# Obtenez votre clé sur https://platform.openai.com/api-keys -OPENAI_API_KEY=sk-... +# ─── IA — Mistral (recommandé — souveraineté européenne) ───────────────────── +# Obtenez votre clé sur https://console.mistral.ai +MISTRAL_API_KEY=sk-... -# ─── (Optionnel) Proxy OpenAI compatible ───────────────────────────────────── -# Décommentez si vous utilisez un proxy (Ollama avec OpenAI compat, Azure, etc.) -# OPENAI_BASE_URL=https://votre-proxy.example.com/v1 +# ─── IA — Alternative OpenAI-compatible (si Mistral non disponible) ────────── +# OPENAI_API_KEY=sk-... +# OPENAI_BASE_URL=https://votre-proxy.example.com/v1 # Optionnel : proxy/Ollama -# ─── Modèles IA (optionnel — valeurs par défaut recommandées) ──────────────── -# OPENAI_FILTER_MODEL=gpt-4o-mini # Modèle de filtrage (rapide, économique) -# OPENAI_SYNTHESIS_MODEL=gpt-4o # Modèle de synthèse (haute qualité) +# ─── Modèles IA (optionnel — valeurs par défaut ci-dessous) ────────────────── +# FILTER_MODEL=mistral-small-latest # Modèle de filtrage (rapide, économique) +# SYNTHESIS_MODEL=mistral-large-latest # Modèle de synthèse (haute qualité) + +# ─── Sécurité Flask ─────────────────────────────────────────────────────────── +# Générez avec : python3 -c "import secrets; print(secrets.token_hex(32))" +SECRET_KEY=CHANGEZ_CE_SECRET_AVEC_UNE_VALEUR_ALEATOIRE_LONGUE + +# ─── Panel d'administration ─────────────────────────────────────────────────── +# Mot de passe pour accéder à /admin +ADMIN_SECRET=CHANGEZ_CE_MOT_DE_PASSE_ADMIN # ─── Flask ──────────────────────────────────────────────────────────────────── FLASK_ENV=production -PORT=8000 +PORT=8080 -# ─── Session (générez avec: python3 -c "import secrets; print(secrets.token_hex(32))") ─── -SESSION_SECRET=CHANGEZ_CE_SECRET_AVEC_UNE_VALEUR_ALEATOIRE_LONGUE +# ─── Anti-abus (optionnel — valeurs par défaut raisonnables) ───────────────── +# REDIS_URL=redis://localhost:6379/0 # Rate limiting persistant (recommandé en prod) +# RATE_LIMIT_CONTRIBUTIONS=5 per minute;3 per hour # Limite de soumissions +# CONTRIBUTION_COOLDOWN_SECONDS=3600 # Délai entre deux soumissions (même session) +# FLOOD_THRESHOLD=10 # Alerte flood : nb soumissions / 5 min / IP + +# ─── hCaptcha (optionnel — recommandé en production) ───────────────────────── +# Créez un compte sur https://www.hcaptcha.com (RGPD-friendly) +# HCAPTCHA_SECRET_KEY=votre-cle-secrete-hcaptcha +# VITE_HCAPTCHA_SITE_KEY=votre-cle-de-site-hcaptcha # Nécessite rebuild frontend + +# ─── Frontend ───────────────────────────────────────────────────────────────── +# URL publique du site (utilisée par le QR code et les exports) +VITE_APP_URL=https://votredomaine.fr diff --git a/artifacts/voix-du-peuple/src/App.tsx b/artifacts/voix-du-peuple/src/App.tsx index d39afdb..cdb0452 100644 --- a/artifacts/voix-du-peuple/src/App.tsx +++ b/artifacts/voix-du-peuple/src/App.tsx @@ -14,6 +14,7 @@ import LegalNotice from "@/pages/legal-notice"; import PrivacyPolicy from "@/pages/privacy-policy"; import ContributionsBrutes from "@/pages/contributions-brutes"; import ConsultationPage from "@/pages/consultation"; +import ConsultationsList from "@/pages/consultations-list"; import { AccessibilityProvider } from "@/hooks/use-accessibility"; import { AccessibilityPanel } from "@/components/accessibility-panel"; import { setVisitorId } from "@workspace/api-client-react"; @@ -75,6 +76,9 @@ function Footer() { Contributions brutes + + Consultations + EUPL-1.2 · billisdead @@ -105,6 +109,7 @@ function Router() { + diff --git a/artifacts/voix-du-peuple/src/pages/consultations-list.tsx b/artifacts/voix-du-peuple/src/pages/consultations-list.tsx new file mode 100644 index 0000000..b05ac78 --- /dev/null +++ b/artifacts/voix-du-peuple/src/pages/consultations-list.tsx @@ -0,0 +1,147 @@ +// Copyright (C) 2026 billisdead — Licence EUPL-1.2 +// Page d'index des consultations publiques actives +import { useState, useEffect } from "react"; +import { Link } from "wouter"; +import { Badge } from "@/components/ui/badge"; +import { Button } from "@/components/ui/button"; +import { + Clock, Users, Building2, ArrowRight, Loader2, Lock, CalendarDays, +} from "lucide-react"; +import { format, formatDistanceToNow } from "date-fns"; +import { fr } from "date-fns/locale"; + +const API_BASE = import.meta.env.VITE_API_URL ?? ""; + +type Consultation = { + id: number; + slug: string; + title: string; + subject: string; + organizerName: string | null; + organizerLogoUrl: string | null; + startsAt: string | null; + endsAt: string | null; + closedAt: string | null; + isOpen: boolean; + stats: { total: number; accepted: number }; +}; + +export default function ConsultationsList() { + const [consultations, setConsultations] = useState([]); + const [loading, setLoading] = useState(true); + const [showClosed, setShowClosed] = useState(false); + + useEffect(() => { + setLoading(true); + fetch(`${API_BASE}/api/consultations?include_closed=${showClosed}`) + .then((r) => r.json()) + .then(setConsultations) + .catch(() => setConsultations([])) + .finally(() => setLoading(false)); + }, [showClosed]); + + return ( +
+
+

Consultations citoyennes

+

+ Chaque consultation est un espace thématique ciblé, ouvert par un organisateur + (collectivité, association, collectif). Contribuez aux consultations ouvertes, + consultez les synthèses des consultations clôturées. +

+
+ +
+ + {loading ? "Chargement…" : `${consultations.length} consultation${consultations.length !== 1 ? "s" : ""}`} + + +
+ + {loading ? ( +
+ Chargement… +
+ ) : consultations.length === 0 ? ( +
+ +

Aucune consultation {showClosed ? "" : "active "}pour le moment.

+
+ ) : ( +
+ {consultations.map((c) => ( + +
+
+
+
+ + {c.title} + + {c.closedAt ? ( + + Clôturée + + ) : c.isOpen ? ( + + + En cours + + ) : ( + + À venir + + )} +
+

{c.subject}

+
+ +
+ +
+ {c.organizerName && ( + + {c.organizerName} + + )} + {c.endsAt && !c.closedAt && ( + + + {c.isOpen + ? `Ferme ${formatDistanceToNow(new Date(c.endsAt), { locale: fr, addSuffix: true })}` + : `Ouvre ${formatDistanceToNow(new Date(c.endsAt), { locale: fr, addSuffix: true })}`} + + )} + {c.closedAt && ( + + + Clôturée le {format(new Date(c.closedAt), "d MMM yyyy", { locale: fr })} + + )} + + + {c.stats.accepted} contribution{c.stats.accepted !== 1 ? "s" : ""} + +
+
+ + ))} +
+ )} + +
+ + + +
+
+ ); +} diff --git a/scripts/set-domain.sh b/scripts/set-domain.sh index ca46938..c13c209 100644 --- a/scripts/set-domain.sh +++ b/scripts/set-domain.sh @@ -44,7 +44,8 @@ export VITE_APP_URL="${DOMAIN}" echo "" echo "Reconstruction du frontend..." -pnpm --filter @workspace/voix-du-peuple run build --config vite.config.selfhost.ts +cd artifacts/voix-du-peuple +pnpm run build echo "" echo "Terminé. Le QR code pointe maintenant vers : ${DOMAIN}"