package handler import ( "encoding/json" "net/http" "github.com/go-chi/chi/v5" "github.com/prabhupadasays/spsays-bff/internal/middleware" "github.com/prabhupadasays/spsays-bff/internal/service" "github.com/prabhupadasays/spsays-bff/pkg/response" ) // CommentHandler serves the post-comment endpoints: listing top-level comments // + a thread's replies (public), and create / like / pin / delete / report // (auth-required). type CommentHandler struct { svc *service.CommentService } func NewCommentHandler(svc *service.CommentService) *CommentHandler { return &CommentHandler{svc: svc} } type createCommentReq struct { Body string `json:"body"` // ParentID nil → top-level comment; otherwise the comment being replied // to (the service flattens replies onto the thread root). ParentID *string `json:"parent_id"` } type commentLikeResp struct { LikesCount int `json:"likes_count"` } // List — GET /posts/{id}/comments?limit=&offset=. Top-level comments, pinned // first then newest. Public (OptionalAuth populates viewer_has_liked). func (h *CommentHandler) List(w http.ResponseWriter, r *http.Request) { postID := chi.URLParam(r, "id") viewerID := middleware.UserID(r.Context()) limit, offset := parseLimitOffset(r) items, total, err := h.svc.List(r.Context(), postID, viewerID, limit, offset) if err != nil { mapSocialErr(w, err) return } response.JSONWithMeta(w, http.StatusOK, items, paginationMeta(total, limit, offset)) } // Replies — GET /comments/{id}/replies?limit=&offset=. A thread's replies, // oldest first. Public (OptionalAuth). func (h *CommentHandler) Replies(w http.ResponseWriter, r *http.Request) { commentID := chi.URLParam(r, "id") viewerID := middleware.UserID(r.Context()) limit, offset := parseLimitOffset(r) items, total, err := h.svc.Replies(r.Context(), commentID, viewerID, limit, offset) if err != nil { mapSocialErr(w, err) return } response.JSONWithMeta(w, http.StatusOK, items, paginationMeta(total, limit, offset)) } // Create — POST /posts/{id}/comments. Body: {body, parent_id?}. RequireAuth. func (h *CommentHandler) Create(w http.ResponseWriter, r *http.Request) { uid := middleware.UserID(r.Context()) postID := chi.URLParam(r, "id") var req createCommentReq if err := json.NewDecoder(r.Body).Decode(&req); err != nil { response.Error(w, http.StatusBadRequest, "invalid request body") return } // Treat an empty parent_id as a top-level comment, not a (missing) reply. if req.ParentID != nil && *req.ParentID == "" { req.ParentID = nil } c, err := h.svc.Add(r.Context(), uid, postID, req.ParentID, req.Body) if err != nil { mapSocialErr(w, err) return } response.JSON(w, http.StatusCreated, c) } // Like — POST /comments/{id}/like. Idempotent. RequireAuth. func (h *CommentHandler) Like(w http.ResponseWriter, r *http.Request) { uid := middleware.UserID(r.Context()) id := chi.URLParam(r, "id") count, err := h.svc.Like(r.Context(), uid, id) if err != nil { mapSocialErr(w, err) return } response.JSON(w, http.StatusOK, commentLikeResp{LikesCount: count}) } // Unlike — DELETE /comments/{id}/like. Idempotent. RequireAuth. func (h *CommentHandler) Unlike(w http.ResponseWriter, r *http.Request) { uid := middleware.UserID(r.Context()) id := chi.URLParam(r, "id") count, err := h.svc.Unlike(r.Context(), uid, id) if err != nil { mapSocialErr(w, err) return } response.JSON(w, http.StatusOK, commentLikeResp{LikesCount: count}) } // Pin — POST /comments/{id}/pin. Post author only; top-level only. RequireAuth. func (h *CommentHandler) Pin(w http.ResponseWriter, r *http.Request) { uid := middleware.UserID(r.Context()) id := chi.URLParam(r, "id") if err := h.svc.Pin(r.Context(), uid, id); err != nil { mapSocialErr(w, err) return } response.NoContent(w) } // Unpin — DELETE /comments/{id}/pin. Post author only. RequireAuth. func (h *CommentHandler) Unpin(w http.ResponseWriter, r *http.Request) { uid := middleware.UserID(r.Context()) id := chi.URLParam(r, "id") if err := h.svc.Unpin(r.Context(), uid, id); err != nil { mapSocialErr(w, err) return } response.NoContent(w) } // Delete — DELETE /comments/{id}. Comment author or post author. RequireAuth. // For a thread root this removes the whole thread. func (h *CommentHandler) Delete(w http.ResponseWriter, r *http.Request) { uid := middleware.UserID(r.Context()) id := chi.URLParam(r, "id") if err := h.svc.Delete(r.Context(), uid, id); err != nil { mapSocialErr(w, err) return } response.NoContent(w) } // Report — POST /comments/{id}/report. Body: {category, reason?}. RequireAuth. func (h *CommentHandler) Report(w http.ResponseWriter, r *http.Request) { uid := middleware.UserID(r.Context()) id := chi.URLParam(r, "id") var req reportPostReq if err := json.NewDecoder(r.Body).Decode(&req); err != nil { response.Error(w, http.StatusBadRequest, "invalid request body") return } if err := h.svc.Report(r.Context(), uid, id, req.Category, req.Reason); err != nil { mapSocialErr(w, err) return } response.NoContent(w) }