A community based topic aggregation platform built on atproto
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// Body: { "community": "did:plc:xxx" or "!gaming@coves.social" } 26func (h *SubscribeHandler) HandleSubscribe(w http.ResponseWriter, r *http.Request) { 27 if r.Method != http.MethodPost { 28 http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) 29 return 30 } 31 32 // Parse request body 33 var req struct { 34 Community string `json:"community"` 35 ContentVisibility int `json:"contentVisibility"` // Optional: 1-5 scale, defaults to 3 36 } 37 38 if err := json.NewDecoder(r.Body).Decode(&req); err != nil { 39 writeError(w, http.StatusBadRequest, "InvalidRequest", "Invalid request body") 40 return 41 } 42 43 if req.Community == "" { 44 writeError(w, http.StatusBadRequest, "InvalidRequest", "community is required") 45 return 46 } 47 48 // Extract authenticated user DID and access token from request context (injected by auth middleware) 49 // Note: contentVisibility defaults and clamping handled by service layer 50 userDID := middleware.GetUserDID(r) 51 if userDID == "" { 52 writeError(w, http.StatusUnauthorized, "AuthRequired", "Authentication required") 53 return 54 } 55 56 userAccessToken := middleware.GetUserAccessToken(r) 57 if userAccessToken == "" { 58 writeError(w, http.StatusUnauthorized, "AuthRequired", "Missing access token") 59 return 60 } 61 62 // Subscribe via service (write-forward to PDS) 63 subscription, err := h.service.SubscribeToCommunity(r.Context(), userDID, userAccessToken, req.Community, req.ContentVisibility) 64 if err != nil { 65 handleServiceError(w, err) 66 return 67 } 68 69 // Return success response 70 response := map[string]interface{}{ 71 "uri": subscription.RecordURI, 72 "cid": subscription.RecordCID, 73 "existing": false, // Would be true if already subscribed 74 } 75 76 w.Header().Set("Content-Type", "application/json") 77 w.WriteHeader(http.StatusOK) 78 if err := json.NewEncoder(w).Encode(response); err != nil { 79 log.Printf("Failed to encode response: %v", err) 80 } 81} 82 83// HandleUnsubscribe unsubscribes a user from a community 84// POST /xrpc/social.coves.community.unsubscribe 85// Body: { "community": "did:plc:xxx" or "!gaming@coves.social" } 86func (h *SubscribeHandler) HandleUnsubscribe(w http.ResponseWriter, r *http.Request) { 87 if r.Method != http.MethodPost { 88 http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) 89 return 90 } 91 92 // Parse request body 93 var req struct { 94 Community string `json:"community"` 95 } 96 97 if err := json.NewDecoder(r.Body).Decode(&req); err != nil { 98 writeError(w, http.StatusBadRequest, "InvalidRequest", "Invalid request body") 99 return 100 } 101 102 if req.Community == "" { 103 writeError(w, http.StatusBadRequest, "InvalidRequest", "community is required") 104 return 105 } 106 107 // Extract authenticated user DID and access token from request context (injected by auth middleware) 108 userDID := middleware.GetUserDID(r) 109 if userDID == "" { 110 writeError(w, http.StatusUnauthorized, "AuthRequired", "Authentication required") 111 return 112 } 113 114 userAccessToken := middleware.GetUserAccessToken(r) 115 if userAccessToken == "" { 116 writeError(w, http.StatusUnauthorized, "AuthRequired", "Missing access token") 117 return 118 } 119 120 // Unsubscribe via service (delete record on PDS) 121 err := h.service.UnsubscribeFromCommunity(r.Context(), userDID, userAccessToken, req.Community) 122 if err != nil { 123 handleServiceError(w, err) 124 return 125 } 126 127 // Return success response 128 w.Header().Set("Content-Type", "application/json") 129 w.WriteHeader(http.StatusOK) 130 if err := json.NewEncoder(w).Encode(map[string]interface{}{ 131 "success": true, 132 }); err != nil { 133 log.Printf("Failed to encode response: %v", err) 134 } 135}