feat(api-key): add independent quota and expiration support

This feature allows API Keys to have their own quota limits and expiration
times, independent of the user's balance.

Backend:
- Add quota, quota_used, expires_at fields to api_key schema
- Implement IsExpired() and IsQuotaExhausted() checks in middleware
- Add ResetQuota and ClearExpiration API endpoints
- Integrate quota billing in gateway handlers (OpenAI, Anthropic, Gemini)
- Include quota/expiration fields in auth cache for performance
- Expiration check returns 403, quota exhausted returns 429

Frontend:
- Add quota and expiration inputs to key create/edit dialog
- Add quick-select buttons for expiration (+7, +30, +90 days)
- Add reset quota confirmation dialog
- Add expires_at column to keys list
- Add i18n translations for new features (en/zh)

Migration:
- Add 045_add_api_key_quota.sql for new columns
This commit is contained in:
bayma888
2026-02-03 19:01:49 +08:00
parent bb3df5785a
commit 6146be1474
32 changed files with 1804 additions and 172 deletions

View File

@@ -374,9 +374,12 @@ export interface ApiKey {
key: string
name: string
group_id: number | null
status: 'active' | 'inactive'
status: 'active' | 'inactive' | 'quota_exhausted' | 'expired'
ip_whitelist: string[]
ip_blacklist: string[]
quota: number // Quota limit in USD (0 = unlimited)
quota_used: number // Used quota amount in USD
expires_at: string | null // Expiration time (null = never expires)
created_at: string
updated_at: string
group?: Group
@@ -388,6 +391,8 @@ export interface CreateApiKeyRequest {
custom_key?: string // Optional custom API Key
ip_whitelist?: string[]
ip_blacklist?: string[]
quota?: number // Quota limit in USD (0 = unlimited)
expires_in_days?: number // Days until expiry (null = never expires)
}
export interface UpdateApiKeyRequest {
@@ -396,6 +401,9 @@ export interface UpdateApiKeyRequest {
status?: 'active' | 'inactive'
ip_whitelist?: string[]
ip_blacklist?: string[]
quota?: number // Quota limit in USD (null = no change, 0 = unlimited)
expires_at?: string | null // Expiration time (null = no change)
reset_quota?: boolean // Reset quota_used to 0
}
export interface CreateGroupRequest {