A community based topic aggregation platform built on atproto
1package community 2 3import ( 4 "encoding/json" 5 "net/http" 6 7 "Coves/internal/core/communities" 8) 9 10// SubscribeHandler handles community subscriptions 11type SubscribeHandler struct { 12 service communities.Service 13} 14 15// NewSubscribeHandler creates a new subscribe handler 16func NewSubscribeHandler(service communities.Service) *SubscribeHandler { 17 return &SubscribeHandler{ 18 service: service, 19 } 20} 21 22// HandleSubscribe subscribes a user to a community 23// POST /xrpc/social.coves.community.subscribe 24// Body: { "community": "did:plc:xxx" or "!gaming@coves.social" } 25func (h *SubscribeHandler) HandleSubscribe(w http.ResponseWriter, r *http.Request) { 26 if r.Method != http.MethodPost { 27 http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) 28 return 29 } 30 31 // Parse request body 32 var req struct { 33 Community string `json:"community"` 34 } 35 36 if err := json.NewDecoder(r.Body).Decode(&req); err != nil { 37 writeError(w, http.StatusBadRequest, "InvalidRequest", "Invalid request body") 38 return 39 } 40 41 if req.Community == "" { 42 writeError(w, http.StatusBadRequest, "InvalidRequest", "community is required") 43 return 44 } 45 46 // TODO(Communities-OAuth): Extract authenticated user DID from request context 47 // This MUST be replaced with OAuth middleware before production deployment 48 // Expected implementation: 49 // userDID := r.Context().Value("authenticated_user_did").(string) 50 // For now, we read from header (INSECURE - allows impersonation) 51 userDID := r.Header.Get("X-User-DID") 52 if userDID == "" { 53 writeError(w, http.StatusUnauthorized, "AuthRequired", "Authentication required") 54 return 55 } 56 57 // Subscribe via service (write-forward to PDS) 58 subscription, err := h.service.SubscribeToCommunity(r.Context(), userDID, req.Community) 59 if err != nil { 60 handleServiceError(w, err) 61 return 62 } 63 64 // Return success response 65 response := map[string]interface{}{ 66 "uri": subscription.RecordURI, 67 "cid": subscription.RecordCID, 68 "existing": false, // Would be true if already subscribed 69 } 70 71 w.Header().Set("Content-Type", "application/json") 72 w.WriteHeader(http.StatusOK) 73 json.NewEncoder(w).Encode(response) 74} 75 76// HandleUnsubscribe unsubscribes a user from a community 77// POST /xrpc/social.coves.community.unsubscribe 78// Body: { "community": "did:plc:xxx" or "!gaming@coves.social" } 79func (h *SubscribeHandler) HandleUnsubscribe(w http.ResponseWriter, r *http.Request) { 80 if r.Method != http.MethodPost { 81 http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) 82 return 83 } 84 85 // Parse request body 86 var req struct { 87 Community string `json:"community"` 88 } 89 90 if err := json.NewDecoder(r.Body).Decode(&req); err != nil { 91 writeError(w, http.StatusBadRequest, "InvalidRequest", "Invalid request body") 92 return 93 } 94 95 if req.Community == "" { 96 writeError(w, http.StatusBadRequest, "InvalidRequest", "community is required") 97 return 98 } 99 100 // TODO(Communities-OAuth): Extract authenticated user DID from request context 101 // This MUST be replaced with OAuth middleware before production deployment 102 // Expected implementation: 103 // userDID := r.Context().Value("authenticated_user_did").(string) 104 // For now, we read from header (INSECURE - allows impersonation) 105 userDID := r.Header.Get("X-User-DID") 106 if userDID == "" { 107 writeError(w, http.StatusUnauthorized, "AuthRequired", "Authentication required") 108 return 109 } 110 111 // Unsubscribe via service (delete record on PDS) 112 err := h.service.UnsubscribeFromCommunity(r.Context(), userDID, req.Community) 113 if err != nil { 114 handleServiceError(w, err) 115 return 116 } 117 118 // Return success response 119 w.Header().Set("Content-Type", "application/json") 120 w.WriteHeader(http.StatusOK) 121 json.NewEncoder(w).Encode(map[string]interface{}{ 122 "success": true, 123 }) 124}