Les jetons se posaient à la main dans docker-compose. Un écran Backend ›
Réseaux sociaux permet désormais de coller les identifiants de l'application,
cliquer Connecter, choisir la page, et ne plus y revenir.
- Table `social_accounts` : identifiants, jetons et cible par réseau. Tous les
secrets sont chiffrés en AES-256-GCM (`src/lib/crypto.ts`), clé
`SOCIAL_TOKEN_KEY` avec repli sur `AUTH_SECRET`.
- Routes `api/social/[network]/{connect,callback}` : `state` anti-CSRF en cookie
httpOnly, échange du code, récupération des pages administrées. Aucun jeton ne
transite par une URL — les cibles sont relistées côté serveur au moment de la
sélection.
- Facebook : jeton utilisateur longue durée puis jeton de PAGE, qui n'expire
pas. LinkedIn : jeton 60 jours, rafraîchi automatiquement si l'application a
obtenu les jetons de rafraîchissement programmatiques.
- Faute de quoi le backoffice affiche la date d'expiration, un bandeau sur le
tableau de bord à J-7 et un bouton Reconnecter.
- `social.ts` lit les identifiants via `social-accounts.ts` : base d'abord,
variables d'environnement ensuite. Les installations existantes continuent de
fonctionner sans modification.
- Le secret d'application n'est jamais renvoyé au navigateur : champ vide =
valeur conservée.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QXNXRC4j5VLfKvpyyisrnb
Le bouton libre « Publier sur X » permettait de diffuser n'importe quelle promo
en ligne, à tout moment. Le choix appartient désormais à l'adhérent, la
publication part à la validation, et le choix est figé ensuite.
- Colonnes `promotions.share_facebook` / `share_linkedin` : réseaux demandés à la
soumission, modifiables tant que la promo est `pending`.
- L'adhérent coche les réseaux dans le formulaire de soumission et peut encore
les changer depuis Mes promotions tant qu'elle est en attente ; une fois
validée, il ne voit plus qu'un état en lecture seule.
- Le modérateur retrouve ces cases pré-cochées dans le formulaire « Valider » et
peut les ajuster : c'est le dernier moment où la diffusion est modifiable.
Valider met en ligne puis publie, dans cet ordre, afin qu'un réseau en panne
ne bloque pas la mise en ligne.
- `publishPromoShares` devient le seul point de publication. Elle ne lève jamais
et ignore tout réseau ayant déjà une publication réussie : une promo n'est
jamais postée deux fois, y compris après un cycle suspension → remise en ligne.
- `sharePromoToSocial` est remplacée par `retryPromoShare`, limitée au rattrapage
d'un échec sur un réseau déjà choisi, sur une promo en ligne uniquement.
- Un réseau non configuré sur le serveur enregistre un échec explicite plutôt
que d'échouer en silence.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QXNXRC4j5VLfKvpyyisrnb
Bloc « Informations pratiques » (fiche adhérent)
- Suppression des dégradés hors palette (radial doré de la section, dégradés
crème/bleu des deux sous-cartes) au profit du vocabulaire visuel du site :
carte blanche bordée, sous-cartes plates sur le fond de champ, variables CSS.
- La pastille « Préparer votre visite » devient une puce arrondie identique aux
badges du reste du site ; le chip d'ouverture reprend les teintes de statut.
- Les glyphes texte ◷ et ↗ sont remplacés par des icônes SVG cohérentes.
Suspension des promotions
- Nouveau statut `suspended` + colonnes `suspended_by` / `suspended_by_id` /
`suspended_at`.
- L'adhérent suspend et réactive ses propres promotions depuis Mon espace ;
l'association suspend et réactive n'importe laquelle depuis le backoffice.
- Une suspension décidée par l'association ne peut pas être levée par
l'adhérent : le motif est affiché dans son espace.
- Les lectures publiques filtrant déjà sur `live`, une promo suspendue quitte
immédiatement l'accueil, l'annuaire et la fiche.
Publication Facebook / LinkedIn
- `src/lib/social.ts` : publication d'une promo en ligne sur la page Facebook
(Graph API, upload multipart car les images sont en data-URI) et sur la page
LinkedIn (Posts API + Images API en trois étapes).
- Jetons d'accès exclusivement en variables d'environnement ; un réseau non
configuré masque son bouton.
- Table `social_posts` : chaque tentative est tracée, un échec est rattrapé et
affiché sur la carte de la promo sans interrompre le backoffice.
- Nouvelle capacité RBAC `publishSocial` (admin + modérateur).
Liens sociaux publics
- Réglages `association_facebook` / `association_linkedin` éditables dans
Paramètres, préremplis avec les pages de l'association.
- Section « Plein R sur les réseaux » sur l'accueil et icônes dans le pied de
page, masquées si le réglage est vide.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QXNXRC4j5VLfKvpyyisrnb
Le compose ne requiert plus AUTH_SECRET (`:?` retiré). Si la variable est vide,
docker-entrypoint.sh génère un secret aléatoire et le persiste dans le volume
app-data (/app/data/auth_secret), réutilisé aux redémarrages. Surcharge possible
en définissant AUTH_SECRET. Déploiement Portainer sans configuration requise.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XwfhYHzC9hQWPDnQ2p53GL
- App : 8413 (hôte) → 3000 (conteneur)
- Postgres : 54329 (hôte) → 5432 (conteneur)
AUTH_URL, .env.example et README mis à jour en conséquence. Les ports internes
des conteneurs (3000, 5432) et DATABASE_URL (réseau interne) restent inchangés.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XwfhYHzC9hQWPDnQ2p53GL