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}