合并 master 并整合 Router 安全修复
合入 origin/master 最新提交 a465e481e
保留后台错误报告、AGC 受控搜索与 Router 计费安全修复
同步 SpacetimeDB migration、技能文档和配置契约
This commit is contained in:
@@ -23,6 +23,9 @@ import type {
|
||||
AdminEditorShowcaseListQuery,
|
||||
AdminEditorShowcaseListResponse,
|
||||
AdminEditorShowcaseReviewRequest,
|
||||
AdminErrorReportDetail,
|
||||
AdminErrorReportEntry,
|
||||
AdminErrorReportListResponse,
|
||||
AdminExternalApiKeyListQuery,
|
||||
AdminExternalApiKeyListResponse,
|
||||
AdminFeatureGateConfigResponse,
|
||||
@@ -271,7 +274,16 @@ export function listAdminTrackingEvents(
|
||||
query: AdminTrackingEventListQuery = {},
|
||||
) {
|
||||
return request<AdminTrackingEventListResponse>(
|
||||
`/admin/api/tracking/events${buildQueryString(query)}`,
|
||||
`/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 },
|
||||
);
|
||||
}
|
||||
@@ -283,6 +295,60 @@ export function listAdminTrackingEventKeys(token: string) {
|
||||
);
|
||||
}
|
||||
|
||||
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,
|
||||
@@ -800,43 +866,44 @@ async function postAdminDirectUploadFile(
|
||||
}
|
||||
}
|
||||
|
||||
function buildQueryString(query: AdminTrackingEventListQuery) {
|
||||
function buildQueryString(append: (params: URLSearchParams) => void) {
|
||||
const params = new URLSearchParams();
|
||||
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);
|
||||
if (typeof query.limit === 'number' && Number.isFinite(query.limit)) {
|
||||
params.set('limit', String(query.limit));
|
||||
}
|
||||
if (query.exportAll) {
|
||||
params.set('exportAll', 'true');
|
||||
}
|
||||
const queryString = params.toString();
|
||||
return queryString ? `?${queryString}` : '';
|
||||
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) {
|
||||
const params = new URLSearchParams();
|
||||
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);
|
||||
if (typeof query.limit === 'number' && Number.isFinite(query.limit)) {
|
||||
params.set('limit', String(query.limit));
|
||||
}
|
||||
const queryString = params.toString();
|
||||
return queryString ? `?${queryString}` : '';
|
||||
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) {
|
||||
@@ -935,6 +1002,21 @@ function appendQueryParam(
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
@@ -96,6 +96,38 @@ export interface AdminMeResponse {
|
||||
admin: AdminSessionPayload;
|
||||
}
|
||||
|
||||
export interface AdminErrorReportEntry {
|
||||
batchId: string;
|
||||
eventCount: number;
|
||||
logCount: number;
|
||||
fingerprint?: string;
|
||||
source?: string;
|
||||
status: 'new' | 'in-progress' | 'resolved';
|
||||
userId: string;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
userDescription?: string;
|
||||
attachmentSizeBytes: number;
|
||||
submissionId?: string;
|
||||
ossObjectKey?: string;
|
||||
archiveSha256?: string;
|
||||
uploadStatus?: 'uploading' | 'ready' | 'failed';
|
||||
}
|
||||
|
||||
export interface AdminErrorReportListResponse {
|
||||
reports: AdminErrorReportEntry[];
|
||||
total: number;
|
||||
offset: number;
|
||||
limit: number;
|
||||
hasMore: boolean;
|
||||
}
|
||||
|
||||
export interface AdminErrorReportDetail extends AdminErrorReportEntry {
|
||||
note?: string;
|
||||
events: Array<Record<string, unknown>>;
|
||||
logNames: string[];
|
||||
}
|
||||
|
||||
export interface AdminOverviewResponse {
|
||||
service: AdminServiceOverviewPayload;
|
||||
database: AdminDatabaseOverviewPayload;
|
||||
|
||||
Reference in New Issue
Block a user