A community based topic aggregation platform built on atproto
1package vote
2
3import (
4 "Coves/internal/api/middleware"
5 "Coves/internal/core/votes"
6 "encoding/json"
7 "log"
8 "net/http"
9)
10
11// CreateVoteHandler handles vote creation
12type CreateVoteHandler struct {
13 service votes.Service
14}
15
16// NewCreateVoteHandler creates a new create vote handler
17func NewCreateVoteHandler(service votes.Service) *CreateVoteHandler {
18 return &CreateVoteHandler{
19 service: service,
20 }
21}
22
23// CreateVoteInput represents the request body for creating a vote
24type CreateVoteInput struct {
25 Subject struct {
26 URI string `json:"uri"`
27 CID string `json:"cid"`
28 } `json:"subject"`
29 Direction string `json:"direction"`
30}
31
32// CreateVoteOutput represents the response body for creating a vote
33type CreateVoteOutput struct {
34 URI string `json:"uri"`
35 CID string `json:"cid"`
36}
37
38// HandleCreateVote creates a vote on a post or comment
39// POST /xrpc/social.coves.vote.create
40//
41// Request body: { "subject": { "uri": "at://...", "cid": "..." }, "direction": "up" }
42// Response: { "uri": "at://...", "cid": "..." }
43//
44// Behavior:
45// - If no vote exists: creates new vote with given direction
46// - If vote exists with same direction: deletes vote (toggle off)
47// - If vote exists with different direction: updates to new direction
48func (h *CreateVoteHandler) HandleCreateVote(w http.ResponseWriter, r *http.Request) {
49 if r.Method != http.MethodPost {
50 http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
51 return
52 }
53
54 // Parse request body
55 var input CreateVoteInput
56 if err := json.NewDecoder(r.Body).Decode(&input); err != nil {
57 writeError(w, http.StatusBadRequest, "InvalidRequest", "Invalid request body")
58 return
59 }
60
61 // Validate required fields
62 if input.Subject.URI == "" {
63 writeError(w, http.StatusBadRequest, "InvalidRequest", "subject.uri is required")
64 return
65 }
66 if input.Subject.CID == "" {
67 writeError(w, http.StatusBadRequest, "InvalidRequest", "subject.cid is required")
68 return
69 }
70 if input.Direction == "" {
71 writeError(w, http.StatusBadRequest, "InvalidRequest", "direction is required")
72 return
73 }
74
75 // Validate direction
76 if input.Direction != "up" && input.Direction != "down" {
77 writeError(w, http.StatusBadRequest, "InvalidRequest", "direction must be 'up' or 'down'")
78 return
79 }
80
81 // Get OAuth session from context (injected by auth middleware)
82 session := middleware.GetOAuthSession(r)
83 if session == nil {
84 writeError(w, http.StatusUnauthorized, "AuthRequired", "Authentication required")
85 return
86 }
87
88 // Create vote request
89 req := votes.CreateVoteRequest{
90 Subject: votes.StrongRef{
91 URI: input.Subject.URI,
92 CID: input.Subject.CID,
93 },
94 Direction: input.Direction,
95 }
96
97 // Call service to create vote
98 response, err := h.service.CreateVote(r.Context(), session, req)
99 if err != nil {
100 handleServiceError(w, err)
101 return
102 }
103
104 // Return success response
105 output := CreateVoteOutput{
106 URI: response.URI,
107 CID: response.CID,
108 }
109
110 w.Header().Set("Content-Type", "application/json")
111 w.WriteHeader(http.StatusOK)
112 if err := json.NewEncoder(w).Encode(output); err != nil {
113 log.Printf("Failed to encode response: %v", err)
114 }
115}