feat(api): tiered cumulative quota + self-service registration
Anonymous callers get a free cumulative quota (1000 calls per IP); once exhausted they get 403 quota_exhausted and must register. Public users can self-register (email+password) to obtain a higher-quota API key, view usage, and regenerate the key. Quota counters live in Redis; the public API stays read-only except for the registration writes. - migration 0011: app_user table + api_key.quota_total + 'registered' tier - ratelimit: IncrTotal/TotalUsed/CopyTotal lifetime counters - middleware: enforce cumulative quota + X-Quota-* headers - store: RegisterUser/Authenticate/RegenerateKey (bcrypt) - handlers: POST /api/v1/register, /account, /account/regenerate - admin: quota_total column + registered tier - public: 'API 密钥' account page + API docs quota section Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
@@ -453,6 +453,7 @@ type APIKey struct {
|
||||
ID string
|
||||
Name string
|
||||
RateLimitPerMin int
|
||||
QuotaTotal int64
|
||||
}
|
||||
|
||||
// APIKeyByHash returns the active (non-revoked) key matching a SHA-256 hash,
|
||||
@@ -460,9 +461,9 @@ type APIKey struct {
|
||||
func (s *Store) APIKeyByHash(ctx context.Context, hash string) (*APIKey, error) {
|
||||
var k APIKey
|
||||
err := s.pool.QueryRow(ctx,
|
||||
`SELECT id, name, rate_limit_per_min
|
||||
`SELECT id, name, rate_limit_per_min, quota_total
|
||||
FROM api_key WHERE key_hash = $1 AND revoked_at IS NULL`, hash,
|
||||
).Scan(&k.ID, &k.Name, &k.RateLimitPerMin)
|
||||
).Scan(&k.ID, &k.Name, &k.RateLimitPerMin, &k.QuotaTotal)
|
||||
if errors.Is(err, pgx.ErrNoRows) {
|
||||
return nil, ErrNotFound
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user