A community based topic aggregation platform built on atproto
1package vote
2
3import (
4 "Coves/internal/api/handlers"
5 "Coves/internal/api/middleware"
6 "Coves/internal/core/votes"
7 "encoding/json"
8 "log"
9 "net/http"
10)
11
12// CreateVoteHandler handles vote creation
13type CreateVoteHandler struct {
14 service votes.Service
15}
16
17// NewCreateVoteHandler creates a new create vote handler
18func NewCreateVoteHandler(service votes.Service) *CreateVoteHandler {
19 return &CreateVoteHandler{
20 service: service,
21 }
22}
23
24// HandleCreateVote creates a vote or toggles an existing vote
25// POST /xrpc/social.coves.interaction.createVote
26//
27// Request body: { "subject": "at://...", "direction": "up" | "down" }
28func (h *CreateVoteHandler) HandleCreateVote(w http.ResponseWriter, r *http.Request) {
29 if r.Method != http.MethodPost {
30 http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
31 return
32 }
33
34 // Parse request body
35 var req votes.CreateVoteRequest
36
37 if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
38 handlers.WriteError(w, http.StatusBadRequest, "InvalidRequest", "Invalid request body")
39 return
40 }
41
42 if req.Subject == "" {
43 handlers.WriteError(w, http.StatusBadRequest, "InvalidRequest", "subject is required")
44 return
45 }
46
47 if req.Direction == "" {
48 handlers.WriteError(w, http.StatusBadRequest, "InvalidRequest", "direction is required")
49 return
50 }
51
52 if req.Direction != "up" && req.Direction != "down" {
53 handlers.WriteError(w, http.StatusBadRequest, "InvalidRequest", "direction must be 'up' or 'down'")
54 return
55 }
56
57 // Extract authenticated user DID and access token from request context (injected by auth middleware)
58 voterDID := middleware.GetUserDID(r)
59 if voterDID == "" {
60 handlers.WriteError(w, http.StatusUnauthorized, "AuthRequired", "Authentication required")
61 return
62 }
63
64 userAccessToken := middleware.GetUserAccessToken(r)
65 if userAccessToken == "" {
66 handlers.WriteError(w, http.StatusUnauthorized, "AuthRequired", "Missing access token")
67 return
68 }
69
70 // Create vote via service (write-forward to user's PDS)
71 response, err := h.service.CreateVote(r.Context(), voterDID, userAccessToken, req)
72 if err != nil {
73 handleServiceError(w, err)
74 return
75 }
76
77 // Handle toggle-off case (vote was deleted, not created)
78 if response.URI == "" {
79 // Vote was toggled off (deleted)
80 w.Header().Set("Content-Type", "application/json")
81 w.WriteHeader(http.StatusOK)
82 if err := json.NewEncoder(w).Encode(map[string]interface{}{
83 "deleted": true,
84 }); err != nil {
85 log.Printf("Failed to encode response: %v", err)
86 }
87 return
88 }
89
90 // Return success response
91 responseMap := map[string]interface{}{
92 "uri": response.URI,
93 "cid": response.CID,
94 }
95
96 if response.Existing != nil {
97 responseMap["existing"] = *response.Existing
98 }
99
100 w.Header().Set("Content-Type", "application/json")
101 w.WriteHeader(http.StatusOK)
102 if err := json.NewEncoder(w).Encode(responseMap); err != nil {
103 log.Printf("Failed to encode response: %v", err)
104 }
105}
106
107// handleServiceError converts service errors to HTTP responses
108func handleServiceError(w http.ResponseWriter, err error) {
109 switch err {
110 case votes.ErrVoteNotFound:
111 handlers.WriteError(w, http.StatusNotFound, "VoteNotFound", "Vote not found")
112 case votes.ErrSubjectNotFound:
113 handlers.WriteError(w, http.StatusNotFound, "SubjectNotFound", "Post or comment not found")
114 case votes.ErrInvalidDirection:
115 handlers.WriteError(w, http.StatusBadRequest, "InvalidRequest", "Invalid vote direction")
116 case votes.ErrInvalidSubject:
117 handlers.WriteError(w, http.StatusBadRequest, "InvalidRequest", "Invalid subject URI")
118 case votes.ErrVoteAlreadyExists:
119 handlers.WriteError(w, http.StatusConflict, "VoteAlreadyExists", "Vote already exists")
120 case votes.ErrNotAuthorized:
121 handlers.WriteError(w, http.StatusForbidden, "NotAuthorized", "Not authorized")
122 case votes.ErrBanned:
123 handlers.WriteError(w, http.StatusForbidden, "Banned", "User is banned from this community")
124 default:
125 // Check for validation errors
126 log.Printf("Vote creation error: %v", err)
127 handlers.WriteError(w, http.StatusInternalServerError, "InternalError", "Failed to create vote")
128 }
129}