A community based topic aggregation platform built on atproto
1package aggregator
2
3import (
4 "log"
5 "net/http"
6
7 "Coves/internal/api/middleware"
8 "Coves/internal/core/aggregators"
9)
10
11// GetAPIKeyHandler handles API key info retrieval for aggregators
12type GetAPIKeyHandler struct {
13 apiKeyService aggregators.APIKeyServiceInterface
14 aggregatorService aggregators.Service
15}
16
17// NewGetAPIKeyHandler creates a new handler for API key info retrieval
18func NewGetAPIKeyHandler(apiKeyService aggregators.APIKeyServiceInterface, aggregatorService aggregators.Service) *GetAPIKeyHandler {
19 return &GetAPIKeyHandler{
20 apiKeyService: apiKeyService,
21 aggregatorService: aggregatorService,
22 }
23}
24
25// APIKeyView represents the nested key metadata (matches social.coves.aggregator.defs#apiKeyView)
26type APIKeyView struct {
27 Prefix string `json:"prefix"` // First 12 chars for identification
28 CreatedAt string `json:"createdAt"` // ISO8601 timestamp when key was created
29 LastUsedAt *string `json:"lastUsedAt,omitempty"` // ISO8601 timestamp when key was last used
30 IsRevoked bool `json:"isRevoked"` // Whether the key has been revoked
31 RevokedAt *string `json:"revokedAt,omitempty"` // ISO8601 timestamp when key was revoked
32}
33
34// GetAPIKeyResponse represents the response when getting API key info
35type GetAPIKeyResponse struct {
36 HasKey bool `json:"hasKey"` // Whether the aggregator has an API key
37 KeyInfo *APIKeyView `json:"keyInfo,omitempty"` // Key metadata (only present if hasKey is true)
38}
39
40// HandleGetAPIKey handles GET /xrpc/social.coves.aggregator.getApiKey
41// This endpoint requires OAuth authentication and returns info about the aggregator's API key.
42// NOTE: The actual key value is NEVER returned - only metadata about the key.
43func (h *GetAPIKeyHandler) HandleGetAPIKey(w http.ResponseWriter, r *http.Request) {
44 if r.Method != http.MethodGet {
45 http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
46 return
47 }
48
49 // Get authenticated DID from context (set by RequireAuth middleware)
50 userDID := middleware.GetUserDID(r)
51 if userDID == "" {
52 writeError(w, http.StatusUnauthorized, "AuthenticationRequired", "Must be authenticated to get API key info")
53 return
54 }
55
56 // Verify the caller is a registered aggregator
57 isAggregator, err := h.aggregatorService.IsAggregator(r.Context(), userDID)
58 if err != nil {
59 log.Printf("ERROR: Failed to check aggregator status: %v", err)
60 writeError(w, http.StatusInternalServerError, "InternalServerError", "Failed to verify aggregator status")
61 return
62 }
63 if !isAggregator {
64 writeError(w, http.StatusForbidden, "AggregatorRequired", "Only registered aggregators can get API key info")
65 return
66 }
67
68 // Get API key info
69 keyInfo, err := h.apiKeyService.GetAPIKeyInfo(r.Context(), userDID)
70 if err != nil {
71 if aggregators.IsNotFound(err) {
72 writeError(w, http.StatusNotFound, "AggregatorNotFound", "Aggregator not found")
73 return
74 }
75 log.Printf("ERROR: Failed to get API key info for %s: %v", userDID, err)
76 writeError(w, http.StatusInternalServerError, "InternalServerError", "Failed to get API key info")
77 return
78 }
79
80 // Build response
81 response := GetAPIKeyResponse{
82 HasKey: keyInfo.HasKey,
83 }
84
85 if keyInfo.HasKey {
86 view := &APIKeyView{
87 Prefix: keyInfo.KeyPrefix,
88 IsRevoked: keyInfo.IsRevoked,
89 }
90
91 if keyInfo.CreatedAt != nil {
92 view.CreatedAt = keyInfo.CreatedAt.Format("2006-01-02T15:04:05.000Z")
93 }
94
95 if keyInfo.LastUsedAt != nil {
96 ts := keyInfo.LastUsedAt.Format("2006-01-02T15:04:05.000Z")
97 view.LastUsedAt = &ts
98 }
99
100 if keyInfo.RevokedAt != nil {
101 ts := keyInfo.RevokedAt.Format("2006-01-02T15:04:05.000Z")
102 view.RevokedAt = &ts
103 }
104
105 response.KeyInfo = view
106 }
107
108 writeJSONResponse(w, http.StatusOK, response)
109}