Commit Graph
23 Commits
Author SHA1 Message Date
Claude d7f002a64f feat(facebook): renouvellement automatique des jetons de page
Un jeton de page collé à la main meurt au bout de 60 jours — ou du jour au
lendemain si le mot de passe change, si le compte perd son rôle d'admin ou si
Meta révoque l'autorisation — et on ne l'apprenait qu'à la première publication
en échec : le contrôle quotidien existant se contentait de marquer la page
« expired ».

On reprend la chaîne recommandée par Meta, comme dans PleinR : autorisation
OAuth, échange du code contre un jeton utilisateur longue durée (~60 j), puis
dérivation des jetons de page — qui n'expirent pas. Le jeton utilisateur est
conservé chiffré sur chaque page : c'est lui qui permet au TokenManager de
régénérer un jeton révoqué sans intervention humaine, et de se prolonger
lui-même avant échéance.

- facebook_config : App ID / App Secret de l'application, saisis dans les
  paramètres (chiffrés en base) ou par variables d'environnement.
- /api/facebook : configuration, connexion OAuth, callback qui crée ou met à
  jour les pages, contrôle d'une page à la demande, contrôle global.
- TokenManager : /debug_token pour connaître l'état réel du jeton, puis
  renouvellement automatique 15 jours avant l'échéance ou dès révocation ;
  repli sur le contrôle historique tant que l'application n'est pas configurée
  ou si Facebook est injoignable, pour ne pas passer l'UI au rouge sur une
  panne réseau. Le cron passe à deux fois par jour.
- Les comptes Instagram professionnels rattachés à une page reçoivent le même
  traitement, puisqu'ils publient avec le jeton de cette page.
- L'UI montre l'état constaté (jeton valide, bientôt expiré, révoqué), l'erreur
  à corriger et un bouton de contrôle immédiat ; un bandeau signale les pages à
  reconnecter.
- /api/pages ne renvoie plus aucun jeton au navigateur, seulement un indicateur
  de renouvellement automatique.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Qns6Gcpd95bCHWUxySh4Ew
2026-09-10 06:11:43 +00:00
Claude ac8b494f24 fix: vignettes vidéo persistantes et remise à zéro des erreurs de types
Deux corrections demandées ensemble.

1. Vignettes vidéo

Le planificateur supprime la vidéo locale une fois publiée, alors que la
ligne `media` survit pour l'historique. L'interface tentait donc de lire
un fichier disparu pour en extraire une image : d'où les avertissements
« Video thumbnail generation failed » et les tuiles vides.

Une vignette JPEG est désormais extraite à la création du reel, dans les
deux flux de génération, et stockée à part : elle reste disponible quand
la vidéo ne l'est plus. La purge quotidienne la nettoie en même temps que
la ligne qu'elle illustre. L'extraction est tolérante à l'échec — une
vignette manquante dégrade l'affichage mais ne fait jamais échouer une
publication — et le composant retombe sur son icône de remplacement pour
les médias créés avant ce changement. La fonction d'extraction, jusque-là
privée à la route Remotion, devient un service partagé.

2. Erreurs de compilation préexistantes (27 → 0)

- remotion-video.tsx (bureau et mobile) : les composants Select étaient
  utilisés sans être importés, ce qui faisait planter le sélecteur de voix
  à l'exécution.
- external.ts : fileSize et aiGenerated étaient transmis dans un type que
  le schéma ne déclare pas, et ne fonctionnaient que par conversion
  implicite de PostgreSQL.
- reels.ts : l'interface MusicTrack, référencée mais jamais déclarée dans
  ce module.
- remotion.ts : chromiumOptions.args retiré. Remotion ne lit jamais cette
  clé, les drapeaux Chrome qu'elle contenait n'atteignaient donc pas le
  navigateur.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018uUPZ1GNpEkYB1dpXHYPek
