feat: add comments
This commit is contained in:
@@ -0,0 +1,156 @@
|
||||
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)
|
||||
}
|
||||
Reference in New Issue
Block a user