Commit Graph
109 Commits
Author SHA1 Message Date
Claude 9b66a6dc5a feat(reels): montage Remotion unifié, aperçu en direct, tests et CI
Montage (étape 3) :
- Nouvelle composition ReelVideo : sous-titres animés mot à mot (3 styles :
  Impact, Surligné, Épuré), logo, effet de fin et fondu, en React
- Le service Python prépare l'image (recadrage, HDR, stabilisation, dernière
  image figée) et la piste son finale (/prepare-reel) ; Remotion compose
- Reels d'images sur les mêmes composants, avec le vrai minutage de la voix
- Police Montserrat embarquée (plus de dépendance à Google Fonts)
- REEL_RENDERER=ffmpeg conserve le rendu FFmpeg, plus rapide, en secours
- Script de pré-bundle réparé (échouait en silence : require en ESM)

Interface (étape 4) :
- Aperçu en direct avec @remotion/player, identique au rendu final ; la voix
  testée cale les sous-titres, sinon minutage estimé
- Choix du style de sous-titres sur les 4 pages Reel
- Vraie progression : étape réelle du rendu, échecs visibles 24 h avec leur
  cause ; fin de la barre simulée et du faux « publié avec succès »

Outillage (étape 5) :
- Tests vitest (minutage identique à Python, validation, sécurité) et
  pytest ; CI GitHub Actions (tsc, tests, build, ruff)
- Captures d'écran, out.mp4 et scripts de test retirés de la racine

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018Ze4bs7tpF1KGWUk6ZZSZ4
2026-09-24 14:37:42 +00:00
Claude 3c6a66bf64 feat(reels): voix Gemini complète, sous-titres mot à mot et rendu de qualité
Service ffmpeg-api réécrit (ffmpeg-service/app/) :
- Appels FFmpeg asynchrones : le service ne se fige plus pendant un rendu
- Vidéo récupérée par téléchargement (GET /files/…) au lieu de base64 en JSON
- Vraies erreurs HTTP ; échec explicite si la voix demandée est impossible
- 30 voix Gemini + ton de lecture (dynamique, chaleureux, promo, calme),
  clé en en-tête, modèle configurable avec repli
- Edge TTS 7 (minutage des mots restauré), secours en voix françaises
- Voix traitée : filtre, compression, niveau constant ; musique bouclée et
  baissée automatiquement sous la voix ; mix final à -14 LUFS
- Sous-titres calés mot à mot (Whisper pour Gemini et la voix d'origine,
  à la place de ffsubsync), style Montserrat, placés hors des boutons Reels
- Vidéo : plus de retouche luminosité forcée, scaling lanczos, HDR iPhone
  converti, BT.709, AAC 48 kHz 192k ; la vidéo s'allonge si la voix dépasse
- Grand logo de fin affiché après la voix ; FFmpeg 7.0.2 épinglé, polices et
  modèle Whisper intégrés à l'image ; tests pytest et ruff

Application :
- Sélecteur de voix partagé (4 pages) : moteur, 30 voix, ton, écoute
- Reel images : minutage réel des mots, interrupteur voix respecté
- sync-info ne génère plus de voix à chaque frappe (estimation locale)
- Stabilisation désactivée par défaut, route /reels/preview inutilisée retirée
- Log « [ReelQueue] Worker démarré » pour vérifier la version déployée

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018Ze4bs7tpF1KGWUk6ZZSZ4
2026-09-24 12:31:27 +00:00
Claude d24ab0d4b7 feat(reels): file d'attente persistante des rendus (reel_jobs)
L'ancienne file comptait les posts « processing » : un redémarrage pendant
un rendu la bloquait définitivement. Les rendus passent désormais par une
table reel_jobs, réservée avec FOR UPDATE SKIP LOCKED, avec heartbeat et
reprise des jobs interrompus au démarrage (2 tentatives maximum).

- Pipeline vidéo et pipeline images (Remotion) extraits des routes vers
  server/services/reels/, les routes ne font que valider (zod) et mettre en file