2026-08-12 08:00:48 +00:00
Claude c69fe4e462 fix(ia): afficher la cause réelle d'un échec de génération de texte
La route /api/ai/generate connaissait la cause exacte de l'échec
(configuration absente, clé invalide, crédits épuisés, modèle inconnu)
mais la remplaçait par un « Failed to generate text » en 500, et
l'interface affichait de son côté un message figé. Sans accès aux logs
du serveur, l'utilisateur n'avait donc aucun moyen de savoir quoi
corriger.

Le motif renvoyé par OpenRouter est désormais extrait, tronqué et
propagé jusqu'au bandeau d'erreur, avec un code HTTP qui distingue un
problème de configuration (400) d'un refus du service tiers (502).
La clé API n'apparaît à aucun moment dans ces messages.

Même traitement pour /api/reels/generate-text, qui souffrait du même
masquage.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018uUPZ1GNpEkYB1dpXHYPek
2026-08-12 07:49:23 +00:00
Claude 0570088722 feat(tiktok): servir le fichier de vérification du domaine
TikTok exige de prouver la propriété du domaine ("URL properties") avant
d'autoriser la Content Posting API, en servant un fichier à la racine du
site.

Le dossier public-root/ est exposé publiquement à la racine du domaine et
monté avant le catch-all du frontend. Y déposer un fichier suffit : pas de
modification de code ni de reconstruction du client, ce qui couvre aussi
les prochaines vérifications (autre domaine, Google, Meta).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018uUPZ1GNpEkYB1dpXHYPek
2026-08-11 16:27:26 +00:00
Claude 3e00a3ca67 feat(legal): préremplir les mentions légales de la société exploitante
Renseigne l'identité de FROUARD DISTRIBUTION (enseigne LA FOIR'FOUILLE)
comme valeur par défaut des pages /terms et /privacy : forme juridique,
capital, siège et numéro RCS. Les relecteurs TikTok ouvrent ces pages
pendant l'audit et refusent les mentions incomplètes.

Chaque champ reste surchargeable par variable d'environnement si l'outil
venait à être exploité par une autre société du groupe.

L'adresse de contact reste à fournir via LEGAL_CONTACT_EMAIL : elle ne
figure pas au registre, et le RGPD impose un point de contact pour
l'exercice des droits.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018uUPZ1GNpEkYB1dpXHYPek
2026-08-11 16:19:16 +00:00
Claude d0f63c68e4 feat(legal): servir les pages /terms et /privacy exigées par TikTok
L'audit de l'application développeur TikTok impose une URL publique pour
les conditions d'utilisation et pour la politique de confidentialité, et
les relecteurs les ouvrent réellement.

