merge: admin work visibility controls

This commit is contained in:
kdletters
2026-05-28 01:01:25 +08:00
55 changed files with 1988 additions and 84 deletions
+24
View File
@@ -13,10 +13,13 @@ import type {
AdminOverviewResponse,
AdminTrackingEventListQuery,
AdminTrackingEventListResponse,
AdminUpdateWorkVisibilityRequest,
AdminUpdateWorkVisibilityResponse,
AdminUpsertProfileInviteCodeRequest,
AdminUpsertProfileRechargeProductRequest,
AdminUpsertProfileRedeemCodeRequest,
AdminUpsertProfileTaskConfigRequest,
AdminWorkVisibilityListResponse,
ApiErrorEnvelope,
ApiMeta,
ApiSuccessEnvelope,
@@ -194,6 +197,27 @@ export function upsertAdminCreationEntryConfig(
);
}
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 listProfileRedeemCodes(token: string) {
return request<ProfileRedeemCodeAdminListResponse>(
'/admin/api/profile/redeem-codes',
+30
View File
@@ -177,6 +177,36 @@ export interface AdminUpsertCreationEntryTypeConfigRequest {
categorySortOrder: number;
}
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 AdminUpsertProfileRedeemCodeRequest {
code: string;
mode: ProfileRedeemCodeMode;
+7
View File
@@ -28,6 +28,7 @@ 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 {AdminShell} from './AdminShell';
import type {AdminRouteId} from './adminRoutes';
import {resolveAdminRoute, routeHash} from './adminRoutes';
@@ -205,6 +206,12 @@ export function AdminApp() {
onUnauthorized={handleUnauthorized}
/>
) : null}
{routeId === 'work-visibility' ? (
<AdminWorkVisibilityPage
token={token}
onUnauthorized={handleUnauthorized}
/>
) : null}
{routeId === 'tasks' ? (
<AdminTaskConfigPage
result={taskConfigResult}
+2
View File
@@ -3,6 +3,7 @@ import {
BadgeDollarSign,
LayoutDashboard,
LogOut,
Eye,
ShieldCheck,
ListChecks,
SlidersHorizontal,
@@ -35,6 +36,7 @@ const routeIcons = {
tasks: ListChecks,
'recharge-products': BadgeDollarSign,
'creation-entry': SlidersHorizontal,
'work-visibility': Eye,
} satisfies Record<AdminRouteId, typeof LayoutDashboard>;
export function AdminShell({
+3 -1
View File
@@ -7,7 +7,8 @@ export type AdminRouteId =
| 'invite'
| 'tasks'
| 'recharge-products'
| 'creation-entry';
| 'creation-entry'
| 'work-visibility';
export interface AdminRouteDefinition {
id: AdminRouteId;
@@ -25,6 +26,7 @@ export const adminRoutes: AdminRouteDefinition[] = [
{id: 'tasks', label: '任务配置', hash: '#tasks'},
{id: 'recharge-products', label: '充值商品', hash: '#recharge-products'},
{id: 'creation-entry', label: '入口开关', hash: '#creation-entry'},
{id: 'work-visibility', label: '作品可见性', hash: '#work-visibility'},
];
export function resolveAdminRoute(hash: string): AdminRouteId {
@@ -0,0 +1,269 @@
import {Eye, EyeOff, RefreshCcw} from 'lucide-react';
import {useEffect, useMemo, useState} from 'react';
import {
listAdminWorkVisibility,
updateAdminWorkVisibility,
} from '../api/adminApiClient';
import type {AdminWorkVisibilityEntryPayload} from '../api/adminApiTypes';
import {useAdminWriteConfirm} from '../components/useAdminWriteConfirm';
import {handlePageError} from './pageUtils';
interface AdminWorkVisibilityPageProps {
token: string;
onUnauthorized: (message?: string) => void;
}
const sourceLabels: Record<string, string> = {
puzzle: '拼图',
'custom-world': '自定义世界',
'jump-hop': '跳一跳',
'wooden-fish': '敲木鱼',
match3d: '抓大鹅',
'square-hole': '方洞挑战',
'visual-novel': '视觉小说',
'big-fish': '大鱼吃小鱼',
'bark-battle': '汪汪声浪',
};
export function AdminWorkVisibilityPage({
token,
onUnauthorized,
}: AdminWorkVisibilityPageProps) {
const [entries, setEntries] = useState<AdminWorkVisibilityEntryPayload[]>([]);
const [keyword, setKeyword] = useState('');
const [isLoading, setIsLoading] = useState(false);
const [savingKey, setSavingKey] = useState('');
const [errorMessage, setErrorMessage] = useState('');
const {confirmWrite, confirmDialog} = useAdminWriteConfirm();
useEffect(() => {
void refreshEntries();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [token]);
const filteredEntries = useMemo(() => {
const normalizedKeyword = keyword.trim().toLowerCase();
if (!normalizedKeyword) {
return entries;
}
return entries.filter((entry) =>
[
entry.sourceType,
sourceLabels[entry.sourceType] ?? '',
entry.title,
entry.subtitle,
entry.authorDisplayName,
entry.publicWorkCode,
entry.profileId,
entry.workId,
]
.join(' ')
.toLowerCase()
.includes(normalizedKeyword),
);
}, [entries, keyword]);
async function refreshEntries() {
setIsLoading(true);
setErrorMessage('');
try {
const response = await listAdminWorkVisibility(token);
setEntries(sortEntries(response.entries));
} catch (error: unknown) {
handlePageError(error, onUnauthorized, setErrorMessage);
} finally {
setIsLoading(false);
}
}
async function handleToggle(entry: AdminWorkVisibilityEntryPayload) {
const nextVisible = !entry.visible;
const target = entry.title.trim() || entry.publicWorkCode || entry.profileId;
const confirmed = await confirmWrite({
action: nextVisible ? '显示作品' : '隐藏作品',
target,
});
if (!confirmed) {
return;
}
const rowKey = buildEntryKey(entry);
setSavingKey(rowKey);
setErrorMessage('');
try {
const response = await updateAdminWorkVisibility(token, {
sourceType: entry.sourceType,
profileId: entry.profileId,
visible: nextVisible,
});
upsertEntry(response.entry);
} catch (error: unknown) {
handlePageError(error, onUnauthorized, setErrorMessage);
} finally {
setSavingKey('');
}
}
function upsertEntry(next: AdminWorkVisibilityEntryPayload) {
setEntries((current) =>
sortEntries([
...current.filter((entry) => buildEntryKey(entry) !== buildEntryKey(next)),
next,
]),
);
}
return (
<section className="admin-page admin-page-wide">
<div className="admin-page-heading">
<div>
<h2></h2>
</div>
<button
className="admin-secondary-button"
disabled={isLoading}
type="button"
onClick={refreshEntries}
>
<RefreshCcw size={17} aria-hidden="true" />
<span>{isLoading ? '刷新中' : '刷新'}</span>
</button>
</div>
<section className="admin-panel">
<label className="admin-field">
<span></span>
<input
placeholder="标题 / 作者 / 公开码 / profileId"
value={keyword}
onChange={(event) => setKeyword(event.target.value)}
/>
</label>
{errorMessage ? (
<div className="admin-alert" role="status">
{errorMessage}
</div>
) : null}
<div className="admin-table-wrap">
<table className="admin-table admin-table-wide">
<thead>
<tr>
<th></th>
<th></th>
<th></th>
<th></th>
<th></th>
<th></th>
<th></th>
</tr>
</thead>
<tbody>
{filteredEntries.map((entry) => {
const rowKey = buildEntryKey(entry);
const isSaving = savingKey === rowKey;
return (
<tr key={rowKey}>
<td>
<span className="admin-tag">
{sourceLabels[entry.sourceType] ?? entry.sourceType}
</span>
</td>
<td>
<strong>{entry.title || entry.profileId}</strong>
<small>{entry.subtitle || entry.profileId}</small>
</td>
<td>
{entry.authorDisplayName || '玩家'}
<small>{entry.ownerUserId}</small>
</td>
<td>
<span className="admin-table-cell-ellipsis">
{entry.publicWorkCode}
</span>
<small>{entry.profileId}</small>
</td>
<td>{formatMicros(entry.updatedAtMicros)}</td>
<td>
<span
className={
entry.visible
? 'admin-status admin-status-ok'
: 'admin-status admin-status-error'
}
>
{entry.visible ? '显示' : '隐藏'}
</span>
</td>
<td>
<button
className={
entry.visible
? 'admin-danger-button'
: 'admin-secondary-button'
}
disabled={isSaving}
type="button"
onClick={() => handleToggle(entry)}
>
{entry.visible ? (
<EyeOff size={16} aria-hidden="true" />
) : (
<Eye size={16} aria-hidden="true" />
)}
<span>
{isSaving
? '处理中'
: entry.visible
? '隐藏'
: '显示'}
</span>
</button>
</td>
</tr>
);
})}
</tbody>
</table>
</div>
{!isLoading && filteredEntries.length === 0 ? (
<div className="admin-empty-state"></div>
) : null}
</section>
{confirmDialog}
</section>
);
}
function sortEntries(entries: AdminWorkVisibilityEntryPayload[]) {
return [...entries].sort((left, right) => {
const timeCompare = right.updatedAtMicros - left.updatedAtMicros;
if (timeCompare !== 0) {
return timeCompare;
}
const sourceCompare = left.sourceType.localeCompare(right.sourceType);
if (sourceCompare !== 0) {
return sourceCompare;
}
return left.profileId.localeCompare(right.profileId);
});
}
function buildEntryKey(entry: AdminWorkVisibilityEntryPayload) {
return `${entry.sourceType}:${entry.profileId}`;
}
function formatMicros(value: number) {
if (!Number.isFinite(value)) {
return '-';
}
const date = new Date(Math.floor(value / 1000));
if (!Number.isFinite(date.getTime())) {
return '-';
}
return date.toLocaleString('zh-CN', {hour12: false});
}
@@ -0,0 +1,83 @@
# 作品可见性后台管理 Implementation Plan
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
**Goal:** 在后台增加统一作品可见性列表与修改能力,让管理员可以把已发布作品从公开 read model 中隐藏或恢复显示。
**Architecture:** 可见性仍以各玩法源表 `visible` 字段为真相源;新增 SpacetimeDB admin procedure 统一列出和更新各玩法作品可见性,`api-server` 只做鉴权、DTO 校验和 BFF 转发,后台前端新增简洁管理页。统一公开 read model 继续只消费 `visible=true` 的 source view,不向公开契约暴露后台字段。
**Tech Stack:** Rust server-rs + SpacetimeDB module/procedure + spacetime-client bindings/facade + shared-contracts DTO + React admin-web TypeScript。
---
### Task 1: 文档契约补齐
**Files:**
- Modify: `docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md`
- Modify: `docs/technical/【后端架构】统一公开作品ReadModel设计-2026-05-26.md`
- [ ] 在 API 路由分组中补充 `/admin/api/works/visibility`
- [ ] 在统一公开作品 ReadModel 文档中写清后台只能修改源表 `visible`,隐藏后不进入 `public_work_gallery_entry` / `public_work_detail_entry`
### Task 2: DTO 与后端路由
**Files:**
- Modify: `server-rs/crates/shared-contracts/src/admin.rs`
- Modify: `server-rs/crates/api-server/src/admin.rs`
- Modify: `server-rs/crates/api-server/src/app.rs` 或现有 admin module router 文件
- [ ] 增加 `AdminWorkVisibilityEntryPayload``AdminWorkVisibilityListResponse``AdminUpdateWorkVisibilityRequest``AdminUpdateWorkVisibilityResponse`
- [ ] 新增 `GET /admin/api/works/visibility` handler,必须走 `require_admin_auth`
- [ ] 新增 `POST /admin/api/works/visibility` handler,校验 `sourceType``profileId` 非空并转发到 SpacetimeDB facade。
### Task 3: SpacetimeDB runtime/procedure 与 facade
**Files:**
- Modify: `server-rs/crates/module-runtime/src/domain.rs`
- Create: `server-rs/crates/spacetime-module/src/runtime/admin_work_visibility.rs`
- Modify: `server-rs/crates/spacetime-module/src/runtime.rs`
- Modify: `server-rs/crates/spacetime-module/src/lib.rs`
- Modify: `server-rs/crates/spacetime-client/src/runtime.rs`
- Modify: `server-rs/crates/spacetime-client/src/mapper/runtime.rs`
- [ ] 增加 module-runtime typed input/output 类型。
- [ ] SpacetimeDB procedure 统一读取各玩法已发布源表/view,并返回可见性列表。
- [ ] SpacetimeDB procedure 根据 `sourceType + profileId` 修改对应源表 `visible``custom-world` 同步 `custom_world_gallery_entry.visible``big-fish` 使用 `session_id``bark-battle` 使用 `work_id`
- [ ] spacetime-client 增加 list/update facade 和 mapper。
### Task 4: 后台前端页面
**Files:**
- Modify: `apps/admin-web/src/api/adminApiTypes.ts`
- Modify: `apps/admin-web/src/api/adminApiClient.ts`
- Create: `apps/admin-web/src/pages/AdminWorkVisibilityPage.tsx`
- Modify: `apps/admin-web/src/app/adminRoutes.ts`
- Modify: `apps/admin-web/src/app/AdminShell.tsx`
- Modify: `apps/admin-web/src/app/AdminApp.tsx`
- [ ] 增加 API 类型和 client 方法。
- [ ] 新增简洁表格页,显示玩法、标题、作者、公开码、更新时间、可见状态。
- [ ] 修改可见性时使用 `useAdminWriteConfirm` 确认。
- [ ] 接入后台导航和 route switch。
### Task 5: 生成绑定与验证
**Files:**
- Generated: `server-rs/crates/spacetime-client/src/module_bindings*`
- Generated: front-end shared bindings if generator updates them
- [ ] Run: `npm run spacetime:generate`
- [ ] Run: `npm run check:spacetime-schema`
- [ ] Run: `cargo check -p spacetime-client --manifest-path server-rs/Cargo.toml`
- [ ] Run: `cargo check -p api-server --manifest-path server-rs/Cargo.toml`
- [ ] Run: `npm run admin-web:typecheck`
- [ ] Run: `npm run check:encoding`
### Task 6: 提交并推送
**Files:**
- All changed files
- [ ] Inspect `git diff` and `git status --short --branch`
- [ ] Commit with message `feat: add admin work visibility controls`
- [ ] Push current branch `codex/visible-work-field`
@@ -39,8 +39,21 @@
- `sort_time_micros`
- `detail_payload_json`
作品源表新增 `visible` 可见性字段,默认 `true``visible` 属于源表 / source view 过滤条件,不作为统一公开契约默认返回字段;当 `visible=false` 时,对应作品不得进入 `public_work_gallery_entry``public_work_detail_entry`
其中 `detail_payload_json` 只承载平台详情页展示扩展,不承载正式 runtime 配置、玩法规则或草稿真相。
## 后台可见性管理
后台通过独立接口管理已发布作品的源表可见性:
- `GET /admin/api/works/visibility`
- `POST /admin/api/works/visibility`
后台操作 key 使用统一的 `sourceType + profileId` 组合。`profileId` 在大多数玩法中对应作品 profile;特殊玩法维持既有源表身份:`big-fish` 对应 `session_id``bark-battle` 对应 `work_id``custom-world` 更新源表时必须同步 `custom_world_gallery_entry.visible`,避免兼容 gallery 缓存与统一公开 read model 出现可见性漂移。
该后台能力只修改源表 / source view 过滤事实,不把 `visible` 暴露到公开列表或公开详情契约。隐藏作品后,统一 `public_work_gallery_entry``public_work_detail_entry` 不再返回该作品;恢复显示后重新进入公开 read model。
## 来源与兼容
统一 public view 由现有玩法 source view 组装:
@@ -63,6 +76,7 @@
- 旧 view 保留,不删除。
- 旧 view 退到底层 source / 兼容职责。
-`public_work_*` view 是 `api-server` 公开列表 / 详情的统一主读模型。
- 各玩法 source view 只暴露 `visible=true` 的已发布作品;旧数据迁移默认补 `visible=true`,避免历史作品被误隐藏。
-`/api/runtime/<play>/gallery` 响应 shape 保持兼容,由 BFF mapper 把统一 cache 再映射回当前 DTO。
- 旧详情 / runtime / 点赞 / 游玩 / Remix 仍走玩法专用路径。
@@ -51,7 +51,7 @@ npm run check:server-rs-ddd
路由树由 `server-rs/crates/api-server/src/app.rs` 统一构造。当前主要分组:
- 健康检查:`GET /healthz`
- 后台管理:`/admin/api/*`,包括登录、概览、HTTP debug、埋点、表查询、创作入口开关、兑换码、邀请码、任务配置和充值商品配置。
- 后台管理:`/admin/api/*`,包括登录、概览、HTTP debug、埋点、表查询、创作入口开关、作品可见性、兑换码、邀请码、任务配置和充值商品配置。
- 认证与账号:`/api/auth/*``/api/profile/me`,包括短信、密码、微信、refresh session、多端会话和登出。
- 个人中心:`/api/profile/*`,包括钱包流水、任务、领奖、充值、反馈、邀请、兑换、存档、历史浏览和游玩统计。
- LLM 与语音:`/api/llm/*``/api/speech/volcengine/*`
@@ -257,6 +257,7 @@ npm run check:server-rs-ddd
- Rust 结构体:`BarkBattlePublishedConfigRow`
- 源码:`server-rs/crates/spacetime-module/src/bark_battle/tables.rs`
- 字段变更:`visible` 控制是否进入公开列表 / 详情,默认 `true`;旧迁移数据由 `migration.rs` 补默认值。
### `bark_battle_runtime_run`
@@ -293,6 +294,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` 补默认值。
### `big_fish_event`
@@ -356,11 +358,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` 补默认值。
### `custom_world_profile`
- Rust 结构体:`CustomWorldProfile`
- 源码:`server-rs/crates/spacetime-module/src/custom_world.rs`
- 字段变更:`visible` 控制是否进入公开列表 / 详情,默认 `true`;旧迁移数据由 `migration.rs` 补默认值。
### `custom_world_session`
@@ -415,6 +419,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` 补默认值。
### `wooden_fish_agent_session`
@@ -450,6 +455,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` 补默认值。
### `match3d_agent_message`
@@ -477,6 +483,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` 补默认值。
### `npc_state`
@@ -663,6 +670,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` 补默认值。
### `quest_log`
@@ -715,6 +723,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` 补默认值。
### `story_event`
@@ -790,3 +799,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` 补默认值。
+51 -1
View File
@@ -24,7 +24,9 @@ use shared_contracts::admin::{
AdminDebugHeaderInput, AdminDebugHttpRequest, AdminDebugHttpResponse, AdminLoginRequest,
AdminLoginResponse, AdminMeResponse, AdminOverviewResponse, AdminServiceOverviewPayload,
AdminSessionPayload, AdminTrackingEventEntryPayload, AdminTrackingEventListQuery,
AdminTrackingEventListResponse, AdminUpsertCreationEntryTypeConfigRequest,
AdminTrackingEventListResponse, AdminUpdateWorkVisibilityRequest,
AdminUpdateWorkVisibilityResponse, AdminUpsertCreationEntryTypeConfigRequest,
AdminWorkVisibilityListResponse,
};
use time::{OffsetDateTime, format_description::well_known::Rfc3339};
@@ -239,6 +241,40 @@ pub async fn admin_upsert_creation_entry_config(
))
}
pub async fn admin_list_work_visibility(
State(state): State<AppState>,
Extension(request_context): Extension<RequestContext>,
Extension(admin): Extension<AuthenticatedAdmin>,
) -> Result<Json<Value>, AppError> {
let admin_user_id = admin.session().subject.clone();
let entries = state
.list_admin_work_visibility(admin_user_id)
.await
.map_err(map_admin_spacetime_error)?;
Ok(json_success_body(
Some(&request_context),
AdminWorkVisibilityListResponse { entries },
))
}
pub async fn admin_update_work_visibility(
State(state): State<AppState>,
Extension(request_context): Extension<RequestContext>,
Extension(admin): Extension<AuthenticatedAdmin>,
Json(payload): Json<AdminUpdateWorkVisibilityRequest>,
) -> Result<Json<Value>, AppError> {
let entry = validate_admin_work_visibility(payload)?;
let admin_user_id = admin.session().subject.clone();
let record = state
.update_admin_work_visibility(admin_user_id, entry.0, entry.1, entry.2)
.await
.map_err(map_admin_spacetime_error)?;
Ok(json_success_body(
Some(&request_context),
AdminUpdateWorkVisibilityResponse { entry: record },
))
}
fn map_admin_creation_entry_type_config(
entry: shared_contracts::creation_entry_config::CreationEntryTypeResponse,
) -> AdminCreationEntryTypeConfigPayload {
@@ -284,6 +320,20 @@ fn validate_admin_creation_entry_config(
})
}
fn validate_admin_work_visibility(
payload: AdminUpdateWorkVisibilityRequest,
) -> Result<(String, String, bool), AppError> {
let source_type = payload.source_type.trim().to_string();
if source_type.is_empty() {
return Err(AppError::from_status(StatusCode::BAD_REQUEST).with_message("sourceType 不能为空"));
}
let profile_id = payload.profile_id.trim().to_string();
if profile_id.is_empty() {
return Err(AppError::from_status(StatusCode::BAD_REQUEST).with_message("profileId 不能为空"));
}
Ok((source_type, profile_id, payload.visible))
}
fn map_admin_spacetime_error(error: spacetime_client::SpacetimeClientError) -> AppError {
AppError::from_status(StatusCode::BAD_GATEWAY).with_details(serde_json::json!({
"provider": "spacetimedb",
@@ -3,8 +3,9 @@ use axum::{Router, middleware, routing::get};
use crate::{
admin::{
admin_debug_http, admin_get_creation_entry_config, admin_list_database_table_rows,
admin_list_database_tables, admin_list_tracking_events, admin_login, admin_me,
admin_overview, admin_upsert_creation_entry_config, require_admin_auth,
admin_list_database_tables, admin_list_tracking_events, admin_list_work_visibility,
admin_login, admin_me, admin_overview, admin_update_work_visibility,
admin_upsert_creation_entry_config, require_admin_auth,
},
runtime_profile::{
admin_disable_profile_redeem_code, admin_disable_profile_task_config,
@@ -70,6 +71,15 @@ pub fn router(state: AppState) -> Router<AppState> {
require_admin_auth,
)),
)
.route(
"/admin/api/works/visibility",
get(admin_list_work_visibility)
.post(admin_update_work_visibility)
.route_layer(middleware::from_fn_with_state(
state.clone(),
require_admin_auth,
)),
)
.route(
"/admin/api/profile/redeem-codes",
get(admin_list_profile_redeem_codes)
+23
View File
@@ -489,6 +489,29 @@ impl AppState {
}
}
pub async fn list_admin_work_visibility(
&self,
admin_user_id: String,
) -> Result<Vec<shared_contracts::admin::AdminWorkVisibilityEntryPayload>, SpacetimeClientError>
{
self.spacetime_client
.admin_list_work_visibility(admin_user_id)
.await
}
pub async fn update_admin_work_visibility(
&self,
admin_user_id: String,
source_type: String,
profile_id: String,
visible: bool,
) -> Result<shared_contracts::admin::AdminWorkVisibilityEntryPayload, SpacetimeClientError>
{
self.spacetime_client
.admin_update_work_visibility(admin_user_id, source_type, profile_id, visible)
.await
}
pub async fn is_creation_entry_route_enabled(
&self,
creation_type_id: &str,
@@ -139,6 +139,59 @@ pub struct CreationEntryConfigProcedureResult {
pub error_message: Option<String>,
}
/// 后台作品可见性列表项。
///
/// source_type/profile_id 是后台统一操作键;少数玩法的 profile_id 会映射到底层
/// session_id 或 work_id,避免后台了解每个源表的主键差异。
#[cfg_attr(feature = "spacetime-types", derive(SpacetimeType))]
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct AdminWorkVisibilitySnapshot {
pub source_type: String,
pub work_id: String,
pub profile_id: String,
pub source_session_id: Option<String>,
pub public_work_code: String,
pub owner_user_id: String,
pub author_display_name: String,
pub title: String,
pub subtitle: String,
pub cover_image_src: Option<String>,
pub visible: bool,
pub published_at_micros: Option<i64>,
pub updated_at_micros: i64,
}
#[cfg_attr(feature = "spacetime-types", derive(SpacetimeType))]
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct AdminWorkVisibilityListInput {
pub admin_user_id: String,
}
#[cfg_attr(feature = "spacetime-types", derive(SpacetimeType))]
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct AdminWorkVisibilityUpdateInput {
pub admin_user_id: String,
pub source_type: String,
pub profile_id: String,
pub visible: bool,
}
#[cfg_attr(feature = "spacetime-types", derive(SpacetimeType))]
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct AdminWorkVisibilityListProcedureResult {
pub ok: bool,
pub entries: Vec<AdminWorkVisibilitySnapshot>,
pub error_message: Option<String>,
}
#[cfg_attr(feature = "spacetime-types", derive(SpacetimeType))]
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct AdminWorkVisibilityProcedureResult {
pub ok: bool,
pub record: Option<AdminWorkVisibilitySnapshot>,
pub error_message: Option<String>,
}
/// 分析日期维表的纯领域快照。
///
/// date_key 沿用现有北京时间自然日桶:floor((occurred_at_micros + 8h) / 1d)。
@@ -53,6 +53,48 @@ pub struct AdminUpsertCreationEntryTypeConfigRequest {
pub category_sort_order: i32,
}
/// 后台作品可见性列表项。
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub struct AdminWorkVisibilityEntryPayload {
pub source_type: String,
pub work_id: String,
pub profile_id: String,
pub source_session_id: Option<String>,
pub public_work_code: String,
pub owner_user_id: String,
pub author_display_name: String,
pub title: String,
pub subtitle: String,
pub cover_image_src: Option<String>,
pub visible: bool,
pub published_at_micros: Option<i64>,
pub updated_at_micros: i64,
}
/// 后台作品可见性列表响应。
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub struct AdminWorkVisibilityListResponse {
pub entries: Vec<AdminWorkVisibilityEntryPayload>,
}
/// 后台修改作品可见性请求。
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub struct AdminUpdateWorkVisibilityRequest {
pub source_type: String,
pub profile_id: String,
pub visible: bool,
}
/// 后台修改作品可见性响应。
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub struct AdminUpdateWorkVisibilityResponse {
pub entry: AdminWorkVisibilityEntryPayload,
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub struct AdminLoginResponse {
+4 -3
View File
@@ -14,9 +14,10 @@ pub use mapper::{
BigFishDraftCompileRecordInput, BigFishGameDraftRecord, BigFishInputSubmitRecordInput,
BigFishLevelBlueprintRecord, BigFishLikeReportRecordInput, BigFishMessageFinalizeRecordInput,
BigFishMessageSubmitRecordInput, BigFishPlayReportRecordInput, BigFishRunStartRecordInput,
BigFishRuntimeEntityRecord, BigFishRuntimeParamsRecord, BigFishRuntimeRunRecord,
BigFishSessionCreateRecordInput, BigFishSessionRecord, BigFishVector2Record,
BigFishWorkRemixRecordInput, BigFishWorkSummaryRecord, CreationEntryConfigRecord,
AdminWorkVisibilityRecord, BigFishRuntimeEntityRecord, BigFishRuntimeParamsRecord,
BigFishRuntimeRunRecord, BigFishSessionCreateRecordInput, BigFishSessionRecord,
BigFishVector2Record, BigFishWorkRemixRecordInput, BigFishWorkSummaryRecord,
CreationEntryConfigRecord,
CustomWorldAgentActionExecuteRecord, CustomWorldAgentActionExecuteRecordInput,
CustomWorldAgentCheckpointRecord, CustomWorldAgentMessageFinalizeRecordInput,
CustomWorldAgentMessageRecord, CustomWorldAgentMessageSubmitRecordInput,
@@ -115,6 +115,7 @@ pub use self::puzzle::{
PuzzleWorkProfileRecord, PuzzleWorkRemixRecordInput, PuzzleWorkUpsertRecordInput,
};
pub use self::runtime::{
AdminWorkVisibilityRecord,
BigFishGameDraftRecord, BigFishRuntimeEntityRecord, BigFishRuntimeParamsRecord,
BigFishRuntimeRunRecord, CreationEntryConfigRecord,
};
@@ -193,7 +194,9 @@ pub(crate) use self::puzzle::{
parse_puzzle_agent_stage_record,
};
pub(crate) use self::runtime::{
build_creation_entry_config_record_from_rows, map_creation_entry_config_procedure_result,
build_admin_work_visibility_list_input, build_admin_work_visibility_update_input,
build_creation_entry_config_record_from_rows, map_admin_work_visibility_list_procedure_result,
map_admin_work_visibility_procedure_result, map_creation_entry_config_procedure_result,
map_runtime_setting_procedure_result, map_runtime_snapshot_delete_procedure_result,
map_runtime_snapshot_procedure_result, map_runtime_snapshot_required_procedure_result,
map_runtime_tracking_event_batch_procedure_result, map_runtime_tracking_event_procedure_result,
@@ -18,6 +18,61 @@ impl From<module_runtime::CreationEntryTypeAdminUpsertInput> for CreationEntryTy
}
}
impl From<module_runtime::AdminWorkVisibilityListInput> for AdminWorkVisibilityListInput {
fn from(input: module_runtime::AdminWorkVisibilityListInput) -> Self {
Self {
admin_user_id: input.admin_user_id,
}
}
}
impl From<module_runtime::AdminWorkVisibilityUpdateInput> for AdminWorkVisibilityUpdateInput {
fn from(input: module_runtime::AdminWorkVisibilityUpdateInput) -> Self {
Self {
admin_user_id: input.admin_user_id,
source_type: input.source_type,
profile_id: input.profile_id,
visible: input.visible,
}
}
}
pub(crate) fn build_admin_work_visibility_list_input(
admin_user_id: String,
) -> Result<module_runtime::AdminWorkVisibilityListInput, String> {
let admin_user_id = admin_user_id.trim().to_string();
if admin_user_id.is_empty() {
return Err("adminUserId 不能为空".to_string());
}
Ok(module_runtime::AdminWorkVisibilityListInput { admin_user_id })
}
pub(crate) fn build_admin_work_visibility_update_input(
admin_user_id: String,
source_type: String,
profile_id: String,
visible: bool,
) -> Result<module_runtime::AdminWorkVisibilityUpdateInput, String> {
let admin_user_id = admin_user_id.trim().to_string();
if admin_user_id.is_empty() {
return Err("adminUserId 不能为空".to_string());
}
let source_type = source_type.trim().to_string();
if source_type.is_empty() {
return Err("sourceType 不能为空".to_string());
}
let profile_id = profile_id.trim().to_string();
if profile_id.is_empty() {
return Err("profileId 不能为空".to_string());
}
Ok(module_runtime::AdminWorkVisibilityUpdateInput {
admin_user_id,
source_type,
profile_id,
visible,
})
}
impl From<module_runtime::RuntimeSettingGetInput> for RuntimeSettingGetInput {
fn from(input: module_runtime::RuntimeSettingGetInput) -> Self {
Self {
@@ -114,6 +169,7 @@ impl From<module_runtime::RuntimeTrackingEventInput> for RuntimeTrackingEventInp
pub type CreationEntryConfigRecord =
shared_contracts::creation_entry_config::CreationEntryConfigResponse;
pub type AdminWorkVisibilityRecord = shared_contracts::admin::AdminWorkVisibilityEntryPayload;
pub(crate) fn map_creation_entry_config_procedure_result(
result: CreationEntryConfigProcedureResult,
@@ -131,6 +187,51 @@ pub(crate) fn map_creation_entry_config_procedure_result(
))
}
pub(crate) fn map_admin_work_visibility_list_procedure_result(
result: AdminWorkVisibilityListProcedureResult,
) -> Result<Vec<AdminWorkVisibilityRecord>, SpacetimeClientError> {
if !result.ok {
return Err(SpacetimeClientError::procedure_failed(result.error_message));
}
Ok(result
.entries
.into_iter()
.map(map_admin_work_visibility_snapshot)
.collect())
}
pub(crate) fn map_admin_work_visibility_procedure_result(
result: AdminWorkVisibilityProcedureResult,
) -> Result<AdminWorkVisibilityRecord, SpacetimeClientError> {
if !result.ok {
return Err(SpacetimeClientError::procedure_failed(result.error_message));
}
result
.record
.map(map_admin_work_visibility_snapshot)
.ok_or_else(|| SpacetimeClientError::missing_snapshot("后台作品可见性快照"))
}
fn map_admin_work_visibility_snapshot(
snapshot: AdminWorkVisibilitySnapshot,
) -> AdminWorkVisibilityRecord {
AdminWorkVisibilityRecord {
source_type: snapshot.source_type,
work_id: snapshot.work_id,
profile_id: snapshot.profile_id,
source_session_id: snapshot.source_session_id,
public_work_code: snapshot.public_work_code,
owner_user_id: snapshot.owner_user_id,
author_display_name: snapshot.author_display_name,
title: snapshot.title,
subtitle: snapshot.subtitle,
cover_image_src: snapshot.cover_image_src,
visible: snapshot.visible,
published_at_micros: snapshot.published_at_micros,
updated_at_micros: snapshot.updated_at_micros,
}
}
pub(crate) fn build_creation_entry_config_record_from_rows(
header: CreationEntryConfig,
mut creation_types: Vec<CreationEntryTypeConfig>,
@@ -14,10 +14,17 @@ pub mod admin_list_profile_invite_codes_procedure;
pub mod admin_list_profile_recharge_products_procedure;
pub mod admin_list_profile_redeem_codes_procedure;
pub mod admin_list_profile_task_configs_procedure;
pub mod admin_list_work_visibility_procedure;
pub mod admin_update_work_visibility_procedure;
pub mod admin_upsert_profile_invite_code_procedure;
pub mod admin_upsert_profile_recharge_product_procedure;
pub mod admin_upsert_profile_redeem_code_procedure;
pub mod admin_upsert_profile_task_config_procedure;
pub mod admin_work_visibility_list_input_type;
pub mod admin_work_visibility_list_procedure_result_type;
pub mod admin_work_visibility_procedure_result_type;
pub mod admin_work_visibility_snapshot_type;
pub mod admin_work_visibility_update_input_type;
pub mod advance_puzzle_next_level_procedure;
pub mod ai_result_reference_input_type;
pub mod ai_result_reference_kind_type;
@@ -1046,10 +1053,17 @@ pub use admin_list_profile_invite_codes_procedure::admin_list_profile_invite_cod
pub use admin_list_profile_recharge_products_procedure::admin_list_profile_recharge_products;
pub use admin_list_profile_redeem_codes_procedure::admin_list_profile_redeem_codes;
pub use admin_list_profile_task_configs_procedure::admin_list_profile_task_configs;
pub use admin_list_work_visibility_procedure::admin_list_work_visibility;
pub use admin_update_work_visibility_procedure::admin_update_work_visibility;
pub use admin_upsert_profile_invite_code_procedure::admin_upsert_profile_invite_code;
pub use admin_upsert_profile_recharge_product_procedure::admin_upsert_profile_recharge_product;
pub use admin_upsert_profile_redeem_code_procedure::admin_upsert_profile_redeem_code;
pub use admin_upsert_profile_task_config_procedure::admin_upsert_profile_task_config;
pub use admin_work_visibility_list_input_type::AdminWorkVisibilityListInput;
pub use admin_work_visibility_list_procedure_result_type::AdminWorkVisibilityListProcedureResult;
pub use admin_work_visibility_procedure_result_type::AdminWorkVisibilityProcedureResult;
pub use admin_work_visibility_snapshot_type::AdminWorkVisibilitySnapshot;
pub use admin_work_visibility_update_input_type::AdminWorkVisibilityUpdateInput;
pub use advance_puzzle_next_level_procedure::advance_puzzle_next_level;
pub use ai_result_reference_input_type::AiResultReferenceInput;
pub use ai_result_reference_kind_type::AiResultReferenceKind;
@@ -0,0 +1,59 @@
// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE
// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD.
#![allow(unused, clippy::all)]
use spacetimedb_sdk::__codegen::{self as __sdk, __lib, __sats, __ws};
use super::admin_work_visibility_list_input_type::AdminWorkVisibilityListInput;
use super::admin_work_visibility_list_procedure_result_type::AdminWorkVisibilityListProcedureResult;
#[derive(__lib::ser::Serialize, __lib::de::Deserialize, Clone, PartialEq, Debug)]
#[sats(crate = __lib)]
struct AdminListWorkVisibilityArgs {
pub input: AdminWorkVisibilityListInput,
}
impl __sdk::InModule for AdminListWorkVisibilityArgs {
type Module = super::RemoteModule;
}
#[allow(non_camel_case_types)]
/// Extension trait for access to the procedure `admin_list_work_visibility`.
///
/// Implemented for [`super::RemoteProcedures`].
pub trait admin_list_work_visibility {
fn admin_list_work_visibility(&self, input: AdminWorkVisibilityListInput) {
self.admin_list_work_visibility_then(input, |_, _| {});
}
fn admin_list_work_visibility_then(
&self,
input: AdminWorkVisibilityListInput,
__callback: impl FnOnce(
&super::ProcedureEventContext,
Result<AdminWorkVisibilityListProcedureResult, __sdk::InternalError>,
) + Send
+ 'static,
);
}
impl admin_list_work_visibility for super::RemoteProcedures {
fn admin_list_work_visibility_then(
&self,
input: AdminWorkVisibilityListInput,
__callback: impl FnOnce(
&super::ProcedureEventContext,
Result<AdminWorkVisibilityListProcedureResult, __sdk::InternalError>,
) + Send
+ 'static,
) {
self.imp
.invoke_procedure_with_callback::<_, AdminWorkVisibilityListProcedureResult>(
"admin_list_work_visibility",
AdminListWorkVisibilityArgs { input },
__callback,
);
}
}
@@ -0,0 +1,59 @@
// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE
// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD.
#![allow(unused, clippy::all)]
use spacetimedb_sdk::__codegen::{self as __sdk, __lib, __sats, __ws};
use super::admin_work_visibility_procedure_result_type::AdminWorkVisibilityProcedureResult;
use super::admin_work_visibility_update_input_type::AdminWorkVisibilityUpdateInput;
#[derive(__lib::ser::Serialize, __lib::de::Deserialize, Clone, PartialEq, Debug)]
#[sats(crate = __lib)]
struct AdminUpdateWorkVisibilityArgs {
pub input: AdminWorkVisibilityUpdateInput,
}
impl __sdk::InModule for AdminUpdateWorkVisibilityArgs {
type Module = super::RemoteModule;
}
#[allow(non_camel_case_types)]
/// Extension trait for access to the procedure `admin_update_work_visibility`.
///
/// Implemented for [`super::RemoteProcedures`].
pub trait admin_update_work_visibility {
fn admin_update_work_visibility(&self, input: AdminWorkVisibilityUpdateInput) {
self.admin_update_work_visibility_then(input, |_, _| {});
}
fn admin_update_work_visibility_then(
&self,
input: AdminWorkVisibilityUpdateInput,
__callback: impl FnOnce(
&super::ProcedureEventContext,
Result<AdminWorkVisibilityProcedureResult, __sdk::InternalError>,
) + Send
+ 'static,
);
}
impl admin_update_work_visibility for super::RemoteProcedures {
fn admin_update_work_visibility_then(
&self,
input: AdminWorkVisibilityUpdateInput,
__callback: impl FnOnce(
&super::ProcedureEventContext,
Result<AdminWorkVisibilityProcedureResult, __sdk::InternalError>,
) + Send
+ 'static,
) {
self.imp
.invoke_procedure_with_callback::<_, AdminWorkVisibilityProcedureResult>(
"admin_update_work_visibility",
AdminUpdateWorkVisibilityArgs { input },
__callback,
);
}
}
@@ -0,0 +1,15 @@
// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE
// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD.
#![allow(unused, clippy::all)]
use spacetimedb_sdk::__codegen::{self as __sdk, __lib, __sats, __ws};
#[derive(__lib::ser::Serialize, __lib::de::Deserialize, Clone, PartialEq, Debug)]
#[sats(crate = __lib)]
pub struct AdminWorkVisibilityListInput {
pub admin_user_id: String,
}
impl __sdk::InModule for AdminWorkVisibilityListInput {
type Module = super::RemoteModule;
}
@@ -0,0 +1,19 @@
// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE
// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD.
#![allow(unused, clippy::all)]
use spacetimedb_sdk::__codegen::{self as __sdk, __lib, __sats, __ws};
use super::admin_work_visibility_snapshot_type::AdminWorkVisibilitySnapshot;
#[derive(__lib::ser::Serialize, __lib::de::Deserialize, Clone, PartialEq, Debug)]
#[sats(crate = __lib)]
pub struct AdminWorkVisibilityListProcedureResult {
pub ok: bool,
pub entries: Vec<AdminWorkVisibilitySnapshot>,
pub error_message: Option<String>,
}
impl __sdk::InModule for AdminWorkVisibilityListProcedureResult {
type Module = super::RemoteModule;
}
@@ -0,0 +1,19 @@
// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE
// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD.
#![allow(unused, clippy::all)]
use spacetimedb_sdk::__codegen::{self as __sdk, __lib, __sats, __ws};
use super::admin_work_visibility_snapshot_type::AdminWorkVisibilitySnapshot;
#[derive(__lib::ser::Serialize, __lib::de::Deserialize, Clone, PartialEq, Debug)]
#[sats(crate = __lib)]
pub struct AdminWorkVisibilityProcedureResult {
pub ok: bool,
pub record: Option<AdminWorkVisibilitySnapshot>,
pub error_message: Option<String>,
}
impl __sdk::InModule for AdminWorkVisibilityProcedureResult {
type Module = super::RemoteModule;
}
@@ -0,0 +1,27 @@
// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE
// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD.
#![allow(unused, clippy::all)]
use spacetimedb_sdk::__codegen::{self as __sdk, __lib, __sats, __ws};
#[derive(__lib::ser::Serialize, __lib::de::Deserialize, Clone, PartialEq, Debug)]
#[sats(crate = __lib)]
pub struct AdminWorkVisibilitySnapshot {
pub source_type: String,
pub work_id: String,
pub profile_id: String,
pub source_session_id: Option<String>,
pub public_work_code: String,
pub owner_user_id: String,
pub author_display_name: String,
pub title: String,
pub subtitle: String,
pub cover_image_src: Option<String>,
pub visible: bool,
pub published_at_micros: Option<i64>,
pub updated_at_micros: i64,
}
impl __sdk::InModule for AdminWorkVisibilitySnapshot {
type Module = super::RemoteModule;
}
@@ -0,0 +1,18 @@
// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE
// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD.
#![allow(unused, clippy::all)]
use spacetimedb_sdk::__codegen::{self as __sdk, __lib, __sats, __ws};
#[derive(__lib::ser::Serialize, __lib::de::Deserialize, Clone, PartialEq, Debug)]
#[sats(crate = __lib)]
pub struct AdminWorkVisibilityUpdateInput {
pub admin_user_id: String,
pub source_type: String,
pub profile_id: String,
pub visible: bool,
}
impl __sdk::InModule for AdminWorkVisibilityUpdateInput {
type Module = super::RemoteModule;
}

Some files were not shown because too many files have changed in this diff Show More