作品公开默认关闭
将各玩法作品可见性默认值改为关闭。 发布与重新发布流程按首次发布和历史已发布状态保留可见性。 收紧公开详情、互动与正式运行态的可见性门禁。 更新后端公开作品契约文档与项目记忆。
This commit is contained in:
@@ -16,6 +16,14 @@
|
||||
|
||||
---
|
||||
|
||||
## 2026-07-03 作品公开默认关闭
|
||||
|
||||
- 背景:作品发布完成不应默认进入公开广场 / 公开详情 / 公开互动消费路径,需要先由后台可见性开关明确开启。
|
||||
- 决策:各玩法源表的 `visible` 新作品默认值改为 `false`;从草稿首次发布时仍保持 `false`,只有已发布作品再次发布 / 更新时才保留既有 `visible`。公开列表、详情、点赞、Remix 和正式公开 runtime 继续按 `Published + visible=true` 判断。旧迁移数据缺少 `visible` 时仍补 `true`,避免历史已公开作品被批量隐藏。
|
||||
- 影响范围:`spacetime-module` 各玩法作品表、发布 / 编译 / Remix 写入路径、统一公开作品 read model、后台作品可见性管理。
|
||||
- 验证方式:运行 `cargo fmt --manifest-path server-rs/Cargo.toml --all`、`cargo check -p spacetime-module --manifest-path server-rs/Cargo.toml`、`npm run check:spacetime-schema`、`npm run check:encoding` 和 `git diff --check`。
|
||||
- 关联文档:`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md`、`docs/technical/【后端架构】统一公开作品ReadModel设计-2026-05-26.md`。
|
||||
|
||||
## 2026-07-03 外部编辑器 API 生成默认写入画布与素材库
|
||||
|
||||
- 背景:外部 API 面向美术 Agent 使用时,需要从自然语言自动选路,并保证生成结果不会只停留在接口回包里;同时后续素材生成需要复用已抽象出的美术规范,避免每次重新追问风格要求。
|
||||
|
||||
@@ -39,7 +39,7 @@
|
||||
- `sort_time_micros`
|
||||
- `detail_payload_json`
|
||||
|
||||
作品源表新增 `visible` 可见性字段,默认 `true`。`visible` 属于源表 / source view 过滤条件,不作为统一公开契约默认返回字段;当 `visible=false` 时,对应作品不得进入 `public_work_gallery_entry` 和 `public_work_detail_entry`。
|
||||
作品源表新增 `visible` 可见性字段,新作品默认 `false`。`visible` 属于源表 / source view 过滤条件,不作为统一公开契约默认返回字段;当 `visible=false` 时,对应作品不得进入 `public_work_gallery_entry` 和 `public_work_detail_entry`。
|
||||
|
||||
其中 `detail_payload_json` 只承载平台详情页展示扩展,不承载正式 runtime 配置、玩法规则或草稿真相。
|
||||
|
||||
@@ -76,7 +76,7 @@
|
||||
- 旧 view 保留,不删除。
|
||||
- 旧 view 退到底层 source / 兼容职责。
|
||||
- 新 `public_work_*` view 是 `api-server` 公开列表 / 详情的统一主读模型。
|
||||
- 各玩法 source view 只暴露 `visible=true` 的已发布作品;旧数据迁移默认补 `visible=true`,避免历史作品被误隐藏。
|
||||
- 各玩法 source view 只暴露 `visible=true` 的已发布作品;旧数据迁移仍补 `visible=true`,避免历史作品被误隐藏;新发布作品默认不公开,需后台开启后才进入公开消费路径。
|
||||
- RPG / 自定义世界旧数据可能缺少 `published_at`。统一公开详情可以用 `updated_at` 作为展示和排序兜底;点赞、游玩、Remix 等写入路径也必须按 `publication_status=Published + visible=true + 未删除` 判断作品存在,不能额外要求 `published_at` 非空。
|
||||
- 临时运行约束:SpacetimeDB 2.2 下抓大鹅 `match_3_d_gallery_view` 的 `publication_status` 索引过滤在源表更新触发统一 view 刷新时可能初始化 panic;为避免后台隐藏作品打爆 module instance,统一 `public_work_*` view 暂不级联抓大鹅 source view,抓大鹅公开入口先保留玩法专用路径。后续应以 source projection 表替代索引 view 后再重新并入统一 read model。
|
||||
- 旧 `/api/runtime/<play>/gallery` 响应 shape 保持兼容,由 BFF mapper 把统一 cache 再映射回当前 DTO。
|
||||
|
||||
@@ -313,7 +313,7 @@ npm run check:server-rs-ddd
|
||||
|
||||
- Rust 结构体:`BarkBattlePublishedConfigRow`
|
||||
- 源码:`server-rs/crates/spacetime-module/src/bark_battle/tables.rs`
|
||||
- 字段变更:`visible` 控制是否进入公开列表 / 详情,默认 `true`;旧迁移数据由 `migration.rs` 补默认值。
|
||||
- 字段变更:`visible` 控制是否进入公开列表 / 详情,新作品默认 `false`;旧迁移数据由 `migration.rs` 按历史公开默认补 `true`。
|
||||
|
||||
### `bark_battle_runtime_run`
|
||||
|
||||
@@ -350,7 +350,7 @@ npm run check:server-rs-ddd
|
||||
- Rust 结构体:`BigFishCreationSession`
|
||||
- 源码:`server-rs/crates/spacetime-module/src/big_fish/tables.rs`
|
||||
- 索引:`by_big_fish_session_owner_user_id`、`by_big_fish_session_stage`。公开广场 view 使用 `by_big_fish_session_stage` 读取已发布会话,避免扫整表。
|
||||
- 字段变更:`visible` 控制是否进入公开列表 / 详情,默认 `true`;旧迁移数据由 `migration.rs` 补默认值。
|
||||
- 字段变更:`visible` 控制是否进入公开列表 / 详情,新作品默认 `false`;旧迁移数据由 `migration.rs` 按历史公开默认补 `true`。
|
||||
|
||||
### `big_fish_event`
|
||||
|
||||
@@ -414,13 +414,13 @@ npm run check:server-rs-ddd
|
||||
- Rust 结构体:`CustomWorldGalleryEntry`
|
||||
- 源码:`server-rs/crates/spacetime-module/src/custom_world.rs`
|
||||
- 作用:自定义世界公开 source 读模型。统一公开列表 / 详情主路径通过 `public_work_gallery_entry` / `public_work_detail_entry` 消费该投影并映射成跨玩法契约;`/api/runtime/custom-world-gallery` 保留旧 HTTP shape,并从统一 public cache 映射回旧 DTO。旧 procedure 只用于兼容旧库缺少 gallery 读模型行时的一次性同步兜底。
|
||||
- 字段变更:`visible` 控制是否进入公开列表 / 详情,默认 `true`;旧迁移数据由 `migration.rs` 补默认值。
|
||||
- 字段变更:`visible` 控制是否进入公开列表 / 详情,新作品默认 `false`;旧迁移数据由 `migration.rs` 按历史公开默认补 `true`。
|
||||
|
||||
### `custom_world_profile`
|
||||
|
||||
- Rust 结构体:`CustomWorldProfile`
|
||||
- 源码:`server-rs/crates/spacetime-module/src/custom_world.rs`
|
||||
- 字段变更:`visible` 控制是否进入公开列表 / 详情,默认 `true`;旧迁移数据由 `migration.rs` 补默认值。
|
||||
- 字段变更:`visible` 控制是否进入公开列表 / 详情,新作品默认 `false`;旧迁移数据由 `migration.rs` 按历史公开默认补 `true`。
|
||||
- 兼容约束:历史公开 RPG / 自定义世界 profile 可能存在 `publication_status=Published` 但 `published_at=None`。公开详情、点赞、游玩、Remix 和 `custom_world_gallery_entry` 同步都以 `Published + deleted_at=None + visible=true` 判断作品可公开互动;展示和 gallery 同步时间在 `published_at` 缺失时回退 `updated_at`,不得仅因 `published_at` 为空返回“已发布作品不存在”。
|
||||
|
||||
### `custom_world_session`
|
||||
@@ -527,7 +527,7 @@ npm run check:server-rs-ddd
|
||||
- 返回类型:`Vec<JumpHopGalleryViewRow>`
|
||||
- 源码:`server-rs/crates/spacetime-module/src/jump_hop.rs`
|
||||
- 说明:跳一跳公开详情兼容投影,包含作品、路径和素材字段;统一公开详情主路径通过 `public_work_detail_entry` 消费该 view,只保留平台详情页展示摘要。
|
||||
- 字段变更:`visible` 控制是否进入公开列表 / 详情,默认 `true`;旧迁移数据由 `migration.rs` 补默认值。
|
||||
- 字段变更:`visible` 控制是否进入公开列表 / 详情,新作品默认 `false`;旧迁移数据由 `migration.rs` 按历史公开默认补 `true`。
|
||||
|
||||
### `wooden_fish_agent_session`
|
||||
|
||||
@@ -563,7 +563,7 @@ npm run check:server-rs-ddd
|
||||
- 返回类型:`Vec<WoodenFishGalleryViewRow>`
|
||||
- 源码:`server-rs/crates/spacetime-module/src/wooden_fish.rs`
|
||||
- 说明:敲木鱼公开详情兼容投影,包含敲击物图案、背景环境图、主题返回按钮图、敲击音效和飘字配置;统一公开详情主路径通过 `public_work_detail_entry` 消费该 view,只保留平台详情页展示摘要。
|
||||
- 字段变更:`visible` 控制是否进入公开列表 / 详情,默认 `true`;旧迁移数据由 `migration.rs` 补默认值。
|
||||
- 字段变更:`visible` 控制是否进入公开列表 / 详情,新作品默认 `false`;旧迁移数据由 `migration.rs` 按历史公开默认补 `true`。
|
||||
|
||||
### `match3d_agent_message`
|
||||
|
||||
@@ -593,7 +593,7 @@ npm run check:server-rs-ddd
|
||||
- 返回类型:`Vec<Match3DGalleryViewRow>`
|
||||
- 源码:`server-rs/crates/spacetime-module/src/match3d.rs`
|
||||
- 说明:抓大鹅公开 source 投影,只暴露 `publication_status = published` 的作品卡片字段;统一公开列表 / 详情主路径通过 `public_work_gallery_entry` / `public_work_detail_entry` 消费该 view 并映射成跨玩法契约。个人作品列表、详情、发布、点赞、游玩记录和 Remix 仍按原有 procedure / reducer 路径处理。
|
||||
- 字段变更:`visible` 控制是否进入公开列表 / 详情,默认 `true`;旧迁移数据由 `migration.rs` 补默认值。
|
||||
- 字段变更:`visible` 控制是否进入公开列表 / 详情,新作品默认 `false`;旧迁移数据由 `migration.rs` 按历史公开默认补 `true`。
|
||||
|
||||
### `npc_state`
|
||||
|
||||
@@ -740,7 +740,7 @@ npm run check:server-rs-ddd
|
||||
- Rust 结构体:`PuzzleWorkProfileRow`
|
||||
- 源码:`server-rs/crates/spacetime-module/src/puzzle.rs`
|
||||
- 说明:拼图作品 profile 表,保存草稿 / 已发布作品的标题、作者、关卡、封面、发布状态、可见性、基础游玩数、点赞数、改造数和积分激励领取状态。
|
||||
- 字段变更:`visible` 控制是否进入公开列表 / 详情、通关后的推荐下一作品候选、公开点赞 / Remix 和正式公开 runtime;默认 `true`。后台隐藏后作品可保留 `publication_status = Published`,但公开消费路径必须按 `Published + visible=true` 判断。
|
||||
- 字段变更:`visible` 控制是否进入公开列表 / 详情、通关后的推荐下一作品候选、公开点赞 / Remix 和正式公开 runtime;新作品默认 `false`,旧迁移数据按历史公开默认补 `true`。后台隐藏后作品可保留 `publication_status = Published`,但公开消费路径必须按 `Published + visible=true` 判断。
|
||||
|
||||
### `puzzle_clear_agent_session`
|
||||
|
||||
@@ -753,7 +753,7 @@ npm run check:server-rs-ddd
|
||||
- Rust 结构体:`PuzzleClearWorkProfileRow`
|
||||
- 源码:`server-rs/crates/spacetime-module/src/puzzle_clear/tables.rs`
|
||||
- 说明:拼消消作品 profile 表,保存中央底图资产、4 张素材工作表切片后合成的最终 atlas、35 个复合图案组、95 个 1x1 卡牌切片、卡背占位图、发布状态、可见性和基础 play count;公开列表 / 详情只通过 read model 消费,不让前端直接订阅源表。
|
||||
- 字段变更:`visible` 控制是否进入公开列表 / 详情,默认 `true`;旧迁移数据由 `migration.rs` 补默认值。
|
||||
- 字段变更:`visible` 控制是否进入公开列表 / 详情,新作品默认 `false`;旧迁移数据由 `migration.rs` 按历史公开默认补 `true`。
|
||||
|
||||
### `puzzle_clear_runtime_run`
|
||||
|
||||
@@ -842,7 +842,7 @@ RPG 创作入口的配置 ID 是 `rpg`,当前 `visible=true`、`open=true`;
|
||||
结构化创作和 RPG 的 LLM JSON 链路默认不启用 Responses `web_search`;只有在明确需要联网增强时,才通过 `GENARRATIVE_RPG_LLM_WEB_SEARCH_ENABLED` 或 `GENARRATIVE_CREATION_AGENT_LLM_WEB_SEARCH_ENABLED` 显式打开。否则未开通工具的上游会先吐自然语言再返回 `ToolNotOpen`,这类失败要按上游工具不可用处理,不要误判成模型返回结果解析失败。
|
||||
|
||||
统一公开作品 BFF 路由是 `GET /api/public-works` 与 `GET /api/public-works/{publicWorkCode}`,响应契约由 `shared-contracts::public_work` 和 `packages/shared/src/contracts/publicWork.ts` 共同维护。前端首期仍走 BFF HTTP,不直接订阅 SpacetimeDB;后续若允许浏览器直连订阅,也只能订阅 `public_work_gallery_entry` / `public_work_detail_entry` 这类稳定公开 read model,不能订阅 `puzzle_work_profile`、`custom_world_profile` 等源表后自行拼装列表。设计细节见 `docs/technical/【后端架构】统一公开作品ReadModel设计-2026-05-26.md`。
|
||||
- 字段变更:`visible` 控制是否进入公开列表 / 详情,默认 `true`;旧迁移数据由 `migration.rs` 补默认值。
|
||||
- 字段变更:`visible` 控制是否进入公开列表 / 详情,新作品默认 `false`;旧迁移数据由 `migration.rs` 按历史公开默认补 `true`。
|
||||
|
||||
### `quest_log`
|
||||
|
||||
@@ -895,7 +895,7 @@ RPG 创作入口的配置 ID 是 `rpg`,当前 `visible=true`、`open=true`;
|
||||
- 返回类型:`Vec<SquareHoleGalleryViewRow>`
|
||||
- 源码:`server-rs/crates/spacetime-module/src/square_hole.rs`
|
||||
- 说明:方洞挑战公开 source 投影,只暴露 `publication_status = published` 的作品卡片字段;统一公开列表 / 详情主路径通过 `public_work_gallery_entry` / `public_work_detail_entry` 消费该 view 并映射成跨玩法契约。个人作品列表、详情、发布、点赞、游玩记录和 Remix 仍按原有 procedure / reducer 路径处理。
|
||||
- 字段变更:`visible` 控制是否进入公开列表 / 详情,默认 `true`;旧迁移数据由 `migration.rs` 补默认值。
|
||||
- 字段变更:`visible` 控制是否进入公开列表 / 详情,新作品默认 `false`;旧迁移数据由 `migration.rs` 按历史公开默认补 `true`。
|
||||
|
||||
### `story_event`
|
||||
|
||||
@@ -972,4 +972,4 @@ RPG 创作入口的配置 ID 是 `rpg`,当前 `visible=true`、`open=true`;
|
||||
- 返回类型:`Vec<VisualNovelGalleryViewRow>`
|
||||
- 源码:`server-rs/crates/spacetime-module/src/visual_novel.rs`
|
||||
- 说明:视觉小说公开 source 投影,只暴露 `publication_status = published` 的作品卡片字段,不把完整 `draft` 暴露给公开列表订阅;统一公开列表 / 详情主路径通过 `public_work_gallery_entry` / `public_work_detail_entry` 消费该 view 并映射成跨玩法契约。个人历史、详情、运行态和发布仍按原有 procedure / reducer 路径处理。
|
||||
- 字段变更:`visible` 控制是否进入公开列表 / 详情,默认 `true`;旧迁移数据由 `migration.rs` 补默认值。
|
||||
- 字段变更:`visible` 控制是否进入公开列表 / 详情,新作品默认 `false`;旧迁移数据由 `migration.rs` 按历史公开默认补 `true`。
|
||||
|
||||
@@ -272,7 +272,7 @@ fn publish_bark_battle_work_tx(
|
||||
created_at: published_at,
|
||||
updated_at: published_at,
|
||||
published_at,
|
||||
visible: true,
|
||||
visible: WORK_VISIBLE_DEFAULT,
|
||||
};
|
||||
let mut published = published;
|
||||
match ctx
|
||||
@@ -283,6 +283,7 @@ fn publish_bark_battle_work_tx(
|
||||
{
|
||||
Some(existing) => {
|
||||
published.created_at = existing.created_at;
|
||||
published.visible = existing.visible;
|
||||
ctx.db
|
||||
.bark_battle_published_config()
|
||||
.work_id()
|
||||
@@ -413,11 +414,20 @@ fn get_bark_battle_runtime_config_tx(
|
||||
.work_id()
|
||||
.find(&input.work_id)
|
||||
.ok_or_else(|| "bark_battle published config 不存在".to_string())?;
|
||||
let requester_is_owner = input
|
||||
.owner_user_id
|
||||
.as_deref()
|
||||
.map(str::trim)
|
||||
.filter(|owner_user_id| !owner_user_id.is_empty())
|
||||
.is_some_and(|owner_user_id| owner_user_id == row.owner_user_id);
|
||||
if let Some(owner_user_id) = input.owner_user_id.as_deref() {
|
||||
if !owner_user_id.trim().is_empty() && row.owner_user_id != owner_user_id.trim() {
|
||||
if !owner_user_id.trim().is_empty() && !requester_is_owner {
|
||||
return Err("bark_battle runtime config owner 不匹配".to_string());
|
||||
}
|
||||
}
|
||||
if !requester_is_owner && !row.visible {
|
||||
return Err("bark_battle runtime config 只开放已公开作品".to_string());
|
||||
}
|
||||
Ok(runtime_config_snapshot(&row))
|
||||
}
|
||||
|
||||
@@ -435,6 +445,9 @@ fn start_bark_battle_run_tx(
|
||||
.work_id()
|
||||
.find(&input.work_id)
|
||||
.ok_or_else(|| "bark_battle published config 不存在".to_string())?;
|
||||
if published.owner_user_id != input.owner_user_id && !published.visible {
|
||||
return Err("bark_battle run 只能启动已公开作品".to_string());
|
||||
}
|
||||
if published.config_version != input.config_version
|
||||
|| published.ruleset_version != input.ruleset_version
|
||||
|| published.difficulty_preset != input.difficulty_preset
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use crate::*;
|
||||
|
||||
const WORK_VISIBLE_DEFAULT: bool = true;
|
||||
pub(super) const WORK_VISIBLE_DEFAULT: bool = false;
|
||||
|
||||
#[spacetimedb::table(
|
||||
accessor = bark_battle_draft_config,
|
||||
@@ -42,7 +42,7 @@ pub struct BarkBattlePublishedConfigRow {
|
||||
pub(crate) created_at: Timestamp,
|
||||
pub(crate) updated_at: Timestamp,
|
||||
pub(crate) published_at: Timestamp,
|
||||
// 后台可见性开关;默认显示,隐藏后不进入公开列表。
|
||||
// 后台可见性开关;新作品默认不公开,开启后才进入公开列表。
|
||||
#[default(WORK_VISIBLE_DEFAULT)]
|
||||
pub(crate) visible: bool,
|
||||
}
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
use crate::big_fish::tables::{big_fish_asset_slot, big_fish_creation_session};
|
||||
use crate::big_fish::tables::{
|
||||
WORK_VISIBLE_DEFAULT, big_fish_asset_slot, big_fish_creation_session,
|
||||
};
|
||||
use crate::*;
|
||||
use module_big_fish::{EvaluateBigFishPublishReadinessCommand, evaluate_publish_readiness};
|
||||
|
||||
@@ -129,7 +131,13 @@ pub(crate) fn generate_big_fish_asset_tx(
|
||||
published_at: session.published_at,
|
||||
created_at: session.created_at,
|
||||
updated_at,
|
||||
visible: session.visible,
|
||||
visible: if session.stage == BigFishCreationStage::Published
|
||||
|| session.published_at.is_some()
|
||||
{
|
||||
session.visible
|
||||
} else {
|
||||
WORK_VISIBLE_DEFAULT
|
||||
},
|
||||
};
|
||||
replace_big_fish_session(ctx, &session, next_session);
|
||||
for event in readiness.events {
|
||||
@@ -201,7 +209,13 @@ pub(crate) fn publish_big_fish_game_tx(
|
||||
published_at: Some(published_at),
|
||||
created_at: session.created_at,
|
||||
updated_at: published_at,
|
||||
visible: session.visible,
|
||||
visible: if session.stage == BigFishCreationStage::Published
|
||||
|| session.published_at.is_some()
|
||||
{
|
||||
session.visible
|
||||
} else {
|
||||
WORK_VISIBLE_DEFAULT
|
||||
},
|
||||
};
|
||||
replace_big_fish_session(ctx, &session, next_session);
|
||||
for event in readiness.events {
|
||||
|
||||
@@ -154,7 +154,7 @@ fn ensure_big_fish_session_playable(
|
||||
if session.owner_user_id == player_user_id {
|
||||
return Ok(());
|
||||
}
|
||||
if session.stage == BigFishCreationStage::Published {
|
||||
if session.stage == BigFishCreationStage::Published && session.visible {
|
||||
return Ok(());
|
||||
}
|
||||
Err("未发布的大鱼吃小鱼作品不允许非作者启动运行态".to_string())
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use crate::big_fish::tables::{
|
||||
big_fish_agent_message, big_fish_creation_session, big_fish_runtime_run,
|
||||
WORK_VISIBLE_DEFAULT, big_fish_agent_message, big_fish_creation_session, big_fish_runtime_run,
|
||||
};
|
||||
use crate::runtime::{
|
||||
ProfilePlayedWorkUpsertInput, PublicWorkLikeRecordInput, PublicWorkPlayRecordInput,
|
||||
@@ -285,7 +285,7 @@ pub(crate) fn create_big_fish_session_tx(
|
||||
published_at: None,
|
||||
created_at,
|
||||
updated_at: created_at,
|
||||
visible: true,
|
||||
visible: WORK_VISIBLE_DEFAULT,
|
||||
});
|
||||
ctx.db.big_fish_agent_message().insert(BigFishAgentMessage {
|
||||
message_id: input.welcome_message_id,
|
||||
@@ -338,7 +338,7 @@ pub(crate) fn list_big_fish_works_tx(
|
||||
.iter()
|
||||
.filter(|row| {
|
||||
if input.published_only {
|
||||
return row.stage == BigFishCreationStage::Published;
|
||||
return row.stage == BigFishCreationStage::Published && row.visible;
|
||||
}
|
||||
|
||||
should_include_big_fish_work(ctx, row)
|
||||
@@ -698,7 +698,7 @@ pub(crate) fn record_big_fish_play_tx(
|
||||
.big_fish_creation_session()
|
||||
.session_id()
|
||||
.find(&input.session_id)
|
||||
.filter(|row| row.stage == BigFishCreationStage::Published)
|
||||
.filter(|row| row.stage == BigFishCreationStage::Published && row.visible)
|
||||
.ok_or_else(|| "big_fish 已发布作品不存在".to_string())?;
|
||||
let played_at = Timestamp::from_micros_since_unix_epoch(input.played_at_micros);
|
||||
let draft = session
|
||||
@@ -795,7 +795,7 @@ pub(crate) fn record_big_fish_like_tx(
|
||||
.big_fish_creation_session()
|
||||
.session_id()
|
||||
.find(&session_id.to_string())
|
||||
.filter(|row| row.stage == BigFishCreationStage::Published)
|
||||
.filter(|row| row.stage == BigFishCreationStage::Published && row.visible)
|
||||
.ok_or_else(|| "big_fish 已发布作品不存在,无法点赞".to_string())?;
|
||||
let inserted_like = record_public_work_like(
|
||||
ctx,
|
||||
@@ -875,7 +875,7 @@ fn remix_big_fish_work_tx(
|
||||
.big_fish_creation_session()
|
||||
.session_id()
|
||||
.find(&source_session_id.to_string())
|
||||
.filter(|row| row.stage == BigFishCreationStage::Published)
|
||||
.filter(|row| row.stage == BigFishCreationStage::Published && row.visible)
|
||||
.ok_or_else(|| "big_fish 已发布源作品不存在".to_string())?;
|
||||
let remixed_at = Timestamp::from_micros_since_unix_epoch(input.remixed_at_micros);
|
||||
let next_source = BigFishCreationSession {
|
||||
@@ -918,7 +918,7 @@ fn remix_big_fish_work_tx(
|
||||
published_at: None,
|
||||
created_at: remixed_at,
|
||||
updated_at: remixed_at,
|
||||
visible: true,
|
||||
visible: WORK_VISIBLE_DEFAULT,
|
||||
};
|
||||
ctx.db.big_fish_creation_session().insert(target_session);
|
||||
ctx.db.big_fish_agent_message().insert(BigFishAgentMessage {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use crate::*;
|
||||
|
||||
const WORK_VISIBLE_DEFAULT: bool = true;
|
||||
pub(super) const WORK_VISIBLE_DEFAULT: bool = false;
|
||||
|
||||
#[spacetimedb::table(
|
||||
accessor = big_fish_creation_session,
|
||||
@@ -30,7 +30,7 @@ pub struct BigFishCreationSession {
|
||||
pub(crate) like_count: u32,
|
||||
#[default(None::<Timestamp>)]
|
||||
pub(crate) published_at: Option<Timestamp>,
|
||||
// 后台可见性开关;默认显示,隐藏后不进入公开列表。
|
||||
// 后台可见性开关;新作品默认不公开,开启后才进入公开列表。
|
||||
#[default(WORK_VISIBLE_DEFAULT)]
|
||||
pub(crate) visible: bool,
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ use crate::*;
|
||||
use spacetimedb::AnonymousViewContext;
|
||||
use std::collections::{HashMap, HashSet};
|
||||
|
||||
const WORK_VISIBLE_DEFAULT: bool = true;
|
||||
const WORK_VISIBLE_DEFAULT: bool = false;
|
||||
|
||||
#[spacetimedb::table(
|
||||
accessor = custom_world_profile,
|
||||
@@ -44,7 +44,7 @@ pub struct CustomWorldProfile {
|
||||
pub(crate) deleted_at: Option<Timestamp>,
|
||||
pub(crate) created_at: Timestamp,
|
||||
pub(crate) updated_at: Timestamp,
|
||||
// 后台可见性开关;默认显示,隐藏后不进入公开列表。
|
||||
// 后台可见性开关;新作品默认不公开,开启后才进入公开列表。
|
||||
#[default(WORK_VISIBLE_DEFAULT)]
|
||||
pub(crate) visible: bool,
|
||||
}
|
||||
@@ -197,7 +197,7 @@ pub struct CustomWorldGalleryEntry {
|
||||
pub(crate) like_count: u32,
|
||||
pub(crate) published_at: Timestamp,
|
||||
pub(crate) updated_at: Timestamp,
|
||||
// 后台可见性开关;默认显示,隐藏后不进入公开列表。
|
||||
// 后台可见性开关;新作品默认不公开,开启后才进入公开列表。
|
||||
#[default(WORK_VISIBLE_DEFAULT)]
|
||||
pub(crate) visible: bool,
|
||||
}
|
||||
@@ -1210,6 +1210,8 @@ fn upsert_custom_world_profile_record(
|
||||
|
||||
let next_row = match current {
|
||||
Some(existing) => {
|
||||
let existing_was_published =
|
||||
existing.publication_status == CustomWorldPublicationStatus::Published;
|
||||
ctx.db
|
||||
.custom_world_profile()
|
||||
.profile_id()
|
||||
@@ -1237,7 +1239,11 @@ fn upsert_custom_world_profile_record(
|
||||
deleted_at: None,
|
||||
created_at: existing.created_at,
|
||||
updated_at,
|
||||
visible: existing.visible,
|
||||
visible: if existing_was_published {
|
||||
existing.visible
|
||||
} else {
|
||||
WORK_VISIBLE_DEFAULT
|
||||
},
|
||||
}
|
||||
}
|
||||
None => CustomWorldProfile {
|
||||
@@ -1263,7 +1269,7 @@ fn upsert_custom_world_profile_record(
|
||||
deleted_at: None,
|
||||
created_at: updated_at,
|
||||
updated_at,
|
||||
visible: true,
|
||||
visible: WORK_VISIBLE_DEFAULT,
|
||||
},
|
||||
};
|
||||
|
||||
@@ -1379,6 +1385,8 @@ fn publish_custom_world_profile_record(
|
||||
.ok_or_else(|| "custom_world_profile 不存在,无法发布".to_string())?;
|
||||
|
||||
let published_at = Timestamp::from_micros_since_unix_epoch(input.published_at_micros);
|
||||
let existing_was_published =
|
||||
existing.publication_status == CustomWorldPublicationStatus::Published;
|
||||
|
||||
ctx.db
|
||||
.custom_world_profile()
|
||||
@@ -1411,7 +1419,11 @@ fn publish_custom_world_profile_record(
|
||||
deleted_at: None,
|
||||
created_at: existing.created_at,
|
||||
updated_at: published_at,
|
||||
visible: existing.visible,
|
||||
visible: if existing_was_published {
|
||||
existing.visible
|
||||
} else {
|
||||
WORK_VISIBLE_DEFAULT
|
||||
},
|
||||
};
|
||||
|
||||
let inserted = ctx.db.custom_world_profile().insert(next_row);
|
||||
@@ -1813,7 +1825,7 @@ fn remix_custom_world_profile_record(
|
||||
deleted_at: None,
|
||||
created_at: remixed_at,
|
||||
updated_at: remixed_at,
|
||||
visible: true,
|
||||
visible: WORK_VISIBLE_DEFAULT,
|
||||
};
|
||||
|
||||
if let Some(existing_target) = ctx
|
||||
@@ -2597,7 +2609,6 @@ fn is_same_agent_draft_profile_candidate(
|
||||
) -> bool {
|
||||
row.owner_user_id == owner_user_id
|
||||
&& row.deleted_at.is_none()
|
||||
&& row.visible
|
||||
&& row.publication_status == CustomWorldPublicationStatus::Draft
|
||||
&& row.source_agent_session_id.as_deref() == Some(source_agent_session_id)
|
||||
}
|
||||
|
||||
@@ -422,6 +422,18 @@ fn compile_jump_hop_draft_tx(
|
||||
.unwrap_or_else(|| JUMP_HOP_GENERATION_READY.to_string()),
|
||||
};
|
||||
let compiled_at = Timestamp::from_micros_since_unix_epoch(input.compiled_at_micros);
|
||||
let previous_published_work = ctx
|
||||
.db
|
||||
.jump_hop_work_profile()
|
||||
.profile_id()
|
||||
.find(&input.profile_id)
|
||||
.filter(|row| row.owner_user_id == input.owner_user_id)
|
||||
.filter(|row| row.publication_status == JUMP_HOP_PUBLICATION_PUBLISHED);
|
||||
let previous_visible = previous_published_work
|
||||
.as_ref()
|
||||
.map(|row| row.visible)
|
||||
.unwrap_or(WORK_VISIBLE_DEFAULT);
|
||||
let previous_published_at = previous_published_work.and_then(|row| row.published_at);
|
||||
let row = JumpHopWorkProfileRow {
|
||||
profile_id: input.profile_id.clone(),
|
||||
work_id: input.profile_id.clone(),
|
||||
@@ -455,8 +467,8 @@ fn compile_jump_hop_draft_tx(
|
||||
publication_status: JUMP_HOP_PUBLICATION_DRAFT.to_string(),
|
||||
play_count: 0,
|
||||
updated_at: compiled_at,
|
||||
published_at: None,
|
||||
visible: true,
|
||||
published_at: previous_published_at,
|
||||
visible: previous_visible,
|
||||
theme_text: Some(draft.theme_text.clone()),
|
||||
};
|
||||
upsert_work(ctx, row);
|
||||
@@ -542,6 +554,13 @@ fn publish_jump_hop_work_tx(
|
||||
publication_status: JUMP_HOP_PUBLICATION_PUBLISHED.to_string(),
|
||||
updated_at: published_at,
|
||||
published_at: Some(published_at),
|
||||
visible: if row.publication_status == JUMP_HOP_PUBLICATION_PUBLISHED
|
||||
|| row.published_at.is_some()
|
||||
{
|
||||
row.visible
|
||||
} else {
|
||||
WORK_VISIBLE_DEFAULT
|
||||
},
|
||||
..clone_work(&row)
|
||||
},
|
||||
);
|
||||
@@ -652,10 +671,12 @@ fn start_jump_hop_run_tx(
|
||||
if runtime_mode == JUMP_HOP_RUNTIME_MODE_DRAFT && work.owner_user_id != input.owner_user_id {
|
||||
return Err("jump_hop draft runtime 只能由作品所有者启动".to_string());
|
||||
}
|
||||
if runtime_mode == JUMP_HOP_RUNTIME_MODE_PUBLISHED
|
||||
&& work.publication_status != JUMP_HOP_PUBLICATION_PUBLISHED
|
||||
{
|
||||
return Err("jump_hop published runtime 只能启动已发布作品".to_string());
|
||||
if runtime_mode == JUMP_HOP_RUNTIME_MODE_PUBLISHED {
|
||||
let visible_to_player = work.publication_status == JUMP_HOP_PUBLICATION_PUBLISHED
|
||||
&& (work.visible || work.owner_user_id == input.owner_user_id);
|
||||
if !visible_to_player {
|
||||
return Err("jump_hop published runtime 只能启动已公开作品".to_string());
|
||||
}
|
||||
}
|
||||
let path = parse_json::<JumpHopPath>(&work.path_json)?;
|
||||
let domain_run = start_run(
|
||||
@@ -743,8 +764,8 @@ fn get_jump_hop_leaderboard_tx(
|
||||
> {
|
||||
require_non_empty(&input.profile_id, "jump_hop profile_id")?;
|
||||
let work = find_work(ctx, &input.profile_id)?;
|
||||
if work.publication_status != JUMP_HOP_PUBLICATION_PUBLISHED {
|
||||
return Err("jump_hop leaderboard 只开放已发布作品".to_string());
|
||||
if work.publication_status != JUMP_HOP_PUBLICATION_PUBLISHED || !work.visible {
|
||||
return Err("jump_hop leaderboard 只开放已公开作品".to_string());
|
||||
}
|
||||
let limit = input.limit.clamp(1, 50) as usize;
|
||||
let mut rows = ctx
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use crate::*;
|
||||
|
||||
const WORK_VISIBLE_DEFAULT: bool = true;
|
||||
pub(super) const WORK_VISIBLE_DEFAULT: bool = false;
|
||||
|
||||
#[spacetimedb::table(
|
||||
accessor = jump_hop_agent_session,
|
||||
@@ -53,7 +53,7 @@ pub struct JumpHopWorkProfileRow {
|
||||
pub(crate) play_count: u32,
|
||||
pub(crate) updated_at: Timestamp,
|
||||
pub(crate) published_at: Option<Timestamp>,
|
||||
// 后台可见性开关;默认显示,隐藏后不进入公开列表。
|
||||
// 后台可见性开关;新作品默认不公开,开启后才进入公开列表。
|
||||
#[default(WORK_VISIBLE_DEFAULT)]
|
||||
pub(crate) visible: bool,
|
||||
// 跳一跳生成主题独立于作品标题;旧行按 work_title 兜底。
|
||||
|
||||
@@ -541,6 +541,11 @@ fn compile_match3d_draft_tx(
|
||||
.map(|work| work.play_count)
|
||||
.unwrap_or(0);
|
||||
let previous_published_at = existing_work.as_ref().and_then(|work| work.published_at);
|
||||
let previous_visible = existing_work
|
||||
.as_ref()
|
||||
.filter(|work| work.publication_status == MATCH3D_PUBLICATION_PUBLISHED)
|
||||
.map(|work| work.visible)
|
||||
.unwrap_or(WORK_VISIBLE_DEFAULT);
|
||||
let cover_image_src = resolve_compile_optional_text(
|
||||
&input.cover_image_src,
|
||||
existing_work
|
||||
@@ -572,7 +577,7 @@ fn compile_match3d_draft_tx(
|
||||
updated_at: compiled_at,
|
||||
published_at: previous_published_at,
|
||||
generated_item_assets_json,
|
||||
visible: true,
|
||||
visible: previous_visible,
|
||||
};
|
||||
upsert_work(ctx, work);
|
||||
replace_session(
|
||||
@@ -661,6 +666,13 @@ fn publish_match3d_work_tx(
|
||||
publication_status: MATCH3D_PUBLICATION_PUBLISHED.to_string(),
|
||||
updated_at: published_at,
|
||||
published_at: Some(published_at),
|
||||
visible: if current.publication_status == MATCH3D_PUBLICATION_PUBLISHED
|
||||
|| current.published_at.is_some()
|
||||
{
|
||||
current.visible
|
||||
} else {
|
||||
WORK_VISIBLE_DEFAULT
|
||||
},
|
||||
..clone_work(¤t)
|
||||
};
|
||||
let snapshot = build_work_snapshot(&next)?;
|
||||
@@ -731,7 +743,7 @@ fn get_match3d_work_detail_tx(
|
||||
.find(&input.profile_id)
|
||||
.filter(|row| {
|
||||
row.owner_user_id == input.owner_user_id
|
||||
|| row.publication_status == MATCH3D_PUBLICATION_PUBLISHED
|
||||
|| (row.publication_status == MATCH3D_PUBLICATION_PUBLISHED && row.visible)
|
||||
})
|
||||
.ok_or_else(|| "match_3_d_work_profile 不存在".to_string())?;
|
||||
build_work_snapshot(&row)
|
||||
@@ -786,7 +798,7 @@ fn start_match3d_run_tx(
|
||||
.find(&input.profile_id)
|
||||
.filter(|row| {
|
||||
row.owner_user_id == input.owner_user_id
|
||||
|| row.publication_status == MATCH3D_PUBLICATION_PUBLISHED
|
||||
|| (row.publication_status == MATCH3D_PUBLICATION_PUBLISHED && row.visible)
|
||||
})
|
||||
.ok_or_else(|| "match_3_d_work_profile 不存在".to_string())?;
|
||||
let started_at_ms = if input.started_at_ms > 0 {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use crate::*;
|
||||
|
||||
const WORK_VISIBLE_DEFAULT: bool = true;
|
||||
pub(super) const WORK_VISIBLE_DEFAULT: bool = false;
|
||||
|
||||
#[spacetimedb::table(
|
||||
accessor = match3d_agent_session,
|
||||
@@ -62,7 +62,7 @@ pub struct Match3DWorkProfileRow {
|
||||
pub(crate) published_at: Option<Timestamp>,
|
||||
#[default(None::<String>)]
|
||||
pub(crate) generated_item_assets_json: Option<String>,
|
||||
// 后台可见性开关;默认显示,隐藏后不进入公开列表。
|
||||
// 后台可见性开关;新作品默认不公开,开启后才进入公开列表。
|
||||
#[default(WORK_VISIBLE_DEFAULT)]
|
||||
pub(crate) visible: bool,
|
||||
}
|
||||
|
||||
@@ -1272,7 +1272,7 @@ fn normalize_migration_row(table_name: &str, value: &serde_json::Value) -> serde
|
||||
}
|
||||
if table_name == "custom_world_profile" || table_name == "custom_world_gallery_entry" {
|
||||
if let Some(object) = next_value.as_object_mut() {
|
||||
// 中文注释:作品可见性字段晚于首版作品表加入,旧迁移包按默认显示兼容。
|
||||
// 中文注释:作品可见性字段晚于首版作品表加入,旧迁移包保留历史公开默认。
|
||||
object
|
||||
.entry("visible".to_string())
|
||||
.or_insert_with(|| serde_json::Value::Bool(true));
|
||||
@@ -1290,7 +1290,7 @@ fn normalize_migration_row(table_name: &str, value: &serde_json::Value) -> serde
|
||||
}
|
||||
if table_name == "puzzle_work_profile" {
|
||||
if let Some(object) = next_value.as_object_mut() {
|
||||
// 中文注释:作品可见性字段晚于首版作品表加入,旧迁移包按默认显示兼容。
|
||||
// 中文注释:作品可见性字段晚于首版作品表加入,旧迁移包保留历史公开默认。
|
||||
object
|
||||
.entry("visible".to_string())
|
||||
.or_insert_with(|| serde_json::Value::Bool(true));
|
||||
@@ -1333,7 +1333,7 @@ fn normalize_migration_row(table_name: &str, value: &serde_json::Value) -> serde
|
||||
}
|
||||
if table_name == "big_fish_creation_session" {
|
||||
if let Some(object) = next_value.as_object_mut() {
|
||||
// 中文注释:作品可见性字段晚于大鱼吃小鱼创作会话表加入,旧迁移包按默认显示兼容。
|
||||
// 中文注释:作品可见性字段晚于大鱼吃小鱼创作会话表加入,旧迁移包保留历史公开默认。
|
||||
object
|
||||
.entry("visible".to_string())
|
||||
.or_insert_with(|| serde_json::Value::Bool(true));
|
||||
@@ -1348,7 +1348,7 @@ fn normalize_migration_row(table_name: &str, value: &serde_json::Value) -> serde
|
||||
| "bark_battle_published_config"
|
||||
) {
|
||||
if let Some(object) = next_value.as_object_mut() {
|
||||
// 中文注释:作品可见性字段晚于首版作品表加入,旧迁移包按默认显示兼容。
|
||||
// 中文注释:作品可见性字段晚于首版作品表加入,旧迁移包保留历史公开默认。
|
||||
object
|
||||
.entry("visible".to_string())
|
||||
.or_insert_with(|| serde_json::Value::Bool(true));
|
||||
@@ -1368,7 +1368,7 @@ fn normalize_migration_row(table_name: &str, value: &serde_json::Value) -> serde
|
||||
}
|
||||
if table_name == "match_3_d_work_profile" || table_name == "match3d_work_profile" {
|
||||
if let Some(object) = next_value.as_object_mut() {
|
||||
// 中文注释:作品可见性字段晚于首版作品表加入,旧迁移包按默认显示兼容。
|
||||
// 中文注释:作品可见性字段晚于首版作品表加入,旧迁移包保留历史公开默认。
|
||||
object
|
||||
.entry("visible".to_string())
|
||||
.or_insert_with(|| serde_json::Value::Bool(true));
|
||||
@@ -1380,7 +1380,7 @@ fn normalize_migration_row(table_name: &str, value: &serde_json::Value) -> serde
|
||||
}
|
||||
if table_name == "wooden_fish_work_profile" {
|
||||
if let Some(object) = next_value.as_object_mut() {
|
||||
// 中文注释:作品可见性字段晚于首版作品表加入,旧迁移包按默认显示兼容。
|
||||
// 中文注释:作品可见性字段晚于首版作品表加入,旧迁移包保留历史公开默认。
|
||||
object
|
||||
.entry("visible".to_string())
|
||||
.or_insert_with(|| serde_json::Value::Bool(true));
|
||||
|
||||
@@ -42,7 +42,7 @@ use crate::validate_external_generation_job_lease_for_tx;
|
||||
|
||||
const PUZZLE_POINT_INCENTIVE_DEFAULT_U64: u64 = 0;
|
||||
const PUZZLE_BACKGROUND_COMPILE_TASK_LEASE_MICROS: i64 = 30 * 60 * 1_000_000;
|
||||
const WORK_VISIBLE_DEFAULT: bool = true;
|
||||
const WORK_VISIBLE_DEFAULT: bool = false;
|
||||
const PUZZLE_EXTERNAL_GENERATION_SOURCE_MODULE: &str = "puzzle";
|
||||
const PUZZLE_COMPILE_DRAFT_JOB_KIND: &str = "puzzle_compile_draft";
|
||||
const PUZZLE_GENERATE_IMAGES_JOB_KIND: &str = "puzzle_generate_images";
|
||||
@@ -137,7 +137,7 @@ pub struct PuzzleWorkProfileRow {
|
||||
pub(crate) point_incentive_total_half_points: u64,
|
||||
#[default(PUZZLE_POINT_INCENTIVE_DEFAULT_U64)]
|
||||
pub(crate) point_incentive_claimed_points: u64,
|
||||
// 后台可见性开关;默认显示,隐藏后不进入公开列表。
|
||||
// 后台可见性开关;新作品默认不公开,开启后才进入公开列表。
|
||||
#[default(WORK_VISIBLE_DEFAULT)]
|
||||
pub(crate) visible: bool,
|
||||
}
|
||||
@@ -2402,7 +2402,7 @@ fn remix_puzzle_work_tx(
|
||||
created_at: remixed_at,
|
||||
updated_at: remixed_at,
|
||||
published_at: None,
|
||||
visible: true,
|
||||
visible: WORK_VISIBLE_DEFAULT,
|
||||
});
|
||||
|
||||
get_puzzle_agent_session_tx(
|
||||
@@ -3445,6 +3445,9 @@ fn upsert_puzzle_work_profile(ctx: &TxContext, profile: PuzzleWorkProfile) -> Re
|
||||
.profile_id()
|
||||
.find(&profile.profile_id)
|
||||
{
|
||||
let existing_was_published =
|
||||
existing.publication_status == PuzzlePublicationStatus::Published;
|
||||
let next_is_published = profile.publication_status == PuzzlePublicationStatus::Published;
|
||||
replace_puzzle_work_profile(
|
||||
ctx,
|
||||
&existing,
|
||||
@@ -3481,7 +3484,11 @@ fn upsert_puzzle_work_profile(ctx: &TxContext, profile: PuzzleWorkProfile) -> Re
|
||||
published_at: profile
|
||||
.published_at_micros
|
||||
.map(Timestamp::from_micros_since_unix_epoch),
|
||||
visible: existing.visible,
|
||||
visible: if existing_was_published && next_is_published {
|
||||
existing.visible
|
||||
} else {
|
||||
WORK_VISIBLE_DEFAULT
|
||||
},
|
||||
},
|
||||
);
|
||||
return Ok(());
|
||||
@@ -3514,7 +3521,7 @@ fn upsert_puzzle_work_profile(ctx: &TxContext, profile: PuzzleWorkProfile) -> Re
|
||||
published_at: profile
|
||||
.published_at_micros
|
||||
.map(Timestamp::from_micros_since_unix_epoch),
|
||||
visible: true,
|
||||
visible: WORK_VISIBLE_DEFAULT,
|
||||
});
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -411,6 +411,18 @@ fn compile_puzzle_clear_draft_tx(
|
||||
card_assets: card_assets.clone(),
|
||||
generation_status: generation_status.clone(),
|
||||
};
|
||||
let previous_published_work = ctx
|
||||
.db
|
||||
.puzzle_clear_work_profile()
|
||||
.profile_id()
|
||||
.find(&input.profile_id)
|
||||
.filter(|row| row.owner_user_id == input.owner_user_id)
|
||||
.filter(|row| row.publication_status == PUZZLE_CLEAR_PUBLICATION_PUBLISHED);
|
||||
let previous_visible = previous_published_work
|
||||
.as_ref()
|
||||
.map(|row| row.visible)
|
||||
.unwrap_or(WORK_VISIBLE_DEFAULT);
|
||||
let previous_published_at = previous_published_work.and_then(|row| row.published_at);
|
||||
let row = PuzzleClearWorkProfileRow {
|
||||
profile_id: input.profile_id.clone(),
|
||||
work_id: input.profile_id.clone(),
|
||||
@@ -435,8 +447,8 @@ fn compile_puzzle_clear_draft_tx(
|
||||
publication_status: PUZZLE_CLEAR_PUBLICATION_DRAFT.to_string(),
|
||||
play_count: 0,
|
||||
updated_at: compiled_at,
|
||||
published_at: None,
|
||||
visible: true,
|
||||
published_at: previous_published_at,
|
||||
visible: previous_visible,
|
||||
};
|
||||
upsert_work(ctx, row);
|
||||
replace_session(
|
||||
@@ -651,6 +663,13 @@ fn publish_puzzle_clear_work_tx(
|
||||
publication_status: PUZZLE_CLEAR_PUBLICATION_PUBLISHED.to_string(),
|
||||
updated_at: published_at,
|
||||
published_at: Some(published_at),
|
||||
visible: if row.publication_status == PUZZLE_CLEAR_PUBLICATION_PUBLISHED
|
||||
|| row.published_at.is_some()
|
||||
{
|
||||
row.visible
|
||||
} else {
|
||||
WORK_VISIBLE_DEFAULT
|
||||
},
|
||||
..clone_work(&row)
|
||||
},
|
||||
);
|
||||
@@ -713,6 +732,9 @@ fn start_puzzle_clear_runtime_run_tx(
|
||||
if work.publication_status != PUZZLE_CLEAR_PUBLICATION_PUBLISHED {
|
||||
return Err("拼消消 runtime 只能启动已发布作品".to_string());
|
||||
}
|
||||
if work.owner_user_id != input.owner_user_id && !work.visible {
|
||||
return Err("拼消消 runtime 只能启动已公开作品".to_string());
|
||||
}
|
||||
let cards = domain_cards_from_work(&work)?;
|
||||
let (board, deck) = build_level_board_and_deck(1, &work.profile_id, &cards)?;
|
||||
let domain_run = start_puzzle_clear_run(
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use crate::*;
|
||||
|
||||
const WORK_VISIBLE_DEFAULT: bool = true;
|
||||
pub(super) const WORK_VISIBLE_DEFAULT: bool = false;
|
||||
|
||||
#[spacetimedb::table(
|
||||
accessor = puzzle_clear_agent_session,
|
||||
@@ -46,7 +46,7 @@ pub struct PuzzleClearWorkProfileRow {
|
||||
pub(crate) play_count: u32,
|
||||
pub(crate) updated_at: Timestamp,
|
||||
pub(crate) published_at: Option<Timestamp>,
|
||||
// 中文注释:后台可见性开关,隐藏后不进入公开列表。
|
||||
// 中文注释:后台可见性开关,新作品默认不公开,开启后才进入公开列表。
|
||||
#[default(WORK_VISIBLE_DEFAULT)]
|
||||
pub(crate) visible: bool,
|
||||
}
|
||||
|
||||
@@ -500,6 +500,18 @@ fn compile_square_hole_draft_tx(
|
||||
let cover_image_src = clean_optional(&input.cover_image_src)
|
||||
.or_else(|| clean_optional(&Some(config.cover_image_src.clone())))
|
||||
.unwrap_or_default();
|
||||
let previous_published_work = ctx
|
||||
.db
|
||||
.square_hole_work_profile()
|
||||
.profile_id()
|
||||
.find(&input.profile_id)
|
||||
.filter(|row| row.owner_user_id == input.owner_user_id)
|
||||
.filter(|row| row.publication_status == SQUARE_HOLE_PUBLICATION_PUBLISHED);
|
||||
let previous_visible = previous_published_work
|
||||
.as_ref()
|
||||
.map(|row| row.visible)
|
||||
.unwrap_or(WORK_VISIBLE_DEFAULT);
|
||||
let previous_published_at = previous_published_work.and_then(|row| row.published_at);
|
||||
let draft = SquareHoleDraftSnapshot {
|
||||
profile_id: input.profile_id.clone(),
|
||||
game_name: domain_draft.game_name.clone(),
|
||||
@@ -537,8 +549,8 @@ fn compile_square_hole_draft_tx(
|
||||
publication_status: SQUARE_HOLE_PUBLICATION_DRAFT.to_string(),
|
||||
play_count: 0,
|
||||
updated_at: compiled_at,
|
||||
published_at: None,
|
||||
visible: true,
|
||||
published_at: previous_published_at,
|
||||
visible: previous_visible,
|
||||
};
|
||||
upsert_work(ctx, work);
|
||||
replace_session(
|
||||
@@ -634,6 +646,13 @@ fn publish_square_hole_work_tx(
|
||||
publication_status: SQUARE_HOLE_PUBLICATION_PUBLISHED.to_string(),
|
||||
updated_at: published_at,
|
||||
published_at: Some(published_at),
|
||||
visible: if current.publication_status == SQUARE_HOLE_PUBLICATION_PUBLISHED
|
||||
|| current.published_at.is_some()
|
||||
{
|
||||
current.visible
|
||||
} else {
|
||||
WORK_VISIBLE_DEFAULT
|
||||
},
|
||||
..clone_work(¤t)
|
||||
};
|
||||
let snapshot = build_work_snapshot(&next)?;
|
||||
@@ -698,7 +717,7 @@ fn get_square_hole_work_detail_tx(
|
||||
.find(&input.profile_id)
|
||||
.ok_or_else(|| "方洞挑战作品不存在".to_string())?;
|
||||
if row.owner_user_id != input.owner_user_id
|
||||
&& row.publication_status != SQUARE_HOLE_PUBLICATION_PUBLISHED
|
||||
&& (row.publication_status != SQUARE_HOLE_PUBLICATION_PUBLISHED || !row.visible)
|
||||
{
|
||||
return Err("无权访问该方洞挑战作品".to_string());
|
||||
}
|
||||
@@ -1033,7 +1052,7 @@ fn find_playable_work(
|
||||
.find(&profile_id.to_string())
|
||||
.ok_or_else(|| "方洞挑战作品不存在".to_string())?;
|
||||
if row.owner_user_id != owner_user_id
|
||||
&& row.publication_status != SQUARE_HOLE_PUBLICATION_PUBLISHED
|
||||
&& (row.publication_status != SQUARE_HOLE_PUBLICATION_PUBLISHED || !row.visible)
|
||||
{
|
||||
return Err("无权试玩该方洞挑战作品".to_string());
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use crate::*;
|
||||
|
||||
const WORK_VISIBLE_DEFAULT: bool = true;
|
||||
pub(super) const WORK_VISIBLE_DEFAULT: bool = false;
|
||||
|
||||
#[spacetimedb::table(
|
||||
accessor = square_hole_agent_session,
|
||||
@@ -61,7 +61,7 @@ pub struct SquareHoleWorkProfileRow {
|
||||
pub(crate) play_count: u32,
|
||||
pub(crate) updated_at: Timestamp,
|
||||
pub(crate) published_at: Option<Timestamp>,
|
||||
// 后台可见性开关;默认显示,隐藏后不进入公开列表。
|
||||
// 后台可见性开关;新作品默认不公开,开启后才进入公开列表。
|
||||
#[default(WORK_VISIBLE_DEFAULT)]
|
||||
pub(crate) visible: bool,
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ use crate::*;
|
||||
use serde::de::DeserializeOwned;
|
||||
use spacetimedb::AnonymousViewContext;
|
||||
|
||||
const WORK_VISIBLE_DEFAULT: bool = true;
|
||||
const WORK_VISIBLE_DEFAULT: bool = false;
|
||||
|
||||
pub const VISUAL_NOVEL_SOURCE_IDEA: &str = "idea";
|
||||
pub const VISUAL_NOVEL_SOURCE_DOCUMENT: &str = "document";
|
||||
@@ -95,7 +95,7 @@ pub struct VisualNovelWorkProfileRow {
|
||||
pub(crate) created_at: Timestamp,
|
||||
pub(crate) updated_at: Timestamp,
|
||||
pub(crate) published_at: Option<Timestamp>,
|
||||
// 管理端可见性开关;默认显示,隐藏后不进入广场列表。
|
||||
// 管理端可见性开关;新作品默认不公开,开启后才进入广场列表。
|
||||
#[default(WORK_VISIBLE_DEFAULT)]
|
||||
pub(crate) visible: bool,
|
||||
}
|
||||
@@ -1015,6 +1015,18 @@ fn compile_visual_novel_work_profile_tx(
|
||||
.or_else(|| draft_string_field(&draft, "coverImageSrc"))
|
||||
.unwrap_or_default();
|
||||
let publish_ready = draft_bool_field(&draft, "publishReady");
|
||||
let previous_published_work = ctx
|
||||
.db
|
||||
.visual_novel_work_profile()
|
||||
.profile_id()
|
||||
.find(&input.profile_id)
|
||||
.filter(|row| row.owner_user_id == input.owner_user_id)
|
||||
.filter(|row| row.publication_status == VISUAL_NOVEL_PUBLICATION_PUBLISHED);
|
||||
let previous_visible = previous_published_work
|
||||
.as_ref()
|
||||
.map(|row| row.visible)
|
||||
.unwrap_or(WORK_VISIBLE_DEFAULT);
|
||||
let previous_published_at = previous_published_work.and_then(|row| row.published_at);
|
||||
|
||||
let work = VisualNovelWorkProfileRow {
|
||||
profile_id: input.profile_id.clone(),
|
||||
@@ -1033,8 +1045,8 @@ fn compile_visual_novel_work_profile_tx(
|
||||
play_count: 0,
|
||||
created_at: compiled_at,
|
||||
updated_at: compiled_at,
|
||||
published_at: None,
|
||||
visible: true,
|
||||
published_at: previous_published_at,
|
||||
visible: previous_visible,
|
||||
};
|
||||
upsert_work(ctx, work);
|
||||
replace_session(
|
||||
@@ -1095,6 +1107,13 @@ fn publish_visual_novel_work_tx(
|
||||
publication_status: VISUAL_NOVEL_PUBLICATION_PUBLISHED.to_string(),
|
||||
updated_at: published_at,
|
||||
published_at: Some(published_at),
|
||||
visible: if current.publication_status == VISUAL_NOVEL_PUBLICATION_PUBLISHED
|
||||
|| current.published_at.is_some()
|
||||
{
|
||||
current.visible
|
||||
} else {
|
||||
WORK_VISIBLE_DEFAULT
|
||||
},
|
||||
..clone_work(¤t)
|
||||
};
|
||||
if !next.source_session_id.is_empty() {
|
||||
@@ -1165,7 +1184,7 @@ fn get_visual_novel_work_detail_tx(
|
||||
.find(&input.profile_id)
|
||||
.filter(|row| {
|
||||
row.owner_user_id == input.owner_user_id
|
||||
|| row.publication_status == VISUAL_NOVEL_PUBLICATION_PUBLISHED
|
||||
|| (row.publication_status == VISUAL_NOVEL_PUBLICATION_PUBLISHED && row.visible)
|
||||
})
|
||||
.ok_or_else(|| "visual_novel_work_profile 不存在".to_string())?;
|
||||
build_work_snapshot(&row)
|
||||
@@ -1437,7 +1456,7 @@ fn find_readable_work(
|
||||
.find(&profile_id.to_string())
|
||||
.filter(|row| {
|
||||
row.owner_user_id == owner_user_id
|
||||
|| row.publication_status == VISUAL_NOVEL_PUBLICATION_PUBLISHED
|
||||
|| (row.publication_status == VISUAL_NOVEL_PUBLICATION_PUBLISHED && row.visible)
|
||||
})
|
||||
.ok_or_else(|| "visual_novel_work_profile 不存在".to_string())
|
||||
}
|
||||
|
||||
@@ -401,6 +401,18 @@ fn compile_wooden_fish_draft_tx(
|
||||
.unwrap_or_else(|| WOODEN_FISH_GENERATION_READY.to_string()),
|
||||
};
|
||||
let compiled_at = Timestamp::from_micros_since_unix_epoch(input.compiled_at_micros);
|
||||
let previous_published_work = ctx
|
||||
.db
|
||||
.wooden_fish_work_profile()
|
||||
.profile_id()
|
||||
.find(&input.profile_id)
|
||||
.filter(|row| row.owner_user_id == input.owner_user_id)
|
||||
.filter(|row| row.publication_status == WOODEN_FISH_PUBLICATION_PUBLISHED);
|
||||
let previous_visible = previous_published_work
|
||||
.as_ref()
|
||||
.map(|row| row.visible)
|
||||
.unwrap_or(WORK_VISIBLE_DEFAULT);
|
||||
let previous_published_at = previous_published_work.and_then(|row| row.published_at);
|
||||
let row = WoodenFishWorkProfileRow {
|
||||
profile_id: input.profile_id.clone(),
|
||||
work_id: input.profile_id.clone(),
|
||||
@@ -430,10 +442,10 @@ fn compile_wooden_fish_draft_tx(
|
||||
publication_status: WOODEN_FISH_PUBLICATION_DRAFT.to_string(),
|
||||
play_count: 0,
|
||||
updated_at: compiled_at,
|
||||
published_at: None,
|
||||
published_at: previous_published_at,
|
||||
background_asset_json: background_asset.as_ref().map(to_json_string),
|
||||
back_button_asset_json: back_button_asset.as_ref().map(to_json_string),
|
||||
visible: true,
|
||||
visible: previous_visible,
|
||||
};
|
||||
upsert_work(ctx, row);
|
||||
let config = config_from_draft(&draft);
|
||||
@@ -567,6 +579,13 @@ fn publish_wooden_fish_work_tx(
|
||||
publication_status: WOODEN_FISH_PUBLICATION_PUBLISHED.to_string(),
|
||||
updated_at: published_at,
|
||||
published_at: Some(published_at),
|
||||
visible: if row.publication_status == WOODEN_FISH_PUBLICATION_PUBLISHED
|
||||
|| row.published_at.is_some()
|
||||
{
|
||||
row.visible
|
||||
} else {
|
||||
WORK_VISIBLE_DEFAULT
|
||||
},
|
||||
..clone_work(&row)
|
||||
},
|
||||
);
|
||||
@@ -680,6 +699,11 @@ fn start_wooden_fish_run_tx(
|
||||
require_non_empty(&input.run_id, "wooden_fish run_id")?;
|
||||
let stored_work = find_work(ctx, &input.profile_id)?;
|
||||
let work = backfill_historical_runtime_content(&stored_work);
|
||||
if work.owner_user_id != input.owner_user_id
|
||||
&& (work.publication_status != WOODEN_FISH_PUBLICATION_PUBLISHED || !work.visible)
|
||||
{
|
||||
return Err("敲木鱼 runtime 只能启动已公开作品".to_string());
|
||||
}
|
||||
if !is_publish_ready(&work) {
|
||||
return Err("敲木鱼运行态需要完整作品配置".to_string());
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use crate::*;
|
||||
|
||||
const WORK_VISIBLE_DEFAULT: bool = true;
|
||||
pub(super) const WORK_VISIBLE_DEFAULT: bool = false;
|
||||
|
||||
#[spacetimedb::table(
|
||||
accessor = wooden_fish_agent_session,
|
||||
@@ -51,7 +51,7 @@ pub struct WoodenFishWorkProfileRow {
|
||||
pub(crate) background_asset_json: Option<String>,
|
||||
#[default(None::<String>)]
|
||||
pub(crate) back_button_asset_json: Option<String>,
|
||||
// 后台可见性开关;默认显示,隐藏后不进入公开列表。
|
||||
// 后台可见性开关;新作品默认不公开,开启后才进入公开列表。
|
||||
#[default(WORK_VISIBLE_DEFAULT)]
|
||||
pub(crate) visible: bool,
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user