feat(游戏共创): 共创主题(一)数据模型与领域纯函数
Project CI / AI game creator shell Rust lane 1/2 (pull_request) Successful in 7m30s
Project CI / AI game creator shell Rust crates (pull_request) Successful in 4m12s
Project CI / AI game creator shell Rust lane 2/2 (pull_request) Successful in 5m44s
Project CI / Backend tests (pull_request) Has been cancelled
Project CI / Frontend tests (pull_request) Has been cancelled
Project CI / AI game creator shell web tests (pull_request) Has been cancelled
Project CI / Native shell tests (pull_request) Has been cancelled
Project CI / Repository checks (pull_request) Has been cancelled
Project CI / AI game creator shell Rust lane 1/2 (pull_request) Successful in 7m30s
Project CI / AI game creator shell Rust crates (pull_request) Successful in 4m12s
Project CI / AI game creator shell Rust lane 2/2 (pull_request) Successful in 5m44s
Project CI / Backend tests (pull_request) Has been cancelled
Project CI / Frontend tests (pull_request) Has been cancelled
Project CI / AI game creator shell web tests (pull_request) Has been cancelled
Project CI / Native shell tests (pull_request) Has been cancelled
Project CI / Repository checks (pull_request) Has been cancelled
按 `docs/【技术方案】游戏共创与作品Fork-2026-10-03.md §3.10` 与里程碑《共创主题与作品树-2026-10-06》的清单 A/B/F 落地(本块**不含** procedure / 路由)。
- **表**:`game_distribution_theme`(`theme_id` 主键、`name`/`summary`/`badge`/`sort_order`/`status ∈ draft|published|archived`/`created_by_user_id`/`created_at`/`updated_at`)+ `game_distribution_theme_member`(确定性主键 `member_id = "{theme_id}:{root_game_id}"`,`theme_id`、`root_game_id` 各具名 btree 索引,`sort_order`、`created_at`);归档 / 下架只改公开投影,不删成员行。`migration.rs` 白名单登记两张表(随迁移导出/导入)。
- **生成绑定**:4 个 `game_distribution_theme*` 生成文件 + `module_bindings.rs` 入口(纯追加)。生成时**不覆盖工作树**(本仓共享工作树禁止 git 写命令):先 `spacetime generate --lang rust --out-dir <仓外临时目录>`,rustfmt 后只拷回本次相关文件,避免上一轮踩过的「rustfmt 漂移把别人文件一起改」。
- **领域纯函数**(`module-game-distribution/src/theme.rs`,风格照同目录 `collection.rs`):状态合法/公开可见判定、`game_distribution_theme_member_id`、成员可见性(**委托** `collection.rs` 的判定,不复制第二套)、`game_distribution_theme_root_acceptable(has_lineage_row)`、页大小(缺省 20 / 上限 50 / 0 取默认 / 超界截断)、游标编解码(`"{createdAtMicros}:{themeId}"`,Err 文案「主题列表游标格式无效」,已核对不含 404/409 关键词)、`sort_public_themes`(`created_at` 倒序 + `theme_id` 兜底)、`page_public_themes`(排序 → retain 游标之后 → 多取一条判 has_more;**先过滤再切页**的顺序语义写进文档注释)、`sort_theme_members`(`sort_order` 升序 + `member_id` 兜底)与 `page_theme_members`。
- **契约**:`shared-contracts` 新增 `GameDistributionThemeStatus` / `GameDistributionThemeReference` / `GameDistributionThemeSummary` / `GameDistributionThemeListResponse` / `GameDistributionThemeDetail` / `GameDistributionCreateThemeRequest` / `GameDistributionUpdateThemeRequest` / `GameDistributionUpsertThemeMemberRequest`,`GameDistributionGameSummary` 追加可选 `themes`(按 `collected` 先例只在详情路径下发);TS 镜像同步;parity 登记 **58 组**(响应构建器待路由落地后再登记,parity 要求函数真实存在)。
- **数据契约表目录**:补两张表小节,94 → **96**。
- **测试**:`theme.rs` 16 个单测(成员 id 确定性、状态三态与公开可见、成员可见性逐格与收藏判定等价、根判定、页大小边界、游标往返与非法输入、排序稳定性与同 `sort_order` 兜底、分页不重不漏)。
- **门禁**:wasm build 0;`cargo check --all-targets` 0;`cargo test -p module-game-distribution` **97 passed**;`cargo test -p spacetime-module` 274 passed / 1 ignored;DTO parity **58 组**;`check:spacetime-schema` 0(**96** tables);`check:server-rs-ddd` 0;`check:encoding` 0(5388 files);`cargo fmt --all -- --check` 0;`git diff --check` 0(8 个改动文件 0 删除行)。
This commit is contained in:
@@ -296,6 +296,14 @@ export type GameDistributionGame = {
|
||||
* 收藏按钮态。公开目录不返回。值来自服务端真实投影,不是前端本地状态。
|
||||
*/
|
||||
collected?: boolean;
|
||||
/**
|
||||
* 该作品所属的**公开**共创主题(先取作品的根、再按根反查,跨主题多归属因此是数组)。
|
||||
*
|
||||
* **只有公开详情返回**(匿名与登录都返回;没有所属主题时是空数组,展示层据此不渲染空容器),
|
||||
* 公开目录与作者侧不带这个键。第 N 代作品同样能看到主题——反查用的是根,不是作品自身。
|
||||
* 排序与公开主题列表共用同一份比较器(创建时间倒序 + themeId 升序兜底)。
|
||||
*/
|
||||
themes?: GameDistributionThemeReference[];
|
||||
playCount: number;
|
||||
createdAt: string;
|
||||
};
|
||||
@@ -318,6 +326,101 @@ export type GameDistributionMyCollectionsResponse = {
|
||||
nextCursor?: string | null;
|
||||
};
|
||||
|
||||
/**
|
||||
* 共创主题的三态处置:`draft`(未发布)/ `published`(公开可见)/ `archived`(归档下架)。
|
||||
*
|
||||
* 公开侧只出现 `published`;`archived` 不是删除(主题行与成员行都保留,只是不再公开)。
|
||||
* 后台写接口的 `status` 用这个联合类型,非法取值由服务端拒绝(400)。
|
||||
*/
|
||||
export type GameDistributionThemeStatus = 'draft' | 'published' | 'archived';
|
||||
|
||||
/**
|
||||
* 作品详情「所属主题」增量的单个条目:只够渲染一个跳转入口(主题页里才有完整信息)。
|
||||
*/
|
||||
export type GameDistributionThemeReference = {
|
||||
themeId: string;
|
||||
name: string;
|
||||
badge: string;
|
||||
};
|
||||
|
||||
/**
|
||||
* 公开主题列表的单条:主题名、简介、角标与**当前公开可见成员数**。
|
||||
*
|
||||
* `memberCount` 与详情响应的 `roots` 长度是同一个数(同一份成员可见性判定),不含草稿 /
|
||||
* 已下架成员;服务端不回报「全部成员行数」,客户端也不该用两个数互相推算。
|
||||
*/
|
||||
export type GameDistributionThemeSummary = {
|
||||
themeId: string;
|
||||
name: string;
|
||||
summary: string;
|
||||
badge: string;
|
||||
memberCount: number;
|
||||
};
|
||||
|
||||
/**
|
||||
* 公开主题列表接口(`GET /api/game-distribution/themes`)的响应体。
|
||||
*
|
||||
* 只含 `published` 主题。分页沿用既有游标惯例:默认 20 条、上限 50 条、超界截断;
|
||||
* `nextCursor` 是真实游标(`"{createdAtMicros}:{themeId}"`),最后一页为 `null`;游标格式非法时
|
||||
* 服务端返回 400(不是 200 的空页)。排序为创建时间倒序 + `themeId` 升序兜底(全序,不重不漏)。
|
||||
*/
|
||||
export type GameDistributionThemeListResponse = {
|
||||
themes: GameDistributionThemeSummary[];
|
||||
nextCursor?: string | null;
|
||||
};
|
||||
|
||||
/**
|
||||
* 公开主题详情接口(`GET /api/game-distribution/themes/{themeId}`)的响应体。
|
||||
*
|
||||
* 顶层扁平形状与 `GET /games/{gameId}` 同形,不引入第二套包装。`roots` 逐条是公开目录同一份
|
||||
* 作品投影,排序为 `sortOrder` 升序 + `memberId` 升序兜底。主题不存在或未发布时接口返回 404
|
||||
* (不返回空壳);已发布但可见成员为空时返回 200 + 空 `roots`(展示层按空态而不是错误渲染)。
|
||||
*/
|
||||
export type GameDistributionThemeDetail = {
|
||||
themeId: string;
|
||||
name: string;
|
||||
summary: string;
|
||||
badge: string;
|
||||
memberCount: number;
|
||||
roots: GameDistributionGame[];
|
||||
};
|
||||
|
||||
/**
|
||||
* 后台创建主题(`POST /admin/api/game-distribution/themes`)的请求体。
|
||||
*
|
||||
* `name` 必填且非空;其余字段省略时按「空文本 / 排序 0 / `draft`」落库(与表列默认一致)。
|
||||
*/
|
||||
export type GameDistributionCreateThemeRequest = {
|
||||
name: string;
|
||||
summary?: string;
|
||||
badge?: string;
|
||||
sortOrder?: number;
|
||||
status?: GameDistributionThemeStatus;
|
||||
};
|
||||
|
||||
/**
|
||||
* 后台更新主题(`PUT /admin/api/game-distribution/themes/{themeId}`)的请求体。
|
||||
*
|
||||
* **整体覆盖**这几个字段(不是部分更新):字段都必填,避免「省略 = 保留旧值」与「省略 = 清空」
|
||||
* 两种解释在客户端之间漂移。
|
||||
*/
|
||||
export type GameDistributionUpdateThemeRequest = {
|
||||
name: string;
|
||||
summary: string;
|
||||
badge: string;
|
||||
sortOrder: number;
|
||||
status: GameDistributionThemeStatus;
|
||||
};
|
||||
|
||||
/**
|
||||
* 后台增 / 改成员(`PUT …/themes/{themeId}/members/{rootGameId}`)的请求体。
|
||||
*
|
||||
* 成员身份完全由路径给出,body 只承载运营权重;重复调用由确定性主键保证幂等。
|
||||
*/
|
||||
export type GameDistributionUpsertThemeMemberRequest = {
|
||||
sortOrder?: number;
|
||||
};
|
||||
|
||||
export type GameDistributionCreateGameRequest = {
|
||||
/** 发布方本地项目标识;同一作者重复发布会复用既有 gameId。 */
|
||||
localProjectId?: string | null;
|
||||
|
||||
Reference in New Issue
Block a user