mirror of
https://github.com/Wei-Shaw/sub2api.git
synced 2026-10-29 16:20:27 +00:00
Merge pull request #2766 from DaydreamCoding/feat/user-platform-quota
feat(quota): 用户 × 平台 USD 配额
This commit is contained in:
@@ -16,6 +16,47 @@ export interface DefaultSubscriptionSetting {
|
||||
validity_days: number;
|
||||
}
|
||||
|
||||
// ── 平台限额类型 ──────────────────────────────────────────────────
|
||||
export type PlatformType = "anthropic" | "openai" | "gemini" | "antigravity"
|
||||
export type QuotaWindowType = "daily" | "weekly" | "monthly"
|
||||
|
||||
/** 单平台三档限额;null = 不限制,undefined = 未填(等价 null) */
|
||||
export interface PlatformQuotaLimits {
|
||||
daily: number | null
|
||||
weekly: number | null
|
||||
monthly: number | null
|
||||
}
|
||||
|
||||
/** 全平台默认限额 map(key = PlatformType) */
|
||||
export type DefaultPlatformQuotasMap = Partial<Record<PlatformType, PlatformQuotaLimits>>
|
||||
|
||||
const PLATFORMS: PlatformType[] = ["anthropic", "openai", "gemini", "antigravity"]
|
||||
|
||||
/** 归一化为全 4 平台 × 3 窗口(缺失填 null),供模板非空绑定 */
|
||||
export function normalizePlatformQuotasMap(input?: DefaultPlatformQuotasMap | null): DefaultPlatformQuotasMap {
|
||||
const result: DefaultPlatformQuotasMap = {}
|
||||
for (const p of PLATFORMS) {
|
||||
const src = input?.[p]
|
||||
result[p] = {
|
||||
daily: typeof src?.daily === "number" ? src.daily : null,
|
||||
weekly: typeof src?.weekly === "number" ? src.weekly : null,
|
||||
monthly: typeof src?.monthly === "number" ? src.monthly : null,
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
/** 提交前清洗:非有限数/负数/空字符串 → null(保留 0 = 显式禁用),返回全 4 平台嵌套 map */
|
||||
export function sanitizePlatformQuotasMap(input?: DefaultPlatformQuotasMap | null): DefaultPlatformQuotasMap {
|
||||
const clean = (v: unknown): number | null => (typeof v === "number" && Number.isFinite(v) && v >= 0 ? v : null)
|
||||
const result: DefaultPlatformQuotasMap = {}
|
||||
for (const p of PLATFORMS) {
|
||||
const src = input?.[p]
|
||||
result[p] = { daily: clean(src?.daily), weekly: clean(src?.weekly), monthly: clean(src?.monthly) }
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
export type AuthSourceType =
|
||||
| "email"
|
||||
| "linuxdo"
|
||||
@@ -31,6 +72,8 @@ export interface AuthSourceDefaultsValue {
|
||||
subscriptions: DefaultSubscriptionSetting[];
|
||||
grant_on_signup: boolean;
|
||||
grant_on_first_bind: boolean;
|
||||
// ★ 新增:平台限额覆盖(key = PlatformType)
|
||||
platform_quotas: DefaultPlatformQuotasMap;
|
||||
}
|
||||
|
||||
export type AuthSourceDefaultsState = Record<
|
||||
@@ -193,6 +236,7 @@ export function buildAuthSourceDefaultsState(
|
||||
raw[`auth_source_default_${source}_grant_on_signup`] === true,
|
||||
grant_on_first_bind:
|
||||
raw[`auth_source_default_${source}_grant_on_first_bind`] === true,
|
||||
platform_quotas: normalizePlatformQuotasMap(raw[`auth_source_default_${source}_platform_quotas`] as DefaultPlatformQuotasMap | undefined),
|
||||
};
|
||||
return acc;
|
||||
}, {} as AuthSourceDefaultsState);
|
||||
@@ -220,6 +264,7 @@ export function appendAuthSourceDefaultsToUpdateRequest(
|
||||
current.grant_on_signup;
|
||||
target[`auth_source_default_${source}_grant_on_first_bind`] =
|
||||
current.grant_on_first_bind;
|
||||
target[`auth_source_default_${source}_platform_quotas`] = sanitizePlatformQuotasMap(current.platform_quotas)
|
||||
}
|
||||
|
||||
return payload;
|
||||
@@ -370,6 +415,15 @@ export interface SystemSettings {
|
||||
auth_source_default_google_grant_on_signup?: boolean;
|
||||
auth_source_default_google_grant_on_first_bind?: boolean;
|
||||
force_email_on_third_party_signup?: boolean;
|
||||
// ── 平台限额(嵌套 JSON,系统层 + 7 auth-source 层)────────────────────────────────
|
||||
default_platform_quotas?: DefaultPlatformQuotasMap;
|
||||
auth_source_default_email_platform_quotas?: DefaultPlatformQuotasMap;
|
||||
auth_source_default_linuxdo_platform_quotas?: DefaultPlatformQuotasMap;
|
||||
auth_source_default_oidc_platform_quotas?: DefaultPlatformQuotasMap;
|
||||
auth_source_default_wechat_platform_quotas?: DefaultPlatformQuotasMap;
|
||||
auth_source_default_github_platform_quotas?: DefaultPlatformQuotasMap;
|
||||
auth_source_default_google_platform_quotas?: DefaultPlatformQuotasMap;
|
||||
auth_source_default_dingtalk_platform_quotas?: DefaultPlatformQuotasMap;
|
||||
// OEM settings
|
||||
site_name: string;
|
||||
site_logo: string;
|
||||
@@ -616,6 +670,15 @@ export interface UpdateSettingsRequest {
|
||||
auth_source_default_google_grant_on_signup?: boolean;
|
||||
auth_source_default_google_grant_on_first_bind?: boolean;
|
||||
force_email_on_third_party_signup?: boolean;
|
||||
// ── 平台限额(嵌套 JSON,系统层 + 7 auth-source 层)────────────────────────────────
|
||||
default_platform_quotas?: DefaultPlatformQuotasMap;
|
||||
auth_source_default_email_platform_quotas?: DefaultPlatformQuotasMap;
|
||||
auth_source_default_linuxdo_platform_quotas?: DefaultPlatformQuotasMap;
|
||||
auth_source_default_oidc_platform_quotas?: DefaultPlatformQuotasMap;
|
||||
auth_source_default_wechat_platform_quotas?: DefaultPlatformQuotasMap;
|
||||
auth_source_default_github_platform_quotas?: DefaultPlatformQuotasMap;
|
||||
auth_source_default_google_platform_quotas?: DefaultPlatformQuotasMap;
|
||||
auth_source_default_dingtalk_platform_quotas?: DefaultPlatformQuotasMap;
|
||||
site_name?: string;
|
||||
site_logo?: string;
|
||||
site_subtitle?: string;
|
||||
|
||||
@@ -297,6 +297,78 @@ export async function bindUserAuthIdentity(
|
||||
return data
|
||||
}
|
||||
|
||||
/**
|
||||
* Platform quota types
|
||||
*/
|
||||
export type PlatformQuotaPlatform = 'anthropic' | 'openai' | 'gemini' | 'antigravity'
|
||||
export type PlatformQuotaWindow = 'daily' | 'weekly' | 'monthly'
|
||||
|
||||
export interface PlatformQuotaItem {
|
||||
platform: PlatformQuotaPlatform
|
||||
daily_limit_usd: number | null
|
||||
weekly_limit_usd: number | null
|
||||
monthly_limit_usd: number | null
|
||||
daily_usage_usd: number
|
||||
weekly_usage_usd: number
|
||||
monthly_usage_usd: number
|
||||
daily_window_start?: string | null
|
||||
weekly_window_start?: string | null
|
||||
monthly_window_start?: string | null
|
||||
daily_window_resets_at?: string | null
|
||||
weekly_window_resets_at?: string | null
|
||||
monthly_window_resets_at?: string | null
|
||||
}
|
||||
|
||||
export interface PlatformQuotaUpdateItem {
|
||||
platform: PlatformQuotaPlatform
|
||||
daily_limit_usd: number | null
|
||||
weekly_limit_usd: number | null
|
||||
monthly_limit_usd: number | null
|
||||
}
|
||||
|
||||
export interface PlatformQuotasResponse {
|
||||
platform_quotas: PlatformQuotaItem[]
|
||||
}
|
||||
|
||||
/**
|
||||
* Get user's platform quotas
|
||||
*/
|
||||
export async function getPlatformQuotas(id: number): Promise<PlatformQuotasResponse> {
|
||||
const { data } = await apiClient.get<PlatformQuotasResponse>(
|
||||
`/admin/users/${id}/platform-quotas`
|
||||
)
|
||||
return data
|
||||
}
|
||||
|
||||
/**
|
||||
* Replace user's platform quotas (全量替换)
|
||||
*/
|
||||
export async function updatePlatformQuotas(
|
||||
id: number,
|
||||
quotas: PlatformQuotaUpdateItem[]
|
||||
): Promise<PlatformQuotasResponse> {
|
||||
const { data } = await apiClient.put<PlatformQuotasResponse>(
|
||||
`/admin/users/${id}/platform-quotas`,
|
||||
{ quotas }
|
||||
)
|
||||
return data
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset a single (platform, window) usage immediately
|
||||
*/
|
||||
export async function resetPlatformQuotaWindow(
|
||||
id: number,
|
||||
platform: PlatformQuotaPlatform,
|
||||
window: PlatformQuotaWindow
|
||||
): Promise<PlatformQuotasResponse> {
|
||||
const { data } = await apiClient.post<PlatformQuotasResponse>(
|
||||
`/admin/users/${id}/platform-quotas/reset`,
|
||||
{ platform, window }
|
||||
)
|
||||
return data
|
||||
}
|
||||
|
||||
export const usersAPI = {
|
||||
list,
|
||||
getById,
|
||||
@@ -310,7 +382,10 @@ export const usersAPI = {
|
||||
getUserUsageStats,
|
||||
getUserBalanceHistory,
|
||||
replaceGroup,
|
||||
bindUserAuthIdentity
|
||||
bindUserAuthIdentity,
|
||||
getPlatformQuotas,
|
||||
updatePlatformQuotas,
|
||||
resetPlatformQuotaWindow,
|
||||
}
|
||||
|
||||
export default usersAPI
|
||||
|
||||
Reference in New Issue
Block a user