Files
Genarrative/packages/shared/src/contracts/gameDistribution.ts
T
lhk229 b60aea4e71
Project CI / AI game creator shell Rust crates (push) Successful in 1m43s
Project CI / AI game creator shell Rust lane 2/2 (push) Failing after 2m7s
Project CI / AI game creator shell Rust smoke (push) Successful in 2m55s
Project CI / AI game creator shell Rust lane 1/2 (push) Failing after 3m23s
Project CI / Frontend tests (push) Successful in 3m3s
Project CI / Repository checks (push) Successful in 3m42s
Project CI / AI game creator shell web tests (push) Successful in 2m3s
Project CI / Backend tests (push) Successful in 6m19s
Project CI / Native shell tests (push) Successful in 8m7s
实现网站游戏评分与评价 (#561)
公开游戏详情页新增评分与评价:登录用户可提交并修改自己对每款游戏的唯一一条 1–10 分评价,评论可空且最多 4000 个 Unicode 码点;游客可分页浏览评价并查看平均分与评分人数。

- 新增私有评价表及共享 DTO/API,事务内校验游戏公开可见性、保证唯一性,并计算分页与全量评分统计;下架或封禁后关闭读写,重新公开及更新版本保留评价。
- 详情页支持编辑预填、取消、提交期间冻结输入、失败保留草稿,以及保存、翻页和账号/游戏切换时的旧响应隔离;复用现有认证与公共组件。
- 同步主规范、里程碑、实施计划、生成绑定、表目录和验收脚本。不扩展 AGC、外部 API、评论审核或评分缓存。

验证:
- 本轮审查复跑前端定向测试 40 项、评价领域测试 2 项,均通过。
- 网站 typecheck、Rust/TypeScript DTO 对齐、SpacetimeDB schema、文档索引、编码与 diff 检查通过。
- 实施文档另记录本地隔离数据库/API 49 项及桌面/移动视口浏览器 smoke 通过;本轮审查未重跑这些运行时验收,也未运行完整测试套件。

发布说明:新增 SpacetimeDB 表,部署时需同步 module 与 API。尚未部署 dev/production,待用户验收。

Reviewed-on: https://git.genarrative.world/git/GenarrativeAI/Genarrative/pulls/561
Co-authored-by: Linghong <ink29535@proton.me>
Co-committed-by: Linghong <ink29535@proton.me>
2026-10-01 19:19:58 +08:00

248 lines
6.8 KiB
TypeScript

export const GAME_DISTRIBUTION_CATEGORIES = [
'休闲',
'益智',
'动作',
'冒险',
'模拟',
'策略',
'其他',
] as const;
export type GameDistributionCategory =
(typeof GAME_DISTRIBUTION_CATEGORIES)[number];
export type GameDistributionPublishMetadataSuggestionRequest = {
name: string;
goal?: string | null;
/** 脱敏且有界的项目上下文摘要。 */
context?: string | null;
};
export type GameDistributionPublishMetadataSuggestion = {
summary: string;
category: GameDistributionCategory;
};
export type GameDistributionDeviceSupport = {
desktop: boolean;
mobile: boolean;
touch: boolean;
};
export type GameDistributionInputMode = 'keyboard' | 'mouse' | 'touch';
export type GameDistributionOrientation =
| 'landscape'
| 'portrait'
| 'responsive';
export type GameDistributionAuthor = {
id: string;
name: string;
avatarUrl?: string | null;
};
export type GameDistributionReview = {
/** 管理员隐藏后仍允许本人读取和编辑,不参与公共统计。 */
isHidden: boolean;
id: string;
gameId: string;
author: GameDistributionAuthor;
score: number;
comment: string;
createdAt: string;
updatedAt: string;
};
export type GameDistributionRatingSummary = {
averageScore: number | null;
ratingCount: number;
};
export type GameDistributionReviewsResponse = {
reviews: GameDistributionReview[];
page: number;
pageSize: number;
total: number;
totalPages: number;
ratingSummary: GameDistributionRatingSummary;
};
export type GameDistributionMyReviewResponse = {
review: GameDistributionReview | null;
};
export type GameDistributionSaveReviewRequest = {
score: number;
comment?: string;
};
export type GameDistributionSaveReviewResponse = {
review: GameDistributionReview;
ratingSummary: GameDistributionRatingSummary;
};
export type GameDistributionVersionStatus =
| 'awaiting_upload'
| 'uploaded'
| 'validating'
| 'pending_review'
| 'published'
| 'upload_failed'
| 'validation_failed'
| 'rejected'
| 'cancelled'
| 'revoked';
export type GameDistributionGameVisibility =
| 'unpublished'
| 'published'
| 'suspended';
export type GameDistributionVersionSummary = {
id: string;
version: string;
/**
* 发行入口:平台同源路径 `/games/<gameId>/`,客户端按当前 origin 解析后再交给 iframe。
* 兼容历史数据的绝对 URL(非当前源的 https 地址),新写入只用相对路径。
*/
entryUrl: string;
sha256: string;
publishedAt: string;
controls: string[];
};
export type GameDistributionGame = {
id: string;
title: string;
summary: string;
description: string;
category: GameDistributionCategory;
tags: string[];
coverColor: string;
icon: string;
/**
* 公开封面的 OSS objectKey;未上传封面时为空,展示层需回退到 coverColor/icon 占位。
* 通过 `/api/assets/read-url` 换签名 URL 读取,不接受直连外链。
*/
coverObjectKey?: string | null;
/** 公开截图的 OSS objectKey 列表,最多 6 张,随版本冻结。 */
screenshots?: string[];
author: GameDistributionAuthor;
deviceSupport: GameDistributionDeviceSupport;
inputModes?: GameDistributionInputMode[];
orientation?: GameDistributionOrientation;
status: Extract<GameDistributionGameVisibility, 'published' | 'unpublished'>;
/** 公开切换 CAS 版本号;作者下架与管理员审核都必须回传当前值。 */
publicationRevision: number;
/**
* 公开目录与公开详情一定带这一项(没有已公开版本时为 null);作者侧响应(我的作品列表、
* 创建、提交、审核、撤回)走服务端同一个 `game_payload`,不带这一项。
*/
currentVersion?: GameDistributionVersionSummary | null;
/** 公开列表和详情返回有效评价摘要;作者侧或旧响应可省略。 */
ratingSummary?: GameDistributionRatingSummary;
playCount: number;
createdAt: string;
};
export type GameDistributionListResponse = {
games: GameDistributionGame[];
nextCursor?: string | null;
};
export type GameDistributionCreateGameRequest = {
/** 发布方本地项目标识;同一作者重复发布会复用既有 gameId。 */
localProjectId?: string | null;
title: string;
summary: string;
description?: string;
category: GameDistributionCategory;
tags?: string[];
coverAssetId?: string;
/** 截图素材 ID(最多 6 张,复用平台图片上传与归属校验)。 */
screenshots?: string[];
deviceSupport: GameDistributionDeviceSupport;
inputModes: GameDistributionInputMode[];
orientation: GameDistributionOrientation;
};
export type GameDistributionCreateVersionRequest = {
localProjectId?: string | null;
packageSha256: string;
packageBytes: number;
packageFileCount: number;
packageEntryPath: 'index.html';
gameMetadata: GameDistributionCreateGameRequest;
};
export type GameDistributionPrivateVersion = {
versionId: string;
gameId: string;
versionNumber: number;
packageSha256: string;
packageBytes: number;
status: GameDistributionVersionStatus;
/** 游戏公开修订号;撤回与审核动作都必须回传当前值做 CAS。 */
publicationRevision: number;
reviewReason?: string | null;
createdAt: string;
updatedAt: string;
};
/**
* 版本状态的下一步动作,由服务端派生;客户端只按它渲染主行动作,不自行推断状态。
*/
export type GameDistributionRecoveryAction =
| 'upload'
| 'submit'
| 'wait'
| 'none'
| 'reupload'
| 'fix_package'
| 'fix_metadata';
/** 冻结资料里的截图:同时保留素材 ID(作者续发可复用)与对象键(展示换签用)。 */
export type GameDistributionFrozenScreenshot = {
assetId: string;
objectKey: string;
};
/**
* 随版本冻结的游戏资料快照。
*
* 只有作者本人(版本回读)与管理员(审核回读)会拿到素材 ID;公开投影只给对象键。
* 历史版本可能没有快照,读取方必须按空值处理。
*/
export type GameDistributionVersionFrozenMetadata = {
title?: string;
summary?: string;
description?: string;
category?: GameDistributionCategory;
tags?: string[];
coverAssetId?: string | null;
coverObjectKey?: string | null;
screenshots?: GameDistributionFrozenScreenshot[];
deviceSupport?: GameDistributionDeviceSupport;
inputModes?: GameDistributionInputMode[];
orientation?: GameDistributionOrientation;
};
export type GameDistributionVersionDetail = {
game: GameDistributionGame;
version: GameDistributionPrivateVersion & {
recoveryAction: GameDistributionRecoveryAction;
frozenMetadata?: GameDistributionVersionFrozenMetadata | null;
};
};
export type GameDistributionCancelVersionRequest = {
expectedPublicationRevision: number;
reason?: string;
};
export type GameDistributionCancelVersionResponse = {
game: GameDistributionGame;
version: GameDistributionPrivateVersion;
replayed: boolean;
};