A community based topic aggregation platform built on atproto
1package comments
2
3import (
4 "Coves/internal/api/middleware"
5 "Coves/internal/core/comments"
6 "encoding/json"
7 "log"
8 "net/http"
9)
10
11// CreateCommentHandler handles comment creation requests
12type CreateCommentHandler struct {
13 service comments.Service
14}
15
16// NewCreateCommentHandler creates a new handler for creating comments
17func NewCreateCommentHandler(service comments.Service) *CreateCommentHandler {
18 return &CreateCommentHandler{
19 service: service,
20 }
21}
22
23// CreateCommentInput matches the lexicon input schema for social.coves.community.comment.create
24type CreateCommentInput struct {
25 Reply struct {
26 Root struct {
27 URI string `json:"uri"`
28 CID string `json:"cid"`
29 } `json:"root"`
30 Parent struct {
31 URI string `json:"uri"`
32 CID string `json:"cid"`
33 } `json:"parent"`
34 } `json:"reply"`
35 Content string `json:"content"`
36 Facets []interface{} `json:"facets,omitempty"`
37 Embed interface{} `json:"embed,omitempty"`
38 Langs []string `json:"langs,omitempty"`
39 Labels interface{} `json:"labels,omitempty"`
40}
41
42// CreateCommentOutput matches the lexicon output schema
43type CreateCommentOutput struct {
44 URI string `json:"uri"`
45 CID string `json:"cid"`
46}
47
48// HandleCreate handles comment creation requests
49// POST /xrpc/social.coves.community.comment.create
50//
51// Request body: { "reply": { "root": {...}, "parent": {...} }, "content": "..." }
52// Response: { "uri": "at://...", "cid": "..." }
53func (h *CreateCommentHandler) HandleCreate(w http.ResponseWriter, r *http.Request) {
54 // 1. Check method is POST
55 if r.Method != http.MethodPost {
56 http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
57 return
58 }
59
60 // 2. Limit request body size to prevent DoS attacks (100KB should be plenty for comments)
61 r.Body = http.MaxBytesReader(w, r.Body, 100*1024)
62
63 // 3. Parse JSON body into CreateCommentInput
64 var input CreateCommentInput
65 if err := json.NewDecoder(r.Body).Decode(&input); err != nil {
66 writeError(w, http.StatusBadRequest, "InvalidRequest", "Invalid request body")
67 return
68 }
69
70 // 4. Get OAuth session from context (injected by auth middleware)
71 session := middleware.GetOAuthSession(r)
72 if session == nil {
73 writeError(w, http.StatusUnauthorized, "AuthRequired", "Authentication required")
74 return
75 }
76
77 // 5. Convert labels interface{} to *comments.SelfLabels if provided
78 var labels *comments.SelfLabels
79 if input.Labels != nil {
80 labelsJSON, err := json.Marshal(input.Labels)
81 if err != nil {
82 writeError(w, http.StatusBadRequest, "InvalidLabels", "Invalid labels format")
83 return
84 }
85 var selfLabels comments.SelfLabels
86 if err := json.Unmarshal(labelsJSON, &selfLabels); err != nil {
87 writeError(w, http.StatusBadRequest, "InvalidLabels", "Invalid labels structure")
88 return
89 }
90 labels = &selfLabels
91 }
92
93 // 6. Convert input to CreateCommentRequest
94 req := comments.CreateCommentRequest{
95 Reply: comments.ReplyRef{
96 Root: comments.StrongRef{
97 URI: input.Reply.Root.URI,
98 CID: input.Reply.Root.CID,
99 },
100 Parent: comments.StrongRef{
101 URI: input.Reply.Parent.URI,
102 CID: input.Reply.Parent.CID,
103 },
104 },
105 Content: input.Content,
106 Facets: input.Facets,
107 Embed: input.Embed,
108 Langs: input.Langs,
109 Labels: labels,
110 }
111
112 // 7. Call service to create comment
113 response, err := h.service.CreateComment(r.Context(), session, req)
114 if err != nil {
115 handleServiceError(w, err)
116 return
117 }
118
119 // 8. Return JSON response with URI and CID
120 output := CreateCommentOutput{
121 URI: response.URI,
122 CID: response.CID,
123 }
124
125 w.Header().Set("Content-Type", "application/json")
126 w.WriteHeader(http.StatusOK)
127 if err := json.NewEncoder(w).Encode(output); err != nil {
128 log.Printf("Failed to encode response: %v", err)
129 }
130}