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
76 lines
1.6 KiB
Go
76 lines
1.6 KiB
Go
package service
|
|
|
|
import "time"
|
|
|
|
// API Key status constants
|
|
const (
|
|
StatusAPIKeyActive = "active"
|
|
StatusAPIKeyDisabled = "disabled"
|
|
StatusAPIKeyQuotaExhausted = "quota_exhausted"
|
|
StatusAPIKeyExpired = "expired"
|
|
)
|
|
|
|
type APIKey struct {
|
|
ID int64
|
|
UserID int64
|
|
Key string
|
|
Name string
|
|
GroupID *int64
|
|
Status string
|
|
IPWhitelist []string
|
|
IPBlacklist []string
|
|
CreatedAt time.Time
|
|
UpdatedAt time.Time
|
|
User *User
|
|
Group *Group
|
|
|
|
// Quota fields
|
|
Quota float64 // Quota limit in USD (0 = unlimited)
|
|
QuotaUsed float64 // Used quota amount
|
|
ExpiresAt *time.Time // Expiration time (nil = never expires)
|
|
}
|
|
|
|
func (k *APIKey) IsActive() bool {
|
|
return k.Status == StatusActive
|
|
}
|
|
|
|
// IsExpired checks if the API key has expired
|
|
func (k *APIKey) IsExpired() bool {
|
|
if k.ExpiresAt == nil {
|
|
return false
|
|
}
|
|
return time.Now().After(*k.ExpiresAt)
|
|
}
|
|
|
|
// IsQuotaExhausted checks if the API key quota is exhausted
|
|
func (k *APIKey) IsQuotaExhausted() bool {
|
|
if k.Quota <= 0 {
|
|
return false // unlimited
|
|
}
|
|
return k.QuotaUsed >= k.Quota
|
|
}
|
|
|
|
// GetQuotaRemaining returns remaining quota (-1 for unlimited)
|
|
func (k *APIKey) GetQuotaRemaining() float64 {
|
|
if k.Quota <= 0 {
|
|
return -1 // unlimited
|
|
}
|
|
remaining := k.Quota - k.QuotaUsed
|
|
if remaining < 0 {
|
|
return 0
|
|
}
|
|
return remaining
|
|
}
|
|
|
|
// GetDaysUntilExpiry returns days until expiry (-1 for never expires)
|
|
func (k *APIKey) GetDaysUntilExpiry() int {
|
|
if k.ExpiresAt == nil {
|
|
return -1 // never expires
|
|
}
|
|
duration := time.Until(*k.ExpiresAt)
|
|
if duration < 0 {
|
|
return 0
|
|
}
|
|
return int(duration.Hours() / 24)
|
|
}
|