package handler import ( "context" "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" ) // RelationshipHandler exposes the follow / block / report actions, all // scoped to a target user identified by {handle} in the path and a // signed-in viewer pulled from the auth middleware. Every mutating // endpoint returns the recomputed RelationshipResult (viewer's state + // the target's live counts) so the frontend updates its buttons and // stat line from the single response — except Report, which returns 204. // // These routes live under /api/v1/u/{handle}/… in a RequireAuth group; // the public profile GET stays anonymous-friendly in its own group. type RelationshipHandler struct{ svc *service.SocialGraphService } func NewRelationshipHandler(svc *service.SocialGraphService) *RelationshipHandler { return &RelationshipHandler{svc: svc} } // Follow sends a follow REQUEST (pending until the target accepts). func (h *RelationshipHandler) Follow(w http.ResponseWriter, r *http.Request) { h.act(w, r, h.svc.RequestFollow) } // Unfollow removes the viewer's edge — stops following or cancels a // still-pending request. func (h *RelationshipHandler) Unfollow(w http.ResponseWriter, r *http.Request) { h.act(w, r, h.svc.Unfollow) } // AcceptFollow accepts the target's pending request to follow the viewer. func (h *RelationshipHandler) AcceptFollow(w http.ResponseWriter, r *http.Request) { h.act(w, r, h.svc.AcceptFollow) } // RejectFollow declines the target's pending request. func (h *RelationshipHandler) RejectFollow(w http.ResponseWriter, r *http.Request) { h.act(w, r, h.svc.RejectFollow) } // RemoveFollower drops the target from the viewer's followers. func (h *RelationshipHandler) RemoveFollower(w http.ResponseWriter, r *http.Request) { h.act(w, r, h.svc.RemoveFollower) } // Block blocks the target (and severs follows both ways). func (h *RelationshipHandler) Block(w http.ResponseWriter, r *http.Request) { h.act(w, r, h.svc.Block) } // Unblock removes the viewer's block on the target. func (h *RelationshipHandler) Unblock(w http.ResponseWriter, r *http.Request) { h.act(w, r, h.svc.Unblock) } type reportReq struct { Category string `json:"category"` Reason string `json:"reason"` } // Report files a report against the target. Body: {category, reason}. func (h *RelationshipHandler) Report(w http.ResponseWriter, r *http.Request) { viewerID := middleware.UserID(r.Context()) handle := chi.URLParam(r, "handle") var req reportReq if err := json.NewDecoder(r.Body).Decode(&req); err != nil { response.Error(w, http.StatusBadRequest, "invalid JSON body") return } if err := h.svc.Report(r.Context(), viewerID, handle, req.Category, req.Reason); err != nil { mapSocialErr(w, err) return } response.NoContent(w) } // act is the shared shim for every RelationshipResult-returning action: // pull viewer + handle, run the service method, map errors, return JSON. func (h *RelationshipHandler) act( w http.ResponseWriter, r *http.Request, fn func(ctx context.Context, viewerID, handle string) (*service.RelationshipResult, error), ) { viewerID := middleware.UserID(r.Context()) handle := chi.URLParam(r, "handle") res, err := fn(r.Context(), viewerID, handle) if err != nil { mapSocialErr(w, err) return } response.JSON(w, http.StatusOK, res) }