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