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