Skip to content

Module — Commentaires & modération

Synchronisé avec le code. Mettre à jour à chaque changement structurel.

Rôle

  • Recueillir les avis clients (commentaire libre + note 0-5) sur les voyages publiés depuis le site web / app mobile.
  • Permettre la modération de ces avis depuis Horizon (Pages/Comments) : approuver, désapprouver, éditer le texte, supprimer.
  • Permettre à un admin Buchard de répondre à un avis client (réponse single-level signée « Buchard Voyages »).

Emplacement

  • Entité : Domain/Entities/TravelComment.cs
  • DbSet : ApplicationDbContext.TravelComments + self-ref FK config (cf. OnModelCreating)
  • Service : méthodes *Comment* / AddAdminReply sur TravelService (interface ITravelService)
  • API client (création) : POST /api/travels/{id}/comments sur Areas/Api/TravelController
  • API client (lifecycle) : Areas/Api/CommentController — GET un, GET mine, PUT, DELETE
  • Back-office modération : Pages/Comments/Index.cshtml{.cs} + _ListActions.cshtml
  • DTO API client : Application/Dtos/Api/Travel/TravelCommentDto.cs
  • DTO listing back-office : Application/Dtos/SearchResult/TravelCommentSearchResultDto.cs
  • Search model : Domain/Models/Search/TravelCommentSearchModel.cs

Entités principales

TravelComment

  • Id, TravelId (FK), CustomerId? (FK, nullable pour les avis legacy sans auteur lié)
  • Author (string, libre), Comment, Rating? (1-5)
  • IsApproved — modération
  • ParentCommentId? — self-ref pour la réponse Buchard (null = commentaire racine client)
  • Navigation : Travel, Customer, ParentComment, Replies (collection)
  • Constante : TravelComment.BuchardReplyAuthorName = "Buchard Voyages"

Règles métier spécifiques

  • Création client (POST /api/travels/{id}/comments) : serveur force IsApproved = false + CustomerId = customer.Id (anti-bypass, cf. commit 2946f14d).
  • Moyenne d'étoiles d'un voyage (Travel.CommentsAverageRating, Travel.CommentsCount) ne compte que les IsApproved && Rating.HasValue.
  • Réponse Buchard — règles :
    • Author = "Buchard Voyages", IsApproved = true, Rating = null, CustomerId = null, TravelId hérité du parent — tout est forcé côté service, jamais lu de l'input.
    • Single-level : refus si parent.ParentCommentId != null.
    • Un seul reply par parent : refus si parent.Replies.Any().
    • Texte non vide.
  • Cascade delete : DeleteComment / BulkDeleteComments suppriment manuellement les Replies avant le parent (la FK est Restrict côté SQL — SQL Server ne supporte pas Cascade sur self-ref).
  • Listing modération (SearchComments) ne renvoie que les racines (ParentCommentId == null) avec Include(Replies). La projection DTO aplatit la (seule possible) réponse.
  • Badge "en attente" : GetPendingCommentsCount filtre ParentCommentId == null && !IsApproved (défense en profondeur).

API publique vs back-office

  • API publique (/api/comments, /api/travels/{id}/comments) : opère uniquement sur les avis clients racine. Le DTO TravelCommentDto n'expose pas ParentCommentId ni les Replies à ce stade — les réponses Buchard sont back-office only (choix produit explicite, à reconsidérer si on veut les afficher sur le site/app).
  • Back-office (Pages/Comments) : modération + réponses, gated [Authorize(Roles = $"{Roles.ADMINISTRATOR},{Roles.PRODUCTION}")]. Les deux rôles ont strictement le même accès sur toutes les actions (voir, approuver, désapprouver, éditer, répondre, supprimer, bulk, badge pending). Dans _Layout.cshtml, l'item "Commentaires" est placé en top-level juste après "Tableau de bord" (visible Admin + Production), volontairement hors de la section "Administration" qui reste Admin-only (Utilisateurs, Paramètres).

Page de modération — UI

  • DataTable colonnes : Checkbox / Statut / Voyage / Auteur / Note / Commentaire / Réponse Buchard / Date / Actions.
  • Colonne « Réponse Buchard » : badge Répondu (+ tooltip + preview tronquée) si hasReply, sinon .
  • Menu Actions (toggle conditionnel sur rowData.hasReply) :
    • Toujours : Voir, Éditer (le commentaire racine), Approuver/Désapprouver (selon isApproved), Supprimer.
    • Si !hasReply : Répondre (ouvre kt_comments_modal_replyPOST /Comments/Reply).
    • Si hasReply : Modifier la réponse (réutilise kt_comments_modal_edit via data-edit-target="reply", prefill replyComment, target = replyId, va sur POST /Comments/Edit) + Supprimer la réponse (POST /Comments/DeleteReply).
  • Modale « Voir » : affiche le commentaire client + bloc « Réponse Buchard Voyages » en dessous si hasReply.
  • Bulk actions inchangées : approve / disapprove / delete (cascade sur les replies).

Points d'attention / pièges

  • La FK self-ref est en Restrict — toute logique de suppression doit cascader manuellement. Ne pas changer ça côté EF sans tester (SQL Server refuse cascade self-ref, c'est intentionnel).
  • Le Author libre n'est pas garanti unique ni typé — "Buchard Voyages" n'est PAS un marqueur fiable pour différencier reply ≠ commentaire racine. Toujours utiliser ParentCommentId comme source de vérité.
  • L'AutoMapper TravelCommentDto ↔ TravelComment est un ReverseMap() brut — toute nouvelle propriété sur le DTO sera automatiquement mappée. Si on ajoute des champs sensibles côté DTO un jour, refaire le ReverseMap manuellement.
  • Travel.CommentsAverageRating ignore les replies naturellement (elles ont Rating = null), mais si on les expose un jour côté API/front, vérifier qu'on n'inclut pas par accident Replies dans le compte.
  • Migrations EF — drift Identity : le tooling dotnet ef 6.0.9 vs runtime 8.0.7 génère systématiquement des AlterColumn parasites sur AspNetUserTokens/AspNetUserLogins. Convention du projet : les supprimer manuellement de la migration générée (cf. 20260518085341_AddTravelCommentParent.cs).

Conventions locales

  • Menus d'action dans _ListActions.cshtml : classes menu-item-approve / menu-item-disapprove / menu-item-reply / menu-item-edit-reply / menu-item-delete-reply togglées en JS dans Index.cshtml.
  • Endpoint Razor pattern : OnPostXxx(Guid id, [FromForm] ...) retourne OkResult() / BadRequest(errors) consommé par AJAX (toaster + reload datatable).

Dépendances

  • Travel (FK), Customer (FK nullable).
  • Travel.CommentsAverageRating / Travel.CommentsCount (NotMapped getters sur Travel).
  • → Badge admin menu : OnGetPendingCount consommé par _Layout.cshtml > refreshCommentsPendingBadge.

Contributors

No contributors

Changelog

No recent changes