A community based topic aggregation platform built on atproto
1package community 2 3import ( 4 "Coves/internal/core/communities" 5 "encoding/json" 6 "log" 7 "net/http" 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 if err := json.NewEncoder(w).Encode(response); err != nil { 74 log.Printf("Failed to encode response: %v", err) 75 } 76} 77 78// HandleUnsubscribe unsubscribes a user from a community 79// POST /xrpc/social.coves.community.unsubscribe 80// Body: { "community": "did:plc:xxx" or "!gaming@coves.social" } 81func (h *SubscribeHandler) HandleUnsubscribe(w http.ResponseWriter, r *http.Request) { 82 if r.Method != http.MethodPost { 83 http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) 84 return 85 } 86 87 // Parse request body 88 var req struct { 89 Community string `json:"community"` 90 } 91 92 if err := json.NewDecoder(r.Body).Decode(&req); err != nil { 93 writeError(w, http.StatusBadRequest, "InvalidRequest", "Invalid request body") 94 return 95 } 96 97 if req.Community == "" { 98 writeError(w, http.StatusBadRequest, "InvalidRequest", "community is required") 99 return 100 } 101 102 // TODO(Communities-OAuth): Extract authenticated user DID from request context 103 // This MUST be replaced with OAuth middleware before production deployment 104 // Expected implementation: 105 // userDID := r.Context().Value("authenticated_user_did").(string) 106 // For now, we read from header (INSECURE - allows impersonation) 107 userDID := r.Header.Get("X-User-DID") 108 if userDID == "" { 109 writeError(w, http.StatusUnauthorized, "AuthRequired", "Authentication required") 110 return 111 } 112 113 // Unsubscribe via service (delete record on PDS) 114 err := h.service.UnsubscribeFromCommunity(r.Context(), userDID, req.Community) 115 if err != nil { 116 handleServiceError(w, err) 117 return 118 } 119 120 // Return success response 121 w.Header().Set("Content-Type", "application/json") 122 w.WriteHeader(http.StatusOK) 123 if err := json.NewEncoder(w).Encode(map[string]interface{}{ 124 "success": true, 125 }); err != nil { 126 log.Printf("Failed to encode response: %v", err) 127 } 128}