A community based topic aggregation platform built on atproto
at main 138 lines 4.4 kB view raw
1package community 2 3import ( 4 "Coves/internal/api/middleware" 5 "Coves/internal/core/communities" 6 "encoding/json" 7 "log" 8 "net/http" 9) 10 11// SubscribeHandler handles community subscriptions 12type SubscribeHandler struct { 13 service communities.Service 14} 15 16// NewSubscribeHandler creates a new subscribe handler 17func NewSubscribeHandler(service communities.Service) *SubscribeHandler { 18 return &SubscribeHandler{ 19 service: service, 20 } 21} 22 23// HandleSubscribe subscribes a user to a community 24// POST /xrpc/social.coves.community.subscribe 25// 26// Request body: { "community": "<identifier>", "contentVisibility": 3 } 27// Where <identifier> can be: 28// - DID: did:plc:xxx 29// - Canonical handle: c-name.coves.social 30// - Scoped identifier: !name@coves.social 31// - At-identifier: @c-name.coves.social 32func (h *SubscribeHandler) HandleSubscribe(w http.ResponseWriter, r *http.Request) { 33 if r.Method != http.MethodPost { 34 http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) 35 return 36 } 37 38 // Parse request body 39 var req struct { 40 Community string `json:"community"` // DID, handle, or scoped identifier 41 ContentVisibility int `json:"contentVisibility"` // Optional: 1-5 scale, defaults to 3 42 } 43 44 if err := json.NewDecoder(r.Body).Decode(&req); err != nil { 45 writeError(w, http.StatusBadRequest, "InvalidRequest", "Invalid request body") 46 return 47 } 48 49 if req.Community == "" { 50 writeError(w, http.StatusBadRequest, "InvalidRequest", "community is required") 51 return 52 } 53 54 // Get OAuth session from context (injected by auth middleware) 55 // The session contains the user's DID and credentials needed for DPoP authentication 56 session := middleware.GetOAuthSession(r) 57 if session == nil { 58 writeError(w, http.StatusUnauthorized, "AuthRequired", "Authentication required") 59 return 60 } 61 62 // Subscribe via service (write-forward to PDS with DPoP authentication) 63 // Service handles identifier resolution (DIDs, handles, scoped identifiers) 64 subscription, err := h.service.SubscribeToCommunity(r.Context(), session, req.Community, req.ContentVisibility) 65 if err != nil { 66 handleServiceError(w, err) 67 return 68 } 69 70 // Return success response 71 response := map[string]interface{}{ 72 "uri": subscription.RecordURI, 73 "cid": subscription.RecordCID, 74 "existing": false, // Would be true if already subscribed 75 } 76 77 w.Header().Set("Content-Type", "application/json") 78 w.WriteHeader(http.StatusOK) 79 if err := json.NewEncoder(w).Encode(response); err != nil { 80 log.Printf("Failed to encode response: %v", err) 81 } 82} 83 84// HandleUnsubscribe unsubscribes a user from a community 85// POST /xrpc/social.coves.community.unsubscribe 86// 87// Request body: { "community": "<identifier>" } 88// Where <identifier> can be: 89// - DID: did:plc:xxx 90// - Canonical handle: c-name.coves.social 91// - Scoped identifier: !name@coves.social 92// - At-identifier: @c-name.coves.social 93func (h *SubscribeHandler) HandleUnsubscribe(w http.ResponseWriter, r *http.Request) { 94 if r.Method != http.MethodPost { 95 http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) 96 return 97 } 98 99 // Parse request body 100 var req struct { 101 Community string `json:"community"` // DID, handle, or scoped identifier 102 } 103 104 if err := json.NewDecoder(r.Body).Decode(&req); err != nil { 105 writeError(w, http.StatusBadRequest, "InvalidRequest", "Invalid request body") 106 return 107 } 108 109 if req.Community == "" { 110 writeError(w, http.StatusBadRequest, "InvalidRequest", "community is required") 111 return 112 } 113 114 // Get OAuth session from context (injected by auth middleware) 115 // The session contains the user's DID and credentials needed for DPoP authentication 116 session := middleware.GetOAuthSession(r) 117 if session == nil { 118 writeError(w, http.StatusUnauthorized, "AuthRequired", "Authentication required") 119 return 120 } 121 122 // Unsubscribe via service (delete record on PDS with DPoP authentication) 123 // Service handles identifier resolution (DIDs, handles, scoped identifiers) 124 err := h.service.UnsubscribeFromCommunity(r.Context(), session, req.Community) 125 if err != nil { 126 handleServiceError(w, err) 127 return 128 } 129 130 // Return success response 131 w.Header().Set("Content-Type", "application/json") 132 w.WriteHeader(http.StatusOK) 133 if err := json.NewEncoder(w).Encode(map[string]interface{}{ 134 "success": true, 135 }); err != nil { 136 log.Printf("Failed to encode response: %v", err) 137 } 138}