退役旧创作模板业务
保留 SpacetimeDB 历史表、迁移白名单与旧业务源码 切换前端 active 入口并解除旧创作页面和路由编译链 移除旧后端路由、worker 与纯业务 crate 依赖 收敛 SpacetimeDB 模块为历史数据壳 同步 Nginx、Pingora、验证门禁与架构文档
This commit is contained in:
+174
-8
@@ -71,12 +71,6 @@ module.exports = {
|
||||
'simple-import-sort/exports': 'off',
|
||||
},
|
||||
},
|
||||
{
|
||||
files: ['src/components/match3d-runtime/Match3DPhysicsBoard.tsx'],
|
||||
rules: {
|
||||
'react-refresh/only-export-components': 'off',
|
||||
},
|
||||
},
|
||||
],
|
||||
plugins: [
|
||||
'@typescript-eslint',
|
||||
@@ -96,22 +90,191 @@ module.exports = {
|
||||
'dist_check',
|
||||
'dist_check_monster_position',
|
||||
'coverage',
|
||||
'build',
|
||||
'output',
|
||||
'retired/**',
|
||||
'node_modules',
|
||||
'server-rs/target',
|
||||
'server-rs/target-*',
|
||||
'apps/desktop-shell/src-tauri/target',
|
||||
'target',
|
||||
'src/main.tsx',
|
||||
'src/App.tsx',
|
||||
'src/routing/appPageRoutes.ts',
|
||||
'src/routing/appPageRoutes.test.ts',
|
||||
'src/routing/appRoutes.tsx',
|
||||
'src/routing/appRoutes.test.ts',
|
||||
'src/services/appTitle.ts',
|
||||
'src/services/appTitle.test.ts',
|
||||
'src/components/CharacterAnimator.tsx',
|
||||
'src/AdventurePanel.tsx',
|
||||
'src/AdventureEntityModal.tsx',
|
||||
'src/App.tsx',
|
||||
'src/App.test.tsx',
|
||||
'src/CharacterCreation.tsx',
|
||||
'src/RpgRuntimeApp.tsx',
|
||||
'src/routing/appPageRoutes.ts',
|
||||
'src/routing/appPageRoutes.test.ts',
|
||||
'src/routing/appRoutes.tsx',
|
||||
'src/routing/appRoutes.test.ts',
|
||||
'src/*PlaygroundApp.tsx',
|
||||
'src/ChildMotionDemoApp.tsx',
|
||||
'src/Match3DPlaygroundApp.tsx',
|
||||
'src/components/child-motion-demo/**',
|
||||
'src/components/asset-studio/**',
|
||||
'src/components/bark-battle-creation/**',
|
||||
'src/components/big-fish-creation/**',
|
||||
'src/components/big-fish-result/**',
|
||||
'src/components/big-fish-runtime/**',
|
||||
'src/components/creation-agent/**',
|
||||
'src/components/creation-home/**',
|
||||
'src/components/creative-agent/**',
|
||||
'src/components/custom-world-agent/**',
|
||||
'src/components/custom-world-home/**',
|
||||
'src/components/edutainment-creation/**',
|
||||
'src/components/edutainment-result/**',
|
||||
'src/components/edutainment-runtime/**',
|
||||
'src/components/game-canvas/**',
|
||||
'src/components/jump-hop-result/**',
|
||||
'src/components/jump-hop-runtime/**',
|
||||
'src/components/match3d-result/**',
|
||||
'src/components/match3d-runtime/**',
|
||||
'src/components/puzzle-clear-creation/**',
|
||||
'src/components/puzzle-clear-result/**',
|
||||
'src/components/puzzle-clear-runtime/**',
|
||||
'src/components/puzzle-gallery/**',
|
||||
'src/components/puzzle-result/**',
|
||||
'src/components/puzzle-runtime/**',
|
||||
'src/components/rpg-creation-asset-studio/**',
|
||||
'src/components/rpg-creation-result/**',
|
||||
'src/components/rpg-runtime-panels/**',
|
||||
'src/components/square-hole-creation/**',
|
||||
'src/components/square-hole-result/**',
|
||||
'src/components/square-hole-runtime/**',
|
||||
'src/components/unified-creation/**',
|
||||
'src/components/visual-novel-creation/**',
|
||||
'src/components/visual-novel-result/**',
|
||||
'src/components/visual-novel-runtime/**',
|
||||
'src/components/wooden-fish-result/**',
|
||||
'src/components/wooden-fish-runtime/**',
|
||||
'src/components/common/PublishShare*',
|
||||
'src/components/common/publishShare*',
|
||||
'src/components/platform-entry/PlatformEntryCreation*',
|
||||
'src/components/platform-entry/PlatformEntryFlowShellImpl.tsx',
|
||||
'src/components/platform-entry/platformEntryTypes.ts',
|
||||
'src/components/platform-entry/PlatformEntryHome*',
|
||||
'src/components/platform-entry/PlatformEntryWorld*',
|
||||
'src/components/platform-entry/PlatformMobileHome*',
|
||||
'src/components/platform-entry/PlatformWork*',
|
||||
'src/components/platform-entry/PlatformDraft*',
|
||||
'src/components/platform-entry/PlatformTask*',
|
||||
'src/components/platform-entry/PlatformError*',
|
||||
'src/components/platform-entry/barkBattle*',
|
||||
'src/components/platform-entry/platformCreation*',
|
||||
'src/components/platform-entry/platformDialog*',
|
||||
'src/components/platform-entry/platformDraft*',
|
||||
'src/components/platform-entry/platformEdutainment*',
|
||||
'src/components/platform-entry/platformEntryCreation*',
|
||||
'src/components/platform-entry/platformExternal*',
|
||||
'src/components/platform-entry/platformGeneration*',
|
||||
'src/components/platform-entry/platformHost*',
|
||||
'src/components/platform-entry/platformMiniGame*',
|
||||
'src/components/platform-entry/platformPlayed*',
|
||||
'src/components/platform-entry/platformPublic*',
|
||||
'src/components/platform-entry/platformPuzzle*',
|
||||
'src/components/platform-entry/platformRecommend*',
|
||||
'src/components/platform-entry/platformRpg*',
|
||||
'src/components/platform-entry/platformSelection*',
|
||||
'src/components/platform-entry/puzzleDraft*',
|
||||
'src/components/platform-entry/usePlatformCreation*',
|
||||
'src/components/platform-entry/usePlatformEntry*',
|
||||
'src/components/platform-entry/PlatformEntryFlowShellImpl/**',
|
||||
'src/components/platform-entry/platformMatch3DRuntimeProfile*',
|
||||
'src/components/unified-creation/workspaces/JumpHopCreationWorkspace*',
|
||||
'src/components/unified-creation/workspaces/Match3DCreationWorkspace*',
|
||||
'src/components/rpg-creation-editor/**',
|
||||
'src/components/rpg-entry/**',
|
||||
'!src/components/rpg-entry/',
|
||||
'!src/components/rpg-entry/rpgEntryProfileFundsViewModel.ts',
|
||||
'src/components/custom-world-home/CustomWorldCreationHub.interaction.test.tsx',
|
||||
'src/components/custom-world-home/CustomWorldCreationHub.test.tsx',
|
||||
'src/components/custom-world-home/CustomWorldCreationHub.testAdapter.tsx',
|
||||
'src/components/custom-world-home/creationWorkShelf.test.ts',
|
||||
'src/components/rpg-runtime-shell/**',
|
||||
'src/hooks/rpg-runtime-story/**',
|
||||
'src/hooks/rpg-session/**',
|
||||
'src/hooks/combat/**',
|
||||
'src/prompts/customWorldPrompts.ts',
|
||||
'src/services/ai.ts',
|
||||
'src/services/appTitle.ts',
|
||||
'src/services/appTitle.test.ts',
|
||||
'src/services/miniGameDraftGenerationProgress.ts',
|
||||
'src/services/bark-battle-creation/**',
|
||||
'src/services/bark-battle-runtime/**',
|
||||
'src/services/big-fish-creation/**',
|
||||
'src/services/big-fish-gallery/**',
|
||||
'src/services/big-fish-runtime/**',
|
||||
'src/services/big-fish-works/**',
|
||||
'src/services/creation-agent/**',
|
||||
'src/services/creation-audio/**',
|
||||
'src/services/creative-agent/**',
|
||||
'src/services/edutainment-baby-drawing/**',
|
||||
'src/services/edutainment-baby-object/**',
|
||||
'src/services/child-motion-demo/**',
|
||||
'src/services/jump-hop/**',
|
||||
'src/services/match3d-creation/**',
|
||||
'src/services/match3d-runtime/**',
|
||||
'src/services/match3d-works/**',
|
||||
'src/services/match3dGeneratedModelCache*',
|
||||
'src/services/match3dSpritesheetParser*',
|
||||
'src/services/puzzle-agent/**',
|
||||
'src/services/puzzle-gallery/**',
|
||||
'src/services/puzzle-onboarding/**',
|
||||
'src/services/puzzle-runtime/**',
|
||||
'src/services/puzzle-works/**',
|
||||
'src/services/rpg-creation/**',
|
||||
'src/services/rpg-entry/index.ts',
|
||||
'src/services/rpg-entry/rpgEntryLibraryClient.ts',
|
||||
'src/services/rpg-entry/rpgProfileClient.test.ts',
|
||||
'src/services/rpg-runtime/**',
|
||||
'!src/services/rpg-runtime/',
|
||||
'!src/services/rpg-runtime/rpgRuntimeRequest.ts',
|
||||
'src/services/square-hole-creation/**',
|
||||
'src/services/square-hole-runtime/**',
|
||||
'src/services/square-hole-works/**',
|
||||
'src/services/storyEngine/**',
|
||||
'src/services/visual-novel-creation/**',
|
||||
'src/services/visual-novel-runtime/**',
|
||||
'src/services/visual-novel-works/**',
|
||||
'src/services/wooden-fish/**',
|
||||
'packages/shared/src/contracts/jumpHop.ts',
|
||||
'packages/shared/src/contracts/match3dAgent.ts',
|
||||
'packages/shared/src/contracts/match3dRuntime.ts',
|
||||
'packages/shared/src/contracts/match3dWorks.ts',
|
||||
'packages/shared/src/contracts/barkBattle*',
|
||||
'packages/shared/src/contracts/bigFish*',
|
||||
'packages/shared/src/contracts/creationAgent*',
|
||||
'packages/shared/src/contracts/creationAudio*',
|
||||
'packages/shared/src/contracts/creativeAgent*',
|
||||
'packages/shared/src/contracts/customWorld*',
|
||||
'packages/shared/src/contracts/edutainment*',
|
||||
'packages/shared/src/contracts/playTypes*',
|
||||
'packages/shared/src/contracts/publicWork*',
|
||||
'packages/shared/src/contracts/puzzle*',
|
||||
'packages/shared/src/contracts/rpg*',
|
||||
'packages/shared/src/contracts/squareHole*',
|
||||
'packages/shared/src/contracts/story*',
|
||||
'packages/shared/src/contracts/visualNovel*',
|
||||
'packages/shared/src/contracts/woodenFish*',
|
||||
'scripts/export-match3d-resource-pipeline*',
|
||||
'scripts/generate-child-motion-demo-assets.mjs',
|
||||
'src/services/puzzle-clear/**',
|
||||
'src/games/**',
|
||||
'src/data/**',
|
||||
'src/prompts/**',
|
||||
'apps/admin-web/src/pages/AdminCreationEntrySwitchPage*',
|
||||
'apps/admin-web/src/pages/AdminGrayReleaseConfigPage*',
|
||||
'apps/admin-web/src/pages/AdminWorkVisibilityPage*',
|
||||
'src/services/recommendedRuntimeGuestLaunch.test.ts',
|
||||
'src/data/sceneEncounterPreviews.ts',
|
||||
'public/Icons',
|
||||
@@ -130,7 +293,10 @@ module.exports = {
|
||||
rules: {
|
||||
'@typescript-eslint/no-var-requires': 'off',
|
||||
'react-hooks/rules-of-hooks': 'error',
|
||||
'react-refresh/only-export-components': ['error', {allowConstantExport: true}],
|
||||
'react-refresh/only-export-components': [
|
||||
'error',
|
||||
{ allowConstantExport: true },
|
||||
],
|
||||
'simple-import-sort/imports': 'error',
|
||||
'simple-import-sort/exports': 'error',
|
||||
'@typescript-eslint/no-unused-vars': 'off',
|
||||
@@ -143,7 +309,7 @@ module.exports = {
|
||||
},
|
||||
],
|
||||
'no-constant-condition': 'error',
|
||||
'no-console': ['error', {allow: ['warn', 'error']}],
|
||||
'no-console': ['error', { allow: ['warn', 'error'] }],
|
||||
'no-useless-escape': 'error',
|
||||
'prefer-const': 'error',
|
||||
},
|
||||
|
||||
@@ -4,7 +4,6 @@ import type {
|
||||
AdminCreateAccountResponse,
|
||||
AdminCreateEditorShowcaseCampaignImageUploadTicketRequest,
|
||||
AdminCreateEditorShowcaseCampaignImageUploadTicketResponse,
|
||||
AdminCreationEntryConfigResponse,
|
||||
AdminDashboardQuery,
|
||||
AdminDashboardResponse,
|
||||
AdminDatabaseTableListResponse,
|
||||
@@ -23,7 +22,6 @@ import type {
|
||||
AdminEditorShowcaseListQuery,
|
||||
AdminEditorShowcaseListResponse,
|
||||
AdminEditorShowcaseReviewRequest,
|
||||
AdminFeatureGateConfigResponse,
|
||||
AdminLoginResponse,
|
||||
AdminMeResponse,
|
||||
AdminOverviewResponse,
|
||||
@@ -40,24 +38,17 @@ import type {
|
||||
AdminTrackingEventListResponse,
|
||||
AdminUpdateAccountRequest,
|
||||
AdminUpdateAccountResponse,
|
||||
AdminUpdateWorkVisibilityRequest,
|
||||
AdminUpdateWorkVisibilityResponse,
|
||||
AdminUploadedEditorShowcaseCampaignImage,
|
||||
AdminUpsertCreationEntryEventBannersRequest,
|
||||
AdminUpsertCreationEntryTypeConfigRequest,
|
||||
AdminUpsertEditorShowcaseCampaignRequest,
|
||||
AdminUpsertFeatureGateConfigRequest,
|
||||
AdminUpsertProfileInviteCodeRequest,
|
||||
AdminUpsertProfileRechargeProductRequest,
|
||||
AdminUpsertProfileRedeemCodeRequest,
|
||||
AdminUpsertProfileTaskConfigRequest,
|
||||
AdminUpsertProfileWalletConfigRequest,
|
||||
AdminUpsertPublicWorkInteractionConfigRequest,
|
||||
AdminUserDetailQuery,
|
||||
AdminUserDetailResponse,
|
||||
AdminWalletRestrictionRequest,
|
||||
AdminWalletRestrictionResponse,
|
||||
AdminWorkVisibilityListResponse,
|
||||
ApiErrorEnvelope,
|
||||
ApiMeta,
|
||||
ApiSuccessEnvelope,
|
||||
@@ -194,7 +185,7 @@ export function getAdminMe(token: string) {
|
||||
}
|
||||
|
||||
export function listAdminAccounts(token: string) {
|
||||
return request<AdminAccountListResponse>('/admin/api/accounts', {token});
|
||||
return request<AdminAccountListResponse>('/admin/api/accounts', { token });
|
||||
}
|
||||
|
||||
export function createAdminAccount(
|
||||
@@ -215,7 +206,7 @@ export function updateAdminAccount(
|
||||
) {
|
||||
return request<AdminUpdateAccountResponse>(
|
||||
`/admin/api/accounts/${encodeURIComponent(accountId)}`,
|
||||
{method: 'PUT', token, body: payload},
|
||||
{ method: 'PUT', token, body: payload },
|
||||
);
|
||||
}
|
||||
|
||||
@@ -275,74 +266,6 @@ export function listAdminTrackingEventKeys(token: string) {
|
||||
);
|
||||
}
|
||||
|
||||
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 getAdminCreationEntryConfig(token: string) {
|
||||
return request<AdminCreationEntryConfigResponse>(
|
||||
'/admin/api/creation-entry/config',
|
||||
{ token },
|
||||
);
|
||||
}
|
||||
|
||||
export function upsertAdminCreationEntryConfig(
|
||||
token: string,
|
||||
payload: AdminUpsertCreationEntryTypeConfigRequest,
|
||||
) {
|
||||
return request<AdminCreationEntryConfigResponse>(
|
||||
'/admin/api/creation-entry/config',
|
||||
{
|
||||
method: 'POST',
|
||||
token,
|
||||
body: payload,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/** 保存创作入口公告表单序列化后的后端传输字段。 */
|
||||
export function upsertAdminCreationEntryBanners(
|
||||
token: string,
|
||||
payload: AdminUpsertCreationEntryEventBannersRequest,
|
||||
) {
|
||||
return request<AdminCreationEntryConfigResponse>(
|
||||
'/admin/api/creation-entry/config/banners',
|
||||
{
|
||||
method: 'POST',
|
||||
token,
|
||||
body: payload,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/** 保存公开作品详情页点赞 / 改造能力配置。 */
|
||||
export function upsertAdminPublicWorkInteractions(
|
||||
token: string,
|
||||
payload: AdminUpsertPublicWorkInteractionConfigRequest,
|
||||
) {
|
||||
return request<AdminCreationEntryConfigResponse>(
|
||||
'/admin/api/creation-entry/config/interactions',
|
||||
{
|
||||
method: 'POST',
|
||||
token,
|
||||
body: payload,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
export function getAdminEditorGenerationPricing(token: string) {
|
||||
return request<EditorGenerationPricingConfigPayload>(
|
||||
'/admin/api/editor-generation-pricing',
|
||||
@@ -364,27 +287,6 @@ export function upsertAdminEditorGenerationPricing(
|
||||
);
|
||||
}
|
||||
|
||||
export function listAdminWorkVisibility(token: string) {
|
||||
return request<AdminWorkVisibilityListResponse>(
|
||||
'/admin/api/works/visibility',
|
||||
{ token },
|
||||
);
|
||||
}
|
||||
|
||||
export function updateAdminWorkVisibility(
|
||||
token: string,
|
||||
payload: AdminUpdateWorkVisibilityRequest,
|
||||
) {
|
||||
return request<AdminUpdateWorkVisibilityResponse>(
|
||||
'/admin/api/works/visibility',
|
||||
{
|
||||
method: 'POST',
|
||||
token,
|
||||
body: payload,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
export function getAdminAssetReadUrl(
|
||||
token: string,
|
||||
query: AdminAssetReadUrlQuery,
|
||||
@@ -471,18 +373,19 @@ export async function uploadAdminEditorShowcaseCampaignImage(
|
||||
): 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,
|
||||
},
|
||||
);
|
||||
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, '');
|
||||
return {
|
||||
@@ -630,17 +533,14 @@ export function listAdminRechargeOrders(
|
||||
) {
|
||||
return request<AdminRechargeOrderListResponse>(
|
||||
`/admin/api/profile/recharge-orders${buildAdminRechargeOrderListQuery(query)}`,
|
||||
{token},
|
||||
{ token },
|
||||
);
|
||||
}
|
||||
|
||||
export function getAdminUserDetail(
|
||||
token: string,
|
||||
query: AdminUserDetailQuery,
|
||||
) {
|
||||
export function getAdminUserDetail(token: string, query: AdminUserDetailQuery) {
|
||||
return request<AdminUserDetailResponse>(
|
||||
`/admin/api/profile/users/detail${buildAdminUserDetailQuery(query)}`,
|
||||
{token},
|
||||
{ token },
|
||||
);
|
||||
}
|
||||
|
||||
@@ -650,7 +550,7 @@ export function previewAdminRechargeRefund(
|
||||
) {
|
||||
return request<AdminRechargeRefundPreviewResponse>(
|
||||
'/admin/api/profile/recharge-refunds/preview',
|
||||
{method: 'POST', token, body: payload},
|
||||
{ method: 'POST', token, body: payload },
|
||||
);
|
||||
}
|
||||
|
||||
@@ -660,7 +560,7 @@ export function executeAdminRechargeRefund(
|
||||
) {
|
||||
return request<AdminRechargeRefundActionResponse>(
|
||||
'/admin/api/profile/recharge-refunds/execute',
|
||||
{method: 'POST', token, body: payload},
|
||||
{ method: 'POST', token, body: payload },
|
||||
);
|
||||
}
|
||||
|
||||
@@ -670,7 +570,7 @@ export function registerAdminRechargeRefund(
|
||||
) {
|
||||
return request<AdminRechargeRefundActionResponse>(
|
||||
'/admin/api/profile/recharge-refunds/register',
|
||||
{method: 'POST', token, body: payload},
|
||||
{ method: 'POST', token, body: payload },
|
||||
);
|
||||
}
|
||||
|
||||
@@ -680,7 +580,7 @@ export function resolveAdminRechargeRefundManualReview(
|
||||
) {
|
||||
return request<AdminRechargeRefundActionResponse>(
|
||||
'/admin/api/profile/recharge-refunds/manual-review/resolve',
|
||||
{method: 'POST', token, body: payload},
|
||||
{ method: 'POST', token, body: payload },
|
||||
);
|
||||
}
|
||||
|
||||
@@ -690,7 +590,7 @@ export function updateAdminWalletRestriction(
|
||||
) {
|
||||
return request<AdminWalletRestrictionResponse>(
|
||||
'/admin/api/profile/wallet-restriction',
|
||||
{method: 'POST', token, body: payload},
|
||||
{ method: 'POST', token, body: payload },
|
||||
);
|
||||
}
|
||||
|
||||
@@ -710,10 +610,7 @@ function buildAssetReadUrlQuery(query: AdminAssetReadUrlQuery) {
|
||||
if (objectKey) {
|
||||
params.set('objectKey', objectKey);
|
||||
} else if (legacyPublicPath) {
|
||||
params.set(
|
||||
'legacyPublicPath',
|
||||
`/${legacyPublicPath.replace(/^\/+/u, '')}`,
|
||||
);
|
||||
params.set('legacyPublicPath', `/${legacyPublicPath.replace(/^\/+/u, '')}`);
|
||||
}
|
||||
if (
|
||||
typeof query.expireSeconds === 'number' &&
|
||||
@@ -731,7 +628,10 @@ function resolveAdminImageContentType(file: File) {
|
||||
if (declaredType.startsWith('image/')) {
|
||||
return declaredType;
|
||||
}
|
||||
const extension = file.name.trim().toLowerCase().match(/\.([a-z0-9]+)$/u)?.[1];
|
||||
const extension = file.name
|
||||
.trim()
|
||||
.toLowerCase()
|
||||
.match(/\.([a-z0-9]+)$/u)?.[1];
|
||||
if (extension === 'jpg' || extension === 'jpeg') {
|
||||
return 'image/jpeg';
|
||||
}
|
||||
|
||||
@@ -286,98 +286,6 @@ export interface AdminTrackingEventListQuery {
|
||||
exportAll?: boolean;
|
||||
}
|
||||
|
||||
export interface AdminFeatureGateConfigPayload {
|
||||
gateKey: string;
|
||||
enabled: boolean;
|
||||
rolloutPercent: number;
|
||||
allowUserIds: string[];
|
||||
allowUserTags: string[];
|
||||
denyUserIds: string[];
|
||||
description: string;
|
||||
updatedAt: string;
|
||||
}
|
||||
|
||||
export interface AdminFeatureGateConfigResponse {
|
||||
gates: AdminFeatureGateConfigPayload[];
|
||||
}
|
||||
|
||||
export type AdminUpsertFeatureGateConfigRequest = Omit<
|
||||
AdminFeatureGateConfigPayload,
|
||||
'updatedAt'
|
||||
>;
|
||||
|
||||
/** 后台创作入口配置响应,同时包含模板入口和独立公告配置。 */
|
||||
export interface AdminCreationEntryConfigResponse {
|
||||
entries: AdminCreationEntryTypeConfigPayload[];
|
||||
eventBanners: AdminCreationEntryEventBannerPayload[];
|
||||
publicWorkInteractions: PublicWorkInteractionConfigPayload[];
|
||||
}
|
||||
|
||||
/** 后台创作入口公告位配置项;旧结构化 banner 字段仅保留兼容。 */
|
||||
export interface AdminCreationEntryEventBannerPayload {
|
||||
title: string;
|
||||
description: string;
|
||||
coverImageSrc: string;
|
||||
prizePoolMudPoints: number;
|
||||
startsAtText: string;
|
||||
endsAtText: string;
|
||||
renderMode: 'structured' | 'html';
|
||||
htmlCode?: string | null;
|
||||
}
|
||||
|
||||
/** 后台单个创作模板入口配置,公告不再绑定在某一个入口上。 */
|
||||
export interface AdminCreationEntryTypeConfigPayload {
|
||||
id: string;
|
||||
title: string;
|
||||
subtitle: string;
|
||||
badge: string;
|
||||
imageSrc: string;
|
||||
visible: boolean;
|
||||
open: boolean;
|
||||
sortOrder: number;
|
||||
categoryId: string;
|
||||
categoryLabel: string;
|
||||
categorySortOrder: number;
|
||||
updatedAtMicros: number;
|
||||
unifiedCreationSpec?: UnifiedCreationSpecPayload | null;
|
||||
}
|
||||
|
||||
/** 后台保存创作模板入口开关与统一创作契约的请求体。 */
|
||||
export interface AdminUpsertCreationEntryTypeConfigRequest {
|
||||
id: string;
|
||||
title: string;
|
||||
subtitle: string;
|
||||
badge: string;
|
||||
imageSrc: string;
|
||||
visible: boolean;
|
||||
open: boolean;
|
||||
sortOrder: number;
|
||||
categoryId: string;
|
||||
categoryLabel: string;
|
||||
categorySortOrder: number;
|
||||
unifiedCreationSpec?: UnifiedCreationSpecPayload | null;
|
||||
}
|
||||
|
||||
/** 后台保存创作入口公告表单序列化结果的请求体。 */
|
||||
export interface AdminUpsertCreationEntryEventBannersRequest {
|
||||
/** 传输字段沿用后端契约,内容由后台表单生成。 */
|
||||
eventBannersJson: string;
|
||||
}
|
||||
|
||||
/** 后台公开作品详情页互动能力配置项。 */
|
||||
export interface PublicWorkInteractionConfigPayload {
|
||||
sourceType: string;
|
||||
likeEnabled: boolean;
|
||||
remixEnabled: boolean;
|
||||
likeDisabledMessage: string;
|
||||
remixDisabledMessage: string;
|
||||
}
|
||||
|
||||
/** 后台保存公开作品点赞 / 改造能力配置请求体。 */
|
||||
export interface AdminUpsertPublicWorkInteractionConfigRequest {
|
||||
publicWorkInteractions: PublicWorkInteractionConfigPayload[];
|
||||
}
|
||||
|
||||
/** 图片画布生成模型泥点定价配置。 */
|
||||
export type EditorGenerationPricingUnitPayload = 'perGeneration' | 'perSecond';
|
||||
|
||||
@@ -391,55 +299,6 @@ export interface EditorGenerationPricingConfigPayload {
|
||||
models: Record<string, EditorGenerationModelPricingPayload>;
|
||||
}
|
||||
|
||||
/** 后台统一创作工作台契约表单的传输结构。 */
|
||||
export interface UnifiedCreationSpecPayload {
|
||||
playId: string;
|
||||
title: string;
|
||||
mudPointCost: number;
|
||||
workspaceStage: string;
|
||||
generationStage: string;
|
||||
resultStage: string;
|
||||
fields: UnifiedCreationFieldPayload[];
|
||||
}
|
||||
|
||||
/** 后台统一创作字段契约,保存前会校验字段类型和必填标记。 */
|
||||
export interface UnifiedCreationFieldPayload {
|
||||
id: string;
|
||||
kind: 'text' | 'select' | 'image' | 'audio';
|
||||
label: string;
|
||||
required: boolean;
|
||||
}
|
||||
|
||||
export interface AdminWorkVisibilityEntryPayload {
|
||||
sourceType: string;
|
||||
workId: string;
|
||||
profileId: string;
|
||||
sourceSessionId?: string | null;
|
||||
publicWorkCode: string;
|
||||
ownerUserId: string;
|
||||
authorDisplayName: string;
|
||||
title: string;
|
||||
subtitle: string;
|
||||
coverImageSrc?: string | null;
|
||||
visible: boolean;
|
||||
publishedAtMicros?: number | null;
|
||||
updatedAtMicros: number;
|
||||
}
|
||||
|
||||
export interface AdminWorkVisibilityListResponse {
|
||||
entries: AdminWorkVisibilityEntryPayload[];
|
||||
}
|
||||
|
||||
export interface AdminUpdateWorkVisibilityRequest {
|
||||
sourceType: string;
|
||||
profileId: string;
|
||||
visible: boolean;
|
||||
}
|
||||
|
||||
export interface AdminUpdateWorkVisibilityResponse {
|
||||
entry: AdminWorkVisibilityEntryPayload;
|
||||
}
|
||||
|
||||
export interface AdminEditorAssetListQuery {
|
||||
cursor?: string | null;
|
||||
ownerUserId?: string | null;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import {useCallback, useEffect, useMemo, useState} from 'react';
|
||||
import { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
|
||||
import {
|
||||
formatAdminApiError,
|
||||
@@ -17,33 +17,30 @@ import {
|
||||
getStoredAdminToken,
|
||||
setStoredAdminToken,
|
||||
} from '../auth/adminAuthStore';
|
||||
import {AdminAccountsPage} from '../pages/AdminAccountsPage';
|
||||
import {AdminCreationEntrySwitchPage} from '../pages/AdminCreationEntrySwitchPage';
|
||||
import {AdminDashboardPage} from '../pages/AdminDashboardPage';
|
||||
import {AdminDatabaseTablesPage} from '../pages/AdminDatabaseTablesPage';
|
||||
import {AdminDebugHttpPage} from '../pages/AdminDebugHttpPage';
|
||||
import {AdminEditorAssetQueryPage} from '../pages/AdminEditorAssetQueryPage';
|
||||
import {AdminEditorGenerationPricingPage} from '../pages/AdminEditorGenerationPricingPage';
|
||||
import {AdminEditorShowcaseReviewPage} from '../pages/AdminEditorShowcaseReviewPage';
|
||||
import {AdminGrayReleaseConfigPage} from '../pages/AdminGrayReleaseConfigPage';
|
||||
import {AdminInviteCodePage} from '../pages/AdminInviteCodePage';
|
||||
import {AdminLoginPage} from '../pages/AdminLoginPage';
|
||||
import {AdminOverviewPage} from '../pages/AdminOverviewPage';
|
||||
import {AdminProfileWalletConfigPage} from '../pages/AdminProfileWalletConfigPage';
|
||||
import {AdminRechargeOrderPage} from '../pages/AdminRechargeOrderPage';
|
||||
import {AdminRechargeProductPage} from '../pages/AdminRechargeProductPage';
|
||||
import {AdminRedeemCodePage} from '../pages/AdminRedeemCodePage';
|
||||
import {AdminTaskConfigPage} from '../pages/AdminTaskConfigPage';
|
||||
import {AdminTrackingEventsPage} from '../pages/AdminTrackingEventsPage';
|
||||
import {AdminWorkVisibilityPage} from '../pages/AdminWorkVisibilityPage';
|
||||
import type {AdminRouteId} from './adminRoutes';
|
||||
import { AdminAccountsPage } from '../pages/AdminAccountsPage';
|
||||
import { AdminDashboardPage } from '../pages/AdminDashboardPage';
|
||||
import { AdminDatabaseTablesPage } from '../pages/AdminDatabaseTablesPage';
|
||||
import { AdminDebugHttpPage } from '../pages/AdminDebugHttpPage';
|
||||
import { AdminEditorAssetQueryPage } from '../pages/AdminEditorAssetQueryPage';
|
||||
import { AdminEditorGenerationPricingPage } from '../pages/AdminEditorGenerationPricingPage';
|
||||
import { AdminEditorShowcaseReviewPage } from '../pages/AdminEditorShowcaseReviewPage';
|
||||
import { AdminInviteCodePage } from '../pages/AdminInviteCodePage';
|
||||
import { AdminLoginPage } from '../pages/AdminLoginPage';
|
||||
import { AdminOverviewPage } from '../pages/AdminOverviewPage';
|
||||
import { AdminProfileWalletConfigPage } from '../pages/AdminProfileWalletConfigPage';
|
||||
import { AdminRechargeOrderPage } from '../pages/AdminRechargeOrderPage';
|
||||
import { AdminRechargeProductPage } from '../pages/AdminRechargeProductPage';
|
||||
import { AdminRedeemCodePage } from '../pages/AdminRedeemCodePage';
|
||||
import { AdminTaskConfigPage } from '../pages/AdminTaskConfigPage';
|
||||
import { AdminTrackingEventsPage } from '../pages/AdminTrackingEventsPage';
|
||||
import type { AdminRouteId } from './adminRoutes';
|
||||
import {
|
||||
getAccessibleAdminRoutes,
|
||||
resolveAccessibleAdminRoute,
|
||||
resolveAdminRoute,
|
||||
routeHash,
|
||||
} from './adminRoutes';
|
||||
import {AdminShell} from './AdminShell';
|
||||
import { AdminShell } from './AdminShell';
|
||||
|
||||
type SessionStatus = 'checking' | 'guest' | 'authenticated';
|
||||
|
||||
@@ -157,17 +154,20 @@ export function AdminApp() {
|
||||
}
|
||||
}, []);
|
||||
|
||||
const handleLogin = useCallback(async (username: string, password: string) => {
|
||||
const response = await loginAdmin(username, password);
|
||||
setStoredAdminToken(response.token);
|
||||
setToken(response.token);
|
||||
setAdmin(response.admin);
|
||||
setTaskConfigResult(null);
|
||||
setProfileWalletConfigResult(null);
|
||||
setRechargeProductResult(null);
|
||||
setLoginNotice('');
|
||||
setStatus('authenticated');
|
||||
}, []);
|
||||
const handleLogin = useCallback(
|
||||
async (username: string, password: string) => {
|
||||
const response = await loginAdmin(username, password);
|
||||
setStoredAdminToken(response.token);
|
||||
setToken(response.token);
|
||||
setAdmin(response.admin);
|
||||
setTaskConfigResult(null);
|
||||
setProfileWalletConfigResult(null);
|
||||
setRechargeProductResult(null);
|
||||
setLoginNotice('');
|
||||
setStatus('authenticated');
|
||||
},
|
||||
[],
|
||||
);
|
||||
|
||||
const handleUnauthorized = useCallback(
|
||||
(message = '登录状态已失效') => {
|
||||
@@ -227,12 +227,6 @@ export function AdminApp() {
|
||||
onUnauthorized={handleUnauthorized}
|
||||
/>
|
||||
) : null}
|
||||
{activeRouteId === 'gray-release' ? (
|
||||
<AdminGrayReleaseConfigPage
|
||||
token={token}
|
||||
onUnauthorized={handleUnauthorized}
|
||||
/>
|
||||
) : null}
|
||||
{activeRouteId === 'redeem' ? (
|
||||
<AdminRedeemCodePage
|
||||
token={token}
|
||||
@@ -245,25 +239,6 @@ export function AdminApp() {
|
||||
onUnauthorized={handleUnauthorized}
|
||||
/>
|
||||
) : null}
|
||||
{activeRouteId === 'creation-announcement' ? (
|
||||
<AdminCreationEntrySwitchPage
|
||||
mode="announcements"
|
||||
token={token}
|
||||
onUnauthorized={handleUnauthorized}
|
||||
/>
|
||||
) : null}
|
||||
{activeRouteId === 'creation-entry' ? (
|
||||
<AdminCreationEntrySwitchPage
|
||||
token={token}
|
||||
onUnauthorized={handleUnauthorized}
|
||||
/>
|
||||
) : null}
|
||||
{activeRouteId === 'work-visibility' ? (
|
||||
<AdminWorkVisibilityPage
|
||||
token={token}
|
||||
onUnauthorized={handleUnauthorized}
|
||||
/>
|
||||
) : null}
|
||||
{activeRouteId === 'tasks' ? (
|
||||
<AdminTaskConfigPage
|
||||
result={taskConfigResult}
|
||||
@@ -313,10 +288,7 @@ export function AdminApp() {
|
||||
/>
|
||||
) : null}
|
||||
{activeRouteId === 'accounts' ? (
|
||||
<AdminAccountsPage
|
||||
token={token}
|
||||
onUnauthorized={handleUnauthorized}
|
||||
/>
|
||||
<AdminAccountsPage token={token} onUnauthorized={handleUnauthorized} />
|
||||
) : null}
|
||||
</AdminShell>
|
||||
);
|
||||
|
||||
@@ -4,16 +4,12 @@ import {
|
||||
Bug,
|
||||
Coins,
|
||||
Database,
|
||||
Eye,
|
||||
GitBranch,
|
||||
Images,
|
||||
LayoutDashboard,
|
||||
ListChecks,
|
||||
LogOut,
|
||||
Megaphone,
|
||||
ReceiptText,
|
||||
ShieldCheck,
|
||||
SlidersHorizontal,
|
||||
Star,
|
||||
Table2,
|
||||
TicketCheck,
|
||||
@@ -21,10 +17,10 @@ import {
|
||||
Users,
|
||||
WalletCards,
|
||||
} from 'lucide-react';
|
||||
import type {ReactNode} from 'react';
|
||||
import type { ReactNode } from 'react';
|
||||
|
||||
import type {AdminSessionPayload} from '../api/adminApiTypes';
|
||||
import type {AdminRouteDefinition, AdminRouteId} from './adminRoutes';
|
||||
import type { AdminSessionPayload } from '../api/adminApiTypes';
|
||||
import type { AdminRouteDefinition, AdminRouteId } from './adminRoutes';
|
||||
|
||||
interface AdminShellProps {
|
||||
admin: AdminSessionPayload;
|
||||
@@ -41,7 +37,6 @@ const routeIcons = {
|
||||
tables: Database,
|
||||
debug: Bug,
|
||||
tracking: Table2,
|
||||
'gray-release': GitBranch,
|
||||
redeem: TicketPercent,
|
||||
invite: TicketCheck,
|
||||
'profile-wallet': WalletCards,
|
||||
@@ -51,9 +46,6 @@ const routeIcons = {
|
||||
'editor-generation-pricing': Coins,
|
||||
'editor-showcase': Star,
|
||||
'editor-assets': Images,
|
||||
'creation-announcement': Megaphone,
|
||||
'creation-entry': SlidersHorizontal,
|
||||
'work-visibility': Eye,
|
||||
accounts: Users,
|
||||
} satisfies Record<AdminRouteId, typeof LayoutDashboard>;
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import {expect, test} from 'vitest';
|
||||
import { expect, test } from 'vitest';
|
||||
|
||||
import {
|
||||
adminRoutes,
|
||||
@@ -19,19 +19,6 @@ test('后台默认进入 Dashboard', () => {
|
||||
expect(routeHash('dashboard')).toBe('#dashboard');
|
||||
});
|
||||
|
||||
// 中文注释:后台入口公告必须作为独立导航存在,避免公告表单被误藏在入口开关页。
|
||||
test('后台入口公告路由可通过导航和 hash 访问', () => {
|
||||
expect(adminRoutes).toContainEqual({
|
||||
id: 'creation-announcement',
|
||||
label: '入口公告',
|
||||
hash: '#creation-announcement',
|
||||
});
|
||||
expect(resolveAdminRoute('#creation-announcement')).toBe(
|
||||
'creation-announcement',
|
||||
);
|
||||
expect(routeHash('creation-announcement')).toBe('#creation-announcement');
|
||||
});
|
||||
|
||||
test('后台模型定价路由可通过导航和 hash 访问', () => {
|
||||
expect(adminRoutes).toContainEqual({
|
||||
id: 'editor-generation-pricing',
|
||||
@@ -46,16 +33,12 @@ test('后台模型定价路由可通过导航和 hash 访问', () => {
|
||||
);
|
||||
});
|
||||
|
||||
test('后台灰度发布路由可通过导航和 hash 访问', () => {
|
||||
expect(adminRoutes).toContainEqual({
|
||||
id: 'gray-release',
|
||||
label: '灰度发布',
|
||||
hash: '#gray-release',
|
||||
});
|
||||
expect(resolveAdminRoute('#gray-release')).toBe('gray-release');
|
||||
expect(routeHash('gray-release')).toBe('#gray-release');
|
||||
test('后台不再暴露旧创作模板管理路由', () => {
|
||||
expect(resolveAdminRoute('#creation-entry')).toBe('dashboard');
|
||||
expect(resolveAdminRoute('#creation-announcement')).toBe('dashboard');
|
||||
expect(resolveAdminRoute('#gray-release')).toBe('dashboard');
|
||||
expect(resolveAdminRoute('#work-visibility')).toBe('dashboard');
|
||||
});
|
||||
|
||||
test('后台素材查询路由可通过导航和 hash 访问', () => {
|
||||
expect(adminRoutes).toContainEqual({
|
||||
id: 'editor-assets',
|
||||
@@ -92,7 +75,7 @@ test('owner 可访问全部业务 Tab 和账号管理', () => {
|
||||
tabPermissions: [],
|
||||
});
|
||||
expect(routes).toEqual(adminRoutes);
|
||||
expect(routes.at(-1)).toMatchObject({id: 'accounts', ownerOnly: true});
|
||||
expect(routes.at(-1)).toMatchObject({ id: 'accounts', ownerOnly: true });
|
||||
});
|
||||
|
||||
test('member 只访问已分配 Tab 且无权 hash 回落到第一项', () => {
|
||||
|
||||
@@ -5,7 +5,6 @@ export type AdminRouteId =
|
||||
| 'tables'
|
||||
| 'debug'
|
||||
| 'tracking'
|
||||
| 'gray-release'
|
||||
| 'redeem'
|
||||
| 'invite'
|
||||
| 'profile-wallet'
|
||||
@@ -15,9 +14,6 @@ export type AdminRouteId =
|
||||
| 'editor-generation-pricing'
|
||||
| 'editor-showcase'
|
||||
| 'editor-assets'
|
||||
| 'creation-announcement'
|
||||
| 'creation-entry'
|
||||
| 'work-visibility'
|
||||
| 'accounts';
|
||||
|
||||
export type AdminTabPermission = Exclude<AdminRouteId, 'accounts'>;
|
||||
@@ -31,25 +27,25 @@ export interface AdminRouteDefinition {
|
||||
}
|
||||
|
||||
export const adminRoutes: AdminRouteDefinition[] = [
|
||||
{id: 'dashboard', label: 'Dashboard', hash: '#dashboard'},
|
||||
{id: 'overview', label: '服务总览', hash: '#overview'},
|
||||
{id: 'tables', label: '表查询', hash: '#tables'},
|
||||
{id: 'debug', label: 'API 调试', hash: '#debug'},
|
||||
{id: 'tracking', label: '埋点数据', hash: '#tracking'},
|
||||
{id: 'gray-release', label: '灰度发布', hash: '#gray-release'},
|
||||
{id: 'redeem', label: '兑换码', hash: '#redeem'},
|
||||
{id: 'invite', label: '邀请码', hash: '#invite'},
|
||||
{id: 'profile-wallet', label: '账号配置', hash: '#profile-wallet'},
|
||||
{id: 'tasks', label: '任务配置', hash: '#tasks'},
|
||||
{id: 'recharge-products', label: '充值商品', hash: '#recharge-products'},
|
||||
{id: 'recharge-orders', label: '充值管理', hash: '#recharge-orders'},
|
||||
{id: 'editor-generation-pricing', label: '模型定价', hash: '#editor-generation-pricing'},
|
||||
{id: 'editor-showcase', label: '精选审核', hash: '#editor-showcase'},
|
||||
{id: 'editor-assets', label: '素材查询', hash: '#editor-assets'},
|
||||
{id: 'creation-announcement', label: '入口公告', hash: '#creation-announcement'},
|
||||
{id: 'creation-entry', label: '入口开关', hash: '#creation-entry'},
|
||||
{id: 'work-visibility', label: '作品可见性', hash: '#work-visibility'},
|
||||
{id: 'accounts', label: '账号管理', hash: '#accounts', ownerOnly: true},
|
||||
{ id: 'dashboard', label: 'Dashboard', hash: '#dashboard' },
|
||||
{ id: 'overview', label: '服务总览', hash: '#overview' },
|
||||
{ id: 'tables', label: '表查询', hash: '#tables' },
|
||||
{ id: 'debug', label: 'API 调试', hash: '#debug' },
|
||||
{ id: 'tracking', label: '埋点数据', hash: '#tracking' },
|
||||
{ id: 'redeem', label: '兑换码', hash: '#redeem' },
|
||||
{ id: 'invite', label: '邀请码', hash: '#invite' },
|
||||
{ id: 'profile-wallet', label: '账号配置', hash: '#profile-wallet' },
|
||||
{ id: 'tasks', label: '任务配置', hash: '#tasks' },
|
||||
{ id: 'recharge-products', label: '充值商品', hash: '#recharge-products' },
|
||||
{ id: 'recharge-orders', label: '充值管理', hash: '#recharge-orders' },
|
||||
{
|
||||
id: 'editor-generation-pricing',
|
||||
label: '模型定价',
|
||||
hash: '#editor-generation-pricing',
|
||||
},
|
||||
{ id: 'editor-showcase', label: '精选审核', hash: '#editor-showcase' },
|
||||
{ id: 'editor-assets', label: '素材查询', hash: '#editor-assets' },
|
||||
{ id: 'accounts', label: '账号管理', hash: '#accounts', ownerOnly: true },
|
||||
];
|
||||
|
||||
export interface AdminRouteAccess {
|
||||
|
||||
@@ -21,8 +21,8 @@ describe('admin tracking event definitions', () => {
|
||||
expect(keys).toContain('auth_login_options_view');
|
||||
expect(keys).toContain('task_center_view');
|
||||
expect(keys).toContain('asset_upload_ticket_create');
|
||||
expect(keys).toContain('creative_agent_route_success');
|
||||
expect(keys).toContain('work_play_start');
|
||||
expect(keys).not.toContain('creative_agent_route_success');
|
||||
expect(keys).not.toContain('work_play_start');
|
||||
});
|
||||
|
||||
test('任务配置候选只开放适合个人任务的事件', () => {
|
||||
@@ -49,13 +49,15 @@ describe('admin tracking event definitions', () => {
|
||||
]);
|
||||
|
||||
expect(options.find(({ key }) => key === 'work_play_start')?.title).toBe(
|
||||
'作品开始游玩',
|
||||
'work_play_start',
|
||||
);
|
||||
expect(options.find(({ key }) => key === 'unknown_event')?.title).toBe(
|
||||
'未知事件',
|
||||
);
|
||||
expect(
|
||||
filterAdminTrackingEventKeyOptions(options, '作品').map(({ key }) => key),
|
||||
filterAdminTrackingEventKeyOptions(options, 'work_play').map(
|
||||
({ key }) => key,
|
||||
),
|
||||
).toEqual(['work_play_start']);
|
||||
});
|
||||
|
||||
@@ -65,7 +67,7 @@ describe('admin tracking event definitions', () => {
|
||||
).toEqual(['asset_upload_ticket_create']);
|
||||
expect(
|
||||
filterAdminTrackingEventDefinitions('work_play').map(({ key }) => key),
|
||||
).toEqual(['work_play_start']);
|
||||
).toEqual([]);
|
||||
expect(findAdminTrackingEventDefinition(' daily_login ')?.title).toBe(
|
||||
'每日登录',
|
||||
);
|
||||
|
||||
@@ -148,42 +148,6 @@ export const adminTrackingEventDefinitions: AdminTrackingEventDefinition[] = [
|
||||
scopeKind: 'user',
|
||||
remark: '领取个人任务奖励成功后记录。',
|
||||
},
|
||||
{
|
||||
key: 'save_archive_list_view',
|
||||
title: '存档列表查看',
|
||||
scopeKind: 'user',
|
||||
remark: '读取个人存档列表成功后记录。',
|
||||
},
|
||||
{
|
||||
key: 'save_archive_detail_view',
|
||||
title: '存档详情查看',
|
||||
scopeKind: 'user',
|
||||
remark: '读取个人存档详情成功后记录。',
|
||||
},
|
||||
{
|
||||
key: 'browse_history_view',
|
||||
title: '浏览历史查看',
|
||||
scopeKind: 'user',
|
||||
remark: '读取浏览历史成功后记录。',
|
||||
},
|
||||
{
|
||||
key: 'browse_history_record',
|
||||
title: '浏览历史写入',
|
||||
scopeKind: 'user',
|
||||
remark: '记录浏览历史成功后记录。',
|
||||
},
|
||||
{
|
||||
key: 'browse_history_clear',
|
||||
title: '浏览历史清空',
|
||||
scopeKind: 'user',
|
||||
remark: '清空浏览历史成功后记录。',
|
||||
},
|
||||
{
|
||||
key: 'play_stats_view',
|
||||
title: '游玩统计查看',
|
||||
scopeKind: 'user',
|
||||
remark: '读取个人游玩统计成功后记录。',
|
||||
},
|
||||
{
|
||||
key: 'profile_analytics_metric_view',
|
||||
title: '个人指标查看',
|
||||
@@ -352,62 +316,6 @@ export const adminTrackingEventDefinitions: AdminTrackingEventDefinition[] = [
|
||||
scopeKind: 'user',
|
||||
remark: '更新运行设置成功后记录。',
|
||||
},
|
||||
{
|
||||
key: 'runtime_snapshot_view',
|
||||
title: '运行快照查看',
|
||||
scopeKind: 'user',
|
||||
remark: '读取运行快照成功后记录。',
|
||||
},
|
||||
{
|
||||
key: 'runtime_snapshot_save',
|
||||
title: '运行快照保存',
|
||||
scopeKind: 'user',
|
||||
remark: '保存运行快照成功后记录。',
|
||||
},
|
||||
{
|
||||
key: 'runtime_snapshot_delete',
|
||||
title: '运行快照删除',
|
||||
scopeKind: 'user',
|
||||
remark: '删除运行快照成功后记录。',
|
||||
},
|
||||
{
|
||||
key: 'puzzle_route_success',
|
||||
title: '拼图路由成功',
|
||||
scopeKind: 'user',
|
||||
remark: '拼图运行或创作接口成功响应后兜底记录;GET 入口可能按 site 统计。',
|
||||
},
|
||||
{
|
||||
key: 'match3d_route_success',
|
||||
title: '抓大鹅路由成功',
|
||||
scopeKind: 'user',
|
||||
remark:
|
||||
'抓大鹅创作或运行接口成功响应后兜底记录;GET 入口可能按 site 统计。',
|
||||
},
|
||||
{
|
||||
key: 'square_hole_route_success',
|
||||
title: '方洞路由成功',
|
||||
scopeKind: 'user',
|
||||
remark: '方洞创作或运行接口成功响应后兜底记录;GET 入口可能按 site 统计。',
|
||||
},
|
||||
{
|
||||
key: 'custom_world_route_success',
|
||||
title: '自定义世界路由成功',
|
||||
scopeKind: 'user',
|
||||
remark: '自定义世界运行接口成功响应后兜底记录;GET 入口可能按 site 统计。',
|
||||
},
|
||||
{
|
||||
key: 'creative_agent_route_success',
|
||||
title: '创意 Agent 路由成功',
|
||||
scopeKind: 'user',
|
||||
remark: '创意 Agent 接口成功响应后兜底记录;GET 入口可能按 site 统计。',
|
||||
},
|
||||
{
|
||||
key: 'work_play_start',
|
||||
title: '作品开始游玩',
|
||||
scopeKind: 'work',
|
||||
remark:
|
||||
'拼图、抓大鹅、方洞、自定义世界、大鱼吃小鱼、Visual Novel 正式开始游玩时记录。',
|
||||
},
|
||||
];
|
||||
|
||||
export const adminProfileTaskTrackingEventDefinitions =
|
||||
|
||||
@@ -15,5 +15,12 @@
|
||||
"noEmit": true,
|
||||
"types": ["vite/client"]
|
||||
},
|
||||
"include": ["src", "vite.config.ts"]
|
||||
"include": ["src", "vite.config.ts"],
|
||||
"exclude": [
|
||||
"src/pages/AdminCreationEntrySwitchPage.tsx",
|
||||
"src/pages/AdminCreationEntrySwitchPage.test.tsx",
|
||||
"src/pages/AdminGrayReleaseConfigPage.tsx",
|
||||
"src/pages/AdminGrayReleaseConfigPage.test.tsx",
|
||||
"src/pages/AdminWorkVisibilityPage.tsx"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -68,7 +68,8 @@ npm run container:up -- spacetimedb
|
||||
spacetime publish genarrative-loadtest --server http://127.0.0.1:13101 --module-path server-rs/crates/spacetime-module --yes --build-options="--debug"
|
||||
npm run container:up
|
||||
npm run container:ps
|
||||
curl -sS http://127.0.0.1:18080/api/runtime/puzzle/gallery
|
||||
curl -sS -H 'Authorization: Bearer <access-token>' \
|
||||
'http://127.0.0.1:18080/api/assets/history?kind=character_visual'
|
||||
```
|
||||
|
||||
查看日志:
|
||||
|
||||
@@ -118,7 +118,7 @@ services:
|
||||
soft: 4096
|
||||
hard: 4096
|
||||
healthcheck:
|
||||
test: ["CMD", "wget", "-qO-", "http://127.0.0.1/api/runtime/puzzle/gallery"]
|
||||
test: ["CMD", "wget", "-qO-", "http://127.0.0.1/healthz"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 12
|
||||
|
||||
@@ -88,85 +88,6 @@ http {
|
||||
try_files $uri =404;
|
||||
}
|
||||
|
||||
location = /api/runtime/puzzle/gallery {
|
||||
default_type application/json;
|
||||
limit_conn genarrative_api_conn 320;
|
||||
limit_req zone=genarrative_gallery_rps burst=4096 nodelay;
|
||||
|
||||
proxy_pass http://genarrative_api;
|
||||
proxy_http_version 1.1;
|
||||
proxy_buffering off;
|
||||
proxy_read_timeout 3600s;
|
||||
proxy_send_timeout 3600s;
|
||||
add_header X-Accel-Buffering no always;
|
||||
proxy_set_header Connection "";
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
proxy_set_header X-Forwarded-Host $host;
|
||||
proxy_set_header X-Request-Id $request_id;
|
||||
}
|
||||
|
||||
location = /api/runtime/custom-world-gallery {
|
||||
default_type application/json;
|
||||
limit_conn genarrative_api_conn 320;
|
||||
limit_req zone=genarrative_gallery_rps burst=4096 nodelay;
|
||||
|
||||
proxy_pass http://genarrative_api;
|
||||
proxy_http_version 1.1;
|
||||
proxy_buffering off;
|
||||
proxy_read_timeout 3600s;
|
||||
proxy_send_timeout 3600s;
|
||||
add_header X-Accel-Buffering no always;
|
||||
proxy_set_header Connection "";
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
proxy_set_header X-Forwarded-Host $host;
|
||||
proxy_set_header X-Request-Id $request_id;
|
||||
}
|
||||
|
||||
location ~ ^/api/runtime/puzzle/gallery/[^/]+$ {
|
||||
default_type application/json;
|
||||
limit_conn genarrative_api_conn 32;
|
||||
limit_req zone=genarrative_api_rps burst=32 nodelay;
|
||||
|
||||
proxy_pass http://genarrative_api;
|
||||
proxy_http_version 1.1;
|
||||
proxy_buffering off;
|
||||
proxy_read_timeout 3600s;
|
||||
proxy_send_timeout 3600s;
|
||||
add_header X-Accel-Buffering no always;
|
||||
proxy_set_header Connection "";
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
proxy_set_header X-Forwarded-Host $host;
|
||||
proxy_set_header X-Request-Id $request_id;
|
||||
}
|
||||
|
||||
location ~ ^/api/runtime/custom-world-gallery/[^/]+/[^/]+$ {
|
||||
default_type application/json;
|
||||
limit_conn genarrative_api_conn 32;
|
||||
limit_req zone=genarrative_api_rps burst=32 nodelay;
|
||||
|
||||
proxy_pass http://genarrative_api;
|
||||
proxy_http_version 1.1;
|
||||
proxy_buffering off;
|
||||
proxy_read_timeout 3600s;
|
||||
proxy_send_timeout 3600s;
|
||||
add_header X-Accel-Buffering no always;
|
||||
proxy_set_header Connection "";
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
proxy_set_header X-Forwarded-Host $host;
|
||||
proxy_set_header X-Request-Id $request_id;
|
||||
}
|
||||
|
||||
location ~ ^/api(?:/|$) {
|
||||
default_type application/json;
|
||||
@@ -220,7 +141,7 @@ http {
|
||||
try_files /index.html =404;
|
||||
}
|
||||
|
||||
location ~* "^/(?:bark-battle|big-fish|child-motion-demo|creation|creation/baby-object-match|creation/baby-object-match/generating|creation/baby-object-match/result|creation/bark-battle|creation/bark-battle/generating|creation/bark-battle/result|creation/big-fish|creation/big-fish/generating|creation/big-fish/result|creation/creative-agent|creation/jump-hop|creation/jump-hop/generating|creation/jump-hop/result|creation/match3d|creation/match3d/generating|creation/match3d/result|creation/puzzle|creation/puzzle-clear|creation/puzzle-clear/generating|creation/puzzle-clear/result|creation/puzzle/generating|creation/puzzle/result|creation/rpg|creation/rpg/agent|creation/rpg/generating|creation/rpg/result|creation/square-hole|creation/square-hole/generating|creation/square-hole/result|creation/visual-novel|creation/visual-novel/generating|creation/visual-novel/result|creation/wooden-fish|creation/wooden-fish/generating|creation/wooden-fish/result|editor/canvas|gallery/jump-hop/detail|gallery/puzzle/detail|gallery/visual-novel/detail|match3d|project|puzzle|runtime/baby-love-drawing|runtime/baby-object-match|runtime/bark-battle|runtime/big-fish|runtime/jump-hop|runtime/match3d|runtime/puzzle|runtime/puzzle-clear|runtime/rpg/adventure|runtime/rpg/characters|runtime/square-hole|runtime/visual-novel|runtime/wooden-fish|works/detail|worlds/detail)/?$" {
|
||||
location ~* "^/(?:editor/canvas|project)/?$" {
|
||||
try_files $uri /index.html =404;
|
||||
}
|
||||
# END GENARRATIVE MAIN SPA ROUTES
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
## gzip
|
||||
|
||||
- `deploy/nginx/genarrative.conf` 与 `deploy/nginx/genarrative-dev-http.conf` 默认开启 gzip。
|
||||
- 覆盖 `application/json`,用于降低 `/api/runtime/*/gallery` 这类 JSON 列表接口的公网带宽占用。
|
||||
- 覆盖 `application/json`,用于降低 `/api/assets/history` 这类 JSON 列表接口的公网带宽占用。
|
||||
- 当前推荐等级为 `gzip_comp_level 5`,兼顾 2C/2G 服务器 CPU 与压缩收益。
|
||||
- Pingora 影子网关已通过 `GENARRATIVE_PINGORA_GATEWAY_COMPRESSION_ALGORITHMS=gzip`、`GENARRATIVE_PINGORA_GATEWAY_GZIP_ENABLED=true`、`GENARRATIVE_PINGORA_GATEWAY_GZIP_LEVEL=5` 与 `GENARRATIVE_PINGORA_GATEWAY_GZIP_MIN_LENGTH_BYTES=1024` 承接 gzip parity;网关会把压缩算法收敛到 gzip allowlist,并按 `gzip_min_length 1024` 避免压缩小响应,同时沿用 Nginx `gzip_types` 边界不压缩图片资源。改动后必须运行 `npm run check:pingora-gateway-smoke` 验证小响应不压缩、图片资源不压缩,以及 `Accept-Encoding: gzip` 和 `Accept-Encoding: br, gzip` 的大响应都返回 gzip。
|
||||
|
||||
@@ -65,14 +65,14 @@
|
||||
## Pingora canary
|
||||
|
||||
- `deploy/nginx/snippets/genarrative-pingora-canary.conf` 是默认不启用的人工前缀 canary 入口,只在需要验证 Nginx -> Pingora handoff 时临时 include。它使用 `/__genarrative_pingora_canary/` 前缀改写后转发到 `127.0.0.1:18081`,并返回 `X-Genarrative-Nginx-Handoff: pingora-canary`。
|
||||
- `deploy/nginx/snippets/genarrative-pingora-realpath-canary.conf` 是前缀 canary 之后、direct 直连之前的真实路径 canary。它只能在 Nginx `http` 上下文人工 include,默认监听 `127.0.0.1:18083`,覆盖 `/api/creation-entry/config`、`/v1/identity`、`/v1/database/<db>/subscribe`、`/assets/app.js` 和拒绝入口,并写入独立 `/var/log/nginx/genarrative-pingora-realpath-canary.access.log`;不要把它 include 到生产 `443` server 内作为 location 覆盖。
|
||||
- `deploy/nginx/snippets/genarrative-pingora-realpath-canary.conf` 是前缀 canary 之后、direct 直连之前的真实路径 canary。它只能在 Nginx `http` 上下文人工 include,默认监听 `127.0.0.1:18083`,覆盖 `/api/assets/history`、`/v1/identity`、`/v1/database/<db>/subscribe`、`/assets/app.js` 和拒绝入口,并写入独立 `/var/log/nginx/genarrative-pingora-realpath-canary.access.log`;不要把它 include 到生产 `443` server 内作为 location 覆盖。
|
||||
- 真实路径 canary 片段使用 `access_log ... genarrative_upstream`,如果以独立文件放入 `/etc/nginx/conf.d/`,文件加载顺序必须晚于定义 `log_format genarrative_upstream` 的主站配置。当前目标机可使用 `/etc/nginx/conf.d/zz-genarrative-pingora-realpath-canary.conf`;若站点把 `log_format` 移到全局 Nginx 配置,则需确保它仍在所有 `conf.d` server include 之前加载。启用前必须先跑 `nginx -t`,不要用会早于 `genarrative.conf` 加载的文件名。
|
||||
- 启用 Nginx canary 前,目标机必须已经有持久 `genarrative-pingora-gateway.service` 运行在 `127.0.0.1:18081`,且带真实 `api-server`、SpacetimeDB、静态目录和 `/var/log/genarrative/pingora-gateway.access.log` 完成本机 shadow 验收;不要从一次性 `/tmp` 网关进程直接切到 Nginx handoff。
|
||||
- 启用前必须运行 `npm run check:nginx-pingora-canary`;目标 agent 有 Nginx 时运行 `node scripts/check-nginx-pingora-canary.mjs --require-nginx`,同时做两个 snippet 的静态护栏和真实 `nginx -t`。本机或 CI 可运行 `npm run check:pingora-canary-docker`,用 Docker Nginx、真实 Pingora 和 mock 上游复现前缀 canary 与真实路径 canary 两条 handoff 链路。
|
||||
- 前缀 canary reload 后运行 `GENARRATIVE_PINGORA_CANARY_BASE_URL=http://127.0.0.1 GENARRATIVE_PINGORA_CANARY_HOST=<域名> npm run check:pingora-canary-live`,再用 `scripts/check-pingora-canary-access-log-parity.mjs` 对账 `/var/log/nginx/genarrative.access.log` 与 `/var/log/genarrative/pingora-gateway.access.log`。
|
||||
- 真实路径 canary 不再手工编辑 `/etc/nginx/conf.d/`。目标机从 current release 执行 `/opt/genarrative/current/scripts/deploy/pingora-realpath-canary-enable.sh --apply --probe-token <token> --host <域名> --base-url http://127.0.0.1:18083`;脚本会渲染到 `/etc/nginx/conf.d/zz-genarrative-pingora-realpath-canary.conf`,保持晚于主站 `log_format genarrative_upstream` 加载,执行 `nginx -t`、reload Nginx,并默认运行 realpath live smoke。`nginx -t`、reload 或 live smoke 任一失败都会恢复写入前配置。关闭时执行 `/opt/genarrative/current/scripts/deploy/pingora-realpath-canary-disable.sh --apply`,脚本会删除该 `zz-...` 文件,执行 `nginx -t` 和 reload,失败时恢复删除前配置。
|
||||
- `npm run check:pingora-release-readiness` 默认包含 `npm run check:pingora-realpath-canary-toggle`,用临时 Nginx 目录和 fake `nginx` / `systemctl` / live smoke 验证 realpath canary 启停脚本默认 dry-run、apply 写入、失败回滚和 disable 恢复逻辑;不再把这条脚本护栏留成单独可选项。
|
||||
- 真实路径 canary reload 后可再次运行 `node -- /opt/genarrative/current/scripts/check-pingora-canary-live.mjs --realpath --base-url http://127.0.0.1:18083 --host <域名>`,再运行 `node -- /opt/genarrative/current/scripts/check-pingora-canary-access-log-parity.mjs --realpath --nginx-log-file /var/log/nginx/genarrative-pingora-realpath-canary.access.log --pingora-log-file /var/log/genarrative/pingora-gateway.access.log --path /__genarrative_pingora_realpath_canary/healthz --path /api/creation-entry/config --path /v1/identity --path /assets/app.js`。真实路径模式除 healthz 探针外要求 Nginx path 与 Pingora path 完全一致。
|
||||
- 真实路径 canary reload 后可再次运行 `node -- /opt/genarrative/current/scripts/check-pingora-canary-live.mjs --realpath --base-url http://127.0.0.1:18083 --host <域名>`,再运行 `node -- /opt/genarrative/current/scripts/check-pingora-canary-access-log-parity.mjs --realpath --nginx-log-file /var/log/nginx/genarrative-pingora-realpath-canary.access.log --pingora-log-file /var/log/genarrative/pingora-gateway.access.log --path /__genarrative_pingora_realpath_canary/healthz --path /api/assets/history --path /v1/identity --path /assets/app.js`。真实路径模式除 healthz 探针外要求 Nginx path 与 Pingora path 完全一致。
|
||||
- 正式切换前在源码 checkout / CI / 构建环境跑聚合门禁,并按目标机已启用的 canary 入口选择参数:前缀 canary 已启用时使用 `node scripts/check-pingora-release-readiness.mjs --require-docker --pull-docker --require-nginx --require-live --live-base-url http://127.0.0.1 --live-host <域名> --live-nginx-access-log /var/log/nginx/genarrative.access.log --live-pingora-access-log /var/log/genarrative/pingora-gateway.access.log`;真实路径 canary 已启用时追加或单独使用 `--require-realpath-live --realpath-live-base-url http://127.0.0.1:18083 --realpath-live-host <域名> --realpath-live-nginx-access-log /var/log/nginx/genarrative-pingora-realpath-canary.access.log --realpath-live-pingora-access-log /var/log/genarrative/pingora-gateway.access.log`。如果现场只启用了真实路径 canary,不要同时传 `--require-live`。
|
||||
- 目标机 `/opt/genarrative/current` 只能跑随包 runtime-only 门禁,并同样按现场已启用入口选择:前缀 canary 用 `/opt/genarrative/current/scripts/check-pingora-release-readiness.mjs --release-runtime-only --require-live --live-base-url http://127.0.0.1 --live-host <域名> --live-nginx-access-log /var/log/nginx/genarrative.access.log --live-pingora-access-log /var/log/genarrative/pingora-gateway.access.log`;真实路径 canary 用 `/opt/genarrative/current/scripts/check-pingora-release-readiness.mjs --release-runtime-only --require-realpath-live --realpath-live-base-url http://127.0.0.1:18083 --realpath-live-host <域名> --realpath-live-nginx-access-log /var/log/nginx/genarrative-pingora-realpath-canary.access.log --realpath-live-pingora-access-log /var/log/genarrative/pingora-gateway.access.log`;验证 Pingora 直连入口时再追加 `--require-direct ...` 参数。未带 `--require-direct` 的 runtime-only 基础门禁会自动执行直连彩排状态复核,确认公网 `80/443` 仍由 Nginx 接流、Pingora shadow `127.0.0.1:18081`、realpath canary `127.0.0.1:18083` 和 current release 自审均通过;启用后 `--require-direct` 复核不再要求 Nginx 接公网彩排状态。生成正式直连切换 runbook 时,`--direct-redirect-host` 和回退 smoke Host 必须与 `--direct-host` 同 hostname,只允许端口不同。
|
||||
|
||||
@@ -84,11 +84,13 @@ ls -l /etc/nginx/modules-enabled/*brotli*
|
||||
nginx -T 2>/dev/null | grep -Ei 'brotli|load_module'
|
||||
|
||||
curl -sSI -H 'Accept-Encoding: gzip' \
|
||||
http://<host>/api/runtime/puzzle/gallery \
|
||||
-H 'Authorization: Bearer <access-token>' \
|
||||
'http://<host>/api/assets/history?kind=character_visual' \
|
||||
| grep -iE 'content-encoding|vary|content-type|content-length'
|
||||
|
||||
curl -sSI -H 'Accept-Encoding: br' \
|
||||
http://<host>/api/runtime/puzzle/gallery \
|
||||
-H 'Authorization: Bearer <access-token>' \
|
||||
'http://<host>/api/assets/history?kind=character_visual' \
|
||||
| grep -iE 'content-encoding|vary|content-type|content-length'
|
||||
```
|
||||
|
||||
|
||||
@@ -116,101 +116,6 @@ server {
|
||||
try_files $uri =404;
|
||||
}
|
||||
|
||||
location = /api/runtime/puzzle/gallery {
|
||||
default_type application/json;
|
||||
limit_conn genarrative_api_conn 320;
|
||||
limit_req zone=genarrative_gallery_rps burst=4096 nodelay;
|
||||
|
||||
if ($genarrative_maintenance) {
|
||||
return 503 '{"ok":false,"error":{"code":"MAINTENANCE","message":"服务维护中"}}';
|
||||
}
|
||||
|
||||
proxy_pass http://genarrative_api;
|
||||
proxy_http_version 1.1;
|
||||
proxy_buffering off;
|
||||
proxy_read_timeout 3600s;
|
||||
proxy_send_timeout 3600s;
|
||||
add_header X-Accel-Buffering no always;
|
||||
proxy_set_header Connection "";
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
proxy_set_header X-Forwarded-Host $host;
|
||||
proxy_set_header X-Request-Id $request_id;
|
||||
}
|
||||
|
||||
location = /api/runtime/custom-world-gallery {
|
||||
default_type application/json;
|
||||
limit_conn genarrative_api_conn 320;
|
||||
limit_req zone=genarrative_gallery_rps burst=4096 nodelay;
|
||||
|
||||
if ($genarrative_maintenance) {
|
||||
return 503 '{"ok":false,"error":{"code":"MAINTENANCE","message":"服务维护中"}}';
|
||||
}
|
||||
|
||||
proxy_pass http://genarrative_api;
|
||||
proxy_http_version 1.1;
|
||||
proxy_buffering off;
|
||||
proxy_read_timeout 3600s;
|
||||
proxy_send_timeout 3600s;
|
||||
add_header X-Accel-Buffering no always;
|
||||
proxy_set_header Connection "";
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
proxy_set_header X-Forwarded-Host $host;
|
||||
proxy_set_header X-Request-Id $request_id;
|
||||
}
|
||||
|
||||
location ~ ^/api/runtime/puzzle/gallery/[^/]+$ {
|
||||
default_type application/json;
|
||||
limit_conn genarrative_api_conn 32;
|
||||
limit_req zone=genarrative_api_rps burst=32 nodelay;
|
||||
|
||||
if ($genarrative_maintenance) {
|
||||
return 503 '{"ok":false,"error":{"code":"MAINTENANCE","message":"服务维护中"}}';
|
||||
}
|
||||
|
||||
proxy_pass http://genarrative_api;
|
||||
proxy_http_version 1.1;
|
||||
proxy_buffering off;
|
||||
proxy_read_timeout 3600s;
|
||||
proxy_send_timeout 3600s;
|
||||
add_header X-Accel-Buffering no always;
|
||||
proxy_set_header Connection "";
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
proxy_set_header X-Forwarded-Host $host;
|
||||
proxy_set_header X-Request-Id $request_id;
|
||||
}
|
||||
|
||||
location ~ ^/api/runtime/custom-world-gallery/[^/]+/[^/]+$ {
|
||||
default_type application/json;
|
||||
limit_conn genarrative_api_conn 32;
|
||||
limit_req zone=genarrative_api_rps burst=32 nodelay;
|
||||
|
||||
if ($genarrative_maintenance) {
|
||||
return 503 '{"ok":false,"error":{"code":"MAINTENANCE","message":"服务维护中"}}';
|
||||
}
|
||||
|
||||
proxy_pass http://genarrative_api;
|
||||
proxy_http_version 1.1;
|
||||
proxy_buffering off;
|
||||
proxy_read_timeout 3600s;
|
||||
proxy_send_timeout 3600s;
|
||||
add_header X-Accel-Buffering no always;
|
||||
proxy_set_header Connection "";
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
proxy_set_header X-Forwarded-Host $host;
|
||||
proxy_set_header X-Request-Id $request_id;
|
||||
}
|
||||
|
||||
# 临时兼容主站仍在使用的 /api/* HTTP facade;前端完成 SpacetimeDB SDK 迁移后删除。
|
||||
location ~ ^/api(?:/|$) {
|
||||
@@ -285,7 +190,7 @@ server {
|
||||
try_files /index.html =404;
|
||||
}
|
||||
|
||||
location ~* "^/(?:bark-battle|big-fish|child-motion-demo|creation|creation/baby-object-match|creation/baby-object-match/generating|creation/baby-object-match/result|creation/bark-battle|creation/bark-battle/generating|creation/bark-battle/result|creation/big-fish|creation/big-fish/generating|creation/big-fish/result|creation/creative-agent|creation/jump-hop|creation/jump-hop/generating|creation/jump-hop/result|creation/match3d|creation/match3d/generating|creation/match3d/result|creation/puzzle|creation/puzzle-clear|creation/puzzle-clear/generating|creation/puzzle-clear/result|creation/puzzle/generating|creation/puzzle/result|creation/rpg|creation/rpg/agent|creation/rpg/generating|creation/rpg/result|creation/square-hole|creation/square-hole/generating|creation/square-hole/result|creation/visual-novel|creation/visual-novel/generating|creation/visual-novel/result|creation/wooden-fish|creation/wooden-fish/generating|creation/wooden-fish/result|editor/canvas|gallery/jump-hop/detail|gallery/puzzle/detail|gallery/visual-novel/detail|match3d|project|puzzle|runtime/baby-love-drawing|runtime/baby-object-match|runtime/bark-battle|runtime/big-fish|runtime/jump-hop|runtime/match3d|runtime/puzzle|runtime/puzzle-clear|runtime/rpg/adventure|runtime/rpg/characters|runtime/square-hole|runtime/visual-novel|runtime/wooden-fish|works/detail|worlds/detail)/?$" {
|
||||
location ~* "^/(?:editor/canvas|project)/?$" {
|
||||
error_page 503 /maintenance.html;
|
||||
|
||||
if ($genarrative_maintenance) {
|
||||
|
||||
@@ -136,101 +136,6 @@ server {
|
||||
try_files $uri =404;
|
||||
}
|
||||
|
||||
location = /api/runtime/puzzle/gallery {
|
||||
default_type application/json;
|
||||
limit_conn genarrative_api_conn 320;
|
||||
limit_req zone=genarrative_gallery_rps burst=4096 nodelay;
|
||||
|
||||
if ($genarrative_maintenance) {
|
||||
return 503 '{"ok":false,"error":{"code":"MAINTENANCE","message":"服务维护中"}}';
|
||||
}
|
||||
|
||||
proxy_pass http://genarrative_api;
|
||||
proxy_http_version 1.1;
|
||||
proxy_buffering off;
|
||||
proxy_read_timeout 3600s;
|
||||
proxy_send_timeout 3600s;
|
||||
add_header X-Accel-Buffering no always;
|
||||
proxy_set_header Connection "";
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
proxy_set_header X-Forwarded-Host $host;
|
||||
proxy_set_header X-Request-Id $request_id;
|
||||
}
|
||||
|
||||
location = /api/runtime/custom-world-gallery {
|
||||
default_type application/json;
|
||||
limit_conn genarrative_api_conn 320;
|
||||
limit_req zone=genarrative_gallery_rps burst=4096 nodelay;
|
||||
|
||||
if ($genarrative_maintenance) {
|
||||
return 503 '{"ok":false,"error":{"code":"MAINTENANCE","message":"服务维护中"}}';
|
||||
}
|
||||
|
||||
proxy_pass http://genarrative_api;
|
||||
proxy_http_version 1.1;
|
||||
proxy_buffering off;
|
||||
proxy_read_timeout 3600s;
|
||||
proxy_send_timeout 3600s;
|
||||
add_header X-Accel-Buffering no always;
|
||||
proxy_set_header Connection "";
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
proxy_set_header X-Forwarded-Host $host;
|
||||
proxy_set_header X-Request-Id $request_id;
|
||||
}
|
||||
|
||||
location ~ ^/api/runtime/puzzle/gallery/[^/]+$ {
|
||||
default_type application/json;
|
||||
limit_conn genarrative_api_conn 32;
|
||||
limit_req zone=genarrative_api_rps burst=32 nodelay;
|
||||
|
||||
if ($genarrative_maintenance) {
|
||||
return 503 '{"ok":false,"error":{"code":"MAINTENANCE","message":"服务维护中"}}';
|
||||
}
|
||||
|
||||
proxy_pass http://genarrative_api;
|
||||
proxy_http_version 1.1;
|
||||
proxy_buffering off;
|
||||
proxy_read_timeout 3600s;
|
||||
proxy_send_timeout 3600s;
|
||||
add_header X-Accel-Buffering no always;
|
||||
proxy_set_header Connection "";
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
proxy_set_header X-Forwarded-Host $host;
|
||||
proxy_set_header X-Request-Id $request_id;
|
||||
}
|
||||
|
||||
location ~ ^/api/runtime/custom-world-gallery/[^/]+/[^/]+$ {
|
||||
default_type application/json;
|
||||
limit_conn genarrative_api_conn 32;
|
||||
limit_req zone=genarrative_api_rps burst=32 nodelay;
|
||||
|
||||
if ($genarrative_maintenance) {
|
||||
return 503 '{"ok":false,"error":{"code":"MAINTENANCE","message":"服务维护中"}}';
|
||||
}
|
||||
|
||||
proxy_pass http://genarrative_api;
|
||||
proxy_http_version 1.1;
|
||||
proxy_buffering off;
|
||||
proxy_read_timeout 3600s;
|
||||
proxy_send_timeout 3600s;
|
||||
add_header X-Accel-Buffering no always;
|
||||
proxy_set_header Connection "";
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
proxy_set_header X-Forwarded-Host $host;
|
||||
proxy_set_header X-Request-Id $request_id;
|
||||
}
|
||||
|
||||
# 临时兼容主站仍在使用的 /api/* HTTP facade;前端完成 SpacetimeDB SDK 迁移后删除。
|
||||
location ~ ^/api(?:/|$) {
|
||||
@@ -305,7 +210,7 @@ server {
|
||||
try_files /index.html =404;
|
||||
}
|
||||
|
||||
location ~* "^/(?:bark-battle|big-fish|child-motion-demo|creation|creation/baby-object-match|creation/baby-object-match/generating|creation/baby-object-match/result|creation/bark-battle|creation/bark-battle/generating|creation/bark-battle/result|creation/big-fish|creation/big-fish/generating|creation/big-fish/result|creation/creative-agent|creation/jump-hop|creation/jump-hop/generating|creation/jump-hop/result|creation/match3d|creation/match3d/generating|creation/match3d/result|creation/puzzle|creation/puzzle-clear|creation/puzzle-clear/generating|creation/puzzle-clear/result|creation/puzzle/generating|creation/puzzle/result|creation/rpg|creation/rpg/agent|creation/rpg/generating|creation/rpg/result|creation/square-hole|creation/square-hole/generating|creation/square-hole/result|creation/visual-novel|creation/visual-novel/generating|creation/visual-novel/result|creation/wooden-fish|creation/wooden-fish/generating|creation/wooden-fish/result|editor/canvas|gallery/jump-hop/detail|gallery/puzzle/detail|gallery/visual-novel/detail|match3d|project|puzzle|runtime/baby-love-drawing|runtime/baby-object-match|runtime/bark-battle|runtime/big-fish|runtime/jump-hop|runtime/match3d|runtime/puzzle|runtime/puzzle-clear|runtime/rpg/adventure|runtime/rpg/characters|runtime/square-hole|runtime/visual-novel|runtime/wooden-fish|works/detail|worlds/detail)/?$" {
|
||||
location ~* "^/(?:editor/canvas|project)/?$" {
|
||||
error_page 503 /maintenance.html;
|
||||
|
||||
if ($genarrative_maintenance) {
|
||||
|
||||
@@ -26,7 +26,7 @@ server {
|
||||
proxy_pass http://127.0.0.1:18081/__genarrative_pingora/healthz;
|
||||
}
|
||||
|
||||
location = /api/creation-entry/config {
|
||||
location = /api/assets/history {
|
||||
default_type application/json;
|
||||
proxy_http_version 1.1;
|
||||
proxy_buffering off;
|
||||
|
||||
@@ -16,7 +16,9 @@
|
||||
"root /var/www/html;"
|
||||
]
|
||||
},
|
||||
"docs": ["`/.well-known/acme-challenge/*`"]
|
||||
"docs": [
|
||||
"`/.well-known/acme-challenge/*`"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "shadow_probe",
|
||||
@@ -24,7 +26,9 @@
|
||||
"expect": {
|
||||
"kind": "shadow_probe"
|
||||
},
|
||||
"docs": ["`/__genarrative_pingora/healthz`"]
|
||||
"docs": [
|
||||
"`/__genarrative_pingora/healthz`"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "admin_redirect",
|
||||
@@ -34,10 +38,19 @@
|
||||
"location": "/admin/"
|
||||
},
|
||||
"nginx": {
|
||||
"production": ["location = /admin", "return 301 /admin/;"],
|
||||
"development": ["location = /admin", "return 301 /admin/;"]
|
||||
"production": [
|
||||
"location = /admin",
|
||||
"return 301 /admin/;"
|
||||
],
|
||||
"development": [
|
||||
"location = /admin",
|
||||
"return 301 /admin/;"
|
||||
]
|
||||
},
|
||||
"docs": ["`/admin`", "301 到 `/admin/`"]
|
||||
"docs": [
|
||||
"`/admin`",
|
||||
"301 到 `/admin/`"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "admin_api_proxy",
|
||||
@@ -62,7 +75,9 @@
|
||||
"limit_req zone=genarrative_admin_rps burst=16 nodelay;"
|
||||
]
|
||||
},
|
||||
"docs": ["`/admin/api/*`"]
|
||||
"docs": [
|
||||
"`/admin/api/*`"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "admin_assets",
|
||||
@@ -73,10 +88,18 @@
|
||||
"mode": "exact"
|
||||
},
|
||||
"nginx": {
|
||||
"production": ["location ^~ /admin/assets/", "try_files $uri =404;"],
|
||||
"development": ["location ^~ /admin/assets/", "try_files $uri =404;"]
|
||||
"production": [
|
||||
"location ^~ /admin/assets/",
|
||||
"try_files $uri =404;"
|
||||
],
|
||||
"development": [
|
||||
"location ^~ /admin/assets/",
|
||||
"try_files $uri =404;"
|
||||
]
|
||||
},
|
||||
"docs": ["`/admin/assets/*`"]
|
||||
"docs": [
|
||||
"`/admin/assets/*`"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "admin_spa_fallback",
|
||||
@@ -96,7 +119,9 @@
|
||||
"try_files $uri $uri/ /admin/index.html;"
|
||||
]
|
||||
},
|
||||
"docs": ["`/admin/*`"]
|
||||
"docs": [
|
||||
"`/admin/*`"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "web_assets",
|
||||
@@ -106,115 +131,23 @@
|
||||
"root": "web",
|
||||
"mode": "exact"
|
||||
},
|
||||
"nginx": {
|
||||
"production": ["location ^~ /assets/", "try_files $uri =404;"],
|
||||
"development": ["location ^~ /assets/", "try_files $uri =404;"]
|
||||
},
|
||||
"docs": ["`/assets/*`"]
|
||||
},
|
||||
{
|
||||
"id": "puzzle_gallery_list",
|
||||
"samplePath": "/api/runtime/puzzle/gallery",
|
||||
"expect": {
|
||||
"kind": "proxy",
|
||||
"target": "api",
|
||||
"bodyLimit": null,
|
||||
"protectionClass": "gallery_list"
|
||||
},
|
||||
"nginx": {
|
||||
"production": [
|
||||
"location = /api/runtime/puzzle/gallery",
|
||||
"limit_conn genarrative_api_conn 320;",
|
||||
"limit_req zone=genarrative_gallery_rps burst=4096 nodelay;",
|
||||
"add_header X-Accel-Buffering no always;"
|
||||
"location ^~ /assets/",
|
||||
"try_files $uri =404;"
|
||||
],
|
||||
"development": [
|
||||
"location = /api/runtime/puzzle/gallery",
|
||||
"limit_conn genarrative_api_conn 320;",
|
||||
"limit_req zone=genarrative_gallery_rps burst=4096 nodelay;",
|
||||
"add_header X-Accel-Buffering no always;"
|
||||
"location ^~ /assets/",
|
||||
"try_files $uri =404;"
|
||||
]
|
||||
},
|
||||
"docs": ["`/api/runtime/puzzle/gallery`"]
|
||||
},
|
||||
{
|
||||
"id": "custom_world_gallery_list",
|
||||
"samplePath": "/api/runtime/custom-world-gallery",
|
||||
"expect": {
|
||||
"kind": "proxy",
|
||||
"target": "api",
|
||||
"bodyLimit": null,
|
||||
"protectionClass": "gallery_list"
|
||||
},
|
||||
"nginx": {
|
||||
"production": [
|
||||
"location = /api/runtime/custom-world-gallery",
|
||||
"limit_conn genarrative_api_conn 320;",
|
||||
"limit_req zone=genarrative_gallery_rps burst=4096 nodelay;",
|
||||
"add_header X-Accel-Buffering no always;"
|
||||
],
|
||||
"development": [
|
||||
"location = /api/runtime/custom-world-gallery",
|
||||
"limit_conn genarrative_api_conn 320;",
|
||||
"limit_req zone=genarrative_gallery_rps burst=4096 nodelay;",
|
||||
"add_header X-Accel-Buffering no always;"
|
||||
]
|
||||
},
|
||||
"docs": ["`/api/runtime/custom-world-gallery`"]
|
||||
},
|
||||
{
|
||||
"id": "puzzle_gallery_detail",
|
||||
"samplePath": "/api/runtime/puzzle/gallery/work-a",
|
||||
"expect": {
|
||||
"kind": "proxy",
|
||||
"target": "api",
|
||||
"bodyLimit": null,
|
||||
"protectionClass": "gallery_detail"
|
||||
},
|
||||
"nginx": {
|
||||
"production": [
|
||||
"location ~ ^/api/runtime/puzzle/gallery/[^/]+$",
|
||||
"limit_conn genarrative_api_conn 32;",
|
||||
"limit_req zone=genarrative_api_rps burst=32 nodelay;",
|
||||
"add_header X-Accel-Buffering no always;"
|
||||
],
|
||||
"development": [
|
||||
"location ~ ^/api/runtime/puzzle/gallery/[^/]+$",
|
||||
"limit_conn genarrative_api_conn 32;",
|
||||
"limit_req zone=genarrative_api_rps burst=32 nodelay;",
|
||||
"add_header X-Accel-Buffering no always;"
|
||||
]
|
||||
},
|
||||
"docs": ["`/api/runtime/puzzle/gallery/{id}`"]
|
||||
},
|
||||
{
|
||||
"id": "custom_world_gallery_detail",
|
||||
"samplePath": "/api/runtime/custom-world-gallery/profile-a/owner-b",
|
||||
"expect": {
|
||||
"kind": "proxy",
|
||||
"target": "api",
|
||||
"bodyLimit": null,
|
||||
"protectionClass": "gallery_detail"
|
||||
},
|
||||
"nginx": {
|
||||
"production": [
|
||||
"location ~ ^/api/runtime/custom-world-gallery/[^/]+/[^/]+$",
|
||||
"limit_conn genarrative_api_conn 32;",
|
||||
"limit_req zone=genarrative_api_rps burst=32 nodelay;",
|
||||
"add_header X-Accel-Buffering no always;"
|
||||
],
|
||||
"development": [
|
||||
"location ~ ^/api/runtime/custom-world-gallery/[^/]+/[^/]+$",
|
||||
"limit_conn genarrative_api_conn 32;",
|
||||
"limit_req zone=genarrative_api_rps burst=32 nodelay;",
|
||||
"add_header X-Accel-Buffering no always;"
|
||||
]
|
||||
},
|
||||
"docs": ["`/api/runtime/custom-world-gallery/{profile}/{owner}`"]
|
||||
"docs": [
|
||||
"`/assets/*`"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "generic_api_proxy",
|
||||
"samplePath": "/api/creation-entry/config",
|
||||
"samplePath": "/api/assets/history",
|
||||
"expect": {
|
||||
"kind": "proxy",
|
||||
"target": "api",
|
||||
@@ -237,7 +170,9 @@
|
||||
"add_header X-Accel-Buffering no always;"
|
||||
]
|
||||
},
|
||||
"docs": ["`/api`、`/api/*`"]
|
||||
"docs": [
|
||||
"`/api`、`/api/*`"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "spacetime_subscribe",
|
||||
@@ -264,7 +199,9 @@
|
||||
"proxy_read_timeout 3600s;"
|
||||
]
|
||||
},
|
||||
"docs": ["`/v1/database/{db}/subscribe`"]
|
||||
"docs": [
|
||||
"`/v1/database/{db}/subscribe`"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "spacetime_identity",
|
||||
@@ -289,7 +226,9 @@
|
||||
"proxy_set_header Connection \"Upgrade\";"
|
||||
]
|
||||
},
|
||||
"docs": ["`/v1/identity*`"]
|
||||
"docs": [
|
||||
"`/v1/identity*`"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "v1_forbidden",
|
||||
@@ -298,10 +237,18 @@
|
||||
"kind": "not_found"
|
||||
},
|
||||
"nginx": {
|
||||
"production": ["location ^~ /v1/", "return 404;"],
|
||||
"development": ["location ^~ /v1/", "return 404;"]
|
||||
"production": [
|
||||
"location ^~ /v1/",
|
||||
"return 404;"
|
||||
],
|
||||
"development": [
|
||||
"location ^~ /v1/",
|
||||
"return 404;"
|
||||
]
|
||||
},
|
||||
"docs": ["`/v1/*`"]
|
||||
"docs": [
|
||||
"`/v1/*`"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "healthz_forbidden",
|
||||
@@ -319,7 +266,9 @@
|
||||
"return 404;"
|
||||
]
|
||||
},
|
||||
"docs": ["`/healthz*`"]
|
||||
"docs": [
|
||||
"`/healthz*`"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "readyz_forbidden",
|
||||
@@ -337,7 +286,9 @@
|
||||
"return 404;"
|
||||
]
|
||||
},
|
||||
"docs": ["`/readyz*`"]
|
||||
"docs": [
|
||||
"`/readyz*`"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "generated_assets_forbidden",
|
||||
@@ -355,11 +306,13 @@
|
||||
"return 404;"
|
||||
]
|
||||
},
|
||||
"docs": ["`/generated-*`"]
|
||||
"docs": [
|
||||
"`/generated-*`"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "web_spa_fallback",
|
||||
"samplePath": "/creation/puzzle/result",
|
||||
"samplePath": "/project",
|
||||
"expect": {
|
||||
"kind": "static",
|
||||
"root": "web",
|
||||
@@ -375,21 +328,33 @@
|
||||
"try_files $uri /index.html =404;"
|
||||
]
|
||||
},
|
||||
"docs": ["主站 SPA allowlist", "失败回退 `/index.html`"]
|
||||
"docs": [
|
||||
"主站 SPA allowlist",
|
||||
"失败回退 `/index.html`"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "web_spa_case_trailing_slash",
|
||||
"samplePath": "/CREATION/PUZZLE/RESULT/",
|
||||
"samplePath": "/PROJECT/",
|
||||
"expect": {
|
||||
"kind": "static",
|
||||
"root": "web",
|
||||
"mode": "spa_fallback"
|
||||
},
|
||||
"nginx": {
|
||||
"production": ["location ~*", "try_files $uri /index.html =404;"],
|
||||
"development": ["location ~*", "try_files $uri /index.html =404;"]
|
||||
"production": [
|
||||
"location ~*",
|
||||
"try_files $uri /index.html =404;"
|
||||
],
|
||||
"development": [
|
||||
"location ~*",
|
||||
"try_files $uri /index.html =404;"
|
||||
]
|
||||
},
|
||||
"docs": ["大小写不敏感", "一个尾部斜杠"]
|
||||
"docs": [
|
||||
"大小写不敏感",
|
||||
"一个尾部斜杠"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "web_unknown_path_exact",
|
||||
@@ -400,10 +365,19 @@
|
||||
"mode": "exact"
|
||||
},
|
||||
"nginx": {
|
||||
"production": ["location /", "try_files $uri $uri/ =404;"],
|
||||
"development": ["location /", "try_files $uri $uri/ =404;"]
|
||||
"production": [
|
||||
"location /",
|
||||
"try_files $uri $uri/ =404;"
|
||||
],
|
||||
"development": [
|
||||
"location /",
|
||||
"try_files $uri $uri/ =404;"
|
||||
]
|
||||
},
|
||||
"docs": ["其它 Web 路径", "缺失时返回真实 404"]
|
||||
"docs": [
|
||||
"其它 Web 路径",
|
||||
"缺失时返回真实 404"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "creation_unknown_path_exact",
|
||||
@@ -414,10 +388,16 @@
|
||||
"mode": "exact"
|
||||
},
|
||||
"nginx": {
|
||||
"production": ["try_files $uri $uri/ =404;"],
|
||||
"development": ["try_files $uri $uri/ =404;"]
|
||||
"production": [
|
||||
"try_files $uri $uri/ =404;"
|
||||
],
|
||||
"development": [
|
||||
"try_files $uri $uri/ =404;"
|
||||
]
|
||||
},
|
||||
"docs": ["`/creation/not-exist`"]
|
||||
"docs": [
|
||||
"`/creation/not-exist`"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "runtime_unknown_path_exact",
|
||||
@@ -428,10 +408,16 @@
|
||||
"mode": "exact"
|
||||
},
|
||||
"nginx": {
|
||||
"production": ["try_files $uri $uri/ =404;"],
|
||||
"development": ["try_files $uri $uri/ =404;"]
|
||||
"production": [
|
||||
"try_files $uri $uri/ =404;"
|
||||
],
|
||||
"development": [
|
||||
"try_files $uri $uri/ =404;"
|
||||
]
|
||||
},
|
||||
"docs": ["`/runtime/not-exist`"]
|
||||
"docs": [
|
||||
"`/runtime/not-exist`"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "puzzle_unknown_path_exact",
|
||||
@@ -442,10 +428,16 @@
|
||||
"mode": "exact"
|
||||
},
|
||||
"nginx": {
|
||||
"production": ["try_files $uri $uri/ =404;"],
|
||||
"development": ["try_files $uri $uri/ =404;"]
|
||||
"production": [
|
||||
"try_files $uri $uri/ =404;"
|
||||
],
|
||||
"development": [
|
||||
"try_files $uri $uri/ =404;"
|
||||
]
|
||||
},
|
||||
"docs": ["`/puzzle/not-exist`"]
|
||||
"docs": [
|
||||
"`/puzzle/not-exist`"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -4213,3 +4213,6 @@
|
||||
- 决策:旧创作模板、旧创作入口及其专属运行服务进入下线范围,不再为跳一跳、抓大鹅 Match3D、儿童动作 Demo 等旧链路修复兼容问题、补生成脚本或维持专属门禁。
|
||||
- 边界:共享账号、钱包、资产、图片编辑器、公开作品、通用 HostBridge、API、SpacetimeDB、发布运维和安全能力不属于旧链路,仍需维持正式门禁。历史文档只作为背景材料,不再作为旧入口继续运行的依据。
|
||||
- 清理方式:允许直接删除已经失效的旧素材生成命令、入口路由、专属服务和对应测试;删除工程链路时仍需核对是否被当前共享能力引用,不能连带移除仍在使用的公共契约或持久化事实。
|
||||
- 最终落地:本次退役范围覆盖整个旧创作模板体系,包括 RPG / 自定义世界、拼图、拼消消、大鱼吃小鱼、敲木鱼、方洞挑战、视觉小说、汪汪声浪、寓教于乐、Creative Agent、Match3D、跳一跳和儿童动作 Demo。全部相关历史表继续作为数据壳参与 `spacetime-module` 编译,`migration.rs` 白名单与历史数据不变;旧 reducer/procedure/view、API 路由/handler/worker、前端页面/工作台/运行态、共享业务 DTO 和纯业务 crate 从编译链与依赖图移除,但旧源码和素材保留在仓库中用于历史追溯。
|
||||
- 兼容读取:只保留历史审计、迁移和资产归属核对所需的最小读取定义;旧 `worldType`、公开作品号、URL、详情页和专属运行态均不再形成用户可访问入口。
|
||||
- 方案文档:`docs/technical/【架构下线】旧创作模板业务退役方案-2026-07-17.md`。
|
||||
|
||||
@@ -10,12 +10,7 @@ Genarrative / 陶泥儿是一个 AI 原生互动内容与小游戏平台,把 A
|
||||
|
||||
## 当前主要能力
|
||||
|
||||
- RPG / 自定义世界创作与运行时。
|
||||
- 拼图玩法创作、草稿、发布、运行态和排行榜。
|
||||
- 拼消消玩法创作、素材图集生成、结果页、发布、统一作品详情、正式运行态和基础统计。
|
||||
- 敲木鱼玩法创作、草稿、发布、运行态、公开详情和分享码。
|
||||
- 抓大鹅 Match3D 创作、2D 多视角素材生成、发布和运行态。
|
||||
- 大鱼吃小鱼、方洞挑战、视觉小说、汪汪声浪和儿童向寓教于乐玩法。
|
||||
- 图片画布编辑器、项目与资产管理。
|
||||
- 账号、短信 / 密码 / 微信登录、个人资料、任务、钱包、邀请码、充值、反馈、法律信息和后台管理。
|
||||
|
||||
## 当前入口
|
||||
@@ -50,6 +45,8 @@ server-rs + Axum + SpacetimeDB
|
||||
|
||||
明确废弃:旧 `server-node`、Express、PostgreSQL、Go 服务端、`maincloud`、人工 `spacetime --root-dir` 口径,以及前端承接正式业务真相的路线。
|
||||
|
||||
全部旧创作模板及其创作、生成、发布、公开业务详情与专属运行态已于 2026-07-17 退役。相关历史持久化表只以最小 schema 数据壳继续参与 `spacetime-module` 编译,迁移白名单与历史数据保持不变;旧前端、API、worker、procedure/reducer 和纯业务 crate 源码仅供追溯,不属于当前编译或运行能力。
|
||||
|
||||
## 当前文档入口
|
||||
|
||||
- `docs/README.md`
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
# 旧创作模板业务退役方案
|
||||
|
||||
更新时间:`2026-07-17`
|
||||
|
||||
## 目标
|
||||
|
||||
退役整个旧创作模板体系及其专属运行态,同时保留全部相关历史持久化表、迁移白名单和必要的兼容读取定义。历史数据不删除,持久化表及字段不删除、不改名、不重排、不改变类型。
|
||||
|
||||
本次执行口径是“数据壳保留,业务实现退役”:历史表继续以最小 schema 代码随 `spacetime-module` 编译和发布;创作入口、模板选择、生成、发布、公开业务详情、作品广场、排行榜和专属运行态不再进入正式编译链或运行路由。
|
||||
|
||||
## 范围
|
||||
|
||||
### 退役
|
||||
|
||||
- RPG / 自定义世界、拼图、拼消消、大鱼吃小鱼、敲木鱼、方洞挑战、视觉小说、汪汪声浪、寓教于乐、Creative Agent、Match3D、跳一跳和儿童动作 Demo 的前端页面、路由、工作台、结果页、运行态、service、测试及专属素材生成入口。
|
||||
- `api-server` 中上述业务的 router、handler、service、生成与发布编排、公开详情、专属 runtime 和 worker 启动点。
|
||||
- `spacetime-client` 的模板业务 facade、mapper、reducer/procedure 调用和业务 DTO 依赖。
|
||||
- `spacetime-module` 的模板 reducer、procedure、业务 view、初始化逻辑和领域规则调用。
|
||||
- 所有纯模板 crate 及 RPG 专属运行态 crate 的 workspace/default 目标和在运依赖边。
|
||||
- 后台及主站中只服务于旧创作入口的配置、灰度、展示、搜索、启动、追踪和运维门禁。
|
||||
|
||||
### 保留
|
||||
|
||||
- 全部旧模板历史持久化表及其原有字段顺序、字段类型、默认值、索引和可见性。
|
||||
- `migration.rs` 中相关表的迁移白名单、表名兼容和字段目录。
|
||||
- 为历史审计、迁移、资产归属核对所必需的最小只读表定义;不得借兼容读取重新暴露旧创作、发布、公开详情或运行接口。
|
||||
- 编辑器、项目、账号、钱包、资产、HostBridge、运维和安全等平台公共能力。
|
||||
- 旧页面、测试、素材、handler、service、worker、生成 bindings 和纯业务 crate 的源码目录;它们仅用于历史追溯,不属于任何正式入口或编译目标。
|
||||
|
||||
## 编译边界
|
||||
|
||||
### 前端
|
||||
|
||||
- 主路由、Vite 入口和运行时动态 import 不得引用旧业务目录。
|
||||
- 正式应用入口使用 `active-main.tsx`、`ActiveApp.tsx`、`routing/activeAppRoutes.tsx`、`routing/activeAppPageRoutes.ts`、`services/activeAppTitle.ts`、`platform-entry/PlatformEntryActiveFlowShell.tsx` 和 `platformEntryActiveTypes.ts`;原同名非 active 文件保持历史源码原貌并退出 Vite、TypeScript、ESLint 和 Vitest。
|
||||
- 旧业务源码与素材保留在仓库中;Vite 不得再引用入口或动态 import,TypeScript、ESLint、Vitest 必须明确排除旧目录和专属测试。退役源码只用于历史追溯,不允许从在运代码重新导入。
|
||||
- 原 `main.tsx`、`App.tsx`、旧路由、旧标题映射、`PlatformEntryFlowShellImpl.tsx` 与旧入口类型保持原样;正式链路由 `active-main.tsx`、`ActiveApp.tsx`、`activeApp*`、`PlatformEntryActiveFlowShell.tsx` 和 `platformEntryActiveTypes.ts` 承载,只包含项目、编辑器、账号、设置与钱包公共能力。`retired/legacy-creation-templates/frontend/original/` 另保留逐文件原样快照。
|
||||
- 旧 URL 统一回落到当前平台公共首页,不再提供旧业务错误页、详情页或兼容壳。
|
||||
|
||||
### Rust
|
||||
|
||||
- `api-server` 不声明模板模块,不挂模板路由,不保留模板 worker 启动点。
|
||||
- `spacetime-client` 不保留模板业务 facade 和 mutation 调用;历史表生成绑定只允许服务必要兼容读取。
|
||||
- `spacetime-module` 对旧模板只编译历史表结构,不导出旧 reducer、procedure、业务 view 或领域规则。
|
||||
- `spacetime-module` 与 `spacetime-client` 的 Cargo `lib.path` 固定指向各自的 `src/active.rs`;原 `src/lib.rs` 及旧业务源码继续原位保留,但不再作为 crate 根参与编译。
|
||||
- 历史表最小定义集中在 `spacetime-module/src/legacy_schema/` 与 `spacetime-module/src/runtime/legacy_schema/`,混合 profile 表的在运数据壳位于 `spacetime-module/src/runtime/active/profile.rs`;这些目录只允许 schema 和必要兼容读取定义。
|
||||
- 纯模板 crate 和专属运行态 crate 不属于 workspace members、default members 或任何在运 crate 的依赖图;源码目录保持原样。
|
||||
|
||||
## 验收
|
||||
|
||||
- 旧 URL 不再命中旧页面或后端路由。
|
||||
- Vite 构建产物和依赖图不包含旧前端业务目录。
|
||||
- `cargo tree` 中不存在纯模板 crate 或专属运行态 crate。
|
||||
- `spacetime-module` 编译结果仍包含全部历史表,但不包含任何旧模板 reducer、procedure 和业务 view。
|
||||
- `npm run check:spacetime-schema`、定向 Rust 检查、前端类型检查、`npm run check:encoding`、`git diff --check` 通过。
|
||||
|
||||
## 历史记录兼容
|
||||
|
||||
- 历史数据库行、作品号、`worldType` 和资产对象前缀可以继续被审计或迁移工具识别,但不再形成面向用户的列表、详情、创作或运行入口。
|
||||
- 旧公开作品号、创作 URL 和统一创作规格不再作为前端可启动契约;未知或旧 URL 统一回落平台公共首页。
|
||||
- 历史表名、资产对象前缀和后台数据库表目录可继续出现旧域名,它们属于数据审计与资产读取边界,不代表旧业务仍在运行。
|
||||
- 原本与历史表混在同一文件的 reducer/procedure 实现按模块保存在 `retired/legacy-creation-templates/rust/`,不属于任何 Cargo workspace/module;从全局样式表移出的专属 CSS 保存在同目录的 `frontend/` 下且不被 Vite 导入。
|
||||
@@ -1,5 +1,7 @@
|
||||
# server-rs 与 SpacetimeDB 数据契约
|
||||
|
||||
> 2026-07-17 状态更新:旧创作入口、全部模板业务 API/worker/运行态及 SpacetimeDB 业务逻辑已退役。本文逐玩法路由、流程和 DTO 章节仅作为历史设计记录;相关持久化表仍按原结构作为最小 schema 数据壳编译,当前边界以 `docs/technical/【架构下线】旧创作模板业务退役方案-2026-07-17.md` 为准。
|
||||
|
||||
更新时间:`2026-07-17`
|
||||
|
||||
## 后端主线
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
# 平台入口与玩法链路
|
||||
|
||||
> 2026-07-17 退役覆盖:全部旧创作模板的前端、API、worker、reducer/procedure、纯业务 crate、生成发布链路、公开业务详情和专属运行态已下线,仅保留相关历史表的数据壳与必要兼容读取。本文后续玩法章节只作为历史设计记录,不再描述当前可用能力;当前实现与验收以 `docs/technical/【架构下线】旧创作模板业务退役方案-2026-07-17.md` 为准。
|
||||
|
||||
更新时间:`2026-06-10`
|
||||
|
||||
## 平台创作入口
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
# 当前产品与工程约束
|
||||
|
||||
> 2026-07-17 状态更新:旧创作入口、全部模板创作/生成/发布/公开业务详情及专属运行态已退役。本文涉及 RPG、拼图、拼消消、大鱼、木鱼、方洞、视觉小说、汪汪声浪、寓教于乐等玩法的内容仅作为历史设计记录;当前退役边界以 `docs/technical/【架构下线】旧创作模板业务退役方案-2026-07-17.md` 为准。
|
||||
|
||||
更新时间:`2026-06-05`
|
||||
|
||||
## 项目定位
|
||||
|
||||
+2
-2
@@ -7,7 +7,7 @@
|
||||
content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no, viewport-fit=cover"
|
||||
/>
|
||||
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
||||
<title>陶泥儿 Genarrative|游戏美术AI创作工具与美术Agent工作台</title>
|
||||
<title>陶泥儿 Genarrative|美术编辑器与项目工作台</title>
|
||||
<meta
|
||||
name="description"
|
||||
content="陶泥儿 Genarrative 是面向个人游戏创作者、独立开发者和UGC游戏社群作者的游戏美术AI创作工具,当前重点提供美术Agent与无限画布,支持素材库管理、AI生成、画布编排、图层编辑、角色素材、游戏图标和宣发图制作,帮助创作者更快准备可展示的游戏美术素材。"
|
||||
@@ -49,6 +49,6 @@
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
<script type="module" src="/src/main.tsx"></script>
|
||||
<script type="module" src="/src/active-main.tsx"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user