回滚误入 master 的游戏分发 WIP(夹带在 OSS v1 签名提交里)
Project CI / AI game creator shell Rust shard 2/4 (push) Failing after 19s
Project CI / AI game creator shell Rust shard 3/4 (push) Failing after 20s
Project CI / AI game creator shell Rust shard 1/4 (push) Failing after 20s
Project CI / AI game creator shell Rust shard 4/4 (push) Failing after 20s
Project CI / Backend tests (push) Failing after 17s
Project CI / AI game creator shell Rust crates (push) Failing after 17s
Project CI / AI game creator shell Rust smoke (push) Failing after 19s
Project CI / Native shell tests (push) Failing after 18s
Project CI / Frontend tests (push) Failing after 8s
Project CI / AI game creator shell web tests (push) Failing after 15s
Project CI / Repository checks (push) Failing after 17s
Project CI / AI game creator shell Rust shard 2/4 (push) Failing after 19s
Project CI / AI game creator shell Rust shard 3/4 (push) Failing after 20s
Project CI / AI game creator shell Rust shard 1/4 (push) Failing after 20s
Project CI / AI game creator shell Rust shard 4/4 (push) Failing after 20s
Project CI / Backend tests (push) Failing after 17s
Project CI / AI game creator shell Rust crates (push) Failing after 17s
Project CI / AI game creator shell Rust smoke (push) Failing after 19s
Project CI / Native shell tests (push) Failing after 18s
Project CI / Frontend tests (push) Failing after 8s
Project CI / AI game creator shell web tests (push) Failing after 15s
Project CI / Repository checks (push) Failing after 17s
- 撤销 39aed2e48 夹带的游戏分发前后端、SpacetimeDB 绑定、主站路由、后台审核页、文档与 nginx 改动,master 恢复可编译状态
- 保留该提交自身的 AGC 发号改动:agc-global-version.mjs 与其参数构造单测
- 恢复被误删的 output 产物,避免把无关清理混进该提交
- 补回两篇技术文档在 docs/README.md 的索引(它们属于后续两个补提交文档,不属于游戏分发)
- 游戏分发完整实现保留在 codex/game-distribution 分支继续开发
This commit is contained in:
@@ -5,11 +5,9 @@ import {
|
||||
executeAdminRechargeRefund,
|
||||
getAdminFeatureGateConfig,
|
||||
getAdminUserDetail,
|
||||
listAdminGameDistributionReviews,
|
||||
listAdminRechargeOrders,
|
||||
reconcileAdminUserConsumption,
|
||||
resolveAdminRechargeRefundManualReview,
|
||||
reviewAdminGameDistributionVersion,
|
||||
updateAdminAccount,
|
||||
uploadAdminEditorShowcaseCampaignImage,
|
||||
upsertAdminFeatureGateConfig,
|
||||
@@ -366,87 +364,3 @@ test('退款人工复核使用独立 resolve 管理员路由', async () => {
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
test('游戏审核列表与审核动作使用约定的 URL、方法和幂等键', async () => {
|
||||
const fetchMock = vi.fn().mockImplementation(() =>
|
||||
Promise.resolve(
|
||||
new Response(JSON.stringify({ entries: [], nextCursor: null }), {
|
||||
status: 200,
|
||||
}),
|
||||
),
|
||||
);
|
||||
vi.stubGlobal('fetch', fetchMock);
|
||||
|
||||
await listAdminGameDistributionReviews('admin-token');
|
||||
await reviewAdminGameDistributionVersion(
|
||||
'admin-token',
|
||||
'gamever/1',
|
||||
'game-review-key-1',
|
||||
{
|
||||
decision: 'approve',
|
||||
expectedPublicationRevision: 3,
|
||||
entryUrl: 'https://games.example.test/releases/game_1/index.html',
|
||||
},
|
||||
);
|
||||
|
||||
expect(fetchMock.mock.calls[0]?.[0]).toBe(
|
||||
'/admin/api/game-distribution/reviews?limit=48',
|
||||
);
|
||||
expect(fetchMock.mock.calls[1]?.[0]).toBe(
|
||||
'/admin/api/game-distribution/versions/gamever%2F1/review',
|
||||
);
|
||||
expect(fetchMock.mock.calls[1]?.[1]).toEqual(
|
||||
expect.objectContaining({
|
||||
method: 'POST',
|
||||
headers: expect.objectContaining({
|
||||
Authorization: 'Bearer admin-token',
|
||||
'Idempotency-Key': 'game-review-key-1',
|
||||
}),
|
||||
body: JSON.stringify({
|
||||
decision: 'approve',
|
||||
expectedPublicationRevision: 3,
|
||||
entryUrl: 'https://games.example.test/releases/game_1/index.html',
|
||||
}),
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
test('游戏审核拒绝请求携带理由,空幂等键在本地失败关闭', async () => {
|
||||
const fetchMock = vi.fn().mockImplementation(() =>
|
||||
Promise.resolve(
|
||||
new Response(JSON.stringify({ version: {}, replayed: false }), {
|
||||
status: 200,
|
||||
}),
|
||||
),
|
||||
);
|
||||
vi.stubGlobal('fetch', fetchMock);
|
||||
|
||||
await reviewAdminGameDistributionVersion(
|
||||
'admin-token',
|
||||
'version-1',
|
||||
'game-review-key-2',
|
||||
{
|
||||
decision: 'reject',
|
||||
expectedPublicationRevision: 0,
|
||||
reviewReason: '运行时报错',
|
||||
},
|
||||
);
|
||||
expect(fetchMock.mock.calls[0]?.[1]).toEqual(
|
||||
expect.objectContaining({
|
||||
body: JSON.stringify({
|
||||
decision: 'reject',
|
||||
expectedPublicationRevision: 0,
|
||||
reviewReason: '运行时报错',
|
||||
}),
|
||||
}),
|
||||
);
|
||||
|
||||
expect(() =>
|
||||
reviewAdminGameDistributionVersion('admin-token', 'version-1', ' ', {
|
||||
decision: 'reject',
|
||||
expectedPublicationRevision: 0,
|
||||
reviewReason: 'x',
|
||||
}),
|
||||
).toThrow('审核幂等键必须是 1 到 128 个字符');
|
||||
expect(fetchMock).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
@@ -29,9 +29,6 @@ import type {
|
||||
AdminExternalApiKeyListQuery,
|
||||
AdminExternalApiKeyListResponse,
|
||||
AdminFeatureGateConfigResponse,
|
||||
AdminGameDistributionReviewListResponse,
|
||||
AdminGameDistributionReviewRequest,
|
||||
AdminGameDistributionReviewResponse,
|
||||
AdminLoginResponse,
|
||||
AdminMeResponse,
|
||||
AdminOverviewResponse,
|
||||
@@ -1179,42 +1176,3 @@ export function saveAgcModelCatalog(
|
||||
{ token, method: 'PUT', body },
|
||||
);
|
||||
}
|
||||
|
||||
export function listAdminGameDistributionReviews(token: string, limit = 48) {
|
||||
const normalizedLimit = Number.isFinite(limit)
|
||||
? Math.min(Math.max(Math.trunc(limit), 1), 48)
|
||||
: 48;
|
||||
return request<AdminGameDistributionReviewListResponse>(
|
||||
`/admin/api/game-distribution/reviews?limit=${normalizedLimit}`,
|
||||
{ token },
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 审核游戏发行版本。幂等键由调用方生成并在同一次提交内复用,避免重复点击产生
|
||||
* 两条审核结论。
|
||||
*/
|
||||
export function reviewAdminGameDistributionVersion(
|
||||
token: string,
|
||||
versionId: string,
|
||||
idempotencyKey: string,
|
||||
payload: AdminGameDistributionReviewRequest,
|
||||
) {
|
||||
const normalizedVersionId = versionId.trim();
|
||||
const normalizedKey = idempotencyKey.trim();
|
||||
if (!normalizedVersionId) {
|
||||
throw new Error('缺少发行版本 ID');
|
||||
}
|
||||
if (!normalizedKey || normalizedKey.length > 128) {
|
||||
throw new Error('审核幂等键必须是 1 到 128 个字符');
|
||||
}
|
||||
return request<AdminGameDistributionReviewResponse>(
|
||||
`/admin/api/game-distribution/versions/${encodeURIComponent(normalizedVersionId)}/review`,
|
||||
{
|
||||
method: 'POST',
|
||||
token,
|
||||
headers: { 'Idempotency-Key': normalizedKey },
|
||||
body: payload,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1031,33 +1031,3 @@ export interface AdminAgcModelCatalog {
|
||||
defaultModelId: string;
|
||||
models: AdminAgcModel[];
|
||||
}
|
||||
|
||||
export interface AdminGameDistributionReviewEntry {
|
||||
versionId: string;
|
||||
gameId: string;
|
||||
versionNumber: number;
|
||||
packageSha256: string;
|
||||
packageBytes: number;
|
||||
status: string;
|
||||
publicationRevision: number;
|
||||
reviewReason: string | null;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
}
|
||||
|
||||
export interface AdminGameDistributionReviewListResponse {
|
||||
entries: AdminGameDistributionReviewEntry[];
|
||||
nextCursor: string | null;
|
||||
}
|
||||
|
||||
export interface AdminGameDistributionReviewRequest {
|
||||
decision: 'approve' | 'reject';
|
||||
expectedPublicationRevision: number;
|
||||
reviewReason?: string;
|
||||
entryUrl?: string;
|
||||
}
|
||||
|
||||
export interface AdminGameDistributionReviewResponse {
|
||||
version: AdminGameDistributionReviewEntry;
|
||||
replayed: boolean;
|
||||
}
|
||||
|
||||
@@ -26,7 +26,6 @@ import { AdminEditorAssetQueryPage } from '../pages/AdminEditorAssetQueryPage';
|
||||
import { AdminEditorGenerationPricingPage } from '../pages/AdminEditorGenerationPricingPage';
|
||||
import { AdminEditorShowcaseReviewPage } from '../pages/AdminEditorShowcaseReviewPage';
|
||||
import { AdminErrorReportsPage } from '../pages/AdminErrorReportsPage';
|
||||
import { AdminGameDistributionReviewPage } from '../pages/AdminGameDistributionReviewPage';
|
||||
import { AdminGrayReleaseConfigPage } from '../pages/AdminGrayReleaseConfigPage';
|
||||
import { AdminInviteCodePage } from '../pages/AdminInviteCodePage';
|
||||
import { AdminLoginPage } from '../pages/AdminLoginPage';
|
||||
@@ -301,12 +300,6 @@ export function AdminApp() {
|
||||
onUnauthorized={handleUnauthorized}
|
||||
/>
|
||||
) : null}
|
||||
{activeRouteId === 'game-distribution' ? (
|
||||
<AdminGameDistributionReviewPage
|
||||
token={token}
|
||||
onUnauthorized={handleUnauthorized}
|
||||
/>
|
||||
) : null}
|
||||
{activeRouteId === 'editor-assets' ? (
|
||||
<AdminEditorAssetQueryPage
|
||||
token={token}
|
||||
|
||||
@@ -5,7 +5,6 @@ import {
|
||||
Coins,
|
||||
Database,
|
||||
FolderArchive,
|
||||
Gamepad2,
|
||||
GitBranch,
|
||||
Images,
|
||||
LayoutDashboard,
|
||||
@@ -50,7 +49,6 @@ const routeIcons = {
|
||||
'recharge-orders': ReceiptText,
|
||||
'editor-generation-pricing': Coins,
|
||||
'editor-showcase': Star,
|
||||
'game-distribution': Gamepad2,
|
||||
'editor-assets': Images,
|
||||
'project-snapshots': FolderArchive,
|
||||
accounts: Users,
|
||||
|
||||
@@ -147,24 +147,3 @@ test('项目工程入口对 owner 与已授权 member 开放且可分配权限',
|
||||
}),
|
||||
).not.toContainEqual(route);
|
||||
});
|
||||
|
||||
test('后台游戏审核路由可通过导航和 hash 访问', () => {
|
||||
expect(adminRoutes).toContainEqual({
|
||||
id: 'game-distribution',
|
||||
label: '游戏审核',
|
||||
hash: '#game-distribution',
|
||||
});
|
||||
expect(resolveAdminRoute('#game-distribution')).toBe('game-distribution');
|
||||
expect(routeHash('game-distribution')).toBe('#game-distribution');
|
||||
});
|
||||
|
||||
test('member 可单独获得游戏审核 Tab 权限', () => {
|
||||
const routes = getAccessibleAdminRoutes({
|
||||
accountRole: 'member',
|
||||
tabPermissions: ['game-distribution'],
|
||||
});
|
||||
expect(routes.map((route) => route.id)).toEqual(['game-distribution']);
|
||||
expect(resolveAccessibleAdminRoute('#game-distribution', routes)).toBe(
|
||||
'game-distribution',
|
||||
);
|
||||
});
|
||||
|
||||
@@ -15,7 +15,6 @@ export type AdminRouteId =
|
||||
| 'recharge-orders'
|
||||
| 'editor-generation-pricing'
|
||||
| 'editor-showcase'
|
||||
| 'game-distribution'
|
||||
| 'editor-assets'
|
||||
| 'project-snapshots'
|
||||
| 'agc-models'
|
||||
@@ -55,7 +54,6 @@ export const adminRoutes: AdminRouteDefinition[] = [
|
||||
},
|
||||
{ id: 'agc-models', label: 'AGC 模型', hash: '#agc-models', ownerOnly: true },
|
||||
{ id: 'editor-showcase', label: '精选审核', hash: '#editor-showcase' },
|
||||
{ id: 'game-distribution', label: '游戏审核', hash: '#game-distribution' },
|
||||
{ id: 'editor-assets', label: '素材查询', hash: '#editor-assets' },
|
||||
{ id: 'project-snapshots', label: '项目工程', hash: '#project-snapshots' },
|
||||
{ id: 'accounts', label: '账号管理', hash: '#accounts', ownerOnly: true },
|
||||
|
||||
Reference in New Issue
Block a user