import type { AdminDebugHttpRequest, AdminDebugHttpResponse, AdminDisableProfileRedeemCodeRequest, AdminDisableProfileTaskConfigRequest, AdminDatabaseTableListResponse, AdminDatabaseTableRowsQuery, AdminDatabaseTableRowsResponse, AdminLoginResponse, AdminMeResponse, AdminOverviewResponse, AdminTrackingEventListQuery, AdminTrackingEventListResponse, AdminUpsertProfileInviteCodeRequest, AdminUpsertProfileRedeemCodeRequest, AdminUpsertProfileTaskConfigRequest, ApiErrorEnvelope, ApiMeta, ApiSuccessEnvelope, ProfileInviteCodeAdminListResponse, ProfileInviteCodeAdminResponse, ProfileRedeemCodeAdminListResponse, ProfileRedeemCodeAdminResponse, ProfileTaskConfigAdminListResponse, ProfileTaskConfigAdminResponse, } 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; signal?: AbortSignal; } export class AdminApiError extends Error { status: number; code: string; details: Record | null; meta: ApiMeta | null; responseText: string; constructor(params: { message: string; status: number; code?: string; details?: Record | 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( path: string, options: AdminRequestOptions = {}, ): Promise { const method = options.method ?? 'GET'; const headers: Record = { 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(payload); } export function loginAdmin(username: string, password: string) { return request('/admin/api/login', { method: 'POST', body: {username, password}, }); } export function getAdminMe(token: string) { return request('/admin/api/me', {token}); } export function getAdminOverview(token: string) { return request('/admin/api/overview', {token}); } export function getAdminDatabaseTables(token: string) { return request('/admin/api/database/tables', { token, }); } export function getAdminDatabaseTableRows( token: string, tableName: string, query: AdminDatabaseTableRowsQuery = {}, ) { return request( `/admin/api/database/tables/${encodeURIComponent(tableName)}/rows${buildDatabaseTableRowsQuery(query)}`, {token}, ); } export function debugAdminHttp(token: string, payload: AdminDebugHttpRequest) { return request('/admin/api/debug/http', { method: 'POST', token, body: payload, }); } export function listAdminTrackingEvents( token: string, query: AdminTrackingEventListQuery = {}, ) { return request( `/admin/api/tracking/events${buildQueryString(query)}`, {token}, ); } export function listProfileRedeemCodes(token: string) { return request( '/admin/api/profile/redeem-codes', {token}, ); } export function upsertProfileRedeemCode( token: string, payload: AdminUpsertProfileRedeemCodeRequest, ) { return request( '/admin/api/profile/redeem-codes', { method: 'POST', token, body: payload, }, ); } export function listProfileInviteCodes(token: string) { return request( '/admin/api/profile/invite-codes', {token}, ); } export function upsertProfileInviteCode( token: string, payload: AdminUpsertProfileInviteCodeRequest, ) { return request( '/admin/api/profile/invite-codes', { method: 'POST', token, body: payload, }, ); } export function disableProfileRedeemCode( token: string, payload: AdminDisableProfileRedeemCodeRequest, ) { return request( '/admin/api/profile/redeem-codes/disable', { method: 'POST', token, body: payload, }, ); } export function listProfileTaskConfigs(token: string) { return request( '/admin/api/profile/tasks', {token}, ); } export function upsertProfileTaskConfig( token: string, payload: AdminUpsertProfileTaskConfigRequest, ) { return request('/admin/api/profile/tasks', { method: 'POST', token, body: payload, }); } export function disableProfileTaskConfig( token: string, payload: AdminDisableProfileTaskConfigRequest, ) { return request( '/admin/api/profile/tasks/disable', { 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 buildQueryString(query: AdminTrackingEventListQuery) { const params = new URLSearchParams(); appendQueryParam(params, 'eventKey', query.eventKey); appendQueryParam(params, 'userId', query.userId); appendQueryParam(params, 'scopeKind', query.scopeKind); appendQueryParam(params, 'scopeId', query.scopeId); if (typeof query.limit === 'number' && Number.isFinite(query.limit)) { params.set('limit', String(query.limit)); } 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)); } 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 parseJsonResponse(responseText: string): unknown { if (!responseText.trim()) { return null; } try { return JSON.parse(responseText) as unknown; } catch { return null; } } function unwrapSuccessPayload(payload: unknown): T { if (isRecord(payload) && 'data' in payload) { return (payload as ApiSuccessEnvelope).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 { return typeof value === 'object' && value !== null && !Array.isArray(value); }