修复后台素材查询分页与任务归组
支持陶泥号解析、稳定游标和后台素材多页查询 补齐查询防抖、请求取消、刷新保留与加载更多状态 新增可信来源索引和拆分批次完成事实,修复跨项目与删除切片归组 同步生成绑定、数据契约、项目记忆和回归测试
This commit is contained in:
@@ -398,10 +398,11 @@ export function getAdminAssetReadUrl(
|
||||
export function listAdminEditorAssets(
|
||||
token: string,
|
||||
query: AdminEditorAssetListQuery = {},
|
||||
signal?: AbortSignal,
|
||||
) {
|
||||
return request<AdminEditorAssetListResponse>(
|
||||
`/admin/api/editor-assets${buildEditorAssetListQuery(query)}`,
|
||||
{ token },
|
||||
{ token, signal },
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -460,6 +460,7 @@ export interface AdminEditorAssetPayload {
|
||||
model?: string | null;
|
||||
provider?: string | null;
|
||||
taskId?: string | null;
|
||||
groupTaskId?: string | null;
|
||||
assetKind?: string | null;
|
||||
generationInputs?: Record<string, unknown> | null;
|
||||
sourceResourceId?: string | null;
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,6 @@
|
||||
import { ChevronDown, ChevronRight, Eye, FileText, RefreshCcw, X } from 'lucide-react';
|
||||
import { Fragment, type ReactNode } from 'react';
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
import { useCallback, useEffect, useRef, useState } from 'react';
|
||||
|
||||
import {
|
||||
getAdminAssetReadUrl,
|
||||
@@ -24,6 +24,7 @@ const ADMIN_ASSET_READ_EXPIRE_SECONDS = 300;
|
||||
const ADMIN_ASSET_READ_DISPATCH_SPACING_MS = 40;
|
||||
const ADMIN_ASSET_READ_RETRY_DELAYS_MS = [400, 1_200, 3_000] as const;
|
||||
const ADMIN_ASSET_THUMBNAIL_ROOT_MARGIN = '240px 0px';
|
||||
const ADMIN_EDITOR_ASSET_FILTER_DEBOUNCE_MS = 300;
|
||||
const AUDIO_ASSET_COVER_SRC = `${import.meta.env.DEV ? import.meta.env.BASE_URL : '/'}creation-home/audio-asset-cover.png`;
|
||||
let adminAssetReadDispatchTail = Promise.resolve();
|
||||
|
||||
@@ -37,6 +38,9 @@ export function AdminEditorAssetQueryPage({
|
||||
const [createdAfter, setCreatedAfter] = useState('');
|
||||
const [createdBefore, setCreatedBefore] = useState('');
|
||||
const [nextCursor, setNextCursor] = useState<string | null>(null);
|
||||
const [committedQueryKey, setCommittedQueryKey] = useState<string | null>(
|
||||
null,
|
||||
);
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [isLoadingMore, setIsLoadingMore] = useState(false);
|
||||
const [errorMessage, setErrorMessage] = useState('');
|
||||
@@ -51,49 +55,147 @@ export function AdminEditorAssetQueryPage({
|
||||
title: string;
|
||||
prompt: string;
|
||||
} | null>(null);
|
||||
const listRequestGenerationRef = useRef(0);
|
||||
const listRequestAbortControllerRef = useRef<AbortController | null>(null);
|
||||
const automaticQueryInitializedRef = useRef(false);
|
||||
const automaticQueryTokenRef = useRef(token);
|
||||
const activeQuery = buildListQuery();
|
||||
const activeQueryKey = editorAssetListQueryKey(token, activeQuery);
|
||||
const hasCurrentQuerySnapshot = committedQueryKey === activeQueryKey;
|
||||
const visibleEntries = hasCurrentQuerySnapshot ? entries : [];
|
||||
const visibleNextCursor = hasCurrentQuerySnapshot ? nextCursor : null;
|
||||
|
||||
useEffect(() => {
|
||||
void refreshPage();
|
||||
const runImmediately =
|
||||
!automaticQueryInitializedRef.current ||
|
||||
automaticQueryTokenRef.current !== token;
|
||||
automaticQueryInitializedRef.current = true;
|
||||
automaticQueryTokenRef.current = token;
|
||||
|
||||
if (runImmediately) {
|
||||
void refreshPage();
|
||||
return;
|
||||
}
|
||||
|
||||
invalidateListRequest();
|
||||
setIsLoading(true);
|
||||
setIsLoadingMore(false);
|
||||
setErrorMessage('');
|
||||
const timer = setTimeout(
|
||||
() => void refreshPage(),
|
||||
ADMIN_EDITOR_ASSET_FILTER_DEBOUNCE_MS,
|
||||
);
|
||||
return () => clearTimeout(timer);
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [token, ownerUserId, keyword, createdAfter, createdBefore]);
|
||||
|
||||
useEffect(
|
||||
() => () => {
|
||||
automaticQueryInitializedRef.current = false;
|
||||
invalidateListRequest();
|
||||
},
|
||||
[],
|
||||
);
|
||||
|
||||
async function refreshPage() {
|
||||
const query = buildListQuery();
|
||||
const queryKey = editorAssetListQueryKey(token, query);
|
||||
const { requestGeneration, abortController } = beginListRequest();
|
||||
setIsLoading(true);
|
||||
setIsLoadingMore(false);
|
||||
setErrorMessage('');
|
||||
try {
|
||||
const response = await listAdminEditorAssets(token, buildListQuery());
|
||||
const response = await listAdminEditorAssets(
|
||||
token,
|
||||
query,
|
||||
abortController.signal,
|
||||
);
|
||||
if (listRequestGenerationRef.current !== requestGeneration) {
|
||||
return;
|
||||
}
|
||||
setEntries(response.entries);
|
||||
setNextCursor(response.nextCursor ?? null);
|
||||
setCommittedQueryKey(queryKey);
|
||||
} catch (error: unknown) {
|
||||
if (
|
||||
listRequestGenerationRef.current !== requestGeneration ||
|
||||
isAbortError(error)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
handlePageError(error, onUnauthorized, setErrorMessage);
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
if (listRequestGenerationRef.current === requestGeneration) {
|
||||
if (listRequestAbortControllerRef.current === abortController) {
|
||||
listRequestAbortControllerRef.current = null;
|
||||
}
|
||||
setIsLoading(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function loadMore() {
|
||||
if (!nextCursor || isLoadingMore) {
|
||||
if (!visibleNextCursor || isLoading || isLoadingMore) {
|
||||
return;
|
||||
}
|
||||
const query = buildListQuery();
|
||||
const queryKey = editorAssetListQueryKey(token, query);
|
||||
if (committedQueryKey !== queryKey) {
|
||||
return;
|
||||
}
|
||||
const cursor = visibleNextCursor;
|
||||
const { requestGeneration, abortController } = beginListRequest();
|
||||
setIsLoadingMore(true);
|
||||
setErrorMessage('');
|
||||
try {
|
||||
const response = await listAdminEditorAssets(token, {
|
||||
...buildListQuery(),
|
||||
cursor: nextCursor,
|
||||
});
|
||||
const response = await listAdminEditorAssets(
|
||||
token,
|
||||
{
|
||||
...query,
|
||||
cursor,
|
||||
},
|
||||
abortController.signal,
|
||||
);
|
||||
if (listRequestGenerationRef.current !== requestGeneration) {
|
||||
return;
|
||||
}
|
||||
setEntries((current) => mergeAssetEntries(current, response.entries));
|
||||
setNextCursor(response.nextCursor ?? null);
|
||||
} catch (error: unknown) {
|
||||
if (
|
||||
listRequestGenerationRef.current !== requestGeneration ||
|
||||
isAbortError(error)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
handlePageError(error, onUnauthorized, setErrorMessage);
|
||||
} finally {
|
||||
setIsLoadingMore(false);
|
||||
if (listRequestGenerationRef.current === requestGeneration) {
|
||||
if (listRequestAbortControllerRef.current === abortController) {
|
||||
listRequestAbortControllerRef.current = null;
|
||||
}
|
||||
setIsLoadingMore(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function beginListRequest() {
|
||||
invalidateListRequest();
|
||||
const abortController = new AbortController();
|
||||
listRequestAbortControllerRef.current = abortController;
|
||||
const requestGeneration = listRequestGenerationRef.current;
|
||||
return { requestGeneration, abortController };
|
||||
}
|
||||
|
||||
function invalidateListRequest() {
|
||||
listRequestGenerationRef.current += 1;
|
||||
listRequestAbortControllerRef.current?.abort();
|
||||
listRequestAbortControllerRef.current = null;
|
||||
}
|
||||
|
||||
function buildListQuery(): AdminEditorAssetListQuery {
|
||||
return {
|
||||
ownerUserId: ownerUserId || null,
|
||||
ownerUserId: ownerUserId.trim() || null,
|
||||
keyword: keyword.trim() || null,
|
||||
createdAfter: dateInputToStartRfc3339(createdAfter),
|
||||
createdBefore: dateInputToEndRfc3339(createdBefore),
|
||||
@@ -143,7 +245,7 @@ export function AdminEditorAssetQueryPage({
|
||||
/>
|
||||
</label>
|
||||
<label className="admin-field">
|
||||
<span>用户 ID</span>
|
||||
<span>用户 ID / 陶泥号</span>
|
||||
<input
|
||||
value={ownerUserId}
|
||||
onChange={(event) => setOwnerUserId(event.target.value)}
|
||||
@@ -173,7 +275,7 @@ export function AdminEditorAssetQueryPage({
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{entries.map((entry) => {
|
||||
{visibleEntries.map((entry) => {
|
||||
const promptText = entry.prompt || entry.actualPrompt || '-';
|
||||
const expanded = expandedTaskIds.has(entry.assetId);
|
||||
const children = entry.children ?? [];
|
||||
@@ -318,13 +420,13 @@ export function AdminEditorAssetQueryPage({
|
||||
</table>
|
||||
</div>
|
||||
|
||||
{!isLoading && entries.length === 0 ? (
|
||||
{!isLoading && !errorMessage && visibleEntries.length === 0 ? (
|
||||
<div className="admin-empty-state">暂无生成素材</div>
|
||||
) : null}
|
||||
{nextCursor ? (
|
||||
{visibleNextCursor ? (
|
||||
<button
|
||||
className="admin-secondary-button"
|
||||
disabled={isLoadingMore}
|
||||
disabled={isLoading || isLoadingMore}
|
||||
type="button"
|
||||
onClick={loadMore}
|
||||
>
|
||||
@@ -645,6 +747,12 @@ function AdminAssetDetailDialog({
|
||||
{entry.provider || '-'}
|
||||
</AdminInfoItem>
|
||||
<AdminInfoItem label="Task ID">{entry.taskId || '-'}</AdminInfoItem>
|
||||
{entry.groupTaskId?.trim() &&
|
||||
entry.groupTaskId.trim() !== entry.taskId?.trim() ? (
|
||||
<AdminInfoItem label="归组 Task ID">
|
||||
{entry.groupTaskId.trim()}
|
||||
</AdminInfoItem>
|
||||
) : null}
|
||||
<AdminInfoItem label="Object Key">
|
||||
{entry.objectKey || '-'}
|
||||
</AdminInfoItem>
|
||||
@@ -988,6 +1096,29 @@ function mergeAssetEntries(
|
||||
);
|
||||
}
|
||||
|
||||
function editorAssetListQueryKey(
|
||||
token: string,
|
||||
query: AdminEditorAssetListQuery,
|
||||
) {
|
||||
return JSON.stringify([
|
||||
token.trim(),
|
||||
query.ownerUserId?.trim() || null,
|
||||
query.keyword?.trim() || null,
|
||||
query.createdAfter?.trim() || null,
|
||||
query.createdBefore?.trim() || null,
|
||||
query.limit ?? null,
|
||||
]);
|
||||
}
|
||||
|
||||
function isAbortError(error: unknown) {
|
||||
return (
|
||||
typeof error === 'object' &&
|
||||
error !== null &&
|
||||
'name' in error &&
|
||||
error.name === 'AbortError'
|
||||
);
|
||||
}
|
||||
|
||||
function dateInputToStartRfc3339(value: string) {
|
||||
return value ? `${value}T00:00:00+08:00` : null;
|
||||
}
|
||||
|
||||
@@ -47,6 +47,14 @@
|
||||
- 验证:`cargo check -p spacetime-client --manifest-path server-rs/Cargo.toml`、`cargo check -p api-server --manifest-path server-rs/Cargo.toml`、`npm run check:spacetime-schema`。
|
||||
- 关联:`server-rs/crates/spacetime-module/src/editor_project_storage.rs`、`server-rs/crates/spacetime-client/src/editor_project.rs`、`server-rs/crates/api-server/src/admin.rs`。
|
||||
|
||||
## 后台素材查询要在分页前归一用户、游标和派生任务
|
||||
|
||||
- 现象:输入 `SY-*` 陶泥号查不到已有素材;无筛选时只显示 80 个任务且没有“读取更多”;手动重拆图集后,`素材 N` 被单独当成零成本父任务,原图集又显示成另一组。
|
||||
- 原因:`editor_asset.owner_user_id` 保存内部 `user_id`,不保存公开陶泥号;作者陶泥号在 procedure 返回后才映射,不能直接参与素材表过滤。`spacetime-client` 的统一时间文本是 `seconds.microsZ`,若 cursor 只按 RFC3339 解析,第 80 个任务无法生成 `nextCursor`。手动图集拆分使用独立 `editor-atlas-split-*` 任务号,但切片项目资源通过 `source_resource_id` 指回原图集资源,只按当前 `task_id` 分组会割裂同一条素材生产链。
|
||||
- 处理:`api-server` 在调用素材 procedure 前把“用户 ID / 陶泥号”字段或精确 `SY-*` keyword 解析成内部 `user_id`;游标解析同时接受整数微秒、统一 `seconds.microsZ` 和 RFC3339,编码失败必须返回服务错误,不能伪装成末页,客户端传入的非法 cursor 必须返回 `400`,不能静默回到第一页。手动拆分素材保留真实 `task_id`,通过 `editor_asset_group_source_provenance` 按原 resource、asset object 或 Object Key 查找服务端生成账号素材的可信来源任务,再把来源任务写入 `editor_asset.group_task_id`;跨项目复用时同时携带当前和原始 source resource,首次历史回溯命中后补写 provenance,后续不再扫描账号全量素材。没有可信来源的新批次显式以自己的拆分任务作为 `group_task_id`,不得落回可读取用户资源元数据的 legacy 分支。每片保存 `group_task_expected_asset_count`,全部切片落库后再写不可逆的 `editor_asset_group_cohort` 完成事实;read model 依据完成事实判断批次资格,不用当前剩余行数猜测初始是否完整,因此用户后来删除切片不会让批次脱组。部分失败批次没有完成事实,始终保留为独立拆分任务。历史行兼容沿资源链回溯,删除项目时只固化直接引用待删资源且尚未固化的历史切片;固化始终保存真实来源任务,有界展示分组不得反写覆盖来源。read model 只让同根任务的一个完整拆分批次并入原任务,重复批次按真实拆分任务独立分页。带 owner 条件时先走 `by_editor_asset_owner_user_id`,不要为单用户查询扫描全站素材。后台筛选输入使用防抖并取消旧 transport;手动刷新同一查询失败时保留已有结果和游标,筛选已变化时不展示旧查询结果。
|
||||
- 验证:API 测试覆盖陶泥号组合条件、未知陶泥号、可信来源归组、`seconds.microsZ` / 极值游标和真实 / 归组 Task ID;SpacetimeDB 测试覆盖资源删除后稳定归组、部分失败批次不抢占根任务、重复拆分有界无丢失和 owner 索引分支;后台页面测试覆盖逐字输入防抖、请求取消、刷新失败保留结果、筛选请求乱序、旧分页响应失效和“用户 ID / 陶泥号”请求。再运行 `cargo test -p api-server admin_editor_asset --manifest-path server-rs/Cargo.toml`、`cargo test -p spacetime-module admin_editor_asset --manifest-path server-rs/Cargo.toml` 和 `npm run test -- apps/admin-web/src/pages/AdminEditorAssetQueryPage.test.tsx`。
|
||||
- 关联:`apps/admin-web/src/pages/AdminEditorAssetQueryPage.tsx`、`server-rs/crates/api-server/src/admin.rs`、`server-rs/crates/spacetime-module/src/editor_project_storage.rs`。
|
||||
|
||||
## 后台素材缩略图不要在首次挂载时全量换签
|
||||
|
||||
- 现象:后台“素材查询”首批缩略图正常,继续向下滚动或读取更多后长期显示占位图;api-server journald 中已到达的 `/admin/api/assets/read-url` 可能全部是 `200`。
|
||||
|
||||
@@ -48,14 +48,14 @@
|
||||
- 新增 `editor_project` 表保存图片画布工程:`projectId`、`ownerUserId`、标题、创建时间和更新时间;历史 layout 字段暂保留为兼容列,不再作为权威画布数据。
|
||||
- 新增 `editor_canvas` 表保存工程下的画布:`canvasId`、`projectId`、`ownerUserId`、标题、viewport、图层布局 JSON、创建时间和更新时间。当前编辑器使用项目默认画布,后续可扩展为一个 project 下多个 canvas。
|
||||
- 新增 `editor_asset_folder` 表保存账号级素材文件夹:`folderId`、`ownerUserId`、名称、排序、折叠状态、系统默认标记、创建时间和更新时间。素材文件夹不归属于 project,同一个账号进入任一项目都能看到。
|
||||
- 新增 `editor_asset` 表保存账号级素材:`assetId`、`ownerUserId`、`folderId`、名称、图片读取地址、可选封面 `thumbnailSrc`、OSS / asset object 引用、图片尺寸、来源类型、prompt、actualPrompt、model、provider、taskId、`assetKind`、`generationInputs`、`generationCostMudPoints`、创建时间和更新时间。素材只跟账号走,不跟 project 走;角色、图标、UI 设计图、视频和音频等生成结果的用户可见输入快照随素材保存。
|
||||
- 新增 `editor_asset` 表保存账号级素材:`assetId`、`ownerUserId`、`folderId`、名称、图片读取地址、可选封面 `thumbnailSrc`、OSS / asset object 引用、图片尺寸、来源类型、prompt、actualPrompt、model、provider、taskId、可选 `groupTaskId`、可选 `groupTaskExpectedAssetCount`、`assetKind`、`generationInputs`、`generationCostMudPoints`、创建时间和更新时间。素材只跟账号走,不跟 project 走;`taskId` 保留真实生成 / 拆分操作身份,`groupTaskId` 保存服务端验证后的来源任务,`groupTaskExpectedAssetCount` 保存该拆分批次完整时应有的素材数;角色、图标、UI 设计图、视频和音频等生成结果的用户可见输入快照随素材保存。
|
||||
- 新增 `editor_showcase_asset`、`editor_showcase_asset_like` 和 `editor_showcase_campaign_config` 表承接 `陶泥儿精选`:生成素材默认不公开,用户在素材菜单中提交精选审核后生成独立快照;后台审核通过后先返还 50% 生成成本泥点,但仍需运营手动设置精选分类并开启展示才进入公开精选。公开列表不再读取 `editor_project_resource.public_showcase_enabled`,而是读取已通过、展示开启且分类合法的精选快照,支持点赞数和首位活动卡。
|
||||
- `editor_project_resource` 表保存工程画布引用过的资源快照:`resourceId`、`projectId`、`ownerUserId`、OSS / asset object 引用、图片尺寸、来源类型、prompt、actualPrompt、model、provider、taskId、sourceResourceId、`assetKind`、`generationInputs`、创建时间和更新时间。上传素材被拖入画布时会复制为 project resource,图层只引用 resourceId;图片、图标和 UI 素材生成 BFF 在请求携带 `projectId` 时由后端直接创建新 resource,并把 `resourceId` 随生成响应返回给前端。图片生成请求如果同时携带 `canvasCompletion`(生成器 `dialogId`、标题和占位框,或无 dialog 的右侧完成占位),BFF / worker 在生成成功后必须直接读取当前项目布局,优先使用最新 `generation-dialog` 占位框位置;只有当前布局仍存在对应 `generation-dialog` 时才插入轻量结果图层、把生成器标记为 `idle` 并写入 `generatedLayerId`,沿用后端当前 viewport 保存布局,再返回或刷新最新项目快照;前端只应用该快照刷新显示,不把生成完成态作为本地业务真相,也不在项目加载时根据资源行推断完成态。有项目上下文但后端没有返回项目快照时,前端不得本地补结果图层,只保留当前生成器交互状态等待下一次项目刷新。
|
||||
- 项目封面图是画布当前视口栅格化后的静态快照资源,不在项目列表页临时重放 `layers + viewport`。前端在项目加载后和防抖保存 layout 时生成 320x240 PNG,走私有 OSS / asset object 上传,再创建 `editor_project_resource`,其中 `assetKind="project-cover-snapshot"`、`sourceType="uploaded"`。`/project` 与 `/creation` 最近项目卡只读取最新封面快照资源渲染;没有封面快照时显示普通项目占位,不回退为实时画布组合。
|
||||
- 图片、音频、视频和角色动画帧文件本体继续走 OSS / asset object;浏览器读取私有 generated 对象统一经 `/api/assets/read-url` 换签,签名 URL 可在 session 内复用,但不得作为持久化真相。`/api/assets/read-url` 属于页面展示层高频后台请求,前端统一在 `assetReadUrlService` 内做同 key pending 去重、session 缓存和跨组件节流;UI 设计切片、角色动画帧或大量素材恢复时不得绕过该服务并发换签,否则单页可在同一秒内打满发布入口 `genarrative_api_rps` burst。
|
||||
- 登录态上传和生成结果必须先落 OSS / asset object,再向 `editor_project_resource` / `editor_asset` 写入轻量 `imageSrc: "/<objectKey>"`、`objectKey` 和 `assetObjectId`;未登录演示态可以在内存里使用 Data URL 预览,但项目、素材库、项目资源和 `editor_canvas.layers_json` 不得写入 `data:image/*`、`data:video/*`、`data:audio/*` 或 `blob:`。旧数据读取时如果已有 `objectKey`,`imageSrc` 归一成 `/<objectKey>`;没有 `objectKey` 的旧 Data URL 需要走修复上传并回写轻量引用。裁扩在项目上下文中虽然由前端 canvas 本地渲染 PNG,也必须先上传 OSS / asset object 并创建 `editor_project_resource`,再把带正式 `resourceId/objectKey/assetObjectId` 的裁扩图层加入画布;不能先把 `local-resource-*` + Data URL 图层交给项目保存或后续去背景。上传到生成面板参考图槽位的图片必须先创建 `editor_project_resource` 行;没有当前工程 ID 时才创建账号级 `editor_asset` 行,随后把对应 `resourceId` 或 `assetId` 写入参考图临时状态,生成请求仍使用临时状态中的图片源或 `objectKey`。
|
||||
- 资源表保存资源和素材级元数据;图层位置、层级、分组选中所需 ID 和 groupId 保存在 `editor_canvas` 的布局 JSON。布局 JSON 是混合数组:普通图层按 `layerId/resourceId` 保存,生成器占位和生成器对话框按 `itemType: "generation-dialog"` 保存,不新增单独表。普通图层的新保存不再把 `assetKind/generationInputs` 写入布局 JSON;刷新时优先从 `editor_project_resource` 恢复,旧布局中的同名字段只作为兼容兜底。生成器快照必须包含生成器 ID、模式、提示词、参数、参考图、状态、占位框位置和可选 `generatedLayerId`;角色、图标等纯色抠图生成器的前端用户路径不保存或恢复 `screenColor` / `segModel`,同源重绘也不再从 `generationInputs.fields` 恢复 `抠图背景色` 或 `抠图模型`;宣发素材生成器还必须保存并恢复 `publicationWorkflowId`、`publicationGameInfo` 和 `publicationReferences`,避免刷新后生成卡片字段或参考图丢失。生成器快照中的参考图同样只保存 `resourceId/sourceAssetId` 行引用和展示所需 label,不保存图片 Data URL、signed URL 或 `objectKey`;刷新时用 `editor_project_resource` / `editor_asset` 行恢复临时生成请求所需图片源。生成成功后仍保存该快照,只是渲染时由 `generatedLayerId` 锚定到成品图层而不重复显示灰色占位框。`generationInputs.references` 是用户可见输入快照中的行级索引,只允许保存 `{ title, label, refType, refId }`;生成接口所需的图片 Data URL、signed URL 或 `objectKey` 只存在于提交前的临时参考图状态和请求体字段,不进入资源 / 素材元数据。图层展示尺寸不再作为独立 `Size` 真相保存,刷新与新建图层均按 `Resolution`(`originalWidth/originalHeight`)原分辨率显示。图层组第一版是画布内布局语义,不单独建表。
|
||||
- 图片类、生成视频和音频结果除作为 `editor_project_resource` 和画布图层保存外,还要写入账号级 `editor_asset` 素材库;该写入由生成 BFF 在请求携带 `assetFolderId` 时完成。角色、图标图集、UI 提取和角色动作等多产物任务把 provider 原始输出及后处理结果分别入库:所有条目沿用 `character`、`icon-spritesheet`、`character-animation` 等真实类型,provider 原始输出承载任务模型成本,后处理派生产物阶段成本为 0。后台素材查询以最终产物为父行、每个中间产物为可展开的独立子行,分页只计算父任务。`GENARRATIVE_EXTERNAL_GENERATION_MODE=queue` 下,画布图片、改图、图标素材、UI 素材提取、角色动作、视频、音效和背景音乐生成都先返回 `queueState`,前端轮询 `/api/runtime/external-generation/jobs/{jobId}` 到完成后重新读取项目快照;`inline` 或无项目上下文时才使用响应中的 resource / asset 快照做本地落画布兜底,不再把同一生成结果二次调用素材创建接口。生成请求失败、inline 完成或 queue 任务终态完成 / 失败后,右上角泥点 chip 必须通过 `/profile/dashboard` 回读余额,不做本地乐观扣减。生成视频会单独抽取首帧封面写入 `thumbnailSrc`,素材栏和拖回画布时沿用该封面作为 poster。
|
||||
- 图片类、生成视频和音频结果除作为 `editor_project_resource` 和画布图层保存外,还要写入账号级 `editor_asset` 素材库;该写入由生成 BFF 在请求携带 `assetFolderId` 时完成。角色、图标图集、UI 提取和角色动作等多产物任务把 provider 原始输出及后处理结果分别入库:所有条目沿用 `character`、`icon-spritesheet`、`character-animation` 等真实类型,provider 原始输出承载任务模型成本,后处理派生产物阶段成本为 0。后台素材查询以最终产物为父行、每个中间产物为可展开的独立子行,分页只计算父任务;手动重拆图集保留独立 `taskId` 用于存储隔离和日志排障,通过私有 provenance 从服务端生成账号素材的 source resource、asset object 或 Object Key 取得可信来源任务,并把它写入 `groupTaskId`,不信任客户端可提交的 resource `taskId/assetKind`;跨项目复用后仍可通过稳定媒体引用找回来源。没有可信来源的新拆分显式归到自身任务,不走历史资源链回溯。每个手动切片同时写入 `groupTaskExpectedAssetCount`,全部切片落库后写独立 cohort 完成事实;后台 read model 只让同一根任务的一个已完成拆分批次并入原图集父项,用户后来删除单片不会让批次脱组,部分失败批次和后续重复拆分批次按各自真实任务分页,避免残缺批次抢占根任务、单组无限增长或素材丢失。历史行在项目资源仍存在时兼容回溯,删除项目资源前只固化直接受影响行的真实来源字段,有界展示 ID 不反写数据库。`GENARRATIVE_EXTERNAL_GENERATION_MODE=queue` 下,画布图片、改图、图标素材、UI 素材提取、角色动作、视频、音效和背景音乐生成都先返回 `queueState`,前端轮询 `/api/runtime/external-generation/jobs/{jobId}` 到完成后重新读取项目快照;`inline` 或无项目上下文时才使用响应中的 resource / asset 快照做本地落画布兜底,不再把同一生成结果二次调用素材创建接口。生成请求失败、inline 完成或 queue 任务终态完成 / 失败后,右上角泥点 chip 必须通过 `/profile/dashboard` 回读余额,不做本地乐观扣减。生成视频会单独抽取首帧封面写入 `thumbnailSrc`,素材栏和拖回画布时沿用该封面作为 poster。
|
||||
- 生成面板不展示资源名称输入,默认使用原有自动编号;提示词输入保持统一可见边框。内部命名契约仍使用可选 `assetLabel`,最大 80 字符并在提交时 trim;历史状态或内部调用携带非空名称时,同一个名称必须贯穿 `assetLabel`、`canvasCompletion.title`、项目资源、账号素材和本地兜底图层,刷新后不得退回模板名。图标图集与角色动作请求同样兼容该字段,中间原图使用主名称加固定后缀,拆分素材继续按素材描述命名。
|
||||
- 画布 Agent 会话按“SpacetimeDB 元数据 + OSS 消息正文”存储:`editor_agent_conversation` 只保存 `conversationId/projectId/ownerUserId/title/messagesObjectKey/deleted/createdAt/updatedAt` 等会话元数据;消息正文整体保存为私有 OSS JSON 文档 `editor-agent/{conversationId}.json`。消息文档单对象上限为 2 MiB,同一会话的消息追加和 SSE 最终写回由 api-server 按 `conversationId` 串行化,避免“读 OSS → 改消息 → 写 OSS”并发覆盖。前端只通过 api-server BFF 读取和发送会话,不直接读写 SpacetimeDB,也不直接读写 OSS。
|
||||
- Agent 消息附件只允许引用当前工程画布资源或账号素材库图片,来源类型为 `canvas_resource` / `library_asset`,最多 9 张。附件请求可携带展示用 `imageSrc/thumbnailSrc/objectKey/width/height/label`,但持久化真相仍以后端校验后的 resource / asset 行和 OSS 对象为准;不得把 Data URL、signed URL 或 blob URL 当作会话长期事实。
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -626,29 +626,40 @@ pub async fn admin_list_editor_assets(
|
||||
.clamp(1, ADMIN_EDITOR_ASSET_MAX_LIMIT);
|
||||
let date_range = resolve_admin_editor_asset_date_range(&query)
|
||||
.map_err(|error| AppError::from_status(StatusCode::BAD_REQUEST).with_message(error))?;
|
||||
let cursor = normalize_admin_optional(query.cursor);
|
||||
validate_admin_editor_asset_cursor(cursor.as_deref())
|
||||
.map_err(|error| AppError::from_status(StatusCode::BAD_REQUEST).with_message(error))?;
|
||||
let Some((owner_user_id, keyword)) = resolve_admin_editor_asset_filters(
|
||||
&state,
|
||||
normalize_admin_optional(query.owner_user_id),
|
||||
normalize_admin_optional(query.keyword),
|
||||
)?
|
||||
else {
|
||||
return Ok(json_success_body(
|
||||
Some(&request_context),
|
||||
AdminEditorAssetListResponse {
|
||||
entries: Vec::new(),
|
||||
next_cursor: None,
|
||||
},
|
||||
));
|
||||
};
|
||||
let records = state
|
||||
.spacetime_client()
|
||||
.admin_list_editor_assets(AdminEditorAssetListRecordInput {
|
||||
limit: page_size + 1,
|
||||
cursor: normalize_admin_optional(query.cursor),
|
||||
owner_user_id: normalize_admin_optional(query.owner_user_id),
|
||||
keyword: normalize_admin_optional(query.keyword),
|
||||
cursor,
|
||||
owner_user_id,
|
||||
keyword,
|
||||
created_after_micros: date_range.created_after_micros,
|
||||
created_before_micros: date_range.created_before_micros,
|
||||
})
|
||||
.await
|
||||
.map_err(map_admin_spacetime_error)?;
|
||||
|
||||
let mut groups = group_admin_editor_asset_records(records);
|
||||
let next_cursor = if groups.len() > page_size as usize {
|
||||
groups
|
||||
.get(page_size.saturating_sub(1) as usize)
|
||||
.and_then(|group| group.first())
|
||||
.and_then(admin_editor_asset_cursor)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
groups.truncate(page_size as usize);
|
||||
let (groups, next_cursor) = paginate_admin_editor_asset_records(records, page_size as usize)
|
||||
.map_err(|error| {
|
||||
AppError::from_status(StatusCode::INTERNAL_SERVER_ERROR).with_message(error)
|
||||
})?;
|
||||
Ok(json_success_body(
|
||||
Some(&request_context),
|
||||
AdminEditorAssetListResponse {
|
||||
@@ -1101,6 +1112,7 @@ fn admin_editor_asset_payload_from_record(
|
||||
model: record.model,
|
||||
provider: record.provider,
|
||||
task_id: record.task_id,
|
||||
group_task_id: record.group_task_id,
|
||||
asset_kind: record.asset_kind,
|
||||
generation_inputs: record.generation_inputs,
|
||||
source_resource_id: record.source_resource_id,
|
||||
@@ -1122,9 +1134,10 @@ fn group_admin_editor_asset_records(
|
||||
let mut indexes = BTreeMap::<String, usize>::new();
|
||||
for record in records {
|
||||
let key = record
|
||||
.task_id
|
||||
.as_ref()
|
||||
.map(|value| value.trim())
|
||||
.group_task_id
|
||||
.as_deref()
|
||||
.or(record.task_id.as_deref())
|
||||
.map(str::trim)
|
||||
.filter(|value| !value.is_empty())
|
||||
.map(|value| format!("owner:{}:task:{value}", record.owner_user_id))
|
||||
.unwrap_or_else(|| format!("asset:{}", record.asset_id));
|
||||
@@ -1141,6 +1154,24 @@ fn group_admin_editor_asset_records(
|
||||
groups
|
||||
}
|
||||
|
||||
fn paginate_admin_editor_asset_records(
|
||||
records: Vec<AdminEditorAssetRecord>,
|
||||
page_size: usize,
|
||||
) -> Result<(Vec<Vec<AdminEditorAssetRecord>>, Option<String>), String> {
|
||||
let mut groups = group_admin_editor_asset_records(records);
|
||||
let next_cursor = if groups.len() > page_size {
|
||||
let cursor_record = groups
|
||||
.get(page_size.saturating_sub(1))
|
||||
.and_then(|group| group.first())
|
||||
.ok_or_else(|| "生成素材分页游标失败:分页末项不存在".to_string())?;
|
||||
Some(admin_editor_asset_cursor(cursor_record)?)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
groups.truncate(page_size);
|
||||
Ok((groups, next_cursor))
|
||||
}
|
||||
|
||||
fn admin_editor_asset_group_payload(
|
||||
state: &AppState,
|
||||
mut records: Vec<AdminEditorAssetRecord>,
|
||||
@@ -1262,49 +1293,88 @@ fn resolve_admin_editor_asset_generator(record: &AdminEditorAssetRecord) -> Stri
|
||||
.to_string()
|
||||
}
|
||||
|
||||
fn admin_editor_asset_cursor(record: &AdminEditorAssetRecord) -> Option<String> {
|
||||
let created_at_micros = parse_timestamp_text_to_micros(record.created_at.as_str())?;
|
||||
Some(format!("{created_at_micros}:{}", record.asset_id))
|
||||
fn admin_editor_asset_cursor(record: &AdminEditorAssetRecord) -> Result<String, String> {
|
||||
let created_at_micros = parse_timestamp_text_to_micros(record.created_at.as_str())
|
||||
.ok_or_else(|| format!("素材 {} 的创建时间格式非法", record.asset_id))?;
|
||||
Ok(format!("{created_at_micros}:{}", record.asset_id))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
fn matches_admin_editor_asset_keyword(
|
||||
payload: &AdminEditorAssetPayload,
|
||||
keyword: Option<&str>,
|
||||
) -> bool {
|
||||
let Some(keyword) = keyword else {
|
||||
return true;
|
||||
fn validate_admin_editor_asset_cursor(cursor: Option<&str>) -> Result<(), String> {
|
||||
let Some(cursor) = cursor else {
|
||||
return Ok(());
|
||||
};
|
||||
let haystack = [
|
||||
payload.asset_id.as_str(),
|
||||
payload.owner_user_id.as_str(),
|
||||
payload.author_display_name.as_deref().unwrap_or_default(),
|
||||
payload
|
||||
.author_public_user_code
|
||||
.as_deref()
|
||||
.unwrap_or_default(),
|
||||
payload.folder_id.as_str(),
|
||||
payload.label.as_str(),
|
||||
payload.asset_object_id.as_deref().unwrap_or_default(),
|
||||
payload.image_src.as_str(),
|
||||
payload.object_key.as_deref().unwrap_or_default(),
|
||||
payload.source_type.as_str(),
|
||||
payload.prompt.as_deref().unwrap_or_default(),
|
||||
payload.actual_prompt.as_deref().unwrap_or_default(),
|
||||
payload.model.as_deref().unwrap_or_default(),
|
||||
payload.provider.as_deref().unwrap_or_default(),
|
||||
payload.task_id.as_deref().unwrap_or_default(),
|
||||
payload.asset_kind.as_deref().unwrap_or_default(),
|
||||
payload.source_resource_id.as_deref().unwrap_or_default(),
|
||||
payload.thumbnail_src.as_deref().unwrap_or_default(),
|
||||
]
|
||||
.join(" ")
|
||||
.to_lowercase();
|
||||
haystack.contains(keyword)
|
||||
|| payload
|
||||
.generation_inputs
|
||||
let Some((created_at_micros, asset_id)) = cursor.split_once(':') else {
|
||||
return Err("cursor 格式非法".to_string());
|
||||
};
|
||||
if created_at_micros.parse::<i64>().is_err() || asset_id.trim().is_empty() {
|
||||
return Err("cursor 格式非法".to_string());
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn resolve_admin_editor_asset_filters(
|
||||
state: &AppState,
|
||||
mut owner_user_id: Option<String>,
|
||||
mut keyword: Option<String>,
|
||||
) -> Result<Option<(Option<String>, Option<String>)>, AppError> {
|
||||
if let Some(public_user_code) = owner_user_id
|
||||
.as_deref()
|
||||
.and_then(normalize_admin_public_user_code_candidate)
|
||||
{
|
||||
owner_user_id = resolve_admin_editor_asset_public_user_id(state, &public_user_code)?;
|
||||
if owner_user_id.is_none() {
|
||||
return Ok(None);
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(public_user_code) = keyword
|
||||
.as_deref()
|
||||
.and_then(normalize_admin_public_user_code_candidate)
|
||||
{
|
||||
let Some(keyword_owner_user_id) =
|
||||
resolve_admin_editor_asset_public_user_id(state, &public_user_code)?
|
||||
else {
|
||||
return Ok(None);
|
||||
};
|
||||
if owner_user_id
|
||||
.as_ref()
|
||||
.is_some_and(|value| value.to_string().to_lowercase().contains(keyword))
|
||||
.is_some_and(|owner_user_id| owner_user_id != &keyword_owner_user_id)
|
||||
{
|
||||
return Ok(None);
|
||||
}
|
||||
owner_user_id = Some(keyword_owner_user_id);
|
||||
keyword = None;
|
||||
}
|
||||
|
||||
Ok(Some((owner_user_id, keyword)))
|
||||
}
|
||||
|
||||
fn normalize_admin_public_user_code_candidate(value: &str) -> Option<String> {
|
||||
let compact = value
|
||||
.trim()
|
||||
.chars()
|
||||
.filter(|character| character.is_ascii_alphanumeric())
|
||||
.collect::<String>()
|
||||
.to_ascii_uppercase();
|
||||
let digits = compact.strip_prefix("SY")?;
|
||||
if digits.is_empty() || !digits.chars().all(|character| character.is_ascii_digit()) {
|
||||
return None;
|
||||
}
|
||||
module_auth::normalize_public_user_code(value).ok()
|
||||
}
|
||||
|
||||
fn resolve_admin_editor_asset_public_user_id(
|
||||
state: &AppState,
|
||||
public_user_code: &str,
|
||||
) -> Result<Option<String>, AppError> {
|
||||
state
|
||||
.auth_user_service()
|
||||
.get_user_by_public_user_code(public_user_code)
|
||||
.map(|user| user.map(|user| user.id))
|
||||
.map_err(|error| {
|
||||
AppError::from_status(StatusCode::BAD_GATEWAY)
|
||||
.with_message(format!("读取素材作者认证信息失败:{error}"))
|
||||
})
|
||||
}
|
||||
|
||||
fn normalize_admin_optional(value: Option<String>) -> Option<String> {
|
||||
@@ -3501,6 +3571,22 @@ fn parse_timestamp_text_to_micros(text: &str) -> Option<i64> {
|
||||
if let Ok(value) = trimmed.parse::<i64>() {
|
||||
return Some(value);
|
||||
}
|
||||
if let Some((seconds, micros)) = trimmed
|
||||
.strip_suffix('Z')
|
||||
.and_then(|value| value.split_once('.'))
|
||||
{
|
||||
if let Ok(seconds) = seconds.parse::<i64>()
|
||||
&& !micros.is_empty()
|
||||
&& micros.len() <= 6
|
||||
&& micros.chars().all(|character| character.is_ascii_digit())
|
||||
{
|
||||
let micros = format!("{micros:0<6}").parse::<i128>().ok()?;
|
||||
let value = i128::from(seconds)
|
||||
.checked_mul(1_000_000)?
|
||||
.checked_add(micros)?;
|
||||
return i64::try_from(value).ok();
|
||||
}
|
||||
}
|
||||
OffsetDateTime::parse(trimmed, &Rfc3339)
|
||||
.ok()
|
||||
.and_then(|timestamp| i64::try_from(timestamp.unix_timestamp_nanos() / 1_000).ok())
|
||||
@@ -3846,12 +3932,14 @@ mod tests {
|
||||
clamp_admin_tracking_event_limit, enforce_admin_request_permission,
|
||||
finalize_admin_database_table_rows_response, group_admin_editor_asset_records,
|
||||
hash_admin_password, is_admin_account_not_found, is_safe_spacetime_table_name,
|
||||
matches_admin_editor_asset_keyword, normalize_debug_path, normalize_table_count_error,
|
||||
normalize_debug_path, normalize_table_count_error, paginate_admin_editor_asset_records,
|
||||
parse_admin_database_table_rows_sql_response, parse_admin_tracking_event_keys_sql_response,
|
||||
parse_admin_tracking_events_sql_response, parse_spacetime_sql_count_response,
|
||||
resolve_admin_dashboard_range, resolve_admin_dashboard_range_at,
|
||||
resolve_admin_database_table_sql_limit, timestamp_value_to_micros, trim_preview,
|
||||
verify_admin_password, wallet_ledger_source_type_to_string,
|
||||
parse_timestamp_text_to_micros, resolve_admin_dashboard_range,
|
||||
resolve_admin_dashboard_range_at, resolve_admin_database_table_sql_limit,
|
||||
resolve_admin_editor_asset_filters, timestamp_value_to_micros, trim_preview,
|
||||
validate_admin_editor_asset_cursor, verify_admin_password,
|
||||
wallet_ledger_source_type_to_string,
|
||||
};
|
||||
use axum::{
|
||||
http::{Method, StatusCode},
|
||||
@@ -3859,8 +3947,7 @@ mod tests {
|
||||
};
|
||||
use serde_json::json;
|
||||
use shared_contracts::admin::{
|
||||
AdminDashboardQuery, AdminDatabaseTableRowsQuery, AdminEditorAssetPayload,
|
||||
AdminTrackingEventListQuery,
|
||||
AdminDashboardQuery, AdminDatabaseTableRowsQuery, AdminTrackingEventListQuery,
|
||||
};
|
||||
use shared_contracts::assets::GetReadUrlQuery;
|
||||
use spacetime_client::{
|
||||
@@ -3934,6 +4021,7 @@ mod tests {
|
||||
model: Some("gpt-image-2".to_string()),
|
||||
provider: Some("VectorEngine".to_string()),
|
||||
task_id: Some(task_id.to_string()),
|
||||
group_task_id: None,
|
||||
asset_kind: Some("character".to_string()),
|
||||
generation_inputs: None,
|
||||
source_resource_id: None,
|
||||
@@ -4034,48 +4122,169 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn admin_editor_asset_keyword_matches_public_author_prompt_and_generation_inputs() {
|
||||
let payload = AdminEditorAssetPayload {
|
||||
asset_id: "asset-1".to_string(),
|
||||
owner_user_id: "user-1".to_string(),
|
||||
author_display_name: Some("作者昵称".to_string()),
|
||||
author_public_user_code: Some("SY-00000042".to_string()),
|
||||
folder_id: "folder-1".to_string(),
|
||||
label: "角色形象".to_string(),
|
||||
asset_object_id: Some("asset-object-1".to_string()),
|
||||
image_src: "/generated-character-drafts/editor/spec.png".to_string(),
|
||||
object_key: Some("generated-character-drafts/editor/spec.png".to_string()),
|
||||
width: 1024,
|
||||
height: 1024,
|
||||
source_type: "generated".to_string(),
|
||||
prompt: Some("完整提示词内容".to_string()),
|
||||
actual_prompt: None,
|
||||
model: Some("gpt-image-2".to_string()),
|
||||
provider: Some("character".to_string()),
|
||||
task_id: Some("task-1".to_string()),
|
||||
asset_kind: Some("character".to_string()),
|
||||
generation_inputs: Some(json!({"style": "clay"})),
|
||||
source_resource_id: Some("resource-1".to_string()),
|
||||
thumbnail_src: None,
|
||||
generation_cost_mud_points: 12,
|
||||
created_at: "2026-07-04T10:00:00Z".to_string(),
|
||||
updated_at: "2026-07-04T10:00:00Z".to_string(),
|
||||
generator: "角色生图".to_string(),
|
||||
task_generator: "角色生成".to_string(),
|
||||
task_cost_mud_points: 12,
|
||||
children: Vec::new(),
|
||||
};
|
||||
fn admin_editor_asset_group_uses_group_task_id_without_hiding_operation_task_id() {
|
||||
let final_output = test_admin_editor_asset_record(
|
||||
"asset-final",
|
||||
"vector-engine-source-task-1",
|
||||
"图标素材图集",
|
||||
"generated-icon-spritesheets/editor/spritesheet-task-1.png",
|
||||
9,
|
||||
);
|
||||
let mut manual_slice = test_admin_editor_asset_record(
|
||||
"asset-slice",
|
||||
"editor-atlas-split-operation-1",
|
||||
"素材 1",
|
||||
"generated-icon-spritesheet-assets/editor/slice-1.png",
|
||||
0,
|
||||
);
|
||||
manual_slice.group_task_id = Some("vector-engine-source-task-1".to_string());
|
||||
|
||||
assert!(matches_admin_editor_asset_keyword(
|
||||
&payload,
|
||||
Some("sy-00000042")
|
||||
));
|
||||
assert!(matches_admin_editor_asset_keyword(&payload, Some("提示词")));
|
||||
assert!(matches_admin_editor_asset_keyword(&payload, Some("clay")));
|
||||
assert!(!matches_admin_editor_asset_keyword(
|
||||
&payload,
|
||||
Some("不存在的素材")
|
||||
));
|
||||
let mut groups = group_admin_editor_asset_records(vec![final_output, manual_slice]);
|
||||
assert_eq!(groups.len(), 1);
|
||||
|
||||
let state = AppState::new(AppConfig::default()).expect("state should build");
|
||||
let payload = admin_editor_asset_group_payload(&state, groups.remove(0))
|
||||
.expect("group payload should exist");
|
||||
|
||||
assert_eq!(
|
||||
payload.task_id.as_deref(),
|
||||
Some("vector-engine-source-task-1")
|
||||
);
|
||||
assert_eq!(payload.children.len(), 1);
|
||||
assert_eq!(
|
||||
payload.children[0].task_id.as_deref(),
|
||||
Some("editor-atlas-split-operation-1")
|
||||
);
|
||||
assert_eq!(
|
||||
payload.children[0].group_task_id.as_deref(),
|
||||
Some("vector-engine-source-task-1")
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn admin_editor_asset_filters_resolve_public_user_code_before_querying_assets() {
|
||||
let state = AppState::new(AppConfig::default()).expect("state should build");
|
||||
state
|
||||
.auth_user_service()
|
||||
.ensure_orphan_work_owner_user("user-author-42", "author-42", "作者昵称", "SY-00000042")
|
||||
.expect("author should be inserted");
|
||||
state
|
||||
.auth_user_service()
|
||||
.ensure_orphan_work_owner_user("user-author-43", "author-43", "另一作者", "SY-00000043")
|
||||
.expect("second author should be inserted");
|
||||
|
||||
let owner_filter = resolve_admin_editor_asset_filters(
|
||||
&state,
|
||||
Some("SY-00000042".to_string()),
|
||||
Some("角色".to_string()),
|
||||
)
|
||||
.expect("owner filter should resolve")
|
||||
.expect("owner should exist");
|
||||
assert_eq!(owner_filter.0.as_deref(), Some("user-author-42"));
|
||||
assert_eq!(owner_filter.1.as_deref(), Some("角色"));
|
||||
|
||||
let keyword_filter =
|
||||
resolve_admin_editor_asset_filters(&state, None, Some("sy00000042".to_string()))
|
||||
.expect("keyword filter should resolve")
|
||||
.expect("owner should exist");
|
||||
assert_eq!(keyword_filter.0.as_deref(), Some("user-author-42"));
|
||||
assert_eq!(keyword_filter.1, None);
|
||||
|
||||
let matching_filters = resolve_admin_editor_asset_filters(
|
||||
&state,
|
||||
Some("user-author-42".to_string()),
|
||||
Some("SY-00000042".to_string()),
|
||||
)
|
||||
.expect("matching filters should resolve")
|
||||
.expect("matching author should exist");
|
||||
assert_eq!(matching_filters.0.as_deref(), Some("user-author-42"));
|
||||
assert_eq!(matching_filters.1, None);
|
||||
|
||||
assert_eq!(
|
||||
resolve_admin_editor_asset_filters(
|
||||
&state,
|
||||
Some("user-author-42".to_string()),
|
||||
Some("SY-00000043".to_string()),
|
||||
)
|
||||
.expect("conflicting filters should resolve to an empty query"),
|
||||
None
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
resolve_admin_editor_asset_filters(&state, None, Some("SY-00009999".to_string()),)
|
||||
.expect("missing author should produce an empty query"),
|
||||
None
|
||||
);
|
||||
assert_eq!(
|
||||
resolve_admin_editor_asset_filters(&state, Some("SY-00009999".to_string()), None,)
|
||||
.expect("missing owner should produce an empty query"),
|
||||
None
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn admin_editor_asset_cursor_accepts_shared_seconds_micros_timestamp() {
|
||||
let mut record = test_admin_editor_asset_record(
|
||||
"asset-page-end",
|
||||
"task-page-end",
|
||||
"分页末项",
|
||||
"generated-images/editor/page-end.png",
|
||||
3,
|
||||
);
|
||||
record.created_at = "1784087532.408003Z".to_string();
|
||||
|
||||
assert_eq!(
|
||||
super::admin_editor_asset_cursor(&record).expect("cursor should build"),
|
||||
"1784087532408003:asset-page-end"
|
||||
);
|
||||
assert_eq!(
|
||||
parse_timestamp_text_to_micros("2026-07-15T11:52:12+08:00"),
|
||||
Some(1_784_087_532_000_000)
|
||||
);
|
||||
assert_eq!(
|
||||
parse_timestamp_text_to_micros("-9223372036855.224192Z"),
|
||||
Some(i64::MIN)
|
||||
);
|
||||
|
||||
record.created_at = "invalid-timestamp".to_string();
|
||||
assert!(super::admin_editor_asset_cursor(&record).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn admin_editor_asset_cursor_rejects_malformed_input_instead_of_repeating_first_page() {
|
||||
assert!(validate_admin_editor_asset_cursor(None).is_ok());
|
||||
assert!(validate_admin_editor_asset_cursor(Some("123:asset-1")).is_ok());
|
||||
assert!(validate_admin_editor_asset_cursor(Some("invalid")).is_err());
|
||||
assert!(validate_admin_editor_asset_cursor(Some("abc:asset-1")).is_err());
|
||||
assert!(validate_admin_editor_asset_cursor(Some("123: ")).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn admin_editor_asset_page_returns_cursor_for_eighty_first_task() {
|
||||
let mut records = (0..81)
|
||||
.map(|index| {
|
||||
test_admin_editor_asset_record(
|
||||
format!("asset-{index}").as_str(),
|
||||
format!("task-{index}").as_str(),
|
||||
format!("素材 {index}").as_str(),
|
||||
format!("generated-images/editor/{index}.png").as_str(),
|
||||
3,
|
||||
)
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
records[79].created_at = "1784087532.408003Z".to_string();
|
||||
|
||||
let (groups, next_cursor) =
|
||||
paginate_admin_editor_asset_records(records, 80).expect("page should build");
|
||||
|
||||
assert_eq!(groups.len(), 80);
|
||||
assert_eq!(next_cursor.as_deref(), Some("1784087532408003:asset-79"));
|
||||
assert!(
|
||||
groups
|
||||
.iter()
|
||||
.flatten()
|
||||
.all(|record| record.asset_id != "asset-80")
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -38,7 +38,8 @@ use shared_kernel::build_prefixed_uuid_id;
|
||||
use spacetime_client::{
|
||||
EditorAssetCreateRecordInput, EditorAssetDeleteRecordInput, EditorAssetFolderCreateRecordInput,
|
||||
EditorAssetFolderDeleteRecordInput, EditorAssetFolderRecord,
|
||||
EditorAssetFolderUpdateRecordInput, EditorAssetLibraryRecord,
|
||||
EditorAssetFolderUpdateRecordInput, EditorAssetGroupCohortCompleteRecordInput,
|
||||
EditorAssetGroupSourceLookupRecordInput, EditorAssetLibraryRecord,
|
||||
EditorAssetMediaRepairRecordInput, EditorAssetRecord, EditorAssetUpdateRecordInput,
|
||||
EditorCanvasRecord, EditorCanvasViewportRecord, EditorProjectCreateRecordInput,
|
||||
EditorProjectDeleteRecordInput, EditorProjectGetRecordInput,
|
||||
@@ -461,6 +462,8 @@ pub(crate) struct PersistEditorGeneratedAssetInput {
|
||||
pub(crate) model: String,
|
||||
pub(crate) provider: String,
|
||||
pub(crate) task_id: String,
|
||||
pub(crate) group_task_id: Option<String>,
|
||||
pub(crate) group_task_expected_asset_count: Option<u32>,
|
||||
pub(crate) source_resource_id: Option<String>,
|
||||
pub(crate) asset_kind: Option<String>,
|
||||
pub(crate) generation_inputs: Option<Value>,
|
||||
@@ -1203,6 +1206,8 @@ pub async fn create_editor_asset(
|
||||
now_micros: current_utc_micros(),
|
||||
thumbnail_src: None,
|
||||
generation_cost_mud_points: 0,
|
||||
group_task_id: None,
|
||||
group_task_expected_asset_count: None,
|
||||
})
|
||||
.await
|
||||
.map_err(map_editor_project_error)?;
|
||||
@@ -1740,6 +1745,8 @@ pub(crate) async fn generate_editor_image_for_owner(
|
||||
model: output_model,
|
||||
provider: output_provider,
|
||||
task_id: generated.task_id.clone(),
|
||||
group_task_id: None,
|
||||
group_task_expected_asset_count: None,
|
||||
source_resource_id: provider_source_resource_id.or(payload.source_resource_id),
|
||||
asset_kind: payload.asset_kind,
|
||||
generation_inputs: output_generation_inputs,
|
||||
@@ -2561,6 +2568,8 @@ pub(crate) async fn edit_editor_image_for_owner(
|
||||
model: generation_options.model.to_string(),
|
||||
provider: "VectorEngine".to_string(),
|
||||
task_id: generated.task_id.clone(),
|
||||
group_task_id: None,
|
||||
group_task_expected_asset_count: None,
|
||||
source_resource_id: payload.source_resource_id,
|
||||
asset_kind: payload.asset_kind,
|
||||
generation_inputs: payload.generation_inputs,
|
||||
@@ -2692,6 +2701,8 @@ pub(crate) async fn remove_editor_image_background_for_owner(
|
||||
model: "BiRefNet".to_string(),
|
||||
provider: "BiRefNet".to_string(),
|
||||
task_id: task_id.clone(),
|
||||
group_task_id: None,
|
||||
group_task_expected_asset_count: None,
|
||||
source_resource_id: payload.source_resource_id,
|
||||
asset_kind: payload.asset_kind,
|
||||
generation_inputs: payload.generation_inputs,
|
||||
@@ -3404,6 +3415,7 @@ struct PersistEditorSpritesheetSlicesInput {
|
||||
asset_folder_id: Option<String>,
|
||||
source_resource_id: Option<String>,
|
||||
task_id: String,
|
||||
group_task_id: Option<String>,
|
||||
prompt: String,
|
||||
actual_prompt: Option<String>,
|
||||
model: String,
|
||||
@@ -3428,6 +3440,12 @@ async fn persist_editor_spritesheet_slices(
|
||||
);
|
||||
}
|
||||
|
||||
let expected_asset_count = u32::try_from(slices.len()).map_err(|_| {
|
||||
AppError::from_status(StatusCode::UNPROCESSABLE_ENTITY).with_details(json!({
|
||||
"provider": "editor-icon-spritesheet-slicing",
|
||||
"message": "图集拆分产物数量超过支持范围。",
|
||||
}))
|
||||
})?;
|
||||
let mut results = Vec::with_capacity(slices.len());
|
||||
for (index, slice) in slices.into_iter().enumerate() {
|
||||
let image = DownloadedOpenAiImage {
|
||||
@@ -3468,6 +3486,11 @@ async fn persist_editor_spritesheet_slices(
|
||||
model: input.model.clone(),
|
||||
provider: input.provider.clone(),
|
||||
task_id: input.task_id.clone(),
|
||||
group_task_id: input.group_task_id.clone(),
|
||||
group_task_expected_asset_count: input
|
||||
.group_task_id
|
||||
.as_ref()
|
||||
.map(|_| expected_asset_count),
|
||||
source_resource_id: input.source_resource_id.clone(),
|
||||
asset_kind: Some("icon".to_string()),
|
||||
generation_inputs: input.generation_inputs.clone(),
|
||||
@@ -3485,6 +3508,24 @@ async fn persist_editor_spritesheet_slices(
|
||||
asset: record.asset,
|
||||
});
|
||||
}
|
||||
if let Some(group_task_id) = input.group_task_id.as_deref() {
|
||||
let asset_ids = results
|
||||
.iter()
|
||||
.filter_map(|result| result.asset.as_ref().map(|asset| asset.asset_id.clone()))
|
||||
.collect::<Vec<_>>();
|
||||
state
|
||||
.spacetime_client()
|
||||
.complete_editor_asset_group_cohort(EditorAssetGroupCohortCompleteRecordInput {
|
||||
owner_user_id: input.owner_user_id,
|
||||
task_id: input.task_id,
|
||||
group_task_id: group_task_id.to_string(),
|
||||
expected_asset_count,
|
||||
asset_ids,
|
||||
completed_at_micros: current_utc_micros(),
|
||||
})
|
||||
.await
|
||||
.map_err(map_editor_project_error)?;
|
||||
}
|
||||
Ok(results)
|
||||
}
|
||||
|
||||
@@ -3778,6 +3819,8 @@ pub(crate) async fn generate_editor_icon_spritesheet_for_owner(
|
||||
model: removal.model.clone(),
|
||||
provider: removal.provider.to_string(),
|
||||
task_id: generated.task_id.clone(),
|
||||
group_task_id: None,
|
||||
group_task_expected_asset_count: None,
|
||||
source_resource_id: source_record
|
||||
.resource
|
||||
.as_ref()
|
||||
@@ -3812,6 +3855,7 @@ pub(crate) async fn generate_editor_icon_spritesheet_for_owner(
|
||||
.as_ref()
|
||||
.map(|resource| resource.resource_id.clone()),
|
||||
task_id: generated.task_id.clone(),
|
||||
group_task_id: None,
|
||||
prompt: "自动拆分图集".to_string(),
|
||||
actual_prompt: None,
|
||||
model: "connected-components".to_string(),
|
||||
@@ -3938,6 +3982,7 @@ pub async fn split_editor_icon_spritesheet(
|
||||
.resources
|
||||
.iter()
|
||||
.find(|resource| resource.resource_id == source_resource_id)
|
||||
.cloned()
|
||||
.ok_or_else(|| {
|
||||
AppError::from_status(StatusCode::NOT_FOUND).with_details(json!({
|
||||
"provider": "editor-icon-spritesheet-slicing",
|
||||
@@ -3952,6 +3997,14 @@ pub async fn split_editor_icon_spritesheet(
|
||||
})),
|
||||
);
|
||||
}
|
||||
let task_id = build_prefixed_uuid_id("editor-atlas-split-");
|
||||
let group_task_id = resolve_editor_manual_atlas_split_group_task_id(
|
||||
&state,
|
||||
owner_user_id.as_str(),
|
||||
&source_resource,
|
||||
)
|
||||
.await?
|
||||
.unwrap_or_else(|| task_id.clone());
|
||||
|
||||
let reference =
|
||||
parse_editor_reference_image(&state, owner_user_id.as_str(), source_resource_id.as_str())
|
||||
@@ -3970,7 +4023,6 @@ pub async fn split_editor_icon_spritesheet(
|
||||
}))
|
||||
})?;
|
||||
validate_editor_icon_spritesheet_manual_slice_count(slices.len())?;
|
||||
let task_id = build_prefixed_uuid_id("editor-atlas-split-");
|
||||
let prompt = source_resource
|
||||
.prompt
|
||||
.clone()
|
||||
@@ -3996,6 +4048,7 @@ pub async fn split_editor_icon_spritesheet(
|
||||
.or_else(|| Some(EDITOR_ASSET_DEFAULT_FOLDER_ID.to_string())),
|
||||
source_resource_id: Some(source_resource_id),
|
||||
task_id,
|
||||
group_task_id: Some(group_task_id),
|
||||
prompt,
|
||||
actual_prompt,
|
||||
model,
|
||||
@@ -4059,6 +4112,47 @@ fn validate_editor_icon_spritesheet_manual_source(
|
||||
validate_editor_icon_spritesheet_manual_dimensions(width, height)
|
||||
}
|
||||
|
||||
async fn resolve_editor_manual_atlas_split_group_task_id(
|
||||
state: &AppState,
|
||||
owner_user_id: &str,
|
||||
source_resource: &EditorProjectResourceRecord,
|
||||
) -> Result<Option<String>, AppError> {
|
||||
let lookup = editor_manual_atlas_split_group_source_lookup_input(
|
||||
owner_user_id,
|
||||
source_resource.resource_id.as_str(),
|
||||
source_resource.source_resource_id.as_deref(),
|
||||
source_resource.asset_object_id.as_deref(),
|
||||
source_resource.object_key.as_deref(),
|
||||
);
|
||||
let asset = state
|
||||
.spacetime_client()
|
||||
.find_editor_asset_group_source(lookup)
|
||||
.await
|
||||
.map_err(map_editor_project_error)?;
|
||||
Ok(asset.and_then(|asset| normalize_optional_string(asset.task_id)))
|
||||
}
|
||||
|
||||
fn editor_manual_atlas_split_group_source_lookup_input(
|
||||
owner_user_id: &str,
|
||||
resource_id: &str,
|
||||
source_resource_id: Option<&str>,
|
||||
asset_object_id: Option<&str>,
|
||||
object_key: Option<&str>,
|
||||
) -> EditorAssetGroupSourceLookupRecordInput {
|
||||
let mut source_resource_ids = vec![resource_id.to_string()];
|
||||
if let Some(source_resource_id) =
|
||||
normalize_optional_string(source_resource_id.map(str::to_string))
|
||||
{
|
||||
source_resource_ids.push(source_resource_id);
|
||||
}
|
||||
EditorAssetGroupSourceLookupRecordInput {
|
||||
owner_user_id: owner_user_id.to_string(),
|
||||
source_resource_ids,
|
||||
asset_object_id: asset_object_id.map(str::to_string),
|
||||
object_key: object_key.map(str::to_string),
|
||||
}
|
||||
}
|
||||
|
||||
fn validate_editor_icon_spritesheet_manual_dimensions(
|
||||
width: u32,
|
||||
height: u32,
|
||||
@@ -4384,6 +4478,8 @@ pub(crate) async fn extract_editor_ui_design_assets_for_owner(
|
||||
model: removal.model.clone(),
|
||||
provider: removal.provider.to_string(),
|
||||
task_id: generated.task_id.clone(),
|
||||
group_task_id: None,
|
||||
group_task_expected_asset_count: None,
|
||||
source_resource_id: source_record
|
||||
.resource
|
||||
.as_ref()
|
||||
@@ -4415,6 +4511,7 @@ pub(crate) async fn extract_editor_ui_design_assets_for_owner(
|
||||
.as_ref()
|
||||
.map(|resource| resource.resource_id.clone()),
|
||||
task_id: generated.task_id.clone(),
|
||||
group_task_id: None,
|
||||
prompt: "自动拆分图集".to_string(),
|
||||
actual_prompt: None,
|
||||
model: "connected-components".to_string(),
|
||||
@@ -5166,6 +5263,8 @@ pub(crate) async fn persist_editor_generated_asset(
|
||||
generation_cost_mud_points: input.generation_cost_mud_points,
|
||||
now_micros,
|
||||
thumbnail_src: normalize_optional_string(input.thumbnail_src),
|
||||
group_task_id: normalize_optional_string(input.group_task_id),
|
||||
group_task_expected_asset_count: input.group_task_expected_asset_count,
|
||||
})
|
||||
.await
|
||||
.map_err(map_editor_project_error)?;
|
||||
@@ -5204,6 +5303,8 @@ pub(crate) async fn persist_editor_generated_media_asset(
|
||||
model: input.model,
|
||||
provider: input.provider,
|
||||
task_id: input.task_id,
|
||||
group_task_id: None,
|
||||
group_task_expected_asset_count: None,
|
||||
source_resource_id: input.source_resource_id,
|
||||
asset_kind: input.asset_kind,
|
||||
generation_inputs: input.generation_inputs,
|
||||
@@ -6471,6 +6572,8 @@ async fn persist_editor_provider_source_resource(
|
||||
model: input.model,
|
||||
provider: "VectorEngine".to_string(),
|
||||
task_id: input.task_id,
|
||||
group_task_id: None,
|
||||
group_task_expected_asset_count: None,
|
||||
source_resource_id: input.source_resource_id,
|
||||
asset_kind: input.asset_kind,
|
||||
generation_inputs: input.generation_inputs,
|
||||
@@ -9233,6 +9336,41 @@ mod tests {
|
||||
assert_eq!(request.canvas_completion.placeholder.width, 360.0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn editor_manual_atlas_split_lookup_keeps_original_provenance_after_project_copy() {
|
||||
let mut copied_resource = editor_project_resource_for_canvas_test(
|
||||
"copied-resource-atlas-1",
|
||||
"icon-spritesheet",
|
||||
1024,
|
||||
1024,
|
||||
);
|
||||
copied_resource.source_resource_id = Some("resource-atlas-1".to_string());
|
||||
copied_resource.asset_object_id = Some("asset-object-atlas-1".to_string());
|
||||
copied_resource.object_key = Some("generated/icon-atlas-1.png".to_string());
|
||||
|
||||
let lookup = editor_manual_atlas_split_group_source_lookup_input(
|
||||
"user-1",
|
||||
copied_resource.resource_id.as_str(),
|
||||
copied_resource.source_resource_id.as_deref(),
|
||||
copied_resource.asset_object_id.as_deref(),
|
||||
copied_resource.object_key.as_deref(),
|
||||
);
|
||||
|
||||
assert_eq!(lookup.owner_user_id, "user-1");
|
||||
assert_eq!(
|
||||
lookup.source_resource_ids,
|
||||
vec!["copied-resource-atlas-1", "resource-atlas-1"]
|
||||
);
|
||||
assert_eq!(
|
||||
lookup.asset_object_id.as_deref(),
|
||||
Some("asset-object-atlas-1")
|
||||
);
|
||||
assert_eq!(
|
||||
lookup.object_key.as_deref(),
|
||||
Some("generated/icon-atlas-1.png")
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn editor_bgfilter_seg_model_parser_defaults_and_rejects_unknown() {
|
||||
assert_eq!(
|
||||
|
||||
@@ -219,6 +219,7 @@ pub struct AdminEditorAssetPayload {
|
||||
pub model: Option<String>,
|
||||
pub provider: Option<String>,
|
||||
pub task_id: Option<String>,
|
||||
pub group_task_id: Option<String>,
|
||||
pub asset_kind: Option<String>,
|
||||
pub generation_inputs: Option<Value>,
|
||||
pub source_resource_id: Option<String>,
|
||||
|
||||
@@ -333,6 +333,31 @@ impl SpacetimeClient {
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn find_editor_asset_group_source(
|
||||
&self,
|
||||
input: EditorAssetGroupSourceLookupRecordInput,
|
||||
) -> Result<Option<EditorAssetRecord>, SpacetimeClientError> {
|
||||
let procedure_input = input.into();
|
||||
|
||||
self.call_after_connect(
|
||||
"find_editor_asset_group_source_and_return",
|
||||
move |connection, sender| {
|
||||
connection
|
||||
.procedures()
|
||||
.find_editor_asset_group_source_and_return_then(
|
||||
procedure_input,
|
||||
move |_, result| {
|
||||
let mapped = result
|
||||
.map_err(SpacetimeClientError::from_sdk_error)
|
||||
.and_then(map_optional_editor_asset_procedure_result);
|
||||
send_once(&sender, mapped);
|
||||
},
|
||||
);
|
||||
},
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn create_editor_asset_folder(
|
||||
&self,
|
||||
input: EditorAssetFolderCreateRecordInput,
|
||||
@@ -431,6 +456,31 @@ impl SpacetimeClient {
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn complete_editor_asset_group_cohort(
|
||||
&self,
|
||||
input: EditorAssetGroupCohortCompleteRecordInput,
|
||||
) -> Result<EditorAssetRecord, SpacetimeClientError> {
|
||||
let procedure_input = input.into();
|
||||
|
||||
self.call_after_connect(
|
||||
"complete_editor_asset_group_cohort_and_return",
|
||||
move |connection, sender| {
|
||||
connection
|
||||
.procedures()
|
||||
.complete_editor_asset_group_cohort_and_return_then(
|
||||
procedure_input,
|
||||
move |_, result| {
|
||||
let mapped = result
|
||||
.map_err(SpacetimeClientError::from_sdk_error)
|
||||
.and_then(map_editor_asset_procedure_result);
|
||||
send_once(&sender, mapped);
|
||||
},
|
||||
);
|
||||
},
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn update_editor_asset(
|
||||
&self,
|
||||
input: EditorAssetUpdateRecordInput,
|
||||
|
||||
@@ -39,7 +39,8 @@ pub use mapper::{
|
||||
EditorAgentConversationTouchRecordInput, EditorAssetCreateRecordInput,
|
||||
EditorAssetDeleteRecordInput, EditorAssetFolderCreateRecordInput,
|
||||
EditorAssetFolderDeleteRecordInput, EditorAssetFolderRecord,
|
||||
EditorAssetFolderUpdateRecordInput, EditorAssetLibraryRecord,
|
||||
EditorAssetFolderUpdateRecordInput, EditorAssetGroupCohortCompleteRecordInput,
|
||||
EditorAssetGroupSourceLookupRecordInput, EditorAssetLibraryRecord,
|
||||
EditorAssetMediaRepairRecordInput, EditorAssetRecord, EditorAssetUpdateRecordInput,
|
||||
EditorCanvasRecord, EditorCanvasViewportRecord, EditorGenerationModelPricingRecord,
|
||||
EditorGenerationPricingConfigRecord, EditorGenerationPricingConfigUpsertRecordInput,
|
||||
|
||||
@@ -91,7 +91,8 @@ pub use self::editor_project::{
|
||||
AdminEditorAssetListRecordInput, AdminEditorAssetRecord, EditorAssetCreateRecordInput,
|
||||
EditorAssetDeleteRecordInput, EditorAssetFolderCreateRecordInput,
|
||||
EditorAssetFolderDeleteRecordInput, EditorAssetFolderRecord,
|
||||
EditorAssetFolderUpdateRecordInput, EditorAssetLibraryRecord,
|
||||
EditorAssetFolderUpdateRecordInput, EditorAssetGroupCohortCompleteRecordInput,
|
||||
EditorAssetGroupSourceLookupRecordInput, EditorAssetLibraryRecord,
|
||||
EditorAssetMediaRepairRecordInput, EditorAssetRecord, EditorAssetUpdateRecordInput,
|
||||
EditorCanvasRecord, EditorCanvasViewportRecord, EditorGenerationModelPricingRecord,
|
||||
EditorGenerationPricingConfigRecord, EditorGenerationPricingConfigUpsertRecordInput,
|
||||
@@ -255,6 +256,7 @@ pub(crate) use self::editor_project::{
|
||||
map_editor_project_resource_procedure_result, map_editor_showcase_asset_list_procedure_result,
|
||||
map_editor_showcase_asset_procedure_result,
|
||||
map_editor_showcase_campaign_config_procedure_result,
|
||||
map_optional_editor_asset_procedure_result,
|
||||
};
|
||||
pub(crate) use self::external_api_key::{
|
||||
map_external_api_key_list_procedure_result, map_external_api_key_single_procedure_result,
|
||||
|
||||
@@ -121,6 +121,7 @@ pub struct AdminEditorAssetRecord {
|
||||
pub model: Option<String>,
|
||||
pub provider: Option<String>,
|
||||
pub task_id: Option<String>,
|
||||
pub group_task_id: Option<String>,
|
||||
pub asset_kind: Option<String>,
|
||||
pub generation_inputs: Option<serde_json::Value>,
|
||||
pub source_resource_id: Option<String>,
|
||||
@@ -344,6 +345,26 @@ pub struct EditorAssetCreateRecordInput {
|
||||
pub now_micros: i64,
|
||||
pub thumbnail_src: Option<String>,
|
||||
pub generation_cost_mud_points: u64,
|
||||
pub group_task_id: Option<String>,
|
||||
pub group_task_expected_asset_count: Option<u32>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub struct EditorAssetGroupSourceLookupRecordInput {
|
||||
pub owner_user_id: String,
|
||||
pub source_resource_ids: Vec<String>,
|
||||
pub asset_object_id: Option<String>,
|
||||
pub object_key: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub struct EditorAssetGroupCohortCompleteRecordInput {
|
||||
pub owner_user_id: String,
|
||||
pub task_id: String,
|
||||
pub group_task_id: String,
|
||||
pub expected_asset_count: u32,
|
||||
pub asset_ids: Vec<String>,
|
||||
pub completed_at_micros: i64,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
@@ -653,6 +674,36 @@ impl From<EditorAssetCreateRecordInput> for crate::module_bindings::EditorAssetC
|
||||
now_micros: input.now_micros,
|
||||
thumbnail_src: input.thumbnail_src,
|
||||
generation_cost_mud_points: input.generation_cost_mud_points,
|
||||
group_task_id: input.group_task_id,
|
||||
group_task_expected_asset_count: input.group_task_expected_asset_count,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<EditorAssetGroupSourceLookupRecordInput>
|
||||
for crate::module_bindings::EditorAssetGroupSourceLookupInput
|
||||
{
|
||||
fn from(input: EditorAssetGroupSourceLookupRecordInput) -> Self {
|
||||
Self {
|
||||
owner_user_id: input.owner_user_id,
|
||||
source_resource_ids: input.source_resource_ids,
|
||||
asset_object_id: input.asset_object_id,
|
||||
object_key: input.object_key,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<EditorAssetGroupCohortCompleteRecordInput>
|
||||
for crate::module_bindings::EditorAssetGroupCohortCompleteInput
|
||||
{
|
||||
fn from(input: EditorAssetGroupCohortCompleteRecordInput) -> Self {
|
||||
Self {
|
||||
owner_user_id: input.owner_user_id,
|
||||
task_id: input.task_id,
|
||||
group_task_id: input.group_task_id,
|
||||
expected_asset_count: input.expected_asset_count,
|
||||
asset_ids: input.asset_ids,
|
||||
completed_at_micros: input.completed_at_micros,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1003,6 +1054,16 @@ pub(crate) fn map_editor_asset_procedure_result(
|
||||
.ok_or_else(|| SpacetimeClientError::missing_snapshot("图片画布素材快照"))
|
||||
}
|
||||
|
||||
pub(crate) fn map_optional_editor_asset_procedure_result(
|
||||
result: EditorAssetProcedureResult,
|
||||
) -> Result<Option<EditorAssetRecord>, SpacetimeClientError> {
|
||||
if !result.ok {
|
||||
return Err(SpacetimeClientError::procedure_failed(result.error_message));
|
||||
}
|
||||
|
||||
result.asset.map(map_editor_asset_snapshot).transpose()
|
||||
}
|
||||
|
||||
pub(crate) fn map_admin_editor_asset_list_procedure_result(
|
||||
result: AdminEditorAssetListProcedureResult,
|
||||
) -> Result<Vec<AdminEditorAssetRecord>, SpacetimeClientError> {
|
||||
@@ -1239,6 +1300,7 @@ fn map_admin_editor_asset_snapshot(
|
||||
model: snapshot.model,
|
||||
provider: snapshot.provider,
|
||||
task_id: snapshot.task_id,
|
||||
group_task_id: snapshot.group_task_id,
|
||||
asset_kind: snapshot.asset_kind,
|
||||
generation_inputs,
|
||||
source_resource_id: snapshot.source_resource_id,
|
||||
|
||||
@@ -261,6 +261,7 @@ pub mod compile_visual_novel_work_profile_procedure;
|
||||
pub mod compile_wooden_fish_draft_procedure;
|
||||
pub mod complete_ai_stage_and_return_procedure;
|
||||
pub mod complete_ai_task_and_return_procedure;
|
||||
pub mod complete_editor_asset_group_cohort_and_return_procedure;
|
||||
pub mod complete_external_generation_job_and_return_procedure;
|
||||
pub mod complete_profile_recharge_order_expiration_schedule_and_return_procedure;
|
||||
pub mod confirm_asset_object_and_return_procedure;
|
||||
@@ -415,6 +416,12 @@ pub mod editor_asset_folder_snapshot_type;
|
||||
pub mod editor_asset_folder_table;
|
||||
pub mod editor_asset_folder_type;
|
||||
pub mod editor_asset_folder_update_input_type;
|
||||
pub mod editor_asset_group_cohort_complete_input_type;
|
||||
pub mod editor_asset_group_cohort_table;
|
||||
pub mod editor_asset_group_cohort_type;
|
||||
pub mod editor_asset_group_source_lookup_input_type;
|
||||
pub mod editor_asset_group_source_provenance_table;
|
||||
pub mod editor_asset_group_source_provenance_type;
|
||||
pub mod editor_asset_library_get_input_type;
|
||||
pub mod editor_asset_library_procedure_result_type;
|
||||
pub mod editor_asset_library_snapshot_type;
|
||||
@@ -534,6 +541,7 @@ pub mod finalize_match_3_d_agent_message_turn_procedure;
|
||||
pub mod finalize_puzzle_agent_message_turn_procedure;
|
||||
pub mod finalize_square_hole_agent_message_turn_procedure;
|
||||
pub mod finalize_visual_novel_agent_message_turn_procedure;
|
||||
pub mod find_editor_asset_group_source_and_return_procedure;
|
||||
pub mod finish_bark_battle_run_procedure;
|
||||
pub mod finish_match_3_d_time_up_procedure;
|
||||
pub mod finish_square_hole_time_up_procedure;
|
||||
@@ -1684,6 +1692,7 @@ pub use compile_visual_novel_work_profile_procedure::compile_visual_novel_work_p
|
||||
pub use compile_wooden_fish_draft_procedure::compile_wooden_fish_draft;
|
||||
pub use complete_ai_stage_and_return_procedure::complete_ai_stage_and_return;
|
||||
pub use complete_ai_task_and_return_procedure::complete_ai_task_and_return;
|
||||
pub use complete_editor_asset_group_cohort_and_return_procedure::complete_editor_asset_group_cohort_and_return;
|
||||
pub use complete_external_generation_job_and_return_procedure::complete_external_generation_job_and_return;
|
||||
pub use complete_profile_recharge_order_expiration_schedule_and_return_procedure::complete_profile_recharge_order_expiration_schedule_and_return;
|
||||
pub use confirm_asset_object_and_return_procedure::confirm_asset_object_and_return;
|
||||
@@ -1838,6 +1847,12 @@ pub use editor_asset_folder_snapshot_type::EditorAssetFolderSnapshot;
|
||||
pub use editor_asset_folder_table::*;
|
||||
pub use editor_asset_folder_type::EditorAssetFolder;
|
||||
pub use editor_asset_folder_update_input_type::EditorAssetFolderUpdateInput;
|
||||
pub use editor_asset_group_cohort_complete_input_type::EditorAssetGroupCohortCompleteInput;
|
||||
pub use editor_asset_group_cohort_table::*;
|
||||
pub use editor_asset_group_cohort_type::EditorAssetGroupCohort;
|
||||
pub use editor_asset_group_source_lookup_input_type::EditorAssetGroupSourceLookupInput;
|
||||
pub use editor_asset_group_source_provenance_table::*;
|
||||
pub use editor_asset_group_source_provenance_type::EditorAssetGroupSourceProvenance;
|
||||
pub use editor_asset_library_get_input_type::EditorAssetLibraryGetInput;
|
||||
pub use editor_asset_library_procedure_result_type::EditorAssetLibraryProcedureResult;
|
||||
pub use editor_asset_library_snapshot_type::EditorAssetLibrarySnapshot;
|
||||
@@ -1957,6 +1972,7 @@ pub use finalize_match_3_d_agent_message_turn_procedure::finalize_match_3_d_agen
|
||||
pub use finalize_puzzle_agent_message_turn_procedure::finalize_puzzle_agent_message_turn;
|
||||
pub use finalize_square_hole_agent_message_turn_procedure::finalize_square_hole_agent_message_turn;
|
||||
pub use finalize_visual_novel_agent_message_turn_procedure::finalize_visual_novel_agent_message_turn;
|
||||
pub use find_editor_asset_group_source_and_return_procedure::find_editor_asset_group_source_and_return;
|
||||
pub use finish_bark_battle_run_procedure::finish_bark_battle_run;
|
||||
pub use finish_match_3_d_time_up_procedure::finish_match_3_d_time_up;
|
||||
pub use finish_square_hole_time_up_procedure::finish_square_hole_time_up;
|
||||
@@ -3178,6 +3194,8 @@ pub struct DbUpdate {
|
||||
editor_agent_conversation: __sdk::TableUpdate<EditorAgentConversation>,
|
||||
editor_asset: __sdk::TableUpdate<EditorAsset>,
|
||||
editor_asset_folder: __sdk::TableUpdate<EditorAssetFolder>,
|
||||
editor_asset_group_cohort: __sdk::TableUpdate<EditorAssetGroupCohort>,
|
||||
editor_asset_group_source_provenance: __sdk::TableUpdate<EditorAssetGroupSourceProvenance>,
|
||||
editor_canvas: __sdk::TableUpdate<EditorCanvas>,
|
||||
editor_generation_pricing_config: __sdk::TableUpdate<EditorGenerationPricingConfig>,
|
||||
editor_generation_runtime_identity_rotation:
|
||||
@@ -3434,6 +3452,16 @@ impl TryFrom<__ws::v2::TransactionUpdate> for DbUpdate {
|
||||
"editor_asset_folder" => db_update
|
||||
.editor_asset_folder
|
||||
.append(editor_asset_folder_table::parse_table_update(table_update)?),
|
||||
"editor_asset_group_cohort" => db_update.editor_asset_group_cohort.append(
|
||||
editor_asset_group_cohort_table::parse_table_update(table_update)?,
|
||||
),
|
||||
"editor_asset_group_source_provenance" => {
|
||||
db_update.editor_asset_group_source_provenance.append(
|
||||
editor_asset_group_source_provenance_table::parse_table_update(
|
||||
table_update,
|
||||
)?,
|
||||
)
|
||||
}
|
||||
"editor_canvas" => db_update
|
||||
.editor_canvas
|
||||
.append(editor_canvas_table::parse_table_update(table_update)?),
|
||||
@@ -4019,6 +4047,18 @@ impl __sdk::DbUpdate for DbUpdate {
|
||||
&self.editor_asset_folder,
|
||||
)
|
||||
.with_updates_by_pk(|row| &row.folder_id);
|
||||
diff.editor_asset_group_cohort = cache
|
||||
.apply_diff_to_table::<EditorAssetGroupCohort>(
|
||||
"editor_asset_group_cohort",
|
||||
&self.editor_asset_group_cohort,
|
||||
)
|
||||
.with_updates_by_pk(|row| &row.cohort_id);
|
||||
diff.editor_asset_group_source_provenance = cache
|
||||
.apply_diff_to_table::<EditorAssetGroupSourceProvenance>(
|
||||
"editor_asset_group_source_provenance",
|
||||
&self.editor_asset_group_source_provenance,
|
||||
)
|
||||
.with_updates_by_pk(|row| &row.lookup_key);
|
||||
diff.editor_canvas = cache
|
||||
.apply_diff_to_table::<EditorCanvas>("editor_canvas", &self.editor_canvas)
|
||||
.with_updates_by_pk(|row| &row.canvas_id);
|
||||
@@ -4699,6 +4739,12 @@ impl __sdk::DbUpdate for DbUpdate {
|
||||
"editor_asset_folder" => db_update
|
||||
.editor_asset_folder
|
||||
.append(__sdk::parse_row_list_as_inserts(table_rows.rows)?),
|
||||
"editor_asset_group_cohort" => db_update
|
||||
.editor_asset_group_cohort
|
||||
.append(__sdk::parse_row_list_as_inserts(table_rows.rows)?),
|
||||
"editor_asset_group_source_provenance" => db_update
|
||||
.editor_asset_group_source_provenance
|
||||
.append(__sdk::parse_row_list_as_inserts(table_rows.rows)?),
|
||||
"editor_canvas" => db_update
|
||||
.editor_canvas
|
||||
.append(__sdk::parse_row_list_as_inserts(table_rows.rows)?),
|
||||
@@ -5153,6 +5199,12 @@ impl __sdk::DbUpdate for DbUpdate {
|
||||
"editor_asset_folder" => db_update
|
||||
.editor_asset_folder
|
||||
.append(__sdk::parse_row_list_as_deletes(table_rows.rows)?),
|
||||
"editor_asset_group_cohort" => db_update
|
||||
.editor_asset_group_cohort
|
||||
.append(__sdk::parse_row_list_as_deletes(table_rows.rows)?),
|
||||
"editor_asset_group_source_provenance" => db_update
|
||||
.editor_asset_group_source_provenance
|
||||
.append(__sdk::parse_row_list_as_deletes(table_rows.rows)?),
|
||||
"editor_canvas" => db_update
|
||||
.editor_canvas
|
||||
.append(__sdk::parse_row_list_as_deletes(table_rows.rows)?),
|
||||
@@ -5525,6 +5577,9 @@ pub struct AppliedDiff<'r> {
|
||||
editor_agent_conversation: __sdk::TableAppliedDiff<'r, EditorAgentConversation>,
|
||||
editor_asset: __sdk::TableAppliedDiff<'r, EditorAsset>,
|
||||
editor_asset_folder: __sdk::TableAppliedDiff<'r, EditorAssetFolder>,
|
||||
editor_asset_group_cohort: __sdk::TableAppliedDiff<'r, EditorAssetGroupCohort>,
|
||||
editor_asset_group_source_provenance:
|
||||
__sdk::TableAppliedDiff<'r, EditorAssetGroupSourceProvenance>,
|
||||
editor_canvas: __sdk::TableAppliedDiff<'r, EditorCanvas>,
|
||||
editor_generation_pricing_config: __sdk::TableAppliedDiff<'r, EditorGenerationPricingConfig>,
|
||||
editor_generation_runtime_identity_rotation:
|
||||
@@ -5856,6 +5911,16 @@ impl<'r> __sdk::AppliedDiff<'r> for AppliedDiff<'r> {
|
||||
&self.editor_asset_folder,
|
||||
event,
|
||||
);
|
||||
callbacks.invoke_table_row_callbacks::<EditorAssetGroupCohort>(
|
||||
"editor_asset_group_cohort",
|
||||
&self.editor_asset_group_cohort,
|
||||
event,
|
||||
);
|
||||
callbacks.invoke_table_row_callbacks::<EditorAssetGroupSourceProvenance>(
|
||||
"editor_asset_group_source_provenance",
|
||||
&self.editor_asset_group_source_provenance,
|
||||
event,
|
||||
);
|
||||
callbacks.invoke_table_row_callbacks::<EditorCanvas>(
|
||||
"editor_canvas",
|
||||
&self.editor_canvas,
|
||||
@@ -7067,6 +7132,8 @@ impl __sdk::SpacetimeModule for RemoteModule {
|
||||
editor_agent_conversation_table::register_table(client_cache);
|
||||
editor_asset_table::register_table(client_cache);
|
||||
editor_asset_folder_table::register_table(client_cache);
|
||||
editor_asset_group_cohort_table::register_table(client_cache);
|
||||
editor_asset_group_source_provenance_table::register_table(client_cache);
|
||||
editor_canvas_table::register_table(client_cache);
|
||||
editor_generation_pricing_config_table::register_table(client_cache);
|
||||
editor_generation_runtime_identity_rotation_table::register_table(client_cache);
|
||||
@@ -7216,6 +7283,8 @@ impl __sdk::SpacetimeModule for RemoteModule {
|
||||
"editor_agent_conversation",
|
||||
"editor_asset",
|
||||
"editor_asset_folder",
|
||||
"editor_asset_group_cohort",
|
||||
"editor_asset_group_source_provenance",
|
||||
"editor_canvas",
|
||||
"editor_generation_pricing_config",
|
||||
"editor_generation_runtime_identity_rotation",
|
||||
|
||||
+1
@@ -29,6 +29,7 @@ pub struct AdminEditorAssetSnapshot {
|
||||
pub generation_cost_mud_points: u64,
|
||||
pub created_at_micros: i64,
|
||||
pub updated_at_micros: i64,
|
||||
pub group_task_id: Option<String>,
|
||||
}
|
||||
|
||||
impl __sdk::InModule for AdminEditorAssetSnapshot {
|
||||
|
||||
+62
@@ -0,0 +1,62 @@
|
||||
// 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::editor_asset_group_cohort_complete_input_type::EditorAssetGroupCohortCompleteInput;
|
||||
use super::editor_asset_procedure_result_type::EditorAssetProcedureResult;
|
||||
|
||||
#[derive(__lib::ser::Serialize, __lib::de::Deserialize, Clone, PartialEq, Debug)]
|
||||
#[sats(crate = __lib)]
|
||||
struct CompleteEditorAssetGroupCohortAndReturnArgs {
|
||||
pub input: EditorAssetGroupCohortCompleteInput,
|
||||
}
|
||||
|
||||
impl __sdk::InModule for CompleteEditorAssetGroupCohortAndReturnArgs {
|
||||
type Module = super::RemoteModule;
|
||||
}
|
||||
|
||||
#[allow(non_camel_case_types)]
|
||||
/// Extension trait for access to the procedure `complete_editor_asset_group_cohort_and_return`.
|
||||
///
|
||||
/// Implemented for [`super::RemoteProcedures`].
|
||||
pub trait complete_editor_asset_group_cohort_and_return {
|
||||
fn complete_editor_asset_group_cohort_and_return(
|
||||
&self,
|
||||
input: EditorAssetGroupCohortCompleteInput,
|
||||
) {
|
||||
self.complete_editor_asset_group_cohort_and_return_then(input, |_, _| {});
|
||||
}
|
||||
|
||||
fn complete_editor_asset_group_cohort_and_return_then(
|
||||
&self,
|
||||
input: EditorAssetGroupCohortCompleteInput,
|
||||
|
||||
__callback: impl FnOnce(
|
||||
&super::ProcedureEventContext,
|
||||
Result<EditorAssetProcedureResult, __sdk::InternalError>,
|
||||
) + Send
|
||||
+ 'static,
|
||||
);
|
||||
}
|
||||
|
||||
impl complete_editor_asset_group_cohort_and_return for super::RemoteProcedures {
|
||||
fn complete_editor_asset_group_cohort_and_return_then(
|
||||
&self,
|
||||
input: EditorAssetGroupCohortCompleteInput,
|
||||
|
||||
__callback: impl FnOnce(
|
||||
&super::ProcedureEventContext,
|
||||
Result<EditorAssetProcedureResult, __sdk::InternalError>,
|
||||
) + Send
|
||||
+ 'static,
|
||||
) {
|
||||
self.imp
|
||||
.invoke_procedure_with_callback::<_, EditorAssetProcedureResult>(
|
||||
"complete_editor_asset_group_cohort_and_return",
|
||||
CompleteEditorAssetGroupCohortAndReturnArgs { input },
|
||||
__callback,
|
||||
);
|
||||
}
|
||||
}
|
||||
+2
@@ -28,6 +28,8 @@ pub struct EditorAssetCreateInput {
|
||||
pub now_micros: i64,
|
||||
pub thumbnail_src: Option<String>,
|
||||
pub generation_cost_mud_points: u64,
|
||||
pub group_task_id: Option<String>,
|
||||
pub group_task_expected_asset_count: Option<u32>,
|
||||
}
|
||||
|
||||
impl __sdk::InModule for EditorAssetCreateInput {
|
||||
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
// 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 EditorAssetGroupCohortCompleteInput {
|
||||
pub owner_user_id: String,
|
||||
pub task_id: String,
|
||||
pub group_task_id: String,
|
||||
pub expected_asset_count: u32,
|
||||
pub asset_ids: Vec<String>,
|
||||
pub completed_at_micros: i64,
|
||||
}
|
||||
|
||||
impl __sdk::InModule for EditorAssetGroupCohortCompleteInput {
|
||||
type Module = super::RemoteModule;
|
||||
}
|
||||
+162
@@ -0,0 +1,162 @@
|
||||
// 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 super::editor_asset_group_cohort_type::EditorAssetGroupCohort;
|
||||
use spacetimedb_sdk::__codegen::{self as __sdk, __lib, __sats, __ws};
|
||||
|
||||
/// Table handle for the table `editor_asset_group_cohort`.
|
||||
///
|
||||
/// Obtain a handle from the [`EditorAssetGroupCohortTableAccess::editor_asset_group_cohort`] method on [`super::RemoteTables`],
|
||||
/// like `ctx.db.editor_asset_group_cohort()`.
|
||||
///
|
||||
/// Users are encouraged not to explicitly reference this type,
|
||||
/// but to directly chain method calls,
|
||||
/// like `ctx.db.editor_asset_group_cohort().on_insert(...)`.
|
||||
pub struct EditorAssetGroupCohortTableHandle<'ctx> {
|
||||
imp: __sdk::TableHandle<EditorAssetGroupCohort>,
|
||||
ctx: std::marker::PhantomData<&'ctx super::RemoteTables>,
|
||||
}
|
||||
|
||||
#[allow(non_camel_case_types)]
|
||||
/// Extension trait for access to the table `editor_asset_group_cohort`.
|
||||
///
|
||||
/// Implemented for [`super::RemoteTables`].
|
||||
pub trait EditorAssetGroupCohortTableAccess {
|
||||
#[allow(non_snake_case)]
|
||||
/// Obtain a [`EditorAssetGroupCohortTableHandle`], which mediates access to the table `editor_asset_group_cohort`.
|
||||
fn editor_asset_group_cohort(&self) -> EditorAssetGroupCohortTableHandle<'_>;
|
||||
}
|
||||
|
||||
impl EditorAssetGroupCohortTableAccess for super::RemoteTables {
|
||||
fn editor_asset_group_cohort(&self) -> EditorAssetGroupCohortTableHandle<'_> {
|
||||
EditorAssetGroupCohortTableHandle {
|
||||
imp: self
|
||||
.imp
|
||||
.get_table::<EditorAssetGroupCohort>("editor_asset_group_cohort"),
|
||||
ctx: std::marker::PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct EditorAssetGroupCohortInsertCallbackId(__sdk::CallbackId);
|
||||
pub struct EditorAssetGroupCohortDeleteCallbackId(__sdk::CallbackId);
|
||||
|
||||
impl<'ctx> __sdk::Table for EditorAssetGroupCohortTableHandle<'ctx> {
|
||||
type Row = EditorAssetGroupCohort;
|
||||
type EventContext = super::EventContext;
|
||||
|
||||
fn count(&self) -> u64 {
|
||||
self.imp.count()
|
||||
}
|
||||
fn iter(&self) -> impl Iterator<Item = EditorAssetGroupCohort> + '_ {
|
||||
self.imp.iter()
|
||||
}
|
||||
|
||||
type InsertCallbackId = EditorAssetGroupCohortInsertCallbackId;
|
||||
|
||||
fn on_insert(
|
||||
&self,
|
||||
callback: impl FnMut(&Self::EventContext, &Self::Row) + Send + 'static,
|
||||
) -> EditorAssetGroupCohortInsertCallbackId {
|
||||
EditorAssetGroupCohortInsertCallbackId(self.imp.on_insert(Box::new(callback)))
|
||||
}
|
||||
|
||||
fn remove_on_insert(&self, callback: EditorAssetGroupCohortInsertCallbackId) {
|
||||
self.imp.remove_on_insert(callback.0)
|
||||
}
|
||||
|
||||
type DeleteCallbackId = EditorAssetGroupCohortDeleteCallbackId;
|
||||
|
||||
fn on_delete(
|
||||
&self,
|
||||
callback: impl FnMut(&Self::EventContext, &Self::Row) + Send + 'static,
|
||||
) -> EditorAssetGroupCohortDeleteCallbackId {
|
||||
EditorAssetGroupCohortDeleteCallbackId(self.imp.on_delete(Box::new(callback)))
|
||||
}
|
||||
|
||||
fn remove_on_delete(&self, callback: EditorAssetGroupCohortDeleteCallbackId) {
|
||||
self.imp.remove_on_delete(callback.0)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct EditorAssetGroupCohortUpdateCallbackId(__sdk::CallbackId);
|
||||
|
||||
impl<'ctx> __sdk::TableWithPrimaryKey for EditorAssetGroupCohortTableHandle<'ctx> {
|
||||
type UpdateCallbackId = EditorAssetGroupCohortUpdateCallbackId;
|
||||
|
||||
fn on_update(
|
||||
&self,
|
||||
callback: impl FnMut(&Self::EventContext, &Self::Row, &Self::Row) + Send + 'static,
|
||||
) -> EditorAssetGroupCohortUpdateCallbackId {
|
||||
EditorAssetGroupCohortUpdateCallbackId(self.imp.on_update(Box::new(callback)))
|
||||
}
|
||||
|
||||
fn remove_on_update(&self, callback: EditorAssetGroupCohortUpdateCallbackId) {
|
||||
self.imp.remove_on_update(callback.0)
|
||||
}
|
||||
}
|
||||
|
||||
/// Access to the `cohort_id` unique index on the table `editor_asset_group_cohort`,
|
||||
/// which allows point queries on the field of the same name
|
||||
/// via the [`EditorAssetGroupCohortCohortIdUnique::find`] method.
|
||||
///
|
||||
/// Users are encouraged not to explicitly reference this type,
|
||||
/// but to directly chain method calls,
|
||||
/// like `ctx.db.editor_asset_group_cohort().cohort_id().find(...)`.
|
||||
pub struct EditorAssetGroupCohortCohortIdUnique<'ctx> {
|
||||
imp: __sdk::UniqueConstraintHandle<EditorAssetGroupCohort, String>,
|
||||
phantom: std::marker::PhantomData<&'ctx super::RemoteTables>,
|
||||
}
|
||||
|
||||
impl<'ctx> EditorAssetGroupCohortTableHandle<'ctx> {
|
||||
/// Get a handle on the `cohort_id` unique index on the table `editor_asset_group_cohort`.
|
||||
pub fn cohort_id(&self) -> EditorAssetGroupCohortCohortIdUnique<'ctx> {
|
||||
EditorAssetGroupCohortCohortIdUnique {
|
||||
imp: self.imp.get_unique_constraint::<String>("cohort_id"),
|
||||
phantom: std::marker::PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'ctx> EditorAssetGroupCohortCohortIdUnique<'ctx> {
|
||||
/// Find the subscribed row whose `cohort_id` column value is equal to `col_val`,
|
||||
/// if such a row is present in the client cache.
|
||||
pub fn find(&self, col_val: &String) -> Option<EditorAssetGroupCohort> {
|
||||
self.imp.find(col_val)
|
||||
}
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
pub(super) fn register_table(client_cache: &mut __sdk::ClientCache<super::RemoteModule>) {
|
||||
let _table =
|
||||
client_cache.get_or_make_table::<EditorAssetGroupCohort>("editor_asset_group_cohort");
|
||||
_table.add_unique_constraint::<String>("cohort_id", |row| &row.cohort_id);
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
pub(super) fn parse_table_update(
|
||||
raw_updates: __ws::v2::TableUpdate,
|
||||
) -> __sdk::Result<__sdk::TableUpdate<EditorAssetGroupCohort>> {
|
||||
__sdk::TableUpdate::parse_table_update(raw_updates).map_err(|e| {
|
||||
__sdk::InternalError::failed_parse("TableUpdate<EditorAssetGroupCohort>", "TableUpdate")
|
||||
.with_cause(e)
|
||||
.into()
|
||||
})
|
||||
}
|
||||
|
||||
#[allow(non_camel_case_types)]
|
||||
/// Extension trait for query builder access to the table `EditorAssetGroupCohort`.
|
||||
///
|
||||
/// Implemented for [`__sdk::QueryTableAccessor`].
|
||||
pub trait editor_asset_group_cohortQueryTableAccess {
|
||||
#[allow(non_snake_case)]
|
||||
/// Get a query builder for the table `EditorAssetGroupCohort`.
|
||||
fn editor_asset_group_cohort(&self) -> __sdk::__query_builder::Table<EditorAssetGroupCohort>;
|
||||
}
|
||||
|
||||
impl editor_asset_group_cohortQueryTableAccess for __sdk::QueryTableAccessor {
|
||||
fn editor_asset_group_cohort(&self) -> __sdk::__query_builder::Table<EditorAssetGroupCohort> {
|
||||
__sdk::__query_builder::Table::new("editor_asset_group_cohort")
|
||||
}
|
||||
}
|
||||
+67
@@ -0,0 +1,67 @@
|
||||
// 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 EditorAssetGroupCohort {
|
||||
pub cohort_id: String,
|
||||
pub owner_user_id: String,
|
||||
pub task_id: String,
|
||||
pub group_task_id: String,
|
||||
pub expected_asset_count: u32,
|
||||
pub completed_at: __sdk::Timestamp,
|
||||
}
|
||||
|
||||
impl __sdk::InModule for EditorAssetGroupCohort {
|
||||
type Module = super::RemoteModule;
|
||||
}
|
||||
|
||||
/// Column accessor struct for the table `EditorAssetGroupCohort`.
|
||||
///
|
||||
/// Provides typed access to columns for query building.
|
||||
pub struct EditorAssetGroupCohortCols {
|
||||
pub cohort_id: __sdk::__query_builder::Col<EditorAssetGroupCohort, String>,
|
||||
pub owner_user_id: __sdk::__query_builder::Col<EditorAssetGroupCohort, String>,
|
||||
pub task_id: __sdk::__query_builder::Col<EditorAssetGroupCohort, String>,
|
||||
pub group_task_id: __sdk::__query_builder::Col<EditorAssetGroupCohort, String>,
|
||||
pub expected_asset_count: __sdk::__query_builder::Col<EditorAssetGroupCohort, u32>,
|
||||
pub completed_at: __sdk::__query_builder::Col<EditorAssetGroupCohort, __sdk::Timestamp>,
|
||||
}
|
||||
|
||||
impl __sdk::__query_builder::HasCols for EditorAssetGroupCohort {
|
||||
type Cols = EditorAssetGroupCohortCols;
|
||||
fn cols(table_name: &'static str) -> Self::Cols {
|
||||
EditorAssetGroupCohortCols {
|
||||
cohort_id: __sdk::__query_builder::Col::new(table_name, "cohort_id"),
|
||||
owner_user_id: __sdk::__query_builder::Col::new(table_name, "owner_user_id"),
|
||||
task_id: __sdk::__query_builder::Col::new(table_name, "task_id"),
|
||||
group_task_id: __sdk::__query_builder::Col::new(table_name, "group_task_id"),
|
||||
expected_asset_count: __sdk::__query_builder::Col::new(
|
||||
table_name,
|
||||
"expected_asset_count",
|
||||
),
|
||||
completed_at: __sdk::__query_builder::Col::new(table_name, "completed_at"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Indexed column accessor struct for the table `EditorAssetGroupCohort`.
|
||||
///
|
||||
/// Provides typed access to indexed columns for query building.
|
||||
pub struct EditorAssetGroupCohortIxCols {
|
||||
pub cohort_id: __sdk::__query_builder::IxCol<EditorAssetGroupCohort, String>,
|
||||
}
|
||||
|
||||
impl __sdk::__query_builder::HasIxCols for EditorAssetGroupCohort {
|
||||
type IxCols = EditorAssetGroupCohortIxCols;
|
||||
fn ix_cols(table_name: &'static str) -> Self::IxCols {
|
||||
EditorAssetGroupCohortIxCols {
|
||||
cohort_id: __sdk::__query_builder::IxCol::new(table_name, "cohort_id"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl __sdk::__query_builder::CanBeLookupTable for EditorAssetGroupCohort {}
|
||||
+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 EditorAssetGroupSourceLookupInput {
|
||||
pub owner_user_id: String,
|
||||
pub source_resource_ids: Vec<String>,
|
||||
pub asset_object_id: Option<String>,
|
||||
pub object_key: Option<String>,
|
||||
}
|
||||
|
||||
impl __sdk::InModule for EditorAssetGroupSourceLookupInput {
|
||||
type Module = super::RemoteModule;
|
||||
}
|
||||
+174
@@ -0,0 +1,174 @@
|
||||
// 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 super::editor_asset_group_source_provenance_type::EditorAssetGroupSourceProvenance;
|
||||
use spacetimedb_sdk::__codegen::{self as __sdk, __lib, __sats, __ws};
|
||||
|
||||
/// Table handle for the table `editor_asset_group_source_provenance`.
|
||||
///
|
||||
/// Obtain a handle from the [`EditorAssetGroupSourceProvenanceTableAccess::editor_asset_group_source_provenance`] method on [`super::RemoteTables`],
|
||||
/// like `ctx.db.editor_asset_group_source_provenance()`.
|
||||
///
|
||||
/// Users are encouraged not to explicitly reference this type,
|
||||
/// but to directly chain method calls,
|
||||
/// like `ctx.db.editor_asset_group_source_provenance().on_insert(...)`.
|
||||
pub struct EditorAssetGroupSourceProvenanceTableHandle<'ctx> {
|
||||
imp: __sdk::TableHandle<EditorAssetGroupSourceProvenance>,
|
||||
ctx: std::marker::PhantomData<&'ctx super::RemoteTables>,
|
||||
}
|
||||
|
||||
#[allow(non_camel_case_types)]
|
||||
/// Extension trait for access to the table `editor_asset_group_source_provenance`.
|
||||
///
|
||||
/// Implemented for [`super::RemoteTables`].
|
||||
pub trait EditorAssetGroupSourceProvenanceTableAccess {
|
||||
#[allow(non_snake_case)]
|
||||
/// Obtain a [`EditorAssetGroupSourceProvenanceTableHandle`], which mediates access to the table `editor_asset_group_source_provenance`.
|
||||
fn editor_asset_group_source_provenance(
|
||||
&self,
|
||||
) -> EditorAssetGroupSourceProvenanceTableHandle<'_>;
|
||||
}
|
||||
|
||||
impl EditorAssetGroupSourceProvenanceTableAccess for super::RemoteTables {
|
||||
fn editor_asset_group_source_provenance(
|
||||
&self,
|
||||
) -> EditorAssetGroupSourceProvenanceTableHandle<'_> {
|
||||
EditorAssetGroupSourceProvenanceTableHandle {
|
||||
imp: self.imp.get_table::<EditorAssetGroupSourceProvenance>(
|
||||
"editor_asset_group_source_provenance",
|
||||
),
|
||||
ctx: std::marker::PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct EditorAssetGroupSourceProvenanceInsertCallbackId(__sdk::CallbackId);
|
||||
pub struct EditorAssetGroupSourceProvenanceDeleteCallbackId(__sdk::CallbackId);
|
||||
|
||||
impl<'ctx> __sdk::Table for EditorAssetGroupSourceProvenanceTableHandle<'ctx> {
|
||||
type Row = EditorAssetGroupSourceProvenance;
|
||||
type EventContext = super::EventContext;
|
||||
|
||||
fn count(&self) -> u64 {
|
||||
self.imp.count()
|
||||
}
|
||||
fn iter(&self) -> impl Iterator<Item = EditorAssetGroupSourceProvenance> + '_ {
|
||||
self.imp.iter()
|
||||
}
|
||||
|
||||
type InsertCallbackId = EditorAssetGroupSourceProvenanceInsertCallbackId;
|
||||
|
||||
fn on_insert(
|
||||
&self,
|
||||
callback: impl FnMut(&Self::EventContext, &Self::Row) + Send + 'static,
|
||||
) -> EditorAssetGroupSourceProvenanceInsertCallbackId {
|
||||
EditorAssetGroupSourceProvenanceInsertCallbackId(self.imp.on_insert(Box::new(callback)))
|
||||
}
|
||||
|
||||
fn remove_on_insert(&self, callback: EditorAssetGroupSourceProvenanceInsertCallbackId) {
|
||||
self.imp.remove_on_insert(callback.0)
|
||||
}
|
||||
|
||||
type DeleteCallbackId = EditorAssetGroupSourceProvenanceDeleteCallbackId;
|
||||
|
||||
fn on_delete(
|
||||
&self,
|
||||
callback: impl FnMut(&Self::EventContext, &Self::Row) + Send + 'static,
|
||||
) -> EditorAssetGroupSourceProvenanceDeleteCallbackId {
|
||||
EditorAssetGroupSourceProvenanceDeleteCallbackId(self.imp.on_delete(Box::new(callback)))
|
||||
}
|
||||
|
||||
fn remove_on_delete(&self, callback: EditorAssetGroupSourceProvenanceDeleteCallbackId) {
|
||||
self.imp.remove_on_delete(callback.0)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct EditorAssetGroupSourceProvenanceUpdateCallbackId(__sdk::CallbackId);
|
||||
|
||||
impl<'ctx> __sdk::TableWithPrimaryKey for EditorAssetGroupSourceProvenanceTableHandle<'ctx> {
|
||||
type UpdateCallbackId = EditorAssetGroupSourceProvenanceUpdateCallbackId;
|
||||
|
||||
fn on_update(
|
||||
&self,
|
||||
callback: impl FnMut(&Self::EventContext, &Self::Row, &Self::Row) + Send + 'static,
|
||||
) -> EditorAssetGroupSourceProvenanceUpdateCallbackId {
|
||||
EditorAssetGroupSourceProvenanceUpdateCallbackId(self.imp.on_update(Box::new(callback)))
|
||||
}
|
||||
|
||||
fn remove_on_update(&self, callback: EditorAssetGroupSourceProvenanceUpdateCallbackId) {
|
||||
self.imp.remove_on_update(callback.0)
|
||||
}
|
||||
}
|
||||
|
||||
/// Access to the `lookup_key` unique index on the table `editor_asset_group_source_provenance`,
|
||||
/// which allows point queries on the field of the same name
|
||||
/// via the [`EditorAssetGroupSourceProvenanceLookupKeyUnique::find`] method.
|
||||
///
|
||||
/// Users are encouraged not to explicitly reference this type,
|
||||
/// but to directly chain method calls,
|
||||
/// like `ctx.db.editor_asset_group_source_provenance().lookup_key().find(...)`.
|
||||
pub struct EditorAssetGroupSourceProvenanceLookupKeyUnique<'ctx> {
|
||||
imp: __sdk::UniqueConstraintHandle<EditorAssetGroupSourceProvenance, String>,
|
||||
phantom: std::marker::PhantomData<&'ctx super::RemoteTables>,
|
||||
}
|
||||
|
||||
impl<'ctx> EditorAssetGroupSourceProvenanceTableHandle<'ctx> {
|
||||
/// Get a handle on the `lookup_key` unique index on the table `editor_asset_group_source_provenance`.
|
||||
pub fn lookup_key(&self) -> EditorAssetGroupSourceProvenanceLookupKeyUnique<'ctx> {
|
||||
EditorAssetGroupSourceProvenanceLookupKeyUnique {
|
||||
imp: self.imp.get_unique_constraint::<String>("lookup_key"),
|
||||
phantom: std::marker::PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'ctx> EditorAssetGroupSourceProvenanceLookupKeyUnique<'ctx> {
|
||||
/// Find the subscribed row whose `lookup_key` column value is equal to `col_val`,
|
||||
/// if such a row is present in the client cache.
|
||||
pub fn find(&self, col_val: &String) -> Option<EditorAssetGroupSourceProvenance> {
|
||||
self.imp.find(col_val)
|
||||
}
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
pub(super) fn register_table(client_cache: &mut __sdk::ClientCache<super::RemoteModule>) {
|
||||
let _table = client_cache.get_or_make_table::<EditorAssetGroupSourceProvenance>(
|
||||
"editor_asset_group_source_provenance",
|
||||
);
|
||||
_table.add_unique_constraint::<String>("lookup_key", |row| &row.lookup_key);
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
pub(super) fn parse_table_update(
|
||||
raw_updates: __ws::v2::TableUpdate,
|
||||
) -> __sdk::Result<__sdk::TableUpdate<EditorAssetGroupSourceProvenance>> {
|
||||
__sdk::TableUpdate::parse_table_update(raw_updates).map_err(|e| {
|
||||
__sdk::InternalError::failed_parse(
|
||||
"TableUpdate<EditorAssetGroupSourceProvenance>",
|
||||
"TableUpdate",
|
||||
)
|
||||
.with_cause(e)
|
||||
.into()
|
||||
})
|
||||
}
|
||||
|
||||
#[allow(non_camel_case_types)]
|
||||
/// Extension trait for query builder access to the table `EditorAssetGroupSourceProvenance`.
|
||||
///
|
||||
/// Implemented for [`__sdk::QueryTableAccessor`].
|
||||
pub trait editor_asset_group_source_provenanceQueryTableAccess {
|
||||
#[allow(non_snake_case)]
|
||||
/// Get a query builder for the table `EditorAssetGroupSourceProvenance`.
|
||||
fn editor_asset_group_source_provenance(
|
||||
&self,
|
||||
) -> __sdk::__query_builder::Table<EditorAssetGroupSourceProvenance>;
|
||||
}
|
||||
|
||||
impl editor_asset_group_source_provenanceQueryTableAccess for __sdk::QueryTableAccessor {
|
||||
fn editor_asset_group_source_provenance(
|
||||
&self,
|
||||
) -> __sdk::__query_builder::Table<EditorAssetGroupSourceProvenance> {
|
||||
__sdk::__query_builder::Table::new("editor_asset_group_source_provenance")
|
||||
}
|
||||
}
|
||||
+64
@@ -0,0 +1,64 @@
|
||||
// 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 EditorAssetGroupSourceProvenance {
|
||||
pub lookup_key: String,
|
||||
pub owner_user_id: String,
|
||||
pub asset_id: String,
|
||||
pub task_id: String,
|
||||
pub source_resource_id: String,
|
||||
pub created_at: __sdk::Timestamp,
|
||||
}
|
||||
|
||||
impl __sdk::InModule for EditorAssetGroupSourceProvenance {
|
||||
type Module = super::RemoteModule;
|
||||
}
|
||||
|
||||
/// Column accessor struct for the table `EditorAssetGroupSourceProvenance`.
|
||||
///
|
||||
/// Provides typed access to columns for query building.
|
||||
pub struct EditorAssetGroupSourceProvenanceCols {
|
||||
pub lookup_key: __sdk::__query_builder::Col<EditorAssetGroupSourceProvenance, String>,
|
||||
pub owner_user_id: __sdk::__query_builder::Col<EditorAssetGroupSourceProvenance, String>,
|
||||
pub asset_id: __sdk::__query_builder::Col<EditorAssetGroupSourceProvenance, String>,
|
||||
pub task_id: __sdk::__query_builder::Col<EditorAssetGroupSourceProvenance, String>,
|
||||
pub source_resource_id: __sdk::__query_builder::Col<EditorAssetGroupSourceProvenance, String>,
|
||||
pub created_at: __sdk::__query_builder::Col<EditorAssetGroupSourceProvenance, __sdk::Timestamp>,
|
||||
}
|
||||
|
||||
impl __sdk::__query_builder::HasCols for EditorAssetGroupSourceProvenance {
|
||||
type Cols = EditorAssetGroupSourceProvenanceCols;
|
||||
fn cols(table_name: &'static str) -> Self::Cols {
|
||||
EditorAssetGroupSourceProvenanceCols {
|
||||
lookup_key: __sdk::__query_builder::Col::new(table_name, "lookup_key"),
|
||||
owner_user_id: __sdk::__query_builder::Col::new(table_name, "owner_user_id"),
|
||||
asset_id: __sdk::__query_builder::Col::new(table_name, "asset_id"),
|
||||
task_id: __sdk::__query_builder::Col::new(table_name, "task_id"),
|
||||
source_resource_id: __sdk::__query_builder::Col::new(table_name, "source_resource_id"),
|
||||
created_at: __sdk::__query_builder::Col::new(table_name, "created_at"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Indexed column accessor struct for the table `EditorAssetGroupSourceProvenance`.
|
||||
///
|
||||
/// Provides typed access to indexed columns for query building.
|
||||
pub struct EditorAssetGroupSourceProvenanceIxCols {
|
||||
pub lookup_key: __sdk::__query_builder::IxCol<EditorAssetGroupSourceProvenance, String>,
|
||||
}
|
||||
|
||||
impl __sdk::__query_builder::HasIxCols for EditorAssetGroupSourceProvenance {
|
||||
type IxCols = EditorAssetGroupSourceProvenanceIxCols;
|
||||
fn ix_cols(table_name: &'static str) -> Self::IxCols {
|
||||
EditorAssetGroupSourceProvenanceIxCols {
|
||||
lookup_key: __sdk::__query_builder::IxCol::new(table_name, "lookup_key"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl __sdk::__query_builder::CanBeLookupTable for EditorAssetGroupSourceProvenance {}
|
||||
@@ -33,6 +33,7 @@ pub struct EditorAssetSnapshot {
|
||||
pub showcase_review_status: Option<String>,
|
||||
pub showcase_display_enabled: Option<bool>,
|
||||
pub showcase_like_count: Option<u64>,
|
||||
pub group_task_id: Option<String>,
|
||||
}
|
||||
|
||||
impl __sdk::InModule for EditorAssetSnapshot {
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user