A community based topic aggregation platform built on atproto
1package post
2
3import (
4 "Coves/internal/api/middleware"
5 "Coves/internal/core/posts"
6 "encoding/json"
7 "errors"
8 "log"
9 "net/http"
10)
11
12// DeleteHandler handles post deletion requests
13type DeleteHandler struct {
14 service posts.Service
15}
16
17// NewDeleteHandler creates a new handler for deleting posts
18func NewDeleteHandler(service posts.Service) *DeleteHandler {
19 return &DeleteHandler{
20 service: service,
21 }
22}
23
24// DeletePostInput matches the lexicon input schema for social.coves.community.post.delete
25type DeletePostInput struct {
26 URI string `json:"uri"`
27}
28
29// DeletePostOutput is empty per lexicon specification
30type DeletePostOutput struct{}
31
32// HandleDelete handles post deletion requests
33// POST /xrpc/social.coves.community.post.delete
34//
35// Request body: { "uri": "at://..." }
36// Response: {}
37func (h *DeleteHandler) HandleDelete(w http.ResponseWriter, r *http.Request) {
38 // 1. Check method is POST
39 if r.Method != http.MethodPost {
40 http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
41 return
42 }
43
44 // 2. Limit request body size to prevent DoS attacks (100KB should be plenty for delete requests)
45 r.Body = http.MaxBytesReader(w, r.Body, 100*1024)
46
47 // 3. Parse JSON body into DeletePostInput
48 var input DeletePostInput
49 if err := json.NewDecoder(r.Body).Decode(&input); err != nil {
50 writeError(w, http.StatusBadRequest, "InvalidRequest", "Invalid request body")
51 return
52 }
53
54 // 4. Get OAuth session from context (injected by auth middleware)
55 session := middleware.GetOAuthSession(r)
56 if session == nil {
57 writeError(w, http.StatusUnauthorized, "AuthRequired", "Authentication required")
58 return
59 }
60
61 // 5. Convert input to DeletePostRequest
62 req := posts.DeletePostRequest{
63 URI: input.URI,
64 }
65
66 // 6. Call service to delete post
67 err := h.service.DeletePost(r.Context(), session, req)
68 if err != nil {
69 handleDeleteError(w, err)
70 return
71 }
72
73 // 7. Return empty JSON object per lexicon specification
74 output := DeletePostOutput{}
75
76 w.Header().Set("Content-Type", "application/json")
77 w.WriteHeader(http.StatusOK)
78 if err := json.NewEncoder(w).Encode(output); err != nil {
79 log.Printf("Failed to encode response: %v", err)
80 }
81}
82
83// handleDeleteError maps delete-specific service errors to HTTP responses
84func handleDeleteError(w http.ResponseWriter, err error) {
85 switch {
86 case errors.Is(err, posts.ErrNotFound):
87 writeError(w, http.StatusNotFound, "PostNotFound", "Post not found")
88
89 case errors.Is(err, posts.ErrNotAuthorized):
90 writeError(w, http.StatusForbidden, "NotAuthorized", "You are not authorized to delete this post")
91
92 case errors.Is(err, posts.ErrCommunityNotFound):
93 writeError(w, http.StatusNotFound, "CommunityNotFound", "Community not found")
94
95 case posts.IsValidationError(err):
96 writeError(w, http.StatusBadRequest, "InvalidRequest", err.Error())
97
98 default:
99 // Don't leak internal error details to clients
100 log.Printf("Unexpected error in post delete handler: %v", err)
101 writeError(w, http.StatusInternalServerError, "InternalServerError",
102 "An internal error occurred")
103 }
104}