Commentaires
Système de commentaires threadés sur les médias, avec réponses jusqu’à COMMENT_MAX_DEPTH niveaux (défaut : 5, soit depth ∈ [0, 4]).
Modèle
Section titled “Modèle”| Champ | Notes |
|---|---|
id | UUID v4 du commentaire. |
mediaId | UUID du média porteur. |
userId | UUID de l’auteur. |
parentId | UUID du commentaire parent, ou null pour un top-level. |
rootId | UUID du top-level ancestral. Pour un top-level, rootId = id (dénormalisation : permet le fetch d’un thread complet avec un seul WHERE root_id = ?, sans CTE récursive). |
depth | 0 pour un top-level, +1 par niveau de réponse. Plafonné à COMMENT_MAX_DEPTH - 1. |
body | Texte du commentaire. null quand le commentaire est soft-supprimé. |
replyCount | Nombre d’enfants directs non supprimés. Maintenu applicativement. |
createdAt | Date d’écriture. |
editedAt | Date de la dernière édition ; null si jamais édité. |
deletedAt | Date de soft-delete ; null si actif. |
Quand deletedAt !== null : la ligne est conservée pour ne pas orphelinser les enfants, mais body est renvoyé à null et meta.deleted = true est ajouté à la ressource.
Compteurs
Section titled “Compteurs”media_comment.reply_count: enfants directs non supprimés (utilisé pour les listings de réponses).media_stats.comments_count: top-level non supprimés (exposé dans la ressource Media en tant quecommentsCount, utilisé pourmeta.totaldu listing/api/media/{mediaId}/comments).
Les deux sont maintenus dans la même transaction que l’INSERT / soft-delete par MediaCommentService. Contrairement aux likes/dislikes (triggers), le choix applicatif évite des triggers complexes autour du soft-delete.
Variables d’environnement
Section titled “Variables d’environnement”| Var | Défaut | Rôle |
|---|---|---|
COMMENT_MAX_LENGTH | 2000 | Longueur max du body (en caractères UTF-8, via mb_strlen). |
COMMENT_MAX_DEPTH | 5 | Niveaux de profondeur autorisés (depth ∈ [0, max-1]). |
COMMENT_EDIT_WINDOW_MINUTES | 15 | Fenêtre d’édition après création. 0 = édition désactivée, -1 = illimitée, N>0 = N minutes. |
COMMENT_DELETE_POLICY | both | Qui peut soft-supprimer : author, owner (propriétaire du média), ou both. |
Notifications
Section titled “Notifications”- Top-level → l’owner du média reçoit
media.comment.received(déduplique sur(media, actor)dans la fenêtre). - Réponse → l’auteur du commentaire parent reçoit
media.comment.reply.received(déduplique sur(parent, actor)). - Pas de notif sur soi-même (commenter / répondre à son propre contenu reste autorisé mais silencieux).
- Pas de notif lors de l’édition ni du soft-delete.
Indexation Meili
Section titled “Indexation Meili”Les commentaires ne sont pas indexés dans Meilisearch. Le volume peut être élevé et il n’y a pas de cas d’usage de recherche full-text aujourd’hui ; un index pourra être ajouté plus tard sans casser l’API (les ressources resteront identiques).
GET /api/media/{mediaId}/comments
Section titled “GET /api/media/{mediaId}/comments”Liste les commentaires top-level d’un média, du plus récent au plus ancien. Paginé en keyset (cursor) — convention partagée avec les autres listings. Public (pas d’auth requise).
Query :
limit(1..100, def 20)cursor/before(mutuellement exclusifs)
meta.total lit le compteur dénormalisé media_stats.comments_count (top-level non supprimés). Les commentaires soft-supprimés sont retournés avec body: null + meta.deleted = true.
Erreurs :
404 Media not found400 Invalid cursor422 Invalid media id
POST /api/media/{mediaId}/comments
Section titled “POST /api/media/{mediaId}/comments”Crée un commentaire top-level OU une réponse. Auth requise (Bearer). L’utilisateur doit avoir confirmé son e-mail et ne pas être banni.
Body (flat ou JSON:API) :
{ "body": "Joli cliché !", "parentId": null }{ "data": { "type": "mediaComments", "attributes": { "body": "Merci pour la réponse 🙏", "parentId": "0192c4e3-…" } }}body(string, requis, trimé non-vide, ≤COMMENT_MAX_LENGTH).parentId(UUID, optionnel). Si renseigné, le parent doit appartenir au même média, ne pas être soft-supprimé, etparent.depth + 1doit rester <COMMENT_MAX_DEPTH.
Réponse 201 : ressource mediaComments (cf. modèle plus haut). CreateMediaCommentAction
Erreurs (meta.code) :
422 comment.bodyMissing— body absent / vide après trim.422 comment.bodyTooLong— >COMMENT_MAX_LENGTH.404 comment.mediaNotFound— média inconnu.404 comment.parentNotFound— parent inconnu ou rattaché à un autre média.403 comment.parentDeleted— répondre à un commentaire supprimé est refusé.422 comment.depthExceeded— profondeur max atteinte.403 comment.actorNotConfirmed/comment.actorBanned.
GET /api/media/comments/{commentId}/thread
Section titled “GET /api/media/comments/{commentId}/thread”Renvoie le thread complet ancré au top-level dont dépend commentId. Si commentId pointe sur une réponse profonde, on remonte automatiquement à son rootId — la réponse est donc toujours la conversation entière (utile pour un deep-link arrivant depuis une notification).
Pas de pagination (le thread est borné par COMMENT_MAX_DEPTH). Les nœuds sont retournés à plat, triés par (depth ASC, createdAt ASC, id ASC) ; le client reconstruit l’arbre via parentId.
Erreurs :
404 Comment not found422 Invalid comment id
GET /api/media/comments/{commentId}/replies
Section titled “GET /api/media/comments/{commentId}/replies”Liste les enfants directs d’un commentaire, du plus ancien au plus récent (ordre chronologique de réponse). Keyset-paginé.
Query : limit (1..100, def 20), cursor / before.
meta.total reprend le replyCount du parent (dénormalisé). ListMediaCommentRepliesAction
Erreurs :
404 Comment not found400 Invalid cursor422 Invalid comment id
PATCH /api/media/comments/{commentId}
Section titled “PATCH /api/media/comments/{commentId}”Édite le body d’un commentaire. Auth requise. Seul l’auteur peut éditer, et uniquement dans la fenêtre COMMENT_EDIT_WINDOW_MINUTES (0 = édition désactivée, -1 = illimitée).
Body (flat ou JSON:API) — mêmes shapes que la création, mais sans parentId (le re-parentage n’est jamais autorisé) :
{ "body": "..." }Réponse 200 : ressource mise à jour avec editedAt rempli. UpdateMediaCommentAction
Erreurs (meta.code) :
422 comment.bodyMissing/comment.bodyTooLong.404 comment.notFound.403 comment.forbidden— pas l’auteur.410 comment.alreadyDeleted— éditer un commentaire supprimé est refusé.403 comment.editWindowClosed— fenêtre expirée (ouCOMMENT_EDIT_WINDOW_MINUTES=0).403 comment.actorNotConfirmed/comment.actorBanned.
DELETE /api/media/comments/{commentId}
Section titled “DELETE /api/media/comments/{commentId}”Soft-supprime le commentaire selon COMMENT_DELETE_POLICY :
author— seul l’auteur peut supprimer.owner— seul le propriétaire du média peut supprimer.both(défaut) — l’un ou l’autre.
La ligne est conservée pour ne pas orphelinser ses enfants. Le body cesse d’être renvoyé (null) et meta.deleted = true est ajouté à la ressource. Les compteurs reply_count du parent (si réponse) ou media_stats.comments_count (si top-level) sont décrémentés dans la même transaction.
Réponse 200 : ressource redactée (pas de 204 pour permettre au client de mettre à jour sa vue en place). DeleteMediaCommentAction
Erreurs (meta.code) :
404 comment.notFound.410 comment.alreadyDeleted— opération idempotente côté UX, mais l’API signale l’état.403 comment.forbidden— la politique en vigueur n’autorise pas le caller.403 comment.actorNotConfirmed/comment.actorBanned.