- Rendus d'images suivis en base au lieu d'une Map en mémoire
- storeName conservé pour les jobs mis en attente
- Publication Facebook en binaire : l'URL relative /uploads/... transmise
  auparavant n'était pas téléchargeable par Facebook
- Job en échec si toutes les pages échouent ou si la voix demandée manque
  (le service Python renseigne enfin tts_error)
- La voix choisie sur la page images est réellement utilisée
- sync-info mesure la voix Gemini avec sa clé
- Timeouts sur les appels au service FFmpeg, vignettes via execFile,
  /debug-ffmpeg protégé par la clé API, plus de voix anglaise en secours

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018Ze4bs7tpF1KGWUk6ZZSZ4
2026-09-24 09:31:31 +00:00
Claude 3af7f9418b fix(oauth): déduire l'URI de redirection du domaine public réel
Sans APP_URL, Facebook et TikTok recevaient
http://localhost:5555/api/{facebook,tiktok}/callback : une URI que les
fournisseurs refusent, donc aucune page connectable derrière un reverse
proxy. Le repli localhost n'est plus utilisé que hors requête HTTP ; en
service, la base publique est reconstruite depuis X-Forwarded-Proto /
X-Forwarded-Host (trust proxy est déjà activé).

- `resolvePublicBaseUrl(req)` centralise la résolution (APP_URL prioritaire)
- l'URI de redirection est déduite de la requête au démarrage du flux, à
  l'échange du code et dans l'affichage des paramètres : les trois restent
  identiques, comme l'exige OAuth
- docker-compose ne force plus APP_URL à localhost, qui rendait l'auto
  détection inopérante
- cookie de session en `secure: 'auto'` à défaut d'APP_URL, pour ne pas
  émettre un cookie non sécurisé derrière un proxy HTTPS

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Db2EazEcvnqgTTeg8oGTif
2026-09-11 18:01:28 +00:00
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 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 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
Michael a9cda4ffd4 Fix sync-info route to use ttsEngine and ttsVoice
- ttsSyncService.calculateSyncTiming now accepts ttsEngine parameter
- sync-info route extracts ttsVoice/ttsEngine from req.body instead of voice
- Previously it always fell back to Edge TTS even when Gemini was selected
  (because server stored 'fr-FR-Standard-B' but route only used 'voice')
2026-05-19 14:12:08 +02:00
Michael 72b13b3e42 Add Google Gemini TTS as alternative to Edge TTS
- Store Gemini API key globally in appConfig (single key for all users)
- Add /api/settings/gemini GET/POST/DELETE routes for API key management
- Backend: add tts_engine parameter ("edge" or "gemini") to FFmpeg service
- Backend: add generate_tts_gemini() using Google Cloud TTS REST API
- Frontend: Settings page shows Google Gemini API key input card
- Frontend: new-reel, mobile/new-reel, remotion-video, mobile/remotion-video
  pages now have Edge/Gemini engine toggle and French voice selector
- Fix tts-preview route to extract ttsVoice from req.body instead of
  undefined voice variable
2026-05-19 12:17:42 +02:00
Michael 59bc56aedc refactor: remove Piper TTS, keep edge_tts only
- Remove piper_url from ReelRequest model and all function signatures
- Remove generate_tts_piper() function and Piper branch in generate_tts_with_subs()
- Remove /api/piper/config routes from server/routes.ts
- Remove piperConfig table, schemas and storage methods
- Remove piper_config migration
- Remove Piper TTS settings UI card
- Update "Piper TTS" labels to "TTS — voix activée"

edge_tts is now the only TTS engine, using precise word-boundary timing
2026-05-19 10:55:01 +02:00
MichaelandClaude Sonnet 4.6 797e12dde5 refactor(tts): replace Minimax+Freesound with Piper TTS
Remove Minimax Speech API and Freesound integrations entirely.
Add Piper TTS as the sole TTS provider via configurable HTTP URL.

