4f3f0f24ff
新增后台 AGC 模型目录、别名、启停和默认项管理 客户端设置页恢复原状,对话框右下角按别名选择模型 服务端按稳定模型标识映射并校验实际模型白名单 修复 AGC 配套后端端口漂移、启动等待和 SpacetimeDB 版本检查 补充迁移、文档、启动与模型选择测试
1091 lines
30 KiB
TypeScript
1091 lines
30 KiB
TypeScript
import type {
|
||
AdminAccountListResponse,
|
||
AdminConfirmEditorShowcaseCampaignImageUploadRequest,
|
||
AdminCreateAccountRequest,
|
||
AdminCreateAccountResponse,
|
||
AdminCreateEditorShowcaseCampaignImageUploadTicketRequest,
|
||
AdminCreateEditorShowcaseCampaignImageUploadTicketResponse,
|
||
AdminDashboardQuery,
|
||
AdminDashboardResponse,
|
||
AdminDatabaseTableListResponse,
|
||
AdminDatabaseTableRowsQuery,
|
||
AdminDatabaseTableRowsResponse,
|
||
AdminDebugHttpRequest,
|
||
AdminDebugHttpResponse,
|
||
AdminDirectUploadTicketPayload,
|
||
AdminDisableProfileRedeemCodeRequest,
|
||
AdminDisableProfileTaskConfigRequest,
|
||
AdminEditorAssetListQuery,
|
||
AdminEditorAssetListResponse,
|
||
AdminEditorShowcaseAssetResponse,
|
||
AdminEditorShowcaseCampaignResponse,
|
||
AdminEditorShowcaseDisplayRequest,
|
||
AdminEditorShowcaseListQuery,
|
||
AdminEditorShowcaseListResponse,
|
||
AdminEditorShowcaseReviewRequest,
|
||
AdminErrorReportDetail,
|
||
AdminErrorReportEntry,
|
||
AdminErrorReportListResponse,
|
||
AdminExternalApiKeyListQuery,
|
||
AdminExternalApiKeyListResponse,
|
||
AdminFeatureGateConfigResponse,
|
||
AdminLoginResponse,
|
||
AdminMeResponse,
|
||
AdminOverviewResponse,
|
||
AdminRechargeOrderListQuery,
|
||
AdminRechargeOrderListResponse,
|
||
AdminRechargeRefundActionResponse,
|
||
AdminRechargeRefundExecuteRequest,
|
||
AdminRechargeRefundManualReviewResolveRequest,
|
||
AdminRechargeRefundPreviewRequest,
|
||
AdminRechargeRefundPreviewResponse,
|
||
AdminRechargeRefundRegisterRequest,
|
||
AdminTrackingEventKeyListResponse,
|
||
AdminTrackingEventListQuery,
|
||
AdminTrackingEventListResponse,
|
||
AdminUpdateAccountRequest,
|
||
AdminUpdateAccountResponse,
|
||
AdminUploadedEditorShowcaseCampaignImage,
|
||
AdminUpsertEditorShowcaseCampaignRequest,
|
||
AdminUpsertFeatureGateConfigRequest,
|
||
AdminUpsertProfileInviteCodeRequest,
|
||
AdminUpsertProfileRechargeProductRequest,
|
||
AdminUpsertProfileRedeemCodeRequest,
|
||
AdminUpsertProfileTaskConfigRequest,
|
||
AdminUpsertProfileWalletConfigRequest,
|
||
AdminUserConsumptionReconcileRequest,
|
||
AdminUserConsumptionReconcileResponse,
|
||
AdminUserDetailQuery,
|
||
AdminUserDetailResponse,
|
||
AdminWalletRestrictionRequest,
|
||
AdminWalletRestrictionResponse,
|
||
ApiErrorEnvelope,
|
||
ApiMeta,
|
||
ApiSuccessEnvelope,
|
||
EditorGenerationPricingConfigPayload,
|
||
ProfileInviteCodeAdminListResponse,
|
||
ProfileInviteCodeAdminResponse,
|
||
ProfileRechargeProductConfigAdminListResponse,
|
||
ProfileRechargeProductConfigAdminResponse,
|
||
ProfileRedeemCodeAdminListResponse,
|
||
ProfileRedeemCodeAdminResponse,
|
||
ProfileTaskConfigAdminListResponse,
|
||
ProfileTaskConfigAdminResponse,
|
||
ProfileWalletConfigAdminResponse,
|
||
} from './adminApiTypes';
|
||
|
||
const API_RESPONSE_ENVELOPE_HEADER = 'x-genarrative-response-envelope';
|
||
const ADMIN_API_BASE_URL = normalizeBaseUrl(
|
||
import.meta.env.VITE_ADMIN_API_BASE_URL ?? '',
|
||
);
|
||
|
||
interface AdminRequestOptions {
|
||
method?: string;
|
||
token?: string;
|
||
body?: unknown;
|
||
headers?: Record<string, string>;
|
||
signal?: AbortSignal;
|
||
}
|
||
|
||
interface AdminAssetReadUrlQuery {
|
||
objectKey?: string | null;
|
||
legacyPublicPath?: string | null;
|
||
expireSeconds?: number | null;
|
||
}
|
||
|
||
export interface AdminAssetReadUrlResponse {
|
||
read?: {
|
||
objectKey?: string;
|
||
signedUrl?: string;
|
||
expiresAt?: string;
|
||
};
|
||
signedUrl?: string;
|
||
objectKey?: string;
|
||
expiresAt?: string;
|
||
}
|
||
|
||
export class AdminApiError extends Error {
|
||
status: number;
|
||
code: string;
|
||
details: Record<string, unknown> | null;
|
||
meta: ApiMeta | null;
|
||
responseText: string;
|
||
|
||
constructor(params: {
|
||
message: string;
|
||
status: number;
|
||
code?: string;
|
||
details?: Record<string, unknown> | null;
|
||
meta?: ApiMeta | null;
|
||
responseText?: string;
|
||
}) {
|
||
super(params.message);
|
||
this.name = 'AdminApiError';
|
||
this.status = params.status;
|
||
this.code = params.code ?? 'ADMIN_API_ERROR';
|
||
this.details = params.details ?? null;
|
||
this.meta = params.meta ?? null;
|
||
this.responseText = params.responseText ?? '';
|
||
}
|
||
}
|
||
|
||
export function isAdminApiError(error: unknown): error is AdminApiError {
|
||
return error instanceof AdminApiError;
|
||
}
|
||
|
||
export function formatAdminApiError(error: unknown) {
|
||
if (isAdminApiError(error)) {
|
||
return error.message;
|
||
}
|
||
|
||
if (error instanceof Error && error.message.trim()) {
|
||
return error.message;
|
||
}
|
||
|
||
return '请求失败';
|
||
}
|
||
|
||
export async function request<T>(
|
||
path: string,
|
||
options: AdminRequestOptions = {},
|
||
): Promise<T> {
|
||
const method = options.method ?? 'GET';
|
||
const headers: Record<string, string> = {
|
||
Accept: 'application/json',
|
||
[API_RESPONSE_ENVELOPE_HEADER]: 'v1',
|
||
...(options.headers ?? {}),
|
||
};
|
||
|
||
const token = options.token?.trim();
|
||
if (token) {
|
||
headers.Authorization = `Bearer ${token}`;
|
||
}
|
||
|
||
const init: RequestInit = {
|
||
method,
|
||
headers,
|
||
signal: options.signal,
|
||
};
|
||
|
||
if (typeof options.body !== 'undefined') {
|
||
headers['Content-Type'] = 'application/json';
|
||
init.body = JSON.stringify(options.body);
|
||
}
|
||
|
||
const response = await fetch(buildRequestUrl(path), init);
|
||
const responseText = await response.text();
|
||
const payload = parseJsonResponse(responseText);
|
||
|
||
if (!response.ok) {
|
||
throw buildAdminApiError(response, payload, responseText);
|
||
}
|
||
|
||
return unwrapSuccessPayload<T>(payload);
|
||
}
|
||
|
||
export function loginAdmin(username: string, password: string) {
|
||
return request<AdminLoginResponse>('/admin/api/login', {
|
||
method: 'POST',
|
||
body: { username, password },
|
||
});
|
||
}
|
||
|
||
export function getAdminMe(token: string) {
|
||
return request<AdminMeResponse>('/admin/api/me', { token });
|
||
}
|
||
|
||
export function listAdminAccounts(token: string) {
|
||
return request<AdminAccountListResponse>('/admin/api/accounts', { token });
|
||
}
|
||
|
||
export function createAdminAccount(
|
||
token: string,
|
||
payload: AdminCreateAccountRequest,
|
||
) {
|
||
return request<AdminCreateAccountResponse>('/admin/api/accounts', {
|
||
method: 'POST',
|
||
token,
|
||
body: payload,
|
||
});
|
||
}
|
||
|
||
export function updateAdminAccount(
|
||
token: string,
|
||
accountId: string,
|
||
payload: AdminUpdateAccountRequest,
|
||
) {
|
||
return request<AdminUpdateAccountResponse>(
|
||
`/admin/api/accounts/${encodeURIComponent(accountId)}`,
|
||
{ method: 'PUT', token, body: payload },
|
||
);
|
||
}
|
||
|
||
export function getAdminOverview(token: string) {
|
||
return request<AdminOverviewResponse>('/admin/api/overview', { token });
|
||
}
|
||
|
||
export function getAdminDashboard(
|
||
token: string,
|
||
query: AdminDashboardQuery = {},
|
||
) {
|
||
return request<AdminDashboardResponse>(
|
||
`/admin/api/dashboard${buildDashboardQuery(query)}`,
|
||
{ token },
|
||
);
|
||
}
|
||
|
||
export function getAdminDatabaseTables(token: string) {
|
||
return request<AdminDatabaseTableListResponse>('/admin/api/database/tables', {
|
||
token,
|
||
});
|
||
}
|
||
|
||
export function getAdminDatabaseTableRows(
|
||
token: string,
|
||
tableName: string,
|
||
query: AdminDatabaseTableRowsQuery = {},
|
||
) {
|
||
return request<AdminDatabaseTableRowsResponse>(
|
||
`/admin/api/database/tables/${encodeURIComponent(tableName)}/rows${buildDatabaseTableRowsQuery(query)}`,
|
||
{ token },
|
||
);
|
||
}
|
||
|
||
export function getAdminExternalApiKeys(
|
||
token: string,
|
||
query: AdminExternalApiKeyListQuery = {},
|
||
) {
|
||
return request<AdminExternalApiKeyListResponse>(
|
||
`/admin/api/external-api-keys${buildExternalApiKeyQuery(query)}`,
|
||
{ token },
|
||
);
|
||
}
|
||
|
||
export function debugAdminHttp(token: string, payload: AdminDebugHttpRequest) {
|
||
return request<AdminDebugHttpResponse>('/admin/api/debug/http', {
|
||
method: 'POST',
|
||
token,
|
||
body: payload,
|
||
});
|
||
}
|
||
|
||
export function listAdminTrackingEvents(
|
||
token: string,
|
||
query: AdminTrackingEventListQuery = {},
|
||
) {
|
||
return request<AdminTrackingEventListResponse>(
|
||
`/admin/api/tracking/events${buildQueryString((params) => {
|
||
appendQueryParam(params, 'eventKey', query.eventKey);
|
||
appendQueryParam(params, 'userId', query.userId);
|
||
appendQueryParam(params, 'scopeKind', query.scopeKind);
|
||
appendQueryParam(params, 'scopeId', query.scopeId);
|
||
appendQueryParam(params, 'startDate', query.startDate);
|
||
appendQueryParam(params, 'endDate', query.endDate);
|
||
appendNumericQueryParam(params, 'limit', query.limit);
|
||
if (query.exportAll) params.set('exportAll', 'true');
|
||
})}`,
|
||
{ token },
|
||
);
|
||
}
|
||
|
||
export function listAdminTrackingEventKeys(token: string) {
|
||
return request<AdminTrackingEventKeyListResponse>(
|
||
'/admin/api/tracking/event-keys',
|
||
{ token },
|
||
);
|
||
}
|
||
|
||
export function listAdminErrorReports(
|
||
token: string,
|
||
query: {
|
||
status?: string;
|
||
fingerprint?: string;
|
||
source?: string;
|
||
limit?: number;
|
||
offset?: number;
|
||
} = {},
|
||
) {
|
||
return request<AdminErrorReportListResponse>(
|
||
`/admin/api/error-reports${buildErrorReportQueryString(query)}`,
|
||
{ token },
|
||
);
|
||
}
|
||
|
||
export function getAdminErrorReport(token: string, batchId: string) {
|
||
return request<AdminErrorReportDetail>(
|
||
`/admin/api/error-reports/${encodeURIComponent(batchId)}`,
|
||
{ token },
|
||
);
|
||
}
|
||
|
||
export function updateAdminErrorReport(
|
||
token: string,
|
||
batchId: string,
|
||
payload: { status: string; note?: string },
|
||
) {
|
||
return request<AdminErrorReportEntry>(
|
||
`/admin/api/error-reports/${encodeURIComponent(batchId)}`,
|
||
{ method: 'PATCH', token, body: payload },
|
||
);
|
||
}
|
||
|
||
export function getAdminErrorReportDownloadUrl(batchId: string) {
|
||
return `/admin/api/error-reports/${encodeURIComponent(batchId)}/download`;
|
||
}
|
||
|
||
export async function downloadAdminErrorReport(token: string, batchId: string) {
|
||
const response = await fetch(
|
||
buildRequestUrl(getAdminErrorReportDownloadUrl(batchId)),
|
||
{
|
||
headers: { Authorization: `Bearer ${token}` },
|
||
},
|
||
);
|
||
if (!response.ok) {
|
||
throw new AdminApiError({
|
||
message: `下载失败(${response.status})`,
|
||
status: response.status,
|
||
});
|
||
}
|
||
return response.blob();
|
||
}
|
||
|
||
export function getAdminFeatureGateConfig(token: string) {
|
||
return request<AdminFeatureGateConfigResponse>('/admin/api/feature-gates', {
|
||
token,
|
||
});
|
||
}
|
||
|
||
export function upsertAdminFeatureGateConfig(
|
||
token: string,
|
||
payload: AdminUpsertFeatureGateConfigRequest,
|
||
) {
|
||
return request<AdminFeatureGateConfigResponse>('/admin/api/feature-gates', {
|
||
method: 'PUT',
|
||
token,
|
||
body: payload,
|
||
});
|
||
}
|
||
|
||
export function getAdminEditorGenerationPricing(token: string) {
|
||
return request<EditorGenerationPricingConfigPayload>(
|
||
'/admin/api/editor-generation-pricing',
|
||
{ token },
|
||
);
|
||
}
|
||
|
||
export function upsertAdminEditorGenerationPricing(
|
||
token: string,
|
||
payload: EditorGenerationPricingConfigPayload,
|
||
) {
|
||
return request<EditorGenerationPricingConfigPayload>(
|
||
'/admin/api/editor-generation-pricing',
|
||
{
|
||
method: 'POST',
|
||
token,
|
||
body: payload,
|
||
},
|
||
);
|
||
}
|
||
|
||
export function getAdminAssetReadUrl(
|
||
token: string,
|
||
query: AdminAssetReadUrlQuery,
|
||
) {
|
||
return request<AdminAssetReadUrlResponse>(
|
||
`/admin/api/assets/read-url${buildAssetReadUrlQuery(query)}`,
|
||
{ token },
|
||
);
|
||
}
|
||
|
||
export function listAdminEditorAssets(
|
||
token: string,
|
||
query: AdminEditorAssetListQuery = {},
|
||
signal?: AbortSignal,
|
||
) {
|
||
return request<AdminEditorAssetListResponse>(
|
||
`/admin/api/editor-assets${buildEditorAssetListQuery(query)}`,
|
||
{ token, signal },
|
||
);
|
||
}
|
||
|
||
export function listAdminEditorShowcaseAssets(
|
||
token: string,
|
||
query: AdminEditorShowcaseListQuery = {},
|
||
) {
|
||
return request<AdminEditorShowcaseListResponse>(
|
||
`/admin/api/editor-showcase/assets${buildEditorShowcaseListQuery(query)}`,
|
||
{ token },
|
||
);
|
||
}
|
||
|
||
export function reviewAdminEditorShowcaseAsset(
|
||
token: string,
|
||
payload: AdminEditorShowcaseReviewRequest,
|
||
) {
|
||
return request<AdminEditorShowcaseAssetResponse>(
|
||
'/admin/api/editor-showcase/assets/review',
|
||
{
|
||
method: 'POST',
|
||
token,
|
||
body: payload,
|
||
},
|
||
);
|
||
}
|
||
|
||
export function updateAdminEditorShowcaseDisplay(
|
||
token: string,
|
||
payload: AdminEditorShowcaseDisplayRequest,
|
||
) {
|
||
return request<AdminEditorShowcaseAssetResponse>(
|
||
'/admin/api/editor-showcase/assets/display',
|
||
{
|
||
method: 'POST',
|
||
token,
|
||
body: payload,
|
||
},
|
||
);
|
||
}
|
||
|
||
export function getAdminEditorShowcaseCampaign(token: string) {
|
||
return request<AdminEditorShowcaseCampaignResponse>(
|
||
'/admin/api/editor-showcase/campaign',
|
||
{ token },
|
||
);
|
||
}
|
||
|
||
export function upsertAdminEditorShowcaseCampaign(
|
||
token: string,
|
||
payload: AdminUpsertEditorShowcaseCampaignRequest,
|
||
) {
|
||
return request<AdminEditorShowcaseCampaignResponse>(
|
||
'/admin/api/editor-showcase/campaign',
|
||
{
|
||
method: 'POST',
|
||
token,
|
||
body: payload,
|
||
},
|
||
);
|
||
}
|
||
|
||
export async function uploadAdminEditorShowcaseCampaignImage(
|
||
token: string,
|
||
file: File,
|
||
): Promise<AdminUploadedEditorShowcaseCampaignImage> {
|
||
const contentType = resolveAdminImageContentType(file);
|
||
const dimensions = await readAdminImageFileDimensions(file);
|
||
const response =
|
||
await request<AdminCreateEditorShowcaseCampaignImageUploadTicketResponse>(
|
||
'/admin/api/editor-showcase/campaign/image-upload-ticket',
|
||
{
|
||
method: 'POST',
|
||
token,
|
||
body: {
|
||
fileName: file.name.trim() || 'showcase-campaign.png',
|
||
contentType,
|
||
contentLength: file.size,
|
||
} satisfies AdminCreateEditorShowcaseCampaignImageUploadTicketRequest,
|
||
},
|
||
);
|
||
await postAdminDirectUploadFile(response.upload, file);
|
||
const objectKey = response.upload.objectKey.trim().replace(/^\/+/u, '');
|
||
await request<unknown>(
|
||
'/admin/api/editor-showcase/campaign/image-upload-confirm',
|
||
{
|
||
method: 'POST',
|
||
token,
|
||
body: {
|
||
bucket: response.upload.bucket,
|
||
objectKey,
|
||
contentType,
|
||
contentLength: file.size,
|
||
} satisfies AdminConfirmEditorShowcaseCampaignImageUploadRequest,
|
||
},
|
||
);
|
||
return {
|
||
imageSrc: objectKey ? `/${objectKey}` : response.upload.legacyPublicPath,
|
||
imageObjectKey: objectKey,
|
||
imageWidth: dimensions.imageWidth,
|
||
imageHeight: dimensions.imageHeight,
|
||
legacyPublicPath: response.upload.legacyPublicPath,
|
||
};
|
||
}
|
||
|
||
export function listProfileRedeemCodes(token: string) {
|
||
return request<ProfileRedeemCodeAdminListResponse>(
|
||
'/admin/api/profile/redeem-codes',
|
||
{ token },
|
||
);
|
||
}
|
||
|
||
export function upsertProfileRedeemCode(
|
||
token: string,
|
||
payload: AdminUpsertProfileRedeemCodeRequest,
|
||
) {
|
||
return request<ProfileRedeemCodeAdminResponse>(
|
||
'/admin/api/profile/redeem-codes',
|
||
{
|
||
method: 'POST',
|
||
token,
|
||
body: payload,
|
||
},
|
||
);
|
||
}
|
||
|
||
export function listProfileInviteCodes(token: string) {
|
||
return request<ProfileInviteCodeAdminListResponse>(
|
||
'/admin/api/profile/invite-codes',
|
||
{ token },
|
||
);
|
||
}
|
||
|
||
export function upsertProfileInviteCode(
|
||
token: string,
|
||
payload: AdminUpsertProfileInviteCodeRequest,
|
||
) {
|
||
return request<ProfileInviteCodeAdminResponse>(
|
||
'/admin/api/profile/invite-codes',
|
||
{
|
||
method: 'POST',
|
||
token,
|
||
body: payload,
|
||
},
|
||
);
|
||
}
|
||
|
||
export function disableProfileRedeemCode(
|
||
token: string,
|
||
payload: AdminDisableProfileRedeemCodeRequest,
|
||
) {
|
||
return request<ProfileRedeemCodeAdminResponse>(
|
||
'/admin/api/profile/redeem-codes/disable',
|
||
{
|
||
method: 'POST',
|
||
token,
|
||
body: payload,
|
||
},
|
||
);
|
||
}
|
||
|
||
export function listProfileTaskConfigs(token: string) {
|
||
return request<ProfileTaskConfigAdminListResponse>(
|
||
'/admin/api/profile/tasks',
|
||
{ token },
|
||
);
|
||
}
|
||
|
||
export function upsertProfileTaskConfig(
|
||
token: string,
|
||
payload: AdminUpsertProfileTaskConfigRequest,
|
||
) {
|
||
return request<ProfileTaskConfigAdminResponse>('/admin/api/profile/tasks', {
|
||
method: 'POST',
|
||
token,
|
||
body: payload,
|
||
});
|
||
}
|
||
|
||
export function disableProfileTaskConfig(
|
||
token: string,
|
||
payload: AdminDisableProfileTaskConfigRequest,
|
||
) {
|
||
return request<ProfileTaskConfigAdminResponse>(
|
||
'/admin/api/profile/tasks/disable',
|
||
{
|
||
method: 'POST',
|
||
token,
|
||
body: payload,
|
||
},
|
||
);
|
||
}
|
||
|
||
export function getProfileWalletConfig(token: string) {
|
||
return request<ProfileWalletConfigAdminResponse>(
|
||
'/admin/api/profile/wallet-config',
|
||
{ token },
|
||
);
|
||
}
|
||
|
||
export function upsertProfileWalletConfig(
|
||
token: string,
|
||
payload: AdminUpsertProfileWalletConfigRequest,
|
||
) {
|
||
return request<ProfileWalletConfigAdminResponse>(
|
||
'/admin/api/profile/wallet-config',
|
||
{
|
||
method: 'POST',
|
||
token,
|
||
body: payload,
|
||
},
|
||
);
|
||
}
|
||
|
||
export function listProfileRechargeProducts(token: string) {
|
||
return request<ProfileRechargeProductConfigAdminListResponse>(
|
||
'/admin/api/profile/recharge-products',
|
||
{ token },
|
||
);
|
||
}
|
||
|
||
export function upsertProfileRechargeProduct(
|
||
token: string,
|
||
payload: AdminUpsertProfileRechargeProductRequest,
|
||
) {
|
||
return request<ProfileRechargeProductConfigAdminResponse>(
|
||
'/admin/api/profile/recharge-products',
|
||
{
|
||
method: 'POST',
|
||
token,
|
||
body: payload,
|
||
},
|
||
);
|
||
}
|
||
|
||
export function listAdminRechargeOrders(
|
||
token: string,
|
||
query: AdminRechargeOrderListQuery = {},
|
||
) {
|
||
return request<AdminRechargeOrderListResponse>(
|
||
`/admin/api/profile/recharge-orders${buildAdminRechargeOrderListQuery(query)}`,
|
||
{ token },
|
||
);
|
||
}
|
||
|
||
export function getAdminUserDetail(token: string, query: AdminUserDetailQuery) {
|
||
return request<AdminUserDetailResponse>(
|
||
`/admin/api/profile/users/detail${buildAdminUserDetailQuery(query)}`,
|
||
{ token },
|
||
);
|
||
}
|
||
|
||
export function reconcileAdminUserConsumption(
|
||
token: string,
|
||
payload: AdminUserConsumptionReconcileRequest,
|
||
) {
|
||
return request<AdminUserConsumptionReconcileResponse>(
|
||
'/admin/api/profile/users/reconcile-consumption',
|
||
{ method: 'POST', token, body: payload },
|
||
);
|
||
}
|
||
|
||
export function previewAdminRechargeRefund(
|
||
token: string,
|
||
payload: AdminRechargeRefundPreviewRequest,
|
||
) {
|
||
return request<AdminRechargeRefundPreviewResponse>(
|
||
'/admin/api/profile/recharge-refunds/preview',
|
||
{ method: 'POST', token, body: payload },
|
||
);
|
||
}
|
||
|
||
export function executeAdminRechargeRefund(
|
||
token: string,
|
||
payload: AdminRechargeRefundExecuteRequest,
|
||
) {
|
||
return request<AdminRechargeRefundActionResponse>(
|
||
'/admin/api/profile/recharge-refunds/execute',
|
||
{ method: 'POST', token, body: payload },
|
||
);
|
||
}
|
||
|
||
export function registerAdminRechargeRefund(
|
||
token: string,
|
||
payload: AdminRechargeRefundRegisterRequest,
|
||
) {
|
||
return request<AdminRechargeRefundActionResponse>(
|
||
'/admin/api/profile/recharge-refunds/register',
|
||
{ method: 'POST', token, body: payload },
|
||
);
|
||
}
|
||
|
||
export function resolveAdminRechargeRefundManualReview(
|
||
token: string,
|
||
payload: AdminRechargeRefundManualReviewResolveRequest,
|
||
) {
|
||
return request<AdminRechargeRefundActionResponse>(
|
||
'/admin/api/profile/recharge-refunds/manual-review/resolve',
|
||
{ method: 'POST', token, body: payload },
|
||
);
|
||
}
|
||
|
||
export function updateAdminWalletRestriction(
|
||
token: string,
|
||
payload: AdminWalletRestrictionRequest,
|
||
) {
|
||
return request<AdminWalletRestrictionResponse>(
|
||
'/admin/api/profile/wallet-restriction',
|
||
{ method: 'POST', token, body: payload },
|
||
);
|
||
}
|
||
|
||
function normalizeBaseUrl(value: string) {
|
||
return value.trim().replace(/\/+$/, '');
|
||
}
|
||
|
||
function buildRequestUrl(path: string) {
|
||
const normalizedPath = path.startsWith('/') ? path : `/${path}`;
|
||
return `${ADMIN_API_BASE_URL}${normalizedPath}`;
|
||
}
|
||
|
||
function buildAssetReadUrlQuery(query: AdminAssetReadUrlQuery) {
|
||
const params = new URLSearchParams();
|
||
const objectKey = query.objectKey?.trim().replace(/^\/+/u, '') ?? '';
|
||
const legacyPublicPath = query.legacyPublicPath?.trim() ?? '';
|
||
if (objectKey) {
|
||
params.set('objectKey', objectKey);
|
||
} else if (legacyPublicPath) {
|
||
params.set('legacyPublicPath', `/${legacyPublicPath.replace(/^\/+/u, '')}`);
|
||
}
|
||
if (
|
||
typeof query.expireSeconds === 'number' &&
|
||
Number.isFinite(query.expireSeconds) &&
|
||
query.expireSeconds > 0
|
||
) {
|
||
params.set('expireSeconds', String(Math.floor(query.expireSeconds)));
|
||
}
|
||
const queryString = params.toString();
|
||
return queryString ? `?${queryString}` : '';
|
||
}
|
||
|
||
function resolveAdminImageContentType(file: File) {
|
||
const declaredType = file.type.trim();
|
||
if (declaredType.startsWith('image/')) {
|
||
return declaredType;
|
||
}
|
||
const extension = file.name
|
||
.trim()
|
||
.toLowerCase()
|
||
.match(/\.([a-z0-9]+)$/u)?.[1];
|
||
if (extension === 'jpg' || extension === 'jpeg') {
|
||
return 'image/jpeg';
|
||
}
|
||
if (extension === 'png') {
|
||
return 'image/png';
|
||
}
|
||
if (extension === 'webp') {
|
||
return 'image/webp';
|
||
}
|
||
if (extension === 'gif') {
|
||
return 'image/gif';
|
||
}
|
||
return declaredType || 'application/octet-stream';
|
||
}
|
||
|
||
function normalizeAdminImageDimensions(width: number, height: number) {
|
||
const imageWidth = Math.round(width);
|
||
const imageHeight = Math.round(height);
|
||
if (
|
||
!Number.isFinite(imageWidth) ||
|
||
!Number.isFinite(imageHeight) ||
|
||
imageWidth <= 0 ||
|
||
imageHeight <= 0
|
||
) {
|
||
return null;
|
||
}
|
||
return { imageWidth, imageHeight };
|
||
}
|
||
|
||
async function readAdminImageFileDimensions(file: File) {
|
||
if (typeof createImageBitmap === 'function') {
|
||
try {
|
||
const bitmap = await createImageBitmap(file);
|
||
const dimensions = normalizeAdminImageDimensions(
|
||
bitmap.width,
|
||
bitmap.height,
|
||
);
|
||
bitmap.close();
|
||
if (dimensions) {
|
||
return dimensions;
|
||
}
|
||
} catch {
|
||
// Fall back to HTMLImageElement decoding below.
|
||
}
|
||
}
|
||
|
||
if (
|
||
typeof Image === 'undefined' ||
|
||
typeof URL === 'undefined' ||
|
||
typeof URL.createObjectURL !== 'function'
|
||
) {
|
||
throw new Error('读取活动卡图片尺寸失败,请重新选择图片');
|
||
}
|
||
|
||
return new Promise<{ imageWidth: number; imageHeight: number }>(
|
||
(resolve, reject) => {
|
||
const objectUrl = URL.createObjectURL(file);
|
||
const image = new Image();
|
||
const cleanup = () => {
|
||
if (typeof URL.revokeObjectURL === 'function') {
|
||
URL.revokeObjectURL(objectUrl);
|
||
}
|
||
};
|
||
image.onload = () => {
|
||
cleanup();
|
||
const dimensions = normalizeAdminImageDimensions(
|
||
image.naturalWidth || image.width,
|
||
image.naturalHeight || image.height,
|
||
);
|
||
if (dimensions) {
|
||
resolve(dimensions);
|
||
return;
|
||
}
|
||
reject(new Error('读取活动卡图片尺寸失败,请重新选择图片'));
|
||
};
|
||
image.onerror = () => {
|
||
cleanup();
|
||
reject(new Error('读取活动卡图片尺寸失败,请重新选择图片'));
|
||
};
|
||
image.src = objectUrl;
|
||
},
|
||
);
|
||
}
|
||
|
||
function buildAdminDirectUploadFormData(
|
||
upload: AdminDirectUploadTicketPayload,
|
||
file: File,
|
||
) {
|
||
const formData = new FormData();
|
||
Object.entries(upload.formFields).forEach(([key, value]) => {
|
||
if (value !== null && value !== undefined) {
|
||
formData.append(key, value);
|
||
}
|
||
});
|
||
formData.append('file', file, file.name);
|
||
return formData;
|
||
}
|
||
|
||
async function postAdminDirectUploadFile(
|
||
upload: AdminDirectUploadTicketPayload,
|
||
file: File,
|
||
) {
|
||
const response = await fetch(upload.host, {
|
||
method: 'POST',
|
||
body: buildAdminDirectUploadFormData(upload, file),
|
||
});
|
||
if (!response.ok) {
|
||
throw new Error(`上传活动卡图片失败:HTTP ${response.status}`);
|
||
}
|
||
}
|
||
|
||
function buildQueryString(append: (params: URLSearchParams) => void) {
|
||
const params = new URLSearchParams();
|
||
append(params);
|
||
return formatQueryString(params);
|
||
}
|
||
|
||
function buildErrorReportQueryString(query: {
|
||
status?: string;
|
||
fingerprint?: string;
|
||
source?: string;
|
||
limit?: number;
|
||
offset?: number;
|
||
}) {
|
||
return buildQueryString((params) => {
|
||
appendQueryParam(params, 'status', query.status);
|
||
appendQueryParam(params, 'fingerprint', query.fingerprint);
|
||
appendQueryParam(params, 'source', query.source);
|
||
appendNumericQueryParam(params, 'limit', query.limit);
|
||
appendNumericQueryParam(params, 'offset', query.offset);
|
||
});
|
||
}
|
||
|
||
function buildAdminRechargeOrderListQuery(query: AdminRechargeOrderListQuery) {
|
||
return buildQueryString((params) => {
|
||
appendQueryParam(params, 'orderId', query.orderId);
|
||
appendQueryParam(
|
||
params,
|
||
'providerTransactionId',
|
||
query.providerTransactionId,
|
||
);
|
||
appendQueryParam(params, 'userId', query.userId);
|
||
appendQueryParam(params, 'publicUserCode', query.publicUserCode);
|
||
appendQueryParam(params, 'paymentChannel', query.paymentChannel);
|
||
appendQueryParam(params, 'status', query.status);
|
||
appendQueryParam(params, 'createdAfter', query.createdAfter);
|
||
appendQueryParam(params, 'createdBefore', query.createdBefore);
|
||
appendNumericQueryParam(params, 'limit', query.limit);
|
||
});
|
||
}
|
||
|
||
function buildAdminUserDetailQuery(query: AdminUserDetailQuery) {
|
||
const params = new URLSearchParams();
|
||
appendQueryParam(params, 'userId', query.userId);
|
||
appendQueryParam(params, 'publicUserCode', query.publicUserCode);
|
||
const queryString = params.toString();
|
||
return queryString ? `?${queryString}` : '';
|
||
}
|
||
|
||
function buildDashboardQuery(query: AdminDashboardQuery) {
|
||
const params = new URLSearchParams();
|
||
appendQueryParam(params, 'granularity', query.granularity);
|
||
appendQueryParam(params, 'anchor', query.anchor);
|
||
appendQueryParam(params, 'startDate', query.startDate);
|
||
appendQueryParam(params, 'endDate', query.endDate);
|
||
const queryString = params.toString();
|
||
return queryString ? `?${queryString}` : '';
|
||
}
|
||
|
||
function buildDatabaseTableRowsQuery(query: AdminDatabaseTableRowsQuery) {
|
||
const params = new URLSearchParams();
|
||
appendQueryParam(params, 'search', query.search);
|
||
appendQueryParam(params, 'filters', query.filters);
|
||
if (typeof query.limit === 'number' && Number.isFinite(query.limit)) {
|
||
params.set('limit', String(query.limit));
|
||
}
|
||
if (typeof query.page === 'number' && Number.isFinite(query.page)) {
|
||
params.set('page', String(query.page));
|
||
}
|
||
appendQueryParam(params, 'sortColumn', query.sortColumn);
|
||
appendQueryParam(params, 'sortDirection', query.sortDirection);
|
||
const queryString = params.toString();
|
||
return queryString ? `?${queryString}` : '';
|
||
}
|
||
|
||
function buildExternalApiKeyQuery(query: AdminExternalApiKeyListQuery) {
|
||
const params = new URLSearchParams();
|
||
appendQueryParam(params, 'ownerUserId', query.ownerUserId);
|
||
appendQueryParam(params, 'publicUserCode', query.publicUserCode);
|
||
appendQueryParam(params, 'keyId', query.keyId);
|
||
appendQueryParam(params, 'name', query.name);
|
||
appendQueryParam(params, 'keyPrefix', query.keyPrefix);
|
||
appendQueryParam(params, 'createdAfter', query.createdAfter);
|
||
appendQueryParam(params, 'createdBefore', query.createdBefore);
|
||
appendQueryParam(params, 'status', query.status);
|
||
if (typeof query.limit === 'number' && Number.isFinite(query.limit)) {
|
||
params.set('limit', String(Math.floor(query.limit)));
|
||
}
|
||
if (typeof query.offset === 'number' && Number.isFinite(query.offset)) {
|
||
params.set('offset', String(Math.floor(query.offset)));
|
||
}
|
||
appendQueryParam(params, 'sortColumn', query.sortColumn);
|
||
appendQueryParam(params, 'sortDirection', query.sortDirection);
|
||
const queryString = params.toString();
|
||
return queryString ? `?${queryString}` : '';
|
||
}
|
||
|
||
function buildEditorAssetListQuery(query: AdminEditorAssetListQuery) {
|
||
const params = new URLSearchParams();
|
||
appendQueryParam(params, 'cursor', query.cursor);
|
||
appendQueryParam(params, 'ownerUserId', query.ownerUserId);
|
||
appendQueryParam(params, 'keyword', query.keyword);
|
||
appendQueryParam(params, 'createdAfter', query.createdAfter);
|
||
appendQueryParam(params, 'createdBefore', query.createdBefore);
|
||
if (typeof query.limit === 'number' && Number.isFinite(query.limit)) {
|
||
params.set('limit', String(query.limit));
|
||
}
|
||
const queryString = params.toString();
|
||
return queryString ? `?${queryString}` : '';
|
||
}
|
||
|
||
function buildEditorShowcaseListQuery(query: AdminEditorShowcaseListQuery) {
|
||
const params = new URLSearchParams();
|
||
appendQueryParam(params, 'cursor', query.cursor);
|
||
appendQueryParam(params, 'ownerUserId', query.ownerUserId);
|
||
appendQueryParam(params, 'reviewStatus', query.reviewStatus);
|
||
appendQueryParam(params, 'submittedAfter', query.submittedAfter);
|
||
appendQueryParam(params, 'submittedBefore', query.submittedBefore);
|
||
if (typeof query.limit === 'number' && Number.isFinite(query.limit)) {
|
||
params.set('limit', String(query.limit));
|
||
}
|
||
const queryString = params.toString();
|
||
return queryString ? `?${queryString}` : '';
|
||
}
|
||
|
||
function appendQueryParam(
|
||
params: URLSearchParams,
|
||
key: string,
|
||
value: string | null | undefined,
|
||
) {
|
||
const trimmed = value?.trim();
|
||
if (trimmed) {
|
||
params.set(key, trimmed);
|
||
}
|
||
}
|
||
|
||
function appendNumericQueryParam(
|
||
params: URLSearchParams,
|
||
key: string,
|
||
value: number | null | undefined,
|
||
) {
|
||
if (typeof value === 'number' && Number.isFinite(value)) {
|
||
params.set(key, String(value));
|
||
}
|
||
}
|
||
|
||
function formatQueryString(params: URLSearchParams) {
|
||
const queryString = params.toString();
|
||
return queryString ? `?${queryString}` : '';
|
||
}
|
||
|
||
function parseJsonResponse(responseText: string): unknown {
|
||
if (!responseText.trim()) {
|
||
return null;
|
||
}
|
||
|
||
try {
|
||
return JSON.parse(responseText) as unknown;
|
||
} catch {
|
||
return null;
|
||
}
|
||
}
|
||
|
||
function unwrapSuccessPayload<T>(payload: unknown): T {
|
||
if (isRecord(payload) && 'data' in payload) {
|
||
return (payload as ApiSuccessEnvelope<T>).data as T;
|
||
}
|
||
|
||
return payload as T;
|
||
}
|
||
|
||
function buildAdminApiError(
|
||
response: Response,
|
||
payload: unknown,
|
||
responseText: string,
|
||
) {
|
||
const envelope = isRecord(payload) ? (payload as ApiErrorEnvelope) : null;
|
||
const errorPayload = envelope?.error;
|
||
const details = isRecord(errorPayload?.details) ? errorPayload.details : null;
|
||
const detailsMessage =
|
||
typeof details?.message === 'string' ? details.message.trim() : '';
|
||
const payloadMessage =
|
||
typeof errorPayload?.message === 'string'
|
||
? errorPayload.message.trim()
|
||
: '';
|
||
const topLevelMessage =
|
||
typeof envelope?.message === 'string' ? envelope.message.trim() : '';
|
||
const message =
|
||
detailsMessage ||
|
||
payloadMessage ||
|
||
topLevelMessage ||
|
||
response.statusText ||
|
||
`HTTP ${response.status}`;
|
||
|
||
return new AdminApiError({
|
||
message,
|
||
status: response.status,
|
||
code: errorPayload?.code,
|
||
details,
|
||
meta: envelope?.meta ?? null,
|
||
responseText,
|
||
});
|
||
}
|
||
|
||
function isRecord(value: unknown): value is Record<string, unknown> {
|
||
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
||
}
|
||
export function getAgcModelCatalog(token: string) {
|
||
return request<import('./adminApiTypes').AdminAgcModelCatalog>(
|
||
'/admin/api/agc-models',
|
||
{ token },
|
||
);
|
||
}
|
||
|
||
export function saveAgcModelCatalog(
|
||
token: string,
|
||
body: import('./adminApiTypes').AdminAgcModelCatalog,
|
||
) {
|
||
return request<import('./adminApiTypes').AdminAgcModelCatalog>(
|
||
'/admin/api/agc-models',
|
||
{ token, method: 'PUT', body },
|
||
);
|
||
}
|