← Phase 2 Overview / Stories, Reels & Remix
📸
Module 9 — Phase 2

Stories, Reels & Remix

Contenu éphémère, vidéos courtes verticales, stickers interactifs et fonction Remix/Duet pour engager la communauté voyage.

Backlog

User Stories

US9.1
En tant qu'utilisateur, je peux publier une story photo/vidéo visible 24h
must 8pts
US9.2
En tant qu'utilisateur, je peux ajouter des stickers à ma story : sondage, question, curseur, tag lieu
must 8pts
US9.3
En tant qu'utilisateur, je vois les stories des abonnements en bulles horizontales en haut du feed
must 4pts
US9.4
En tant qu'utilisateur, je peux répondre à une story par message privé ou emoji
must 3pts
US9.5
En tant qu'utilisateur, je vois les résultats de mes sondages en story
must 4pts
US9.6
En tant qu'utilisateur, je peux archiver mes stories après expiration
should 2pts
US9.7
En tant qu'utilisateur, je peux publier un Reel (vidéo verticale max 90s) avec texte overlay
must 10pts
US9.8
En tant qu'utilisateur, je navigue dans un feed Reels (swipe vertical full-screen)
must 5pts
US9.9
En tant qu'utilisateur, je peux ajouter un son/musique à mon reel depuis une bibliothèque
should 8pts
US9.10
En tant qu'utilisateur, je peux remixer un itinéraire existant (fork, modifier, republier avec crédit)
must 10pts
US9.11
En tant qu'utilisateur, je peux faire un Duet vidéo (ma réaction côte à côte)
should 8pts
US9.12
En tant qu'utilisateur, je peux opt-out de la fonction Remix pour mes contenus
must 3pts
US9.13
En tant que système, les stories expirent automatiquement et sont archivées en S3 Glacier
must 5pts
/stories, /reels, /remix

Endpoints API

POST /stories Créer une story (multipart: media + metadata) 🔒 auth
GET /stories/feed Stories des abonnements (bulles horizontales) 🔒 auth
GET /stories/:id Détail d'une story 🔒 auth
POST /stories/:id/views Marquer comme vue 🔒 auth
POST /stories/:id/reply Répondre à une story (→ message privé) 🔒 auth
POST /stories/:id/poll-vote Voter sur un sondage story (body: option) 🔒 auth
GET /stories/:id/poll-results Résultats d'un sondage 🔒 auth
GET /users/:id/stories Stories actives d'un utilisateur 🔒 auth
GET /users/:id/story-archive Archive stories (auteur seulement) 🔒 auth
POST /reels Uploader un reel (body: video_url, caption, tags) 🔒 auth
GET /reels/feed Feed reels vertical paginé (cursor) 🔒 auth
GET /reels/:id Détail d'un reel 🔒 auth
POST /reels/:id/duet Créer un duet vidéo 🔒 auth
POST /itineraries/:id/remix Remixer un itinéraire (fork + attribution) 🔒 auth
GET /itineraries/:id/remixes Liste des remixes d'un itinéraire 🔒 auth
Schémas de données

Tables Stories & Reels

TABLE stories
id UUID PRIMARY KEY author_id UUID REFERENCES users(id) media_url TEXT NOT NULL media_type ENUM('image','video') duration_sec INT -- max 15s image, 30s vidéo stickers JSONB[] -- [{type, data, position}] place_id UUID REFERENCES places(id) views_count INT DEFAULT 0 replies_count INT DEFAULT 0 allow_remix BOOLEAN DEFAULT true is_archived BOOLEAN DEFAULT false expires_at TIMESTAMPTZ NOT NULL -- +24h archived_s3_key TEXT -- après expiration created_at TIMESTAMPTZ DEFAULT NOW()
TABLE reels
id UUID PRIMARY KEY author_id UUID REFERENCES users(id) cloudflare_video_id TEXT NOT NULL -- Cloudflare Stream ID thumbnail_url TEXT caption TEXT duration_sec INT CHECK (duration_sec <= 90) audio_track_id UUID REFERENCES audio_tracks(id) place_id UUID REFERENCES places(id) tags TEXT[] likes_count INT DEFAULT 0 comments_count INT DEFAULT 0 shares_count INT DEFAULT 0 duet_of_id UUID REFERENCES reels(id) allow_remix BOOLEAN DEFAULT true status ENUM('processing','active','hidden','deleted') created_at TIMESTAMPTZ DEFAULT NOW()
Business Logic

Règles Métier

🎬
Pipeline vidéo Reel
Upload → Cloudflare Stream (HLS auto) → webhook "ready" → status passe à "active". Thumbnail auto générée à t=2s. Compression mobile (react-native-compressor) avant upload.
Expiration stories
Cron BullMQ toutes les 5 min : SELECT * FROM stories WHERE expires_at < NOW() AND NOT is_archived → move to S3 Glacier + is_archived=true. Suppression S3 standard après 30j.
🎵
Bibliothèque musicale
Uniquement musiques libres de droits (Pixabay Music API). Pas de contenus RIAA. Chaque reel stocke audio_track_id. Watermark audio si reel partagé hors app.
🔀
Système Remix
Remix crée un nouveau itinéraire/post avec remixed_from_id. Crédit affiché "Remixé de @username". Opt-out : allow_remix=false → bouton Remix désactivé. Notification au créateur original.
🗳️
Stickers interactifs
Type JSONB : {type:"poll", options:["Paris","Rome"], votes:[0,0], voters:[]}. Votes anonymes (on stocke user_id dans voters pour dédupliquer, mais pas affiché).
Critères d'acceptation
Story visible par les abonnements dès publication (< 3s)
Expiration automatique vérifiée et effective dans la fenêtre de 5 min
Reel traité par Cloudflare Stream et disponible en < 90s
Remix avec attribution affichée correctement sur iOS et Android
Opt-out Remix : bouton absent sur les contenus protégés