← Phase 1 Overview / Feed Social
📰
Module 2 — Phase 1 MVP

Feed Social & Posts

Le coeur social de NOMIIQ : publication de contenu voyage, fil d'actualité personnalisé, interactions et partage. Pas de stories/reels en Phase 1.

Backlog

User Stories

US2.1
En tant qu'utilisateur, je peux créer un post avec jusqu'à 10 photos ou 1 vidéo (max 60s)
must 8 pts
US2.2
En tant qu'utilisateur, je peux ajouter un lieu, un coût estimé, du texte et des tags à mon post
must 5 pts
US2.3
En tant qu'utilisateur, je vois le feed de mes abonnés, trié par fraîcheur + pertinence IA
must 8 pts
US2.4
En tant qu'utilisateur, je peux liker un post (optimistic UI — réponse instantanée)
must 3 pts
US2.5
En tant qu'utilisateur, je peux commenter un post et voir les réponses imbriquées (1 niveau)
must 5 pts
US2.6
En tant qu'utilisateur, je peux sauvegarder un post dans mes collections
must 3 pts
US2.7
En tant qu'utilisateur, je peux partager un post via deep link ou share sheet
must 3 pts
US2.8
En tant qu'utilisateur, je peux reposter un contenu sur mon feed avec mention de l'auteur
should 4 pts
US2.9
En tant qu'utilisateur, je peux signaler un post (spam, harcèlement, faux, IA non déclaré)
must 2 pts
US2.10
En tant que système, le post est labellisé "Généré par IA" si la détection Hive > 80%
must 5 pts
US2.11
En tant que système, les métadonnées EXIF sont extraites pour détecter la localisation réelle
must 4 pts
US2.12
En tant qu'utilisateur, le feed inclut des recommandations IA (destinations similaires à mes posts)
should 5 pts
US2.13
En tant qu'utilisateur, je peux filtrer le feed par type : Tout / Mes abonnés / Trending
should 2 pts
REST API /posts & /feed

Endpoints API

GET /feed Feed paginé (?cursor=&limit=20&type=all|following|trending) 🔒 auth
POST /posts Créer un post (multipart ou JSON + presigned) 🔒 auth
GET /posts/:id Détail d'un post public
PATCH /posts/:id Modifier un post (auteur seulement) 🔒 auth
DELETE /posts/:id Supprimer un post (auteur ou admin) 🔒 auth
POST /posts/:id/like Liker (idempotent) 🔒 auth
DELETE /posts/:id/like Unlike 🔒 auth
GET /posts/:id/likes Liste des likers (paginé) public
POST /posts/:id/comments Poster un commentaire 🔒 auth
GET /posts/:id/comments Liste commentaires (paginé + cursor) public
DELETE /comments/:id Supprimer un commentaire 🔒 auth
POST /posts/:id/save Sauvegarder dans collections 🔒 auth
DELETE /posts/:id/save Retirer des sauvegardes 🔒 auth
POST /posts/:id/report Signaler un post 🔒 auth
POST /posts/:id/repost Reposter avec mention 🔒 auth
GET /users/:id/posts Posts d'un utilisateur (paginé) public
GET /users/me/saves Posts sauvegardés (paginé) 🔒 auth
GET /media/presign Obtenir URL S3 presignée pour upload 🔒 auth
Schémas de données

Tables Posts & Interactions

TABLE posts
id UUID PRIMARY KEY author_id UUID REFERENCES users(id) type ENUM('post','repost','itinerary_share') text_content TEXT media_urls TEXT[] -- S3 URLs (max 10) media_types TEXT[] -- 'image'|'video' place_id UUID REFERENCES places(id) cost_amount DECIMAL(10,2) cost_currency CHAR(3) tags TEXT[] ai_generated BOOLEAN DEFAULT false ai_confidence FLOAT -- score Hive 0-1 exif_lat DECIMAL(10,7) exif_lng DECIMAL(10,7) exif_verified BOOLEAN DEFAULT false repost_of_id UUID REFERENCES posts(id) likes_count INT DEFAULT 0 -- dénormalisé comments_count INT DEFAULT 0 saves_count INT DEFAULT 0 visibility ENUM('public','followers','private') status ENUM('active','hidden','deleted') DEFAULT 'active' published_at TIMESTAMPTZ DEFAULT NOW()
TABLE post_likes
user_id UUID REFERENCES users(id) post_id UUID REFERENCES posts(id) created_at TIMESTAMPTZ DEFAULT NOW() PRIMARY KEY (user_id, post_id)
TABLE comments
id UUID PRIMARY KEY post_id UUID REFERENCES posts(id) author_id UUID REFERENCES users(id) parent_id UUID REFERENCES comments(id) content TEXT NOT NULL likes_count INT DEFAULT 0 status ENUM('active','deleted') DEFAULT 'active' created_at TIMESTAMPTZ DEFAULT NOW()
TABLE post_saves
user_id UUID REFERENCES users(id) post_id UUID REFERENCES posts(id) collection TEXT DEFAULT 'default' created_at TIMESTAMPTZ DEFAULT NOW() PRIMARY KEY (user_id, post_id)
Business Logic

Règles Métier & Algorithme Feed

📸
Upload médias via Presigned S3
Le client demande une URL S3 presignée (15 min), uploade directement, puis envoie seulement la key S3 dans le POST /posts. L'API ne touche jamais le binaire.
🤖
Détection IA via Hive Moderation
Tout post avec image est envoyé async à Hive. Si score > 0.8 → ai_generated=true + label visible. Si score 0.5-0.8 → label "Non vérifié". Job BullMQ, pas bloquant.
📍
Vérification EXIF
Extraction lat/lng EXIF via sharp/exifr. Si EXIF lat/lng correspond (±50km) au lieu taggé → exif_verified=true → badge "Présence vérifiée".
❤️
Likes dénormalisés + Redis
likes_count dénormalisé en DB (incr/decr trigger). Optimistic UI : +1 instantané côté mobile, vrai count sync toutes les 5s via Redis.
🔢
Algorithme feed
Score = 0.4×freshness + 0.3×engagement_rate + 0.2×user_affinity + 0.1×diversity. Recalculé toutes les 5 min par user via BullMQ worker. Fallback chronologique si <10 abonnements.
🔞
Modération auto
Contenu NSFW (Hive NSFW score > 0.85) → auto-masqué + ticket modération créé. Texte passé via OpenAI Moderation API.
Critères d'acceptation
Post avec 10 photos uploadé en < 8 secondes sur connexion 4G standard
Feed se charge en < 1.5s (P95) avec cache Redis
Like optimistic : réponse visuelle < 50ms, sans spinner
Label IA visible sur le post dans les 30 secondes après publication
Report d'un post → ticket modération créé en base dans < 2s
Feed "Trending" recalculé toutes les 5 minutes maximum