A community based topic aggregation platform built on atproto
at main 4.4 kB view raw
1package aggregator 2 3import ( 4 "Coves/internal/core/aggregators" 5 "encoding/json" 6 "log" 7 "net/http" 8 "strconv" 9) 10 11// GetAuthorizationsHandler handles listing authorizations for an aggregator 12type GetAuthorizationsHandler struct { 13 service aggregators.Service 14} 15 16// NewGetAuthorizationsHandler creates a new get authorizations handler 17func NewGetAuthorizationsHandler(service aggregators.Service) *GetAuthorizationsHandler { 18 return &GetAuthorizationsHandler{ 19 service: service, 20 } 21} 22 23// HandleGetAuthorizations lists all communities that authorized an aggregator 24// GET /xrpc/social.coves.aggregator.getAuthorizations?aggregatorDid=did:plc:abc123&enabledOnly=true&limit=50&cursor=xyz 25// Following Bluesky's pattern for listing feed subscribers 26func (h *GetAuthorizationsHandler) HandleGetAuthorizations(w http.ResponseWriter, r *http.Request) { 27 if r.Method != http.MethodGet { 28 http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) 29 return 30 } 31 32 // Parse request 33 req, err := h.parseRequest(r) 34 if err != nil { 35 writeError(w, http.StatusBadRequest, "InvalidRequest", err.Error()) 36 return 37 } 38 39 // Get aggregator details first (needed for nested aggregator object in response) 40 agg, err := h.service.GetAggregator(r.Context(), req.AggregatorDID) 41 if err != nil { 42 if aggregators.IsNotFound(err) { 43 writeError(w, http.StatusNotFound, "AggregatorNotFound", "Aggregator DID does not exist or has no service declaration") 44 return 45 } 46 handleServiceError(w, err) 47 return 48 } 49 50 // Get authorizations from service 51 auths, err := h.service.GetAuthorizationsForAggregator(r.Context(), req) 52 if err != nil { 53 handleServiceError(w, err) 54 return 55 } 56 57 // Build response 58 response := GetAuthorizationsResponse{ 59 Authorizations: make([]CommunityAuthView, 0, len(auths)), 60 } 61 62 // Convert aggregator to view for nesting in each authorization 63 aggregatorView := toAggregatorView(agg) 64 65 for _, auth := range auths { 66 response.Authorizations = append(response.Authorizations, toCommunityAuthView(auth, aggregatorView)) 67 } 68 69 // Return response 70 w.Header().Set("Content-Type", "application/json") 71 w.WriteHeader(http.StatusOK) 72 if err := json.NewEncoder(w).Encode(response); err != nil { 73 log.Printf("ERROR: Failed to encode getAuthorizations response: %v", err) 74 } 75} 76 77// parseRequest parses query parameters 78func (h *GetAuthorizationsHandler) parseRequest(r *http.Request) (aggregators.GetAuthorizationsRequest, error) { 79 req := aggregators.GetAuthorizationsRequest{} 80 81 // Required: aggregatorDid 82 req.AggregatorDID = r.URL.Query().Get("aggregatorDid") 83 84 // Optional: enabledOnly (default: false) 85 if enabledOnlyStr := r.URL.Query().Get("enabledOnly"); enabledOnlyStr == "true" { 86 req.EnabledOnly = true 87 } 88 89 // Optional: limit (default: 50, set by service) 90 if limitStr := r.URL.Query().Get("limit"); limitStr != "" { 91 if limit, err := strconv.Atoi(limitStr); err == nil { 92 req.Limit = limit 93 } 94 } 95 96 // Optional: offset (default: 0) 97 if offsetStr := r.URL.Query().Get("offset"); offsetStr != "" { 98 if offset, err := strconv.Atoi(offsetStr); err == nil { 99 req.Offset = offset 100 } 101 } 102 103 return req, nil 104} 105 106// GetAuthorizationsResponse matches the lexicon output 107type GetAuthorizationsResponse struct { 108 Cursor *string `json:"cursor,omitempty"` 109 Authorizations []CommunityAuthView `json:"authorizations"` 110} 111 112// CommunityAuthView matches social.coves.aggregator.defs#communityAuthView 113// Shows authorization from aggregator's perspective with nested aggregator details 114type CommunityAuthView struct { 115 Config interface{} `json:"config,omitempty"` 116 Aggregator AggregatorView `json:"aggregator"` 117 CreatedAt string `json:"createdAt"` 118 RecordUri string `json:"recordUri,omitempty"` 119 Enabled bool `json:"enabled"` 120} 121 122// toCommunityAuthView converts domain model to API view 123func toCommunityAuthView(auth *aggregators.Authorization, aggregatorView AggregatorView) CommunityAuthView { 124 view := CommunityAuthView{ 125 Aggregator: aggregatorView, // Nested aggregator object 126 Enabled: auth.Enabled, 127 CreatedAt: auth.CreatedAt.Format("2006-01-02T15:04:05.000Z"), 128 } 129 130 // Add optional fields 131 if len(auth.Config) > 0 { 132 // Config is JSONB, unmarshal it 133 var config interface{} 134 if err := json.Unmarshal(auth.Config, &config); err == nil { 135 view.Config = config 136 } 137 } 138 if auth.RecordURI != "" { 139 view.RecordUri = auth.RecordURI 140 } 141 142 return view 143}