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
125 lines
3.1 KiB
TypeScript
125 lines
3.1 KiB
TypeScript
/**
|
|
* API Keys management endpoints
|
|
* Handles CRUD operations for user API keys
|
|
*/
|
|
|
|
import { apiClient } from './client'
|
|
import type { ApiKey, CreateApiKeyRequest, UpdateApiKeyRequest, PaginatedResponse } from '@/types'
|
|
|
|
/**
|
|
* List all API keys for current user
|
|
* @param page - Page number (default: 1)
|
|
* @param pageSize - Items per page (default: 10)
|
|
* @param options - Optional request options
|
|
* @returns Paginated list of API keys
|
|
*/
|
|
export async function list(
|
|
page: number = 1,
|
|
pageSize: number = 10,
|
|
options?: {
|
|
signal?: AbortSignal
|
|
}
|
|
): Promise<PaginatedResponse<ApiKey>> {
|
|
const { data } = await apiClient.get<PaginatedResponse<ApiKey>>('/keys', {
|
|
params: { page, page_size: pageSize },
|
|
signal: options?.signal
|
|
})
|
|
return data
|
|
}
|
|
|
|
/**
|
|
* Get API key by ID
|
|
* @param id - API key ID
|
|
* @returns API key details
|
|
*/
|
|
export async function getById(id: number): Promise<ApiKey> {
|
|
const { data } = await apiClient.get<ApiKey>(`/keys/${id}`)
|
|
return data
|
|
}
|
|
|
|
/**
|
|
* Create new API key
|
|
* @param name - Key name
|
|
* @param groupId - Optional group ID
|
|
* @param customKey - Optional custom key value
|
|
* @param ipWhitelist - Optional IP whitelist
|
|
* @param ipBlacklist - Optional IP blacklist
|
|
* @param quota - Optional quota limit in USD (0 = unlimited)
|
|
* @param expiresInDays - Optional days until expiry (undefined = never expires)
|
|
* @returns Created API key
|
|
*/
|
|
export async function create(
|
|
name: string,
|
|
groupId?: number | null,
|
|
customKey?: string,
|
|
ipWhitelist?: string[],
|
|
ipBlacklist?: string[],
|
|
quota?: number,
|
|
expiresInDays?: number
|
|
): Promise<ApiKey> {
|
|
const payload: CreateApiKeyRequest = { name }
|
|
if (groupId !== undefined) {
|
|
payload.group_id = groupId
|
|
}
|
|
if (customKey) {
|
|
payload.custom_key = customKey
|
|
}
|
|
if (ipWhitelist && ipWhitelist.length > 0) {
|
|
payload.ip_whitelist = ipWhitelist
|
|
}
|
|
if (ipBlacklist && ipBlacklist.length > 0) {
|
|
payload.ip_blacklist = ipBlacklist
|
|
}
|
|
if (quota !== undefined && quota > 0) {
|
|
payload.quota = quota
|
|
}
|
|
if (expiresInDays !== undefined && expiresInDays > 0) {
|
|
payload.expires_in_days = expiresInDays
|
|
}
|
|
|
|
const { data } = await apiClient.post<ApiKey>('/keys', payload)
|
|
return data
|
|
}
|
|
|
|
/**
|
|
* Update API key
|
|
* @param id - API key ID
|
|
* @param updates - Fields to update
|
|
* @returns Updated API key
|
|
*/
|
|
export async function update(id: number, updates: UpdateApiKeyRequest): Promise<ApiKey> {
|
|
const { data } = await apiClient.put<ApiKey>(`/keys/${id}`, updates)
|
|
return data
|
|
}
|
|
|
|
/**
|
|
* Delete API key
|
|
* @param id - API key ID
|
|
* @returns Success confirmation
|
|
*/
|
|
export async function deleteKey(id: number): Promise<{ message: string }> {
|
|
const { data } = await apiClient.delete<{ message: string }>(`/keys/${id}`)
|
|
return data
|
|
}
|
|
|
|
/**
|
|
* Toggle API key status (active/inactive)
|
|
* @param id - API key ID
|
|
* @param status - New status
|
|
* @returns Updated API key
|
|
*/
|
|
export async function toggleStatus(id: number, status: 'active' | 'inactive'): Promise<ApiKey> {
|
|
return update(id, { status })
|
|
}
|
|
|
|
export const keysAPI = {
|
|
list,
|
|
getById,
|
|
create,
|
|
update,
|
|
delete: deleteKey,
|
|
toggleStatus
|
|
}
|
|
|
|
export default keysAPI
|