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:
@@ -517,6 +517,27 @@ Responses 的终态载荷既是工具调用的恢复源,也是正文的恢复
|
||||
- 读取口径:`list_game_distribution_collections_and_return` 按 `user_id` 索引取行、按 `created_at` 倒序(同刻按 `game_id` 升序)稳定排序,逐条用**既有** `public_game_distribution_snapshot` 投影,只返回当前公开可读的作品(`module_game_distribution::game_distribution_collection_visible`)。下架 / 软删除的行在**投影时被跳过但不删除**,作品重新公开后自动回到列表。公开详情的 `collected` 由 `is_game_distribution_collected_and_return` 单独查询,不改动既有公开快照契约(`api-server` 在登录时把它并入公开详情负载,匿名不发该键)。
|
||||
- 迁移:随迁移导入导出(用户态事实),作品下架不删除行。`migration_tables!` 已登记该表。
|
||||
|
||||
### `game_distribution_theme`
|
||||
|
||||
- Rust 结构体:`GameDistributionTheme`,源码:`server-rs/crates/spacetime-module/src/game_distribution.rs`;2026-10-06 新增。私有表,仅通过受信 API 服务身份的 procedure 与 BFF 提供投影。
|
||||
- 用途:平台 / 运营**命名**的「共创主题」——一棵(或一组)作品树的归组实体。主题不是作品的属性,而是运营叙事:作品在游戏 Tab 里仍作为独立作品展示,主题只是额外的归组维度。字段:`theme_id: String`(主键,形如 `theme-*`,由服务端签发、不接受客户端指定)、`name`(运营命名,不是根作品标题的派生)、`summary`、`badge`(角标短文本,本轮与 `name` 共同承担「封面」呈现职责,无封面图列)、`sort_order: i64`(成员排序权重)、`status: String`、`created_by_user_id: String`(创建者审计,不参与鉴权判定)、`created_at: Timestamp`、`updated_at: Timestamp`。
|
||||
- 状态口径:`status` 三态白名单 `draft` / `published` / `archived`(`module_game_distribution::GAME_DISTRIBUTION_THEME_STATUSES`);只有 `published` 进入公开投影(`game_distribution_theme_public_visible`),`draft` 与 `archived` 在公开列表 / 公开详情 / 作品详情 `themes` 里都不可见,后台仍可见全量。归档**不是删除**:行与成员行都保留,语义可逆。
|
||||
- 索引:`by_game_distribution_theme_created_at`。公开列表按创建时间倒序 + `theme_id` 升序兜底翻页(游标 `"{created_at_micros}:{theme_id}"`,默认 20 / 上限 50 / 超界截断 / 非法游标 400);`sort_order` 只用于主题内成员排序与后台列表展示,不参与公开列表排序。
|
||||
- 迁移:随迁移导出/导入(运营业务事实);归档只改变公开投影,不删除行。`migration_tables!` 已登记该表。
|
||||
- 写入路径:后台写接口(`create_game_distribution_theme_and_return` / `update_game_distribution_theme_and_return`)属后续里程碑;本轮只落表、生成绑定、迁移登记与契约。
|
||||
|
||||
### `game_distribution_theme_member`
|
||||
|
||||
- Rust 结构体:`GameDistributionThemeMember`,源码:`server-rs/crates/spacetime-module/src/game_distribution.rs`;2026-10-06 新增。私有表。
|
||||
- 用途:「主题 ↔ 根作品」的运营归组事实。字段:`member_id: String`(主键)、`theme_id`、`root_game_id`、`sort_order: i64`、`created_at: Timestamp`。
|
||||
- 只允许**根作品**:根 = 该作品没有血缘行(第 0 代)。判定事实由调用方用血缘点查给出(`game_distribution_lineage().game_id().find(&game_id)` 命中即有行 ⇒ 非根),规则由纯函数 `game_distribution_theme_root_acceptable` 表达;非根写入被拒(`THEME_MEMBER_NOT_ROOT` 409)。理由:主题页呈现的作品树由 `/games/{id}/lineage` **按根**聚合,若允许子作品也入主题,同一棵树会在主题页里出现多次。
|
||||
- 防重:主键是确定性构造的 `{theme_id}:{root_game_id}`(`module_game_distribution::game_distribution_theme_member_id`),同一 (主题, 根) 在结构上不可能出现第二行——去重由主键约束本身承担,而不是「事务里先查后写」。因此**不需要**额外的 `(theme_id, root_game_id)` 唯一索引。跨主题的同一根会有**多行**(每主题一行),这是预期行为而不是重复数据。
|
||||
- 索引:`by_game_distribution_theme_member_theme_id`(主题页按主题取成员)、`by_game_distribution_theme_member_root_game_id`(作品详情按根反查所属公开主题,因此第 N 代作品也能看到自己的主题)。
|
||||
- 可见性:成员只出现在「作品公开未删且存在当前公开版本」时——判定**委托**收藏的同一条口径 `module_game_distribution::game_distribution_theme_member_visible`(内部即 `game_distribution_collection_visible`),不新写第二个同义判定。作品下架 / 软删除时该成员在**投影里跳过但不删除行**,作品重新公开后自动回到主题页;主题详情的 `roots` 与列表的 `memberCount` 用同一判定,因此两个数永远一致。
|
||||
- 排序:主题内成员按 `sort_order` 升序 + `member_id` 升序兜底(`sort_order` 允许重复,兜底键保证全序、翻页/渲染不重不漏);可见性过滤必须发生在排序切页之前。
|
||||
- 迁移:随迁移导出/导入(运营业务事实);归档主题与下架作品都不删除成员行。`migration_tables!` 已登记该表。
|
||||
- 写入路径:后台成员写接口(`upsert_game_distribution_theme_member_and_return` / `remove_game_distribution_theme_member_and_return`)属后续里程碑;本轮只落表、生成绑定、迁移登记与契约。
|
||||
|
||||
### `game_distribution_review`
|
||||
|
||||
- Rust 结构体:`GameDistributionReview`,源码:`server-rs/crates/spacetime-module/src/game_distribution.rs`;私有表,仅通过受信 API 服务身份的 procedure 与 BFF 提供评价投影。
|
||||
|
||||
Reference in New Issue
Block a user