- Add piper_config DB table (url field, per-user)
- Add GET/POST /api/piper/config routes
- Python: replace generate_tts_minimax with generate_tts_piper (GET ?text=, WAV→MP3)
- Simplify generate_tts_with_subs: piper_url param replaces tts_provider+minimax fields
- Drop ttsVoice/ttsProvider from all UI, API, and background job params
- Settings page: replace Minimax+Freesound cards with single Piper URL input
- Remove freeSoundService init from server startup and CSP headers

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-19 09:15:26 +02:00
Michael 0209c3eacf feat(minimax): add Group ID support to fix rate limit quota
Pass GroupId query param to Minimax T2A v2 API — required for paid
plan quota allocation. Without it, Minimax defaults to (0/0 used).

- shared/schema.ts: groupId field on minimaxConfig table
- server/migrate.ts: ADD COLUMN IF NOT EXISTS group_id
- server/routes/reels.ts: fetch and pass groupId alongside apiKey
- server/services/ffmpeg.ts: minimax_group_id in request interface/body
- ffmpeg-service/main.py: GroupId in URL, threaded through all call sites
- client/src/pages/settings.tsx: Group ID input in Minimax settings card
2026-05-18 13:50:41 +02:00
Michael cf20b0a264 fix(tts): surface TTS errors from Python to Node.js logs and DB
- Python: capture tts_error_msg on exception, return in response
- Python: log tts_provider, minimax_api_key presence before call
- ffmpeg.ts: read tts_error from response, log and return it
- reels.ts: store TTS error in post.generationError for visibility
2026-05-18 13:26:13 +02:00
Michael 211b7dc272 fix(minimax): correct API payload and add TTS debug logging
- Fix model name: speech-2.8-hd -> speech-02-hd
- Fix bitrate: 128 -> 128000 bps
- Add channel, speed, pitch, vol to voice/audio settings
- Handle binary audio response (Content-Type: audio/*)
- Fallback audio field lookup: data.data.audio || data.audio
- Add background job log: ttsProvider + hasMinimaxKey
- Log full FFmpeg request body (text truncated, key masked)
2026-05-18 12:50:59 +02:00
Michael b7ef4940f7 feat: add Minimax Speech as alternative TTS provider for Reels
- Add minimax_config table (migration + schema + storage CRUD)
- Add GET/POST /api/minimax/config routes
- Pass tts_provider + minimax_api_key through ffmpeg service
- Add generate_tts_minimax() in Python using Minimax T2A v2 API
- Fall back to ffsubsync for subtitle sync (no WordBoundary events)
- Add Minimax config card in Settings page
- Add provider toggle (Edge TTS / Minimax) + French voices in new-reel
2026-05-18 11:55:07 +02:00
MichaelandClaude Opus 4.7 d763be6322 fix: prevent ENOENT on scheduled reel publishing when local file deleted early
- facebook.ts: use resolvePublicUrl + getMediaBuffer for reels so missing local files fall back to HTTP fetch.
- scheduler.ts: only delete local video files after the last pending scheduled post for that postId is published.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-04 14:08:15 +02:00
MichaelandClaude Opus 4.7 37aba33425 fix: precise TTS word-boundary timing and remove wasted sync calculations
- ffmpeg-service/main.py: replace ffsubsync path with exact word-boundary timing from edge_tts for TTS subtitles. Karaoke styling preserved.
- server/services/ttsSync.ts: fix word count to match TTS-cleaned text, remove artificial punctuationPause subtraction, strip punctuation tokens from count.
- server/routes/reels.ts: remove redundant ttsSyncService calls in preview/background; word_duration is ignored by Python, these only wasted TTS generations.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-04 13:13:55 +02:00
MichaelandClaude Opus 4.7 9ce735dc23 feat: add adaptive TTS text-to-speech synchronization for Reels with real-time sync info widget
Calculate optimal word_duration based on actual TTS audio duration and text punctuation.

- server/services/ttsSync.ts: new service to measure TTS audio and compute sync timing

- server/routes/reels.ts: integrate sync into preview and background processing

- client: auto-calculate sync info widget in desktop and mobile Reel creation

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-04 12:35:04 +02:00
Michael f56a89e61c feat: implement FacebookService to handle post publishing for text, photos, videos, and multi-photo carousels 2026-04-01 20:19:48 +02:00
Michael e82d73c4e1 feat: Add FFmpeg service for video processing, TTS generation, and dynamic subtitles. 2026-03-27 09:34:16 +01:00
MichaelandClaude Sonnet 4.6 2a93c2aea7 fix: detect publish_video permission error + show required permissions in Pages UI
- Detect Facebook error subcode 1363042 (missing publish_video permission)
  and throw a clear actionable message explaining how to fix it
- Add permission notice banner in Pages admin showing the 3 required
  Facebook permissions: pages_manage_posts, pages_read_engagement, publish_video

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-25 20:51:47 +01:00
MichaelandClaude Sonnet 4.6 8f13d3021e fix: use multipart/form-data for all resumable upload phases + verbose logging
Facebook resumable upload START/FINISH phases require multipart/form-data
(not application/x-www-form-urlencoded). Add full raw response logging for
each phase to diagnose any remaining issues (subcode, etc).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-25 20:46:35 +01:00
MichaelandClaude Sonnet 4.6 47ed1711dd fix: use Facebook Resumable Upload API for video publishing
Replace simple multipart upload (which fails with error 6000 on large
files) with the 3-phase Resumable Upload API (start → transfer → finish).
This is the recommended Facebook approach for files > ~50 MB and is much
more reliable regardless of file size.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-25 20:15:11 +01:00
MichaelandClaude Sonnet 4.6 5d7ab93bf8 feat: add Ken Burns effect, fix caption visibility and Facebook binary upload
- Add slow zoom/pan (Ken Burns) effect to each image slide, cycling through
  8 deterministic presets per image index for natural variety
- CapCut-style captions: semi-transparent pill background + yellow glow on
  active word; background/text now only visible while voice is speaking
- Fix Facebook error 6000: pass Node.js Buffer directly instead of unsafe
  ArrayBuffer pool-slice conversion which could corrupt the upload payload
- Fix TypeScript error: replace u-flag emoji regex with BMP surrogate pairs

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-25 17:01:52 +01:00
Michael 56210687a2 feat: implement Facebook posting service, add new sidebar component, and introduce Remotion server route. 2026-03-25 16:36:57 +01:00
Michael e1a6d1e06c feat: add mobile Remotion video generator with image, text, audio, and Facebook publishing features. 2026-03-25 16:16:30 +01:00
MichaelandClaude Sonnet 4.6 b353fb6ded fix: use relative URLs for local media storage to fix CSP blocking
Media files were stored with absolute http://localhost:5555/... URLs,
which got blocked by the Content Security Policy when the app runs at
https://socialflow.fnancy.fr. Now stores relative /uploads/... paths
and resolves to absolute URLs only when needed by external APIs
(Facebook, FFmpeg internal service).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-25 08:30:07 +01:00
MichaelandClaude Sonnet 4.6 7733acbc22 feat: Replace external storage with local file storage + auto cleanup
Media is now stored directly on the server disk (no external service).
Files are served via Express static middleware at /uploads/*.

Cleanup rules:
- Videos deleted immediately after successful publish to Facebook/Instagram
- Videos older than 7 days purged daily by cron job
- Images older than 30 days purged daily by cron job

Changes:
- server/services/minio.ts: replaced S3 client with local fs read/write
- server/index.ts: added static serving for /uploads/media, /logos, /stories
- server/services/media-purge.ts: new rules (images 30d, videos 7d)
- server/services/scheduler.ts: delete local videos after successful publish
- docker-compose.yml: add media_uploads, logos_uploads, stories_uploads volumes
- settings UI: removed credentials card, shows local storage info
- package.json: removed googleapis (unneeded)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-25 07:35:27 +01:00
MichaelandClaude Sonnet 4.6 ba94e7cc7d feat: Add MinIO endpoint/public URL configuration via settings UI
Store endpointUrl and publicUrl in cloudinary_config DB table so
MinIO connection can be fully configured from the app settings
without needing environment variables.

- DB migration: adds endpoint_url and public_url columns
- Schema: adds endpointUrl/publicUrl to cloudinaryConfig table
- MinIO service: reads endpoint from DB (falls back to MINIO_ENDPOINT env)
- buildMinioUrl: accepts optional publicUrl override from DB config
- All routes updated to pass config.publicUrl to buildMinioUrl
- Settings UI (desktop + mobile): adds Endpoint URL and Public URL fields

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-24 21:19:49 +01:00
MichaelandClaude Sonnet 4.6 e39fa1026d feat: replace Cloudinary with MinIO for all media storage
- Create server/services/minio.ts: S3-compatible service (same interface as
  cloudinaryService) using @aws-sdk/client-s3 and MINIO_ENDPOINT env var
- Replace all cloudinaryService imports with minioService across routes.ts,
  routes/reels.ts, routes/remotion.ts, services/media-purge.ts
- Replace Cloudinary logo URL pattern (res.cloudinary.com/...) with
  buildMinioUrl() helper throughout all server routes
- GET /api/cloudinary/config now returns a logoUrl field (full MinIO URL)
  so clients never need to build the URL themselves
- Update settings pages (desktop + mobile): labels changed to MinIO
  (Bucket Name, Access Key, Secret Key)
- Update image-editor.tsx: remove Cloudinary URL transformation, use logoUrl
- Update media-utils.ts: simplify getVideoThumbnailUrl (no URL transforms)
- Add MINIO_ENDPOINT and MINIO_PUBLIC_URL to docker-compose.yml + .env.example
- DB table reuse: cloudinary_config.cloud_name = bucket, api_key = access key,
  api_secret = secret key — no migration needed

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-24 21:05:37 +01:00
Michael 3a5acd425c feat: Implement new Reel creation functionality with dedicated mobile and desktop UIs, server-side API routes, and a new FFmpeg processing service. 2026-03-19 14:51:16 +01:00
Michael a01194cc79 fix(cloudinary): correct method name to getCloudinaryConfig across backend 2026-03-04 14:45:38 +01:00
Michael 60c830c88b feat: implement automatic purging of old media and schedule its daily execution via cron. 2026-03-04 14:32:27 +01:00
Michael 4fcc8cf53c feat: Add music search and favorite API routes, and introduce FFmpeg service for Reels. 2026-03-03 12:34:09 +01:00
Michael 0e9622b5eb feat: Implement internal MP3 management and logo overlay functionality for Reels. 2026-03-02 11:33:22 +01:00
Michael 3859170d35 fix(analytics): upsert history one data point per day per page, add gte/lt imports 2026-02-12 11:01:43 +01:00
Michael 330c42c6ff feat(analytics): resilient Graph API calls, CRON sync every 6h, polling frontend, engagement+views metrics 2026-02-12 10:51:06 +01:00
Michael 4b5ab1ca16 feat: enable feed distribution for Reels via /videos endpoint 2026-02-10 11:11:42 +01:00
Michael c9f7f7e9ec fix: ensure persistence of video data for scheduled reels and correct publication type 2026-01-29 11:58:29 +01:00
Michael 7223bbbfb2 feat(reels): optimize quality to 1080p, increase text size to 64, and add stabilization toggle 2026-01-23 17:19:32 +01:00
Michael 891b374500 feat: support large file uploads up to 4GB (disk storage + streaming) 2026-01-23 16:27:27 +01:00
Michael aac776777a feat(reels): add video stabilization, quality improvements and processing monitoring 2026-01-23 13:42:42 +01:00
Michael 9ff1c4aa18 feat: enhance reels with default overlay, more voices, and tts preview 2026-01-23 09:38:59 +01:00
Jacques fe638b9895 fix: reduce font size to 16, improve TTS sync (3 words/chunk, 0.3s/word) 2026-01-22 21:39:06 +01:00
Jacques e9ff262991 fix(ffmpeg): reduce hardcoded font_size from 60 to 24 for smaller text overlay 2026-01-22 21:22:36 +01:00
Michael 77a6dd7188 fix 2026-01-22 16:58:12 +01:00