merge: admin work visibility controls
This commit is contained in:
@@ -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',
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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({
|
||||
|
||||
@@ -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` 补默认值。
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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;
|
||||
|
||||
+59
@@ -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,
|
||||
);
|
||||
}
|
||||
}
|
||||
+59
@@ -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,
|
||||
);
|
||||
}
|
||||
}
|
||||
+15
@@ -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;
|
||||
}
|
||||
+19
@@ -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;
|
||||
}
|
||||
+19
@@ -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;
|
||||
}
|
||||
+27
@@ -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;
|
||||
}
|
||||
+18
@@ -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
Reference in New Issue
Block a user