summaryrefslogtreecommitdiff
path: root/internal/controlplane/snapshot.go
diff options
context:
space:
mode:
authorChia <Chia@93.nz>2026-08-06 09:29:41 +1200
committerChia <Chia@93.nz>2026-08-06 09:32:46 +1200
commit41e322c53d7b4b796eb377d0df9c29ecd10ba431 (patch)
treec730526150e55e39b822d5197e4a20318ecaa449 /internal/controlplane/snapshot.go
parenteadb2ffe85c43cf6fc741c9823cd28eedb4a844c (diff)
feat: complete commercial control plane, billing, auth, and model catalog
- add PostgreSQL control-plane persistence with Redis-degraded hot reload - implement prepaid balance, usage ledger, Stripe top-up and reconciliation - add registration, email verification, password reset, invitations and RBAC - support TOTP, Passkey MFA, device sessions, quotas and rate limits - add tenant billing profiles, audit logs and operational readiness checks - build authenticated admin console, Quickstart, Playground and usage analytics - add public model catalog with pricing, filtering and cost estimation - support OpenAI Responses providers and provider health failover - validate real upstream usage reporting and balance settlement
Diffstat (limited to '')
-rw-r--r--internal/controlplane/snapshot.go35
1 files changed, 27 insertions, 8 deletions
diff --git a/internal/controlplane/snapshot.go b/internal/controlplane/snapshot.go
index b9bddaf..75a3a80 100644
--- a/internal/controlplane/snapshot.go
+++ b/internal/controlplane/snapshot.go
@@ -74,7 +74,7 @@ func loadLimitPolicies(ctx context.Context, tx pgx.Tx) ([]domain.LimitPolicy, er
func (s *Store) loadProviders(ctx context.Context, tx pgx.Tx) (map[string]domain.Provider, error) {
rows, err := tx.Query(ctx, `
- SELECT id::text, name, protocol, base_url, api_key_ciphertext
+ SELECT id::text, slug, name, protocol, wire_api, base_url, api_key_ciphertext
FROM providers
WHERE enabled = TRUE
ORDER BY name`)
@@ -84,16 +84,16 @@ func (s *Store) loadProviders(ctx context.Context, tx pgx.Tx) (map[string]domain
defer rows.Close()
providers := make(map[string]domain.Provider)
for rows.Next() {
- var id, name, protocol, baseURL string
+ var id, slug, name, protocol, wireAPI, baseURL string
var ciphertext []byte
- if err := rows.Scan(&id, &name, &protocol, &baseURL, &ciphertext); err != nil {
+ if err := rows.Scan(&id, &slug, &name, &protocol, &wireAPI, &baseURL, &ciphertext); err != nil {
return nil, fmt.Errorf("scan provider: %w", err)
}
apiKey, err := s.cipher.Decrypt(ciphertext)
if err != nil {
return nil, fmt.Errorf("decrypt provider %q credential: %w", name, err)
}
- providers[id] = domain.Provider{ID: id, Protocol: domain.Protocol(protocol), BaseURL: strings.TrimRight(baseURL, "/"), APIKey: apiKey}
+ providers[id] = domain.Provider{ID: id, Slug: slug, Protocol: domain.Protocol(protocol), WireAPI: wireAPI, BaseURL: strings.TrimRight(baseURL, "/"), APIKey: apiKey}
}
if err := rows.Err(); err != nil {
return nil, fmt.Errorf("read providers: %w", err)
@@ -245,11 +245,18 @@ func loadModels(ctx context.Context, tx pgx.Tx, providers map[string]domain.Prov
func loadAPIKeys(ctx context.Context, tx pgx.Tx) ([]auth.HashedKeyRecord, error) {
rows, err := tx.Query(ctx, `
- SELECT k.id::text, k.key_hash, k.tenant_id::text, k.project_id::text, k.scopes
+ SELECT k.id::text, k.key_hash, k.tenant_id::text, k.project_id::text, k.scopes,
+ k.monthly_spend_micros, k.expires_at,
+ COALESCE((
+ SELECT jsonb_agg(m.public_id ORDER BY m.public_id)
+ FROM api_key_model_restrictions r
+ JOIN models m ON m.id = r.model_id
+ WHERE r.api_key_id = k.id
+ ), '[]'::jsonb)
FROM api_keys k
JOIN tenants t ON t.id = k.tenant_id AND t.status = 'active'
JOIN projects p ON p.id = k.project_id AND p.status = 'active'
- WHERE k.status = 'active'`)
+ WHERE k.status = 'active' AND (k.expires_at IS NULL OR k.expires_at > now())`)
if err != nil {
return nil, fmt.Errorf("query API keys: %w", err)
}
@@ -257,8 +264,11 @@ func loadAPIKeys(ctx context.Context, tx pgx.Tx) ([]auth.HashedKeyRecord, error)
records := make([]auth.HashedKeyRecord, 0)
for rows.Next() {
var keyID, tenantID, projectID string
- var hashBytes, scopesJSON []byte
- if err := rows.Scan(&keyID, &hashBytes, &tenantID, &projectID, &scopesJSON); err != nil {
+ var hashBytes, scopesJSON, allowedModelsJSON []byte
+ var monthlySpendMicros int64
+ var expiresAt *time.Time
+ if err := rows.Scan(&keyID, &hashBytes, &tenantID, &projectID, &scopesJSON,
+ &monthlySpendMicros, &expiresAt, &allowedModelsJSON); err != nil {
return nil, fmt.Errorf("scan API key: %w", err)
}
if len(hashBytes) != sha256.Size {
@@ -270,8 +280,17 @@ func loadAPIKeys(ctx context.Context, tx pgx.Tx) ([]auth.HashedKeyRecord, error)
if err := json.Unmarshal(scopesJSON, &scopes); err != nil {
return nil, fmt.Errorf("decode API key %s scopes: %w", keyID, err)
}
+ var modelIDs []string
+ if err := json.Unmarshal(allowedModelsJSON, &modelIDs); err != nil {
+ return nil, fmt.Errorf("decode API key %s model restrictions: %w", keyID, err)
+ }
+ allowedModels := make(map[string]struct{}, len(modelIDs))
+ for _, modelID := range modelIDs {
+ allowedModels[modelID] = struct{}{}
+ }
records = append(records, auth.HashedKeyRecord{Hash: hash, Principal: domain.Principal{
KeyID: keyID, TenantID: tenantID, ProjectID: projectID, Scopes: scopes,
+ AllowedModels: allowedModels, MonthlySpendMicros: monthlySpendMicros, ExpiresAt: expiresAt,
}})
}
if err := rows.Err(); err != nil {