157 lines
5.0 KiB
Go
157 lines
5.0 KiB
Go
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)
|
|
}
|