Les pages sont rendues en HTML côté serveur, sans authentification, pour
rester lisibles par un robot qui n'exécute pas le JavaScript du client.
La politique décrit précisément ce que l'intégration fait : données reçues
de TikTok (open_id, nom d'affichage, avatar, jetons), finalité de
publication, chiffrement des jetons au repos, suppression à la
déconnexion et marche à suivre pour retirer l'autorisation.

Les mentions de l'exploitant se configurent via LEGAL_COMPANY_NAME,
LEGAL_COMPANY_ADDRESS et LEGAL_CONTACT_EMAIL.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018uUPZ1GNpEkYB1dpXHYPek
2026-08-11 16:14:13 +00:00
Claude b95cd23ebc feat(tiktok): publier les reels sur les comptes TikTok des magasins
Ajoute la gestion multi-comptes TikTok et permet d'envoyer la même vidéo
sur plusieurs pages Facebook ET plusieurs comptes TikTok en une seule
publication.

Un compte TikTok vit dans social_pages comme une page Facebook : il hérite
donc des permissions par utilisateur, de la planification et du calendrier.

Trois particularités de l'API TikTok structurent l'implémentation :
- pas de jeton saisi à la main : chaque compte passe par OAuth, et
  l'access token (24h) est renouvelé via le refresh token (1 an) avant
  chaque publication ;
- la publication est asynchrone : l'upload rend un publish_id, et un
  poller récupère l'identifiant définitif du post ;
- creator_info doit être interrogé avant chaque envoi pour ne demander
  qu'un niveau de confidentialité réellement autorisé sur le compte.

Serveur :
- service TikTok (OAuth, creator_info, upload FILE_UPLOAD par chunks,
  suivi de statut) et routes de connexion/configuration
- schéma : platform 'tiktok', refresh token et scopes sur social_pages,
  publish_id/publish_status sur scheduled_posts, table tiktok_config
- routage par plateforme dans les deux flux de reels et le planificateur
- cron de suivi des publications, token manager et analytics adaptés

Client :
- connexion et reconnexion des comptes TikTok depuis « Pages gérées »
- sélection combinée pages Facebook / comptes TikTok à la publication
- configuration de l'application TikTok dans les paramètres (admin)

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018uUPZ1GNpEkYB1dpXHYPek
2026-08-11 14:29:38 +00:00
Claude 2188fa458d fix: recover gracefully from stale sessions causing repeated 401s
The SPA's session check (/api/auth/session) is cached indefinitely by
react-query (staleTime: Infinity, no refetch on focus/interval), so once a
protected page mounts successfully, the app never re-verifies auth. If the
server-side session later becomes invalid (server restart with in-memory
sessions, cookie/session expiry, etc.), every subsequent API call
(pages, scheduled-posts, audio-tracks, media, media/upload, ...) starts
failing with 401 in a loop with no way for the user to recover short of a
manual page reload.

Add a shared handleUnauthorized() in queryClient.ts that redirects to
/login on any non-auth API 401, wired into both the shared fetch helpers
(apiRequest/getQueryFn) and the raw fetch() calls used for file uploads
and Remotion rendering, which also lacked this recovery path. Also add the
missing credentials: "include" to those raw fetch() calls for consistency
with the rest of the app's API requests.
2026-07-27 11:56:56 +00:00
Claude 8155b30c63 fix(openrouter): stop logging API key material, use a hash fingerprint
Address Codex review: the debug log previously included the key's
first 10 characters. Replace with a non-reversible SHA-256 fingerprint
so logs remain useful for correlating support reports without ever
exposing key material.
2026-07-14 06:31:18 +00:00
Claude de7fc7d03c fix(openrouter): trim API key and guard against empty key
An OpenRouter key with trailing whitespace/newline (common from
copy-paste) produces "Missing Authentication header" from their API,
which looked identical to a missing key. Trim apiKey on save (schema
level) and on use (defense in depth), fail fast with a clear message
if the stored key is empty, and log a masked key preview + model on
each generation call to make future auth failures diagnosable from
container logs.
2026-07-14 06:25:47 +00:00
Claude 09b4e597ae fix(openrouter): respect the requesting user's saved model config
generatePostText ignored the userId it received and always read an
arbitrary row via getAnyOpenrouterConfig() (LIMIT 1, no ORDER BY),
so a model chosen and saved in Settings could be shadowed by another
stale config row. Now it prefers the requesting user's own config,
falling back to the most recently updated shared one. Also swap the
retired anthropic/claude-3.5-sonnet default for a live OpenRouter slug.
2026-07-14 05:49:31 +00:00
Claude 07fd43a5ed Add GEMINI_API_KEY env var fallback for TTS
Add GEMINI_API_KEY to docker-compose so it can be set in Portainer.
Fall back to this env var when the DB app_config has no geminiApiKey,
so Gemini TTS works without requiring the user to save the key through
the Settings UI.

https://claude.ai/code/session_01QBvwHAMZzVYfWvy1U5paat
2026-05-19 13:39:37 +00:00
Claude 89bc1bf88b Decode Gemini TTS raw PCM with correct format flags
Gemini returns raw PCM (audio/L16;codec=pcm;rate=24000), not a real WAV
file with a header, so FFmpeg fails to auto-detect the format. Pass
-f s16le, -ar (from mime type), and -ac 1 explicitly so FFmpeg can
decode the byte stream. Also surface FFmpeg stderr on failure instead
of swallowing it.

https://claude.ai/code/session_01QBvwHAMZzVYfWvy1U5paat
2026-05-19 13:31:15 +00:00
Claude 50a011ce23 Simplify Gemini voice options and default to male voice (Charon)
Replace the 8-item Google Cloud TTS voice list with 2 Gemini native
voices: Charon (homme) and Kore (femme). Set Charon as the default
when switching to Gemini engine in all four reel pages.

https://claude.ai/code/session_01QBvwHAMZzVYfWvy1U5paat
2026-05-19 13:19:15 +00:00
Claude 46c31a1447 Switch Gemini TTS from Google Cloud TTS to Gemini native API
Replace the texttospeech.googleapis.com call (which requires a separate
GCP project with Cloud TTS enabled and billing) with the Gemini native
TTS endpoint (gemini-2.5-flash-preview-tts). This uses the same Gemini
API key already configured in the app, with no extra GCP setup needed.

Voice mapping: fr-FR-Standard-A/C -> Kore (female), B/D -> Charon (male).
Audio is returned as WAV and converted to MP3 via FFmpeg. Subtitle sync
uses ffsubsync as Gemini TTS does not return word boundaries.

https://claude.ai/code/session_01QBvwHAMZzVYfWvy1U5paat
2026-05-19 13:15:30 +00:00
Claude e2002cf163 Pass Gemini API key in /reels/tts-preview route
The TTS preview route hardcoded the Gemini API key to undefined when
calling the ffmpeg service, causing the service to silently fall back to
Edge TTS even when the user selected Gemini. Mirror the lookup already
used in the reel processing route so the selected engine is honored.

https://claude.ai/code/session_01QBvwHAMZzVYfWvy1U5paat
2026-05-19 12:49:16 +00:00
Claude a146705d15 Fix Gemini TTS crash from surrogate escape sequences in log strings
Replace \uXXXX surrogate-pair escapes with the actual emoji characters
in print() calls. Lone surrogates are invalid in UTF-8, causing
UnicodeEncodeError on stdout encode and aborting TTS generation before
the Gemini API call.

https://claude.ai/code/session_01QBvwHAMZzVYfWvy1U5paat
2026-05-19 12:39:06 +00:00
Claude 3cc72fb80d chore: Ajouter dépendance @twemoji/api pour support emojis 2025-11-29 12:27:13 +00:00
Claude 88d5a56b01 fix: Utiliser cache local pour les emojis dans les stories Facebook
Problème: Les emojis s'affichaient comme des codes hexadécimaux (01F, 4E2, ABC)
dans les stories Facebook car le serveur ne pouvait pas télécharger les images
depuis les CDN externes (jsDelivr, Twemoji).

Solution: Système de cache local à 3 niveaux
1. **Cache mémoire**: Les emojis déjà chargés restent en RAM
2. **Cache disque**: Les emojis téléchargés sont sauvegardés dans server/assets/emoji/
3. **Téléchargement à la demande**: Si un emoji n'existe pas localement, il est téléchargé une seule fois puis réutilisé

Avantages:
- ✅ Les emojis ne sont téléchargés qu'une seule fois
- ✅ Temps de chargement ultra-rapide après le premier téléchargement
- ✅ Fonctionne même si le CDN est temporairement indisponible
- ✅ Pas besoin de pré-télécharger tous les emojis (3000+ fichiers)
- ✅ Les emojis utilisés fréquemment sont mis en cache

Les emojis devraient maintenant s'afficher correctement comme de vraies
images colorées dans les stories Facebook, pas comme des codes hexadécimaux !
2025-11-29 12:26:06 +00:00
Claude ad97da0f50 feat: Ajouter versions mobiles complètes pour toutes les pages
Implémentation d'un système responsive robuste avec détection automatique
du type d'appareil et switch transparent entre versions desktop et mobile.

## Nouvelles fonctionnalités

### 1. Hook de détection mobile (useIsMobile)
- Détection via media query (max-width: 768px)
- Validation supplémentaire via user agent
- Support des changements dynamiques de taille d'écran
- Hook useDeviceType pour détection granulaire (mobile/tablet/desktop)

### 2. Composant ResponsiveRoute
- Switch automatique entre versions desktop/mobile
- Lazy loading des composants pour performances optimales
- Factory function createResponsiveRoute pour création facile
- Suspense avec fallback de chargement

### 3. Pages mobiles optimisées (10 pages)
**Pages créées :**
- dashboard.tsx - Tableau de bord
- new-post.tsx - Création de posts (interface tactile optimisée)
- calendar.tsx - Calendrier
- media.tsx - Bibliothèque de médias
- image-editor.tsx - Éditeur d'images
- pages.tsx - Gestion des pages sociales
- ai.tsx - Assistant IA (admin)
- settings.tsx - Paramètres (admin)
- users-admin.tsx - Gestion utilisateurs (admin)
- sql.tsx - Administration SQL (admin)

**Optimisations mobiles appliquées :**
- Layout en colonne unique (au lieu de multi-colonnes)
- Boutons tactiles : min-height 44-52px (Apple guidelines)
- Grilles adaptées (2 colonnes au lieu de 3 pour médias)
- Navigation simplifiée avec menu hamburger
- Dialogs en plein écran (95vw sur mobile)
- Tables converties en cards empilées
- Boutons d'action en colonne au lieu de ligne
- Padding et espacement optimisés pour petits écrans
- Typographie réduite (text-2xl au lieu de 3xl)
- Touch-manipulation sur zones interactives
- Bottom fixed buttons pour actions principales
- Inputs full-width avec min-height 48px

### 4. Routing automatique
- App.tsx modifié pour utiliser createResponsiveRoute
- Détection transparente du device
- Lazy loading de toutes les routes
- Aucun changement d'URL entre versions

## Performances

- Code splitting automatique (lazy loading)
- Seule la version nécessaire est chargée
- Cache des composants chargés
- Réduction de la taille du bundle initial

## Tests à effectuer

1. Rétrécir la fenêtre du navigateur < 768px
2. Utiliser les DevTools mobile
3. Tester sur un vrai appareil mobile
4. Vérifier que toutes les fonctionnalités marchent

Les utilisateurs bénéficient maintenant d'une expérience optimale sur mobile !
2025-11-29 10:59:41 +00:00
Claude 4506c2b7c4 fix: Corriger l'affichage des emojis dans les stories Facebook
Problème: Les emojis s'affichaient comme des carrés bizarres dans les
stories publiées sur Facebook (alors que la prévisualisation fonctionnait).

Cause: Le téléchargement des images emoji depuis le CDN Twemoji par défaut
échouait lors de la génération de l'image de story côté serveur.

Solution:
1. Utilisation d'un CDN plus fiable (jsDelivr) au lieu du CDN Twemoji par défaut
2. Ajout d'une logique de retry (3 tentatives avec exponential backoff)
3. Ajout d'un timeout de 10 secondes pour éviter les blocages
4. Meilleure gestion des erreurs avec logs détaillés
5. Cache des emojis téléchargés pour améliorer les performances

Les emojis devraient maintenant s'afficher correctement dans les stories
Facebook publiées depuis l'application.
2025-11-29 10:35:09 +00:00
Claude 53054ccd87 debug: Ajouter des logs détaillés pour diagnostiquer le problème d'emojis
Ajout de logs pour tracer:
- Le parsing des emojis (combien détectés, URLs générées)
- Le téléchargement des images (succès/échec, codes HTTP)
- Le rendu final (succès/erreur avec détails)

Cela permettra d'identifier si le problème vient de:
- URLs Twemoji incorrectes
- Échec de téléchargement depuis le CDN
- Problème de chargement/décodage de l'image
2025-11-29 10:27:01 +00:00
Claude 50d5c77f8d fix: Corriger l'alignement vertical des emojis dans les stories Facebook
Le problème était causé par un calcul incorrect du positionnement des emojis
lorsque textBaseline est défini à 'middle'. Le code calculait la position comme
si le baseline était en mode standard (alphabetic), ce qui décalait les emojis
verticalement.

Changement: emojiY = y - (emojiSize / 2) au lieu de y - fontSize * 0.8
Cela centre correctement l'emoji autour du point Y qui représente le centre
du texte en mode 'middle'.
2025-11-29 10:23:55 +00:00