1e186369c9
Project CI / AI game creator shell Rust crates (push) Successful in 1m24s
Project CI / AI game creator shell Rust smoke (push) Successful in 1m56s
Project CI / AI game creator shell Rust lane 1/2 (push) Has been cancelled
Project CI / Frontend tests (push) Has been cancelled
Project CI / Backend tests (push) Has been cancelled
Project CI / Repository checks (push) Has been cancelled
Project CI / AI game creator shell web tests (push) Has been cancelled
Project CI / AI game creator shell Rust lane 2/2 (push) Has been cancelled
Project CI / Native shell tests (push) Has been cancelled
Reviewed-on: https://git.genarrative.world/git/GenarrativeAI/Genarrative/pulls/446 Co-authored-by: Linghong <ink29535@proton.me> Co-committed-by: Linghong <ink29535@proton.me>
1543 lines
54 KiB
Rust
1543 lines
54 KiB
Rust
use std::collections::BTreeMap;
|
||
|
||
use serde::{Deserialize, Serialize};
|
||
use serde_json::Value;
|
||
|
||
#[cfg(any())]
|
||
use crate::creation_entry_config::{
|
||
CreationEntryEventBannerResponse, PublicWorkInteractionConfigResponse,
|
||
UnifiedCreationSpecResponse,
|
||
};
|
||
|
||
/// 后台 member 可被授予的一级 Tab 权限;账号管理仅 owner 可见,不进入该集合。
|
||
pub const ADMIN_TAB_PERMISSIONS: [&str; 19] = [
|
||
"dashboard",
|
||
"overview",
|
||
"tables",
|
||
"debug",
|
||
"tracking",
|
||
"agc-tracking",
|
||
"gray-release",
|
||
"redeem",
|
||
"invite",
|
||
"profile-wallet",
|
||
"tasks",
|
||
"recharge-products",
|
||
"recharge-orders",
|
||
"editor-generation-pricing",
|
||
"editor-showcase",
|
||
"editor-assets",
|
||
"agc-templates",
|
||
"error-reports",
|
||
"project-snapshots",
|
||
];
|
||
|
||
/// 私有工程快照的后台分页查询。
|
||
#[derive(Clone, Debug, Default, Deserialize)]
|
||
#[serde(rename_all = "camelCase")]
|
||
pub struct AdminProjectSnapshotsQuery {
|
||
pub cursor: Option<String>,
|
||
pub limit: Option<usize>,
|
||
/// 目标渠道;缺省用本部署渠道。
|
||
pub channel: Option<String>,
|
||
}
|
||
|
||
/// 后台可查看的快照渠道列表。
|
||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
|
||
#[serde(rename_all = "camelCase")]
|
||
pub struct AdminProjectSnapshotChannelsResponse {
|
||
/// 本部署渠道:上传与默认查询都用它。
|
||
pub default_channel: String,
|
||
/// 本部署渠道与远端已存在渠道的并集(升序,去重)。
|
||
pub channels: Vec<String>,
|
||
}
|
||
|
||
/// 私有工程快照下载查询:只接受渠道。
|
||
#[derive(Clone, Debug, Default, Deserialize)]
|
||
#[serde(rename_all = "camelCase")]
|
||
pub struct AdminProjectSnapshotDownloadQuery {
|
||
/// 目标渠道;缺省用本部署渠道。
|
||
pub channel: Option<String>,
|
||
}
|
||
|
||
#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq)]
|
||
#[serde(rename_all = "camelCase")]
|
||
pub enum AdminProjectSnapshotStatus {
|
||
Ready,
|
||
Partial,
|
||
Unverified,
|
||
}
|
||
|
||
/// 仅投影清单统计,不向浏览器下发私有对象键或签名。
|
||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
|
||
#[serde(rename_all = "camelCase")]
|
||
pub struct AdminProjectSnapshotItem {
|
||
pub user_id: String,
|
||
pub project_id: String,
|
||
pub project_name: String,
|
||
pub sync_revision: u64,
|
||
pub synced_at_ms: u64,
|
||
pub file_count: u32,
|
||
pub total_bytes: u64,
|
||
pub status: AdminProjectSnapshotStatus,
|
||
/// 项目所在渠道。
|
||
pub channel: String,
|
||
/// 项目归属用户昵称,与素材查询同口径;账号不可解析时为占位作者。
|
||
#[serde(default)]
|
||
pub author_display_name: Option<String>,
|
||
/// 项目归属用户陶泥号,与素材查询同口径;账号不可解析时为占位账号。
|
||
#[serde(default)]
|
||
pub author_public_user_code: Option<String>,
|
||
}
|
||
|
||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
|
||
#[serde(rename_all = "camelCase")]
|
||
pub struct AdminProjectSnapshotsResponse {
|
||
pub items: Vec<AdminProjectSnapshotItem>,
|
||
pub next_cursor: Option<String>,
|
||
}
|
||
|
||
/// 不随一级 Tab 自动授予的后台高风险操作权限。
|
||
pub const ADMIN_ACTION_PROFILE_WALLET_CONSUMPTION_RECONCILE: &str =
|
||
"profile-wallet-consumption-reconcile";
|
||
pub const ADMIN_ACTION_PERMISSIONS: [&str; 1] = [ADMIN_ACTION_PROFILE_WALLET_CONSUMPTION_RECONCILE];
|
||
|
||
#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq)]
|
||
#[serde(rename_all = "camelCase")]
|
||
pub enum AdminAccountRole {
|
||
Owner,
|
||
Member,
|
||
}
|
||
|
||
// 管理后台协议统一收口在 shared-contracts,避免页面脚本和 Rust handler 各自手拼字段。
|
||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
|
||
#[serde(rename_all = "camelCase")]
|
||
pub struct AdminLoginRequest {
|
||
pub username: String,
|
||
pub password: String,
|
||
}
|
||
|
||
/// AGC 模板管理快照;revision 对应读取到的完整 OSS 清单字节。
|
||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
|
||
#[serde(rename_all = "camelCase")]
|
||
pub struct AdminAgcTemplateListResponse {
|
||
pub revision: String,
|
||
pub writable: bool,
|
||
pub templates: Vec<AdminAgcTemplatePayload>,
|
||
}
|
||
|
||
/// 合并上架与下架条目的后台展示投影。
|
||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
|
||
#[serde(rename_all = "camelCase")]
|
||
pub struct AdminAgcTemplatePayload {
|
||
pub id: String,
|
||
pub title: String,
|
||
pub summary: String,
|
||
pub tags: Vec<String>,
|
||
pub runtime: String,
|
||
pub engine: String,
|
||
pub engine_version: String,
|
||
pub template_version: String,
|
||
pub enabled: bool,
|
||
pub cover_url: String,
|
||
pub zip_size_bytes: u64,
|
||
}
|
||
|
||
/// 仅修改模板展示字段及上下架状态,不接受包、引擎或版本字段。
|
||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
|
||
#[serde(rename_all = "camelCase", deny_unknown_fields)]
|
||
pub struct AdminUpdateAgcTemplateRequest {
|
||
pub expected_revision: String,
|
||
pub title: String,
|
||
pub summary: String,
|
||
pub tags: Vec<String>,
|
||
pub enabled: bool,
|
||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||
pub cover: Option<AdminAgcTemplateCoverInput>,
|
||
}
|
||
|
||
/// 待保存的封面原始图片;格式、字节和尺寸由后端验证。
|
||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
|
||
#[serde(rename_all = "camelCase", deny_unknown_fields)]
|
||
pub struct AdminAgcTemplateCoverInput {
|
||
pub content_type: String,
|
||
pub data_base64: String,
|
||
}
|
||
|
||
/// 后台批量导入模板的 manifest(`multipart/form-data` 的 `manifest` 文本字段)。
|
||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
|
||
#[serde(rename_all = "camelCase", deny_unknown_fields)]
|
||
pub struct AdminImportAgcTemplatesManifest {
|
||
/// 上传前读到的清单字节摘要,锁内用于 CAS;过期返回 409。
|
||
pub expected_revision: String,
|
||
pub templates: Vec<AdminImportAgcTemplateItem>,
|
||
}
|
||
|
||
/// 单条导入模板:ZIP 与可选封面通过字段名引用同一请求里的文件字段。
|
||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
|
||
#[serde(rename_all = "camelCase", deny_unknown_fields)]
|
||
pub struct AdminImportAgcTemplateItem {
|
||
pub id: String,
|
||
pub title: String,
|
||
#[serde(default)]
|
||
pub summary: String,
|
||
#[serde(default)]
|
||
pub tags: Vec<String>,
|
||
pub runtime: String,
|
||
#[serde(default)]
|
||
pub engine: String,
|
||
#[serde(default)]
|
||
pub engine_version: String,
|
||
pub template_version: String,
|
||
pub entry: String,
|
||
/// 该条目的 ZIP 文件字段名(例如 `zip_0`)。
|
||
pub zip_field: String,
|
||
/// 该条目的封面文件字段名(例如 `cover_0`);与 CLI 源布局一致,封面必填。
|
||
pub cover_field: String,
|
||
}
|
||
|
||
/// 批量导入结果:最新快照 + 逐条结果。
|
||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
|
||
#[serde(rename_all = "camelCase")]
|
||
pub struct AdminImportAgcTemplatesResponse {
|
||
pub revision: String,
|
||
pub writable: bool,
|
||
pub templates: Vec<AdminAgcTemplatePayload>,
|
||
pub imported: Vec<AdminImportAgcTemplateResult>,
|
||
}
|
||
|
||
/// 单条导入结果。
|
||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
|
||
#[serde(rename_all = "camelCase")]
|
||
pub struct AdminImportAgcTemplateResult {
|
||
pub id: String,
|
||
pub template_version: String,
|
||
pub zip_size_bytes: u64,
|
||
pub zip_sha256: String,
|
||
/// 同一版本上传完全相同的字节时按内容复用既有对象,没有新写内容。
|
||
pub reused_objects: bool,
|
||
}
|
||
|
||
// 登录成功后返回管理员访问令牌与基础会话信息。
|
||
|
||
/// 后台创作入口开关列表响应。
|
||
#[cfg(any())]
|
||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
|
||
#[serde(rename_all = "camelCase")]
|
||
pub struct AdminCreationEntryConfigResponse {
|
||
pub entries: Vec<AdminCreationEntryTypeConfigPayload>,
|
||
/// 底部加号创作入口页的后台公告列表。
|
||
pub event_banners: Vec<CreationEntryEventBannerResponse>,
|
||
/// 公开作品详情页点赞 / 改造能力配置。
|
||
pub public_work_interactions: Vec<PublicWorkInteractionConfigResponse>,
|
||
}
|
||
|
||
/// 后台单个创作入口开关配置。
|
||
#[cfg(any())]
|
||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
|
||
#[serde(rename_all = "camelCase")]
|
||
pub struct AdminCreationEntryTypeConfigPayload {
|
||
pub id: String,
|
||
pub title: String,
|
||
pub subtitle: String,
|
||
pub badge: String,
|
||
pub image_src: String,
|
||
pub visible: bool,
|
||
pub open: bool,
|
||
pub sort_order: i32,
|
||
pub category_id: String,
|
||
pub category_label: String,
|
||
pub category_sort_order: i32,
|
||
pub updated_at_micros: i64,
|
||
#[serde(skip_serializing_if = "Option::is_none")]
|
||
pub unified_creation_spec: Option<UnifiedCreationSpecResponse>,
|
||
}
|
||
|
||
/// 后台保存创作入口开关配置请求。
|
||
#[cfg(any())]
|
||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
|
||
#[serde(rename_all = "camelCase")]
|
||
pub struct AdminUpsertCreationEntryTypeConfigRequest {
|
||
pub id: String,
|
||
pub title: String,
|
||
pub subtitle: String,
|
||
pub badge: String,
|
||
pub image_src: String,
|
||
pub visible: bool,
|
||
pub open: bool,
|
||
pub sort_order: i32,
|
||
pub category_id: String,
|
||
pub category_label: String,
|
||
pub category_sort_order: i32,
|
||
#[serde(skip_serializing_if = "Option::is_none")]
|
||
pub unified_creation_spec: Option<UnifiedCreationSpecResponse>,
|
||
}
|
||
|
||
/// 后台保存创作入口公告表单序列化结果请求。
|
||
#[cfg(any())]
|
||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
|
||
#[serde(rename_all = "camelCase")]
|
||
pub struct AdminUpsertCreationEntryEventBannersRequest {
|
||
/// 传输字段沿用既有契约,内容由后台标题 / HTML 表单生成。
|
||
pub event_banners_json: String,
|
||
}
|
||
|
||
/// 后台保存公开作品点赞 / 改造能力配置请求。
|
||
#[cfg(any())]
|
||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
|
||
#[serde(rename_all = "camelCase")]
|
||
pub struct AdminUpsertPublicWorkInteractionConfigRequest {
|
||
pub public_work_interactions: Vec<PublicWorkInteractionConfigResponse>,
|
||
}
|
||
|
||
/// 后台灰度配置列表响应。
|
||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
|
||
#[serde(rename_all = "camelCase")]
|
||
pub struct AdminFeatureGateConfigResponse {
|
||
pub gates: Vec<AdminFeatureGateConfigPayload>,
|
||
}
|
||
|
||
/// 后台单个灰度 gate 配置。
|
||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
|
||
#[serde(rename_all = "camelCase")]
|
||
pub struct AdminFeatureGateConfigPayload {
|
||
pub gate_key: String,
|
||
pub enabled: bool,
|
||
pub rollout_percent: u32,
|
||
pub allow_user_ids: Vec<String>,
|
||
pub allow_user_tags: Vec<String>,
|
||
pub deny_user_ids: Vec<String>,
|
||
pub description: String,
|
||
pub updated_at: String,
|
||
}
|
||
|
||
/// 后台保存灰度 gate 配置请求。
|
||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
|
||
#[serde(rename_all = "camelCase")]
|
||
pub struct AdminUpsertFeatureGateConfigRequest {
|
||
pub gate_key: String,
|
||
pub enabled: bool,
|
||
pub rollout_percent: u32,
|
||
pub allow_user_ids: Vec<String>,
|
||
pub allow_user_tags: Vec<String>,
|
||
pub deny_user_ids: Vec<String>,
|
||
pub description: String,
|
||
}
|
||
|
||
/// 后台作品可见性列表项。
|
||
#[cfg(any())]
|
||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
|
||
#[serde(rename_all = "camelCase")]
|
||
pub struct AdminWorkVisibilityEntryPayload {
|
||
pub source_type: String,
|
||
pub work_id: String,
|
||
pub profile_id: String,
|
||
pub source_session_id: Option<String>,
|
||
pub public_work_code: String,
|
||
pub owner_user_id: String,
|
||
pub author_display_name: String,
|
||
pub title: String,
|
||
pub subtitle: String,
|
||
pub cover_image_src: Option<String>,
|
||
pub visible: bool,
|
||
pub published_at_micros: Option<i64>,
|
||
pub updated_at_micros: i64,
|
||
}
|
||
|
||
/// 后台作品可见性列表响应。
|
||
#[cfg(any())]
|
||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
|
||
#[serde(rename_all = "camelCase")]
|
||
pub struct AdminWorkVisibilityListResponse {
|
||
pub entries: Vec<AdminWorkVisibilityEntryPayload>,
|
||
}
|
||
|
||
/// 后台修改作品可见性请求。
|
||
#[cfg(any())]
|
||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
|
||
#[serde(rename_all = "camelCase")]
|
||
pub struct AdminUpdateWorkVisibilityRequest {
|
||
pub source_type: String,
|
||
pub profile_id: String,
|
||
pub visible: bool,
|
||
}
|
||
|
||
/// 后台修改作品可见性响应。
|
||
#[cfg(any())]
|
||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
|
||
#[serde(rename_all = "camelCase")]
|
||
pub struct AdminUpdateWorkVisibilityResponse {
|
||
pub entry: AdminWorkVisibilityEntryPayload,
|
||
}
|
||
|
||
#[derive(Clone, Debug, Serialize, Deserialize, Default, PartialEq, Eq)]
|
||
#[serde(rename_all = "camelCase")]
|
||
pub struct AdminEditorAssetListQuery {
|
||
pub cursor: Option<String>,
|
||
pub owner_user_id: Option<String>,
|
||
pub keyword: Option<String>,
|
||
pub created_after: Option<String>,
|
||
pub created_before: Option<String>,
|
||
pub limit: Option<u32>,
|
||
}
|
||
|
||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
|
||
#[serde(rename_all = "camelCase")]
|
||
pub struct AdminEditorImageSequenceFramePayload {
|
||
pub image_src: String,
|
||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||
pub object_key: Option<String>,
|
||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||
pub asset_object_id: Option<String>,
|
||
pub width: u32,
|
||
pub height: u32,
|
||
}
|
||
|
||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
|
||
#[serde(rename_all = "camelCase")]
|
||
pub struct AdminEditorAssetPayload {
|
||
pub asset_id: String,
|
||
pub owner_user_id: String,
|
||
pub author_display_name: Option<String>,
|
||
pub author_public_user_code: Option<String>,
|
||
pub folder_id: String,
|
||
pub label: String,
|
||
pub asset_object_id: Option<String>,
|
||
pub image_src: String,
|
||
pub object_key: Option<String>,
|
||
pub width: u32,
|
||
pub height: u32,
|
||
pub source_type: String,
|
||
pub prompt: Option<String>,
|
||
pub actual_prompt: Option<String>,
|
||
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>,
|
||
pub source_image_src: Option<String>,
|
||
pub source_object_key: Option<String>,
|
||
pub source_asset_object_id: Option<String>,
|
||
pub source_label: Option<String>,
|
||
pub thumbnail_src: Option<String>,
|
||
pub generation_cost_mud_points: u64,
|
||
pub created_at: String,
|
||
pub updated_at: String,
|
||
pub generator: String,
|
||
pub task_generator: String,
|
||
pub task_cost_mud_points: u64,
|
||
pub children: Vec<AdminEditorAssetPayload>,
|
||
pub image_sequence_frames: Option<Vec<AdminEditorImageSequenceFramePayload>>,
|
||
pub image_sequence_duration_ms: Option<u64>,
|
||
}
|
||
|
||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
|
||
#[serde(rename_all = "camelCase")]
|
||
pub struct AdminEditorAssetListResponse {
|
||
pub entries: Vec<AdminEditorAssetPayload>,
|
||
pub next_cursor: Option<String>,
|
||
}
|
||
|
||
#[derive(Clone, Debug, Serialize, Deserialize, Default, PartialEq, Eq)]
|
||
#[serde(rename_all = "camelCase")]
|
||
pub struct AdminEditorShowcaseListQuery {
|
||
pub cursor: Option<String>,
|
||
pub owner_user_id: Option<String>,
|
||
pub review_status: Option<String>,
|
||
pub submitted_after: Option<String>,
|
||
pub submitted_before: Option<String>,
|
||
pub limit: Option<u32>,
|
||
}
|
||
|
||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
|
||
#[serde(rename_all = "camelCase")]
|
||
pub struct AdminEditorShowcaseAssetPayload {
|
||
pub showcase_id: String,
|
||
pub asset_id: String,
|
||
pub owner_user_id: String,
|
||
pub author_display_name: Option<String>,
|
||
pub author_public_user_code: Option<String>,
|
||
pub label: String,
|
||
pub image_src: String,
|
||
pub object_key: Option<String>,
|
||
pub width: u32,
|
||
pub height: u32,
|
||
pub prompt: Option<String>,
|
||
pub actual_prompt: Option<String>,
|
||
pub model: Option<String>,
|
||
pub provider: Option<String>,
|
||
pub task_id: Option<String>,
|
||
pub source_resource_id: Option<String>,
|
||
pub source_image_src: Option<String>,
|
||
pub source_object_key: Option<String>,
|
||
pub source_asset_object_id: Option<String>,
|
||
pub source_label: Option<String>,
|
||
pub asset_kind: Option<String>,
|
||
pub generation_inputs: Option<Value>,
|
||
pub thumbnail_src: Option<String>,
|
||
pub generation_cost_mud_points: u64,
|
||
pub refund_mud_points: u64,
|
||
pub review_status: String,
|
||
pub display_enabled: bool,
|
||
pub like_count: u64,
|
||
pub asset_deleted_while_pending: bool,
|
||
pub reviewed_by_admin_user_id: Option<String>,
|
||
pub review_note: Option<String>,
|
||
pub refund_ledger_id: Option<String>,
|
||
pub refund_completed_at: Option<String>,
|
||
pub submitted_at: String,
|
||
pub reviewed_at: Option<String>,
|
||
pub approved_at: Option<String>,
|
||
pub rejected_at: Option<String>,
|
||
pub updated_at: String,
|
||
pub showcase_category: Option<String>,
|
||
pub image_sequence_frames: Option<Vec<AdminEditorImageSequenceFramePayload>>,
|
||
pub image_sequence_duration_ms: Option<u64>,
|
||
}
|
||
|
||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
|
||
#[serde(rename_all = "camelCase")]
|
||
pub struct AdminEditorShowcaseListResponse {
|
||
pub entries: Vec<AdminEditorShowcaseAssetPayload>,
|
||
pub next_cursor: Option<String>,
|
||
}
|
||
|
||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
|
||
#[serde(rename_all = "camelCase")]
|
||
pub struct AdminEditorShowcaseReviewRequest {
|
||
pub showcase_id: String,
|
||
pub review_status: String,
|
||
pub review_note: Option<String>,
|
||
}
|
||
|
||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
|
||
#[serde(rename_all = "camelCase")]
|
||
pub struct AdminEditorShowcaseDisplayRequest {
|
||
pub showcase_id: String,
|
||
pub display_enabled: bool,
|
||
pub showcase_category: Option<String>,
|
||
}
|
||
|
||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
|
||
#[serde(rename_all = "camelCase")]
|
||
pub struct AdminEditorShowcaseAssetResponse {
|
||
pub entry: AdminEditorShowcaseAssetPayload,
|
||
}
|
||
|
||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
|
||
#[serde(rename_all = "camelCase")]
|
||
pub struct AdminEditorShowcaseCampaignPayload {
|
||
pub enabled: bool,
|
||
pub title: String,
|
||
pub image_src: String,
|
||
pub image_object_key: Option<String>,
|
||
pub image_width: Option<u32>,
|
||
pub image_height: Option<u32>,
|
||
pub prompt: String,
|
||
pub author: String,
|
||
pub cost_text: String,
|
||
pub updated_at: String,
|
||
}
|
||
|
||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
|
||
#[serde(rename_all = "camelCase")]
|
||
pub struct AdminEditorShowcaseCampaignResponse {
|
||
pub campaign: Option<AdminEditorShowcaseCampaignPayload>,
|
||
}
|
||
|
||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
|
||
#[serde(rename_all = "camelCase")]
|
||
pub struct AdminUpsertEditorShowcaseCampaignRequest {
|
||
pub enabled: bool,
|
||
pub title: String,
|
||
pub image_src: String,
|
||
pub image_object_key: Option<String>,
|
||
pub image_width: Option<u32>,
|
||
pub image_height: Option<u32>,
|
||
pub prompt: String,
|
||
pub author: String,
|
||
pub cost_text: String,
|
||
}
|
||
|
||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
|
||
#[serde(rename_all = "camelCase")]
|
||
pub struct AdminCreateEditorShowcaseCampaignImageUploadTicketRequest {
|
||
pub file_name: String,
|
||
pub content_type: String,
|
||
pub content_length: u64,
|
||
}
|
||
|
||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
|
||
#[serde(rename_all = "camelCase")]
|
||
pub struct AdminCreateEditorShowcaseCampaignImageUploadTicketResponse {
|
||
pub upload: AdminDirectUploadTicketPayload,
|
||
}
|
||
|
||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
|
||
#[serde(rename_all = "camelCase")]
|
||
pub struct AdminConfirmEditorShowcaseCampaignImageUploadRequest {
|
||
pub bucket: String,
|
||
pub object_key: String,
|
||
pub content_type: String,
|
||
pub content_length: u64,
|
||
}
|
||
|
||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
|
||
#[serde(rename_all = "camelCase")]
|
||
pub struct AdminDirectUploadTicketPayload {
|
||
pub bucket: String,
|
||
pub host: String,
|
||
pub object_key: String,
|
||
pub legacy_public_path: String,
|
||
pub form_fields: BTreeMap<String, String>,
|
||
}
|
||
|
||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
|
||
#[serde(rename_all = "camelCase")]
|
||
pub struct AdminLoginResponse {
|
||
pub token: String,
|
||
pub admin: AdminSessionPayload,
|
||
}
|
||
|
||
// 管理员会话只暴露页面展示和鉴权调试所需的最小字段。
|
||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
|
||
#[serde(rename_all = "camelCase")]
|
||
pub struct AdminSessionPayload {
|
||
pub subject: String,
|
||
pub username: String,
|
||
pub display_name: String,
|
||
pub roles: Vec<String>,
|
||
pub account_role: AdminAccountRole,
|
||
pub tab_permissions: Vec<String>,
|
||
pub action_permissions: Vec<String>,
|
||
pub issued_at: String,
|
||
pub expires_at: String,
|
||
}
|
||
|
||
/// 后台账号列表和写操作响应均复用该安全视图,不返回密码摘要。
|
||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
|
||
#[serde(rename_all = "camelCase")]
|
||
pub struct AdminAccountPayload {
|
||
pub account_id: String,
|
||
pub username: String,
|
||
pub display_name: String,
|
||
pub account_role: AdminAccountRole,
|
||
pub tab_permissions: Vec<String>,
|
||
pub action_permissions: Vec<String>,
|
||
pub enabled: bool,
|
||
pub token_version: u64,
|
||
pub created_by: String,
|
||
pub updated_by: String,
|
||
pub created_at: String,
|
||
pub updated_at: String,
|
||
}
|
||
|
||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
|
||
#[serde(rename_all = "camelCase")]
|
||
pub struct AdminAccountListResponse {
|
||
pub accounts: Vec<AdminAccountPayload>,
|
||
}
|
||
|
||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
|
||
#[serde(rename_all = "camelCase")]
|
||
pub struct AdminCreateAccountRequest {
|
||
pub username: String,
|
||
pub display_name: String,
|
||
pub password: String,
|
||
pub tab_permissions: Vec<String>,
|
||
#[serde(default)]
|
||
pub action_permissions: Vec<String>,
|
||
pub enabled: bool,
|
||
}
|
||
|
||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
|
||
#[serde(rename_all = "camelCase")]
|
||
pub struct AdminCreateAccountResponse {
|
||
pub account: AdminAccountPayload,
|
||
}
|
||
|
||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
|
||
#[serde(rename_all = "camelCase")]
|
||
pub struct AdminUpdateAccountRequest {
|
||
pub display_name: String,
|
||
#[serde(skip_serializing_if = "Option::is_none")]
|
||
pub password: Option<String>,
|
||
pub tab_permissions: Vec<String>,
|
||
pub action_permissions: Vec<String>,
|
||
pub enabled: bool,
|
||
}
|
||
|
||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
|
||
#[serde(rename_all = "camelCase")]
|
||
pub struct AdminUpdateAccountResponse {
|
||
pub account: AdminAccountPayload,
|
||
}
|
||
|
||
// 页面恢复登录态时读取当前管理员会话。
|
||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
|
||
#[serde(rename_all = "camelCase")]
|
||
pub struct AdminMeResponse {
|
||
pub admin: AdminSessionPayload,
|
||
}
|
||
|
||
// 后台概览统一返回服务信息与数据库信息两块,前端不再额外拼装。
|
||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
||
#[serde(rename_all = "camelCase")]
|
||
pub struct AdminOverviewResponse {
|
||
pub service: AdminServiceOverviewPayload,
|
||
pub database: AdminDatabaseOverviewPayload,
|
||
}
|
||
|
||
/// 后台 dashboard 查询参数;日期均使用北京时间日历日期 YYYY-MM-DD。
|
||
#[derive(Clone, Debug, Serialize, Deserialize, Default, PartialEq, Eq)]
|
||
#[serde(rename_all = "camelCase")]
|
||
pub struct AdminDashboardQuery {
|
||
pub granularity: Option<String>,
|
||
/// day / week / month 兼容旧查询口径,表示被选中的日期。
|
||
pub anchor: Option<String>,
|
||
/// period 自定义时段起始日期,闭区间。
|
||
pub start_date: Option<String>,
|
||
/// period 自定义时段终止日期,闭区间。
|
||
pub end_date: Option<String>,
|
||
}
|
||
|
||
/// 后台 dashboard 返回运营指标、趋势图和读取告警。
|
||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
|
||
#[serde(rename_all = "camelCase")]
|
||
pub struct AdminDashboardResponse {
|
||
pub range: AdminDashboardRangePayload,
|
||
pub metrics: AdminDashboardMetricsPayload,
|
||
pub charts: Vec<AdminDashboardChartPayload>,
|
||
pub operations: AdminDashboardOperationsPayload,
|
||
pub warnings: Vec<String>,
|
||
pub generated_at: String,
|
||
}
|
||
|
||
/// dashboard 当前筛选范围,所有日/周/月图表共用同一时间窗。
|
||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
|
||
#[serde(rename_all = "camelCase")]
|
||
pub struct AdminDashboardRangePayload {
|
||
pub granularity: String,
|
||
pub anchor_date: String,
|
||
pub period_start_date: String,
|
||
pub period_end_date: String,
|
||
pub period_label: String,
|
||
}
|
||
|
||
/// dashboard 首屏核心指标。
|
||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
|
||
#[serde(rename_all = "camelCase")]
|
||
pub struct AdminDashboardMetricsPayload {
|
||
pub generated_assets: u64,
|
||
pub consumed_mud_points: u64,
|
||
pub total_registered_users: u64,
|
||
pub new_registered_users: u64,
|
||
pub day1_retention: AdminDashboardRetentionPayload,
|
||
pub day7_retention: AdminDashboardRetentionPayload,
|
||
pub visit_users: u64,
|
||
pub total_visit_users: u64,
|
||
pub visit_count: u64,
|
||
pub total_visit_count: u64,
|
||
pub current_users: u64,
|
||
pub new_user_payment_conversion: AdminDashboardPaymentConversionPayload,
|
||
}
|
||
|
||
/// dashboard 新增用户付费转化;rate_basis_points 取值 0..=10000,1 表示 0.01%。
|
||
#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq)]
|
||
#[serde(rename_all = "camelCase")]
|
||
pub struct AdminDashboardPaymentConversionPayload {
|
||
pub paid_users: u64,
|
||
pub new_registered_users: u64,
|
||
pub rate_basis_points: u32,
|
||
}
|
||
|
||
/// dashboard 新增用户留存;rate_basis_points 取值 0..=10000,1 表示 0.01%。
|
||
#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq)]
|
||
#[serde(rename_all = "camelCase")]
|
||
pub struct AdminDashboardRetentionPayload {
|
||
pub eligible_users: u64,
|
||
pub retained_users: u64,
|
||
pub rate_basis_points: u32,
|
||
}
|
||
|
||
/// dashboard 图表定义,前端只渲染后端给出的 bucket。
|
||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
|
||
#[serde(rename_all = "camelCase")]
|
||
pub struct AdminDashboardChartPayload {
|
||
pub id: String,
|
||
pub title: String,
|
||
pub unit: String,
|
||
pub total: u64,
|
||
pub buckets: Vec<AdminDashboardChartBucketPayload>,
|
||
}
|
||
|
||
/// dashboard 单个趋势桶。
|
||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
|
||
#[serde(rename_all = "camelCase")]
|
||
pub struct AdminDashboardChartBucketPayload {
|
||
pub key: String,
|
||
pub label: String,
|
||
pub value: u64,
|
||
}
|
||
|
||
/// dashboard 运营页签的常用运营看板。
|
||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
|
||
#[serde(rename_all = "camelCase")]
|
||
pub struct AdminDashboardOperationsPayload {
|
||
pub cards: Vec<AdminDashboardOperationMetricPayload>,
|
||
pub asset_kind_breakdown: Vec<AdminDashboardBreakdownRowPayload>,
|
||
pub module_visit_breakdown: Vec<AdminDashboardBreakdownRowPayload>,
|
||
}
|
||
|
||
/// dashboard 运营指标卡。
|
||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
|
||
#[serde(rename_all = "camelCase")]
|
||
pub struct AdminDashboardOperationMetricPayload {
|
||
pub id: String,
|
||
pub label: String,
|
||
pub value: u64,
|
||
pub unit: String,
|
||
}
|
||
|
||
/// dashboard 常用排行 / 分布行。
|
||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
|
||
#[serde(rename_all = "camelCase")]
|
||
pub struct AdminDashboardBreakdownRowPayload {
|
||
pub key: String,
|
||
pub label: String,
|
||
pub value: u64,
|
||
}
|
||
|
||
// 服务概览描述当前 api-server 与 SpacetimeDB 连接配置。
|
||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
|
||
#[serde(rename_all = "camelCase")]
|
||
pub struct AdminServiceOverviewPayload {
|
||
pub bind_host: String,
|
||
pub bind_port: u16,
|
||
pub jwt_issuer: String,
|
||
pub admin_enabled: bool,
|
||
pub spacetime_server_url: String,
|
||
pub spacetime_database: String,
|
||
}
|
||
|
||
// 数据库概览返回真实数据库元信息、表清单与统计错误。
|
||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
|
||
#[serde(rename_all = "camelCase")]
|
||
pub struct AdminDatabaseOverviewPayload {
|
||
pub database_identity: Option<String>,
|
||
pub owner_identity: Option<String>,
|
||
pub host_type: Option<String>,
|
||
pub schema_table_names: Vec<String>,
|
||
pub table_stats: Vec<AdminDatabaseTableStatPayload>,
|
||
pub fetch_errors: Vec<String>,
|
||
}
|
||
|
||
// 单表统计允许成功和失败并存,避免某张表失败导致整页概览不可用。
|
||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
|
||
#[serde(rename_all = "camelCase")]
|
||
pub struct AdminDatabaseTableStatPayload {
|
||
pub table_name: String,
|
||
pub row_count: Option<u64>,
|
||
pub error_message: Option<String>,
|
||
}
|
||
|
||
// 后台表清单独立用于“表查询”页,避免页面必须先拉完整总览。
|
||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
|
||
#[serde(rename_all = "camelCase")]
|
||
pub struct AdminDatabaseTableListResponse {
|
||
pub tables: Vec<String>,
|
||
pub fetch_errors: Vec<String>,
|
||
}
|
||
|
||
// 后台通用表查询参数,用户输入不进入 SQL,只在 API Server 内存中过滤。
|
||
// filters 支持两种 JSON 形式:object(列名到等值,如 {"user_id":"u1"},兼容旧入口)
|
||
// 与条件数组(如 [{"column":"points","op":"gt","value":"5"}])。条件数组运算符包含
|
||
// eq、ne、gt、gte、lt、lte、contains、notContains、startsWith、endsWith、in、notIn、
|
||
// isEmpty、isNotEmpty;允许同列多条件,全部条件之间为 AND。
|
||
#[derive(Clone, Debug, Serialize, Deserialize, Default, PartialEq, Eq)]
|
||
#[serde(rename_all = "camelCase")]
|
||
pub struct AdminDatabaseTableRowsQuery {
|
||
pub limit: Option<u32>,
|
||
pub page: Option<u32>,
|
||
pub search: Option<String>,
|
||
pub filters: Option<String>,
|
||
pub sort_column: Option<String>,
|
||
pub sort_direction: Option<String>,
|
||
}
|
||
|
||
// 后台通用表查询响应,cells 使用列名映射,raw 保留原始行便于详情排障。
|
||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
||
#[serde(rename_all = "camelCase")]
|
||
pub struct AdminDatabaseTableRowsResponse {
|
||
pub table_name: String,
|
||
pub columns: Vec<String>,
|
||
pub rows: Vec<AdminDatabaseTableRowPayload>,
|
||
pub total_returned: usize,
|
||
pub total_matched: usize,
|
||
pub limit: u32,
|
||
pub page: u32,
|
||
pub scanned_count: usize,
|
||
pub scan_limit: u32,
|
||
pub scan_limit_reached: bool,
|
||
}
|
||
|
||
// 单行查询结果,值统一用 JSON 承载以兼容不同表字段类型。
|
||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
||
#[serde(rename_all = "camelCase")]
|
||
pub struct AdminDatabaseTableRowPayload {
|
||
pub cells: Value,
|
||
pub raw: Value,
|
||
}
|
||
|
||
/// API Key 专用查询参数。该接口只返回安全元数据,不复用通用表查询的
|
||
/// 原始 JSON 行,以避免后台详情页意外暴露 key_hash 或密文。
|
||
#[derive(Clone, Debug, Serialize, Deserialize, Default, PartialEq, Eq)]
|
||
#[serde(rename_all = "camelCase")]
|
||
pub struct AdminExternalApiKeyListQuery {
|
||
pub owner_user_id: Option<String>,
|
||
pub public_user_code: Option<String>,
|
||
pub key_id: Option<String>,
|
||
pub name: Option<String>,
|
||
pub key_prefix: Option<String>,
|
||
pub created_after: Option<String>,
|
||
pub created_before: Option<String>,
|
||
pub status: Option<String>,
|
||
pub limit: Option<u32>,
|
||
pub offset: Option<u32>,
|
||
pub sort_column: Option<String>,
|
||
pub sort_direction: Option<String>,
|
||
}
|
||
|
||
/// API Key 后台安全元数据。永不包含 key_hash、明文 Key 或密文。
|
||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
|
||
#[serde(rename_all = "camelCase")]
|
||
pub struct AdminExternalApiKeyPayload {
|
||
pub key_id: String,
|
||
pub owner_user_id: String,
|
||
pub name: String,
|
||
pub key_prefix: String,
|
||
pub scopes: Vec<String>,
|
||
pub created_at: String,
|
||
pub last_used_at: Option<String>,
|
||
pub revoked_at: Option<String>,
|
||
pub updated_at: String,
|
||
pub status: String,
|
||
}
|
||
|
||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
|
||
#[serde(rename_all = "camelCase")]
|
||
pub struct AdminExternalApiKeyListResponse {
|
||
pub keys: Vec<AdminExternalApiKeyPayload>,
|
||
pub total: usize,
|
||
pub limit: u32,
|
||
pub offset: u32,
|
||
pub scanned_count: usize,
|
||
pub scan_limit: u32,
|
||
pub scan_limit_reached: bool,
|
||
}
|
||
|
||
// 调试请求只允许同源路径、受控请求头和有限请求体。
|
||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
||
#[serde(rename_all = "camelCase")]
|
||
pub struct AdminDebugHttpRequest {
|
||
pub method: String,
|
||
pub path: String,
|
||
pub headers: Option<Vec<AdminDebugHeaderInput>>,
|
||
pub body: Option<String>,
|
||
}
|
||
|
||
// 调试请求头使用显式结构,避免页面直接塞任意对象。
|
||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
|
||
#[serde(rename_all = "camelCase")]
|
||
pub struct AdminDebugHeaderInput {
|
||
pub name: String,
|
||
pub value: String,
|
||
}
|
||
|
||
// 调试响应回显状态、响应头与文本/JSON 预览,便于后台排查接口问题。
|
||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
||
#[serde(rename_all = "camelCase")]
|
||
pub struct AdminDebugHttpResponse {
|
||
pub status: u16,
|
||
pub status_text: String,
|
||
pub headers: Vec<AdminDebugHeaderInput>,
|
||
pub body_text: String,
|
||
pub body_json: Option<Value>,
|
||
}
|
||
|
||
// 后台埋点明细查询参数只保留运营筛选需要的只读字段。
|
||
#[derive(Clone, Debug, Serialize, Deserialize, Default, PartialEq, Eq)]
|
||
#[serde(rename_all = "camelCase")]
|
||
pub struct AdminTrackingEventListQuery {
|
||
pub event_key: Option<String>,
|
||
pub user_id: Option<String>,
|
||
pub scope_kind: Option<String>,
|
||
pub scope_id: Option<String>,
|
||
/// 北京时间日历日期 YYYY-MM-DD,按 tracking_event.day_key 闭区间过滤。
|
||
pub start_date: Option<String>,
|
||
/// 北京时间日历日期 YYYY-MM-DD,按 tracking_event.day_key 闭区间过滤。
|
||
pub end_date: Option<String>,
|
||
pub limit: Option<u32>,
|
||
/// 后台导出专用开关;列表查询仍使用较小上限,避免日常误刷拖慢数据库。
|
||
pub export_all: Option<bool>,
|
||
}
|
||
|
||
// 单条埋点原始事件明细,字段与 tracking_event 表一一对应并补充事件名称。
|
||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
|
||
#[serde(rename_all = "camelCase")]
|
||
pub struct AdminTrackingEventEntryPayload {
|
||
pub event_id: String,
|
||
pub event_key: String,
|
||
pub event_title: String,
|
||
pub scope_kind: String,
|
||
pub scope_id: String,
|
||
pub day_key: i64,
|
||
pub user_id: Option<String>,
|
||
pub owner_user_id: Option<String>,
|
||
pub profile_id: Option<String>,
|
||
pub module_key: Option<String>,
|
||
pub metadata_json: String,
|
||
pub occurred_at: String,
|
||
}
|
||
|
||
// 后台埋点明细列表响应,前端导出 Excel 时直接使用 entries。
|
||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
|
||
#[serde(rename_all = "camelCase")]
|
||
pub struct AdminTrackingEventListResponse {
|
||
pub entries: Vec<AdminTrackingEventEntryPayload>,
|
||
}
|
||
|
||
// 后台埋点 key 候选来自真实库中已经落库的 tracking_event,静态标题仅用于展示补充。
|
||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
|
||
#[serde(rename_all = "camelCase")]
|
||
pub struct AdminTrackingEventKeyPayload {
|
||
pub event_key: String,
|
||
pub event_title: String,
|
||
pub scope_kinds: Vec<String>,
|
||
}
|
||
|
||
// 后台埋点 key 列表响应,任务配置页和埋点明细页共用。
|
||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
|
||
#[serde(rename_all = "camelCase")]
|
||
pub struct AdminTrackingEventKeyListResponse {
|
||
pub event_keys: Vec<AdminTrackingEventKeyPayload>,
|
||
}
|
||
|
||
/// 后台充值订单筛选参数。陶泥号由 BFF 解析为内部用户 ID 后再访问运行时读模型。
|
||
#[derive(Clone, Debug, Serialize, Deserialize, Default, PartialEq, Eq)]
|
||
#[serde(rename_all = "camelCase")]
|
||
pub struct AdminRechargeOrderListQuery {
|
||
pub order_id: Option<String>,
|
||
pub provider_transaction_id: Option<String>,
|
||
pub user_id: Option<String>,
|
||
pub public_user_code: Option<String>,
|
||
pub payment_channel: Option<String>,
|
||
pub status: Option<String>,
|
||
pub created_after: Option<String>,
|
||
pub created_before: Option<String>,
|
||
pub limit: Option<u32>,
|
||
}
|
||
|
||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
|
||
#[serde(rename_all = "camelCase")]
|
||
pub struct AdminUserSummaryPayload {
|
||
pub user_id: String,
|
||
pub public_user_code: String,
|
||
pub display_name: String,
|
||
pub avatar_url: Option<String>,
|
||
}
|
||
|
||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
|
||
#[serde(rename_all = "camelCase")]
|
||
pub struct AdminWalletManualRestrictionPayload {
|
||
pub frozen: bool,
|
||
pub reason: String,
|
||
pub created_by_admin_user_id: String,
|
||
pub created_by_admin_display_name: String,
|
||
pub created_at_micros: i64,
|
||
pub updated_by_admin_user_id: String,
|
||
pub updated_by_admin_display_name: String,
|
||
pub updated_at_micros: i64,
|
||
}
|
||
|
||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
|
||
#[serde(rename_all = "camelCase")]
|
||
pub struct AdminProfileWalletPayload {
|
||
pub user_id: String,
|
||
pub total_balance: u64,
|
||
pub spendable_balance: u64,
|
||
pub daily_free_points: u64,
|
||
pub membership_limited_points: u64,
|
||
pub permanent_points: u64,
|
||
pub held_points: u64,
|
||
pub refund_debt_points: u64,
|
||
pub manual_frozen: bool,
|
||
pub refund_debt_frozen: bool,
|
||
pub wallet_frozen: bool,
|
||
pub manual_restriction: Option<AdminWalletManualRestrictionPayload>,
|
||
}
|
||
|
||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
|
||
#[serde(rename_all = "camelCase")]
|
||
pub struct AdminRechargeRefundPayload {
|
||
pub out_refund_no: String,
|
||
pub provider_refund_id: String,
|
||
pub provider_transaction_id: String,
|
||
pub provider_status: String,
|
||
pub total_cents: u64,
|
||
pub refund_cents: u64,
|
||
pub payer_refund_cents: u64,
|
||
pub success_at_micros: Option<i64>,
|
||
pub first_observed_at_micros: i64,
|
||
pub updated_at_micros: i64,
|
||
pub observation_source: String,
|
||
pub target_recovery_points: u64,
|
||
pub recovered_points: u64,
|
||
pub unrecovered_points: u64,
|
||
pub recovery_status: String,
|
||
pub last_error_code: Option<String>,
|
||
pub manual_review_resolved_by_admin_user_id: Option<String>,
|
||
pub manual_review_resolution_reason: Option<String>,
|
||
pub manual_review_resolved_at_micros: Option<i64>,
|
||
pub manual_review_resolved_error_code: Option<String>,
|
||
}
|
||
|
||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
|
||
#[serde(rename_all = "camelCase")]
|
||
pub struct AdminRechargeRefundHoldPayload {
|
||
pub out_refund_no: String,
|
||
pub refund_cents: u64,
|
||
pub held_points: u64,
|
||
pub status: String,
|
||
pub admin_user_id: String,
|
||
pub reason: String,
|
||
pub created_at_micros: i64,
|
||
pub updated_at_micros: i64,
|
||
}
|
||
|
||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
|
||
#[serde(rename_all = "camelCase")]
|
||
pub struct AdminRechargeOrderEntryPayload {
|
||
pub order_id: String,
|
||
pub user_id: String,
|
||
pub user: Option<AdminUserSummaryPayload>,
|
||
pub product_id: String,
|
||
pub product_title: String,
|
||
pub product_kind: String,
|
||
pub amount_cents: u64,
|
||
pub status: String,
|
||
pub payment_channel: String,
|
||
pub paid_at_micros: Option<i64>,
|
||
pub provider_transaction_id: Option<String>,
|
||
pub created_at_micros: i64,
|
||
pub points_delta: i64,
|
||
pub cumulative_success_refund_cents: u64,
|
||
pub target_recovery_points: u64,
|
||
pub recovered_points: u64,
|
||
pub unrecovered_points: u64,
|
||
pub recovery_status: Option<String>,
|
||
pub wallet: AdminProfileWalletPayload,
|
||
pub refunds: Vec<AdminRechargeRefundPayload>,
|
||
pub active_hold: Option<AdminRechargeRefundHoldPayload>,
|
||
pub remaining_refundable_cents: u64,
|
||
pub refund_eligible: bool,
|
||
pub refund_block_reason_code: Option<String>,
|
||
}
|
||
|
||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
|
||
#[serde(rename_all = "camelCase")]
|
||
pub struct AdminRechargeOrderListResponse {
|
||
pub entries: Vec<AdminRechargeOrderEntryPayload>,
|
||
}
|
||
|
||
#[derive(Clone, Debug, Serialize, Deserialize, Default, PartialEq, Eq)]
|
||
#[serde(rename_all = "camelCase")]
|
||
pub struct AdminUserDetailQuery {
|
||
pub user_id: Option<String>,
|
||
pub public_user_code: Option<String>,
|
||
}
|
||
|
||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
|
||
#[serde(rename_all = "camelCase")]
|
||
pub struct AdminUserDetailResponse {
|
||
pub user_id: String,
|
||
pub public_user_code: String,
|
||
pub display_name: String,
|
||
pub avatar_url: Option<String>,
|
||
pub phone_number_masked: Option<String>,
|
||
pub login_method: String,
|
||
pub binding_status: String,
|
||
pub phone_bound: bool,
|
||
pub wechat_bound: bool,
|
||
pub historical_consumed_points: u64,
|
||
pub can_reconcile_consumption: bool,
|
||
pub wallet: AdminProfileWalletPayload,
|
||
pub recharge_orders: Vec<AdminRechargeOrderEntryPayload>,
|
||
}
|
||
|
||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
|
||
#[serde(rename_all = "camelCase")]
|
||
pub struct AdminUserConsumptionReconcileRequest {
|
||
pub user_id: String,
|
||
}
|
||
|
||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
|
||
#[serde(rename_all = "camelCase")]
|
||
pub struct AdminUserConsumptionReconcileResponse {
|
||
pub user_id: String,
|
||
pub previous_historical_consumed_points: Option<u64>,
|
||
pub historical_consumed_points: u64,
|
||
pub changed: bool,
|
||
pub reconciled_at_micros: i64,
|
||
}
|
||
|
||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
|
||
#[serde(rename_all = "camelCase")]
|
||
pub struct AdminUserConsumptionProjectionInitializeResponse {
|
||
pub scanned_ledger_count: u64,
|
||
pub projected_user_count: u64,
|
||
pub inserted_projection_count: u64,
|
||
pub updated_projection_count: u64,
|
||
pub initialized_at_micros: i64,
|
||
}
|
||
|
||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
|
||
#[serde(rename_all = "camelCase")]
|
||
pub struct AdminRechargeRefundPreviewRequest {
|
||
pub order_id: String,
|
||
pub refund_amount_cents: u64,
|
||
}
|
||
|
||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
|
||
#[serde(rename_all = "camelCase")]
|
||
pub struct AdminRechargeRefundExecuteRequest {
|
||
pub order_id: String,
|
||
pub refund_amount_cents: u64,
|
||
pub request_id: String,
|
||
pub reason: Option<String>,
|
||
}
|
||
|
||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
|
||
#[serde(rename_all = "camelCase")]
|
||
pub struct AdminRechargeRefundRegisterRequest {
|
||
pub out_refund_no: String,
|
||
}
|
||
|
||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
|
||
#[serde(rename_all = "camelCase")]
|
||
pub struct AdminRechargeRefundManualReviewResolveRequest {
|
||
pub out_refund_no: String,
|
||
pub reason: String,
|
||
pub expected_error_code: String,
|
||
}
|
||
|
||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
|
||
#[serde(rename_all = "camelCase")]
|
||
pub struct AdminWalletRestrictionRequest {
|
||
pub user_id: String,
|
||
pub frozen: bool,
|
||
pub reason: String,
|
||
}
|
||
|
||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
|
||
#[serde(rename_all = "camelCase")]
|
||
pub struct AdminWechatPaymentCheckPayload {
|
||
pub verified: bool,
|
||
pub trade_state: String,
|
||
pub transaction_id: Option<String>,
|
||
pub amount_total_cents: Option<u64>,
|
||
pub known_refunds_refreshed: u32,
|
||
}
|
||
|
||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
|
||
#[serde(rename_all = "camelCase")]
|
||
pub struct AdminRechargeRefundPreviewResponse {
|
||
pub order: AdminRechargeOrderEntryPayload,
|
||
pub payment_check: AdminWechatPaymentCheckPayload,
|
||
pub refund_amount_cents: u64,
|
||
pub incremental_recovery_points: u64,
|
||
pub remaining_refundable_cents: u64,
|
||
pub can_submit: bool,
|
||
pub block_reason_code: Option<String>,
|
||
}
|
||
|
||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
|
||
#[serde(rename_all = "camelCase")]
|
||
pub struct AdminRechargeRefundActionResponse {
|
||
pub out_refund_no: String,
|
||
pub provider_status: String,
|
||
pub result_code: String,
|
||
pub provider_status_unknown: bool,
|
||
pub order: AdminRechargeOrderEntryPayload,
|
||
}
|
||
|
||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
|
||
#[serde(rename_all = "camelCase")]
|
||
pub struct AdminWalletRestrictionResponse {
|
||
pub wallet: AdminProfileWalletPayload,
|
||
}
|
||
|
||
#[cfg(test)]
|
||
mod tests {
|
||
use serde_json::json;
|
||
|
||
use super::{
|
||
ADMIN_TAB_PERMISSIONS, AdminAgcTemplateListResponse, AdminAgcTemplatePayload,
|
||
AdminConfirmEditorShowcaseCampaignImageUploadRequest, AdminEditorImageSequenceFramePayload,
|
||
AdminEditorShowcaseAssetPayload, AdminRechargeRefundManualReviewResolveRequest,
|
||
AdminUpdateAgcTemplateRequest,
|
||
};
|
||
|
||
#[test]
|
||
fn agc_template_snapshot_uses_camel_case_and_assignable_tab_permission() {
|
||
let value = serde_json::to_value(AdminAgcTemplateListResponse {
|
||
revision: "a".repeat(64),
|
||
writable: true,
|
||
templates: vec![AdminAgcTemplatePayload {
|
||
id: "cocos-empty-2d".to_owned(),
|
||
title: "二维模板".to_owned(),
|
||
summary: "简介".to_owned(),
|
||
tags: vec!["cocos".to_owned()],
|
||
runtime: "cocos".to_owned(),
|
||
engine: "cocos-creator".to_owned(),
|
||
engine_version: "3.8.8".to_owned(),
|
||
template_version: "0.1.0".to_owned(),
|
||
enabled: false,
|
||
cover_url: "https://example.test/cover.svg".to_owned(),
|
||
zip_size_bytes: 1024,
|
||
}],
|
||
})
|
||
.unwrap();
|
||
assert_eq!(
|
||
value,
|
||
json!({
|
||
"revision": "a".repeat(64), "writable": true,
|
||
"templates": [{"id": "cocos-empty-2d", "title": "二维模板", "summary": "简介",
|
||
"tags": ["cocos"], "runtime": "cocos", "engine": "cocos-creator",
|
||
"engineVersion": "3.8.8", "templateVersion": "0.1.0", "enabled": false,
|
||
"coverUrl": "https://example.test/cover.svg", "zipSizeBytes": 1024}]
|
||
})
|
||
);
|
||
assert!(ADMIN_TAB_PERMISSIONS.contains(&"agc-templates"));
|
||
}
|
||
|
||
#[test]
|
||
fn agc_template_edit_rejects_uneditable_and_unknown_cover_fields() {
|
||
let request = json!({"expectedRevision": "a".repeat(64), "title": "模板", "summary": "",
|
||
"tags": [], "enabled": true});
|
||
let parsed: AdminUpdateAgcTemplateRequest =
|
||
serde_json::from_value(request.clone()).unwrap();
|
||
assert!(parsed.cover.is_none());
|
||
assert_eq!(serde_json::to_value(parsed).unwrap(), request);
|
||
for field in ["id", "templateVersion", "runtime", "zipKey"] {
|
||
let mut invalid = request.clone();
|
||
invalid[field] = json!("不可修改");
|
||
assert!(serde_json::from_value::<AdminUpdateAgcTemplateRequest>(invalid).is_err());
|
||
}
|
||
let mut with_cover = request;
|
||
with_cover["cover"] = json!({"contentType": "image/png", "dataBase64": "AQID"});
|
||
let parsed: AdminUpdateAgcTemplateRequest =
|
||
serde_json::from_value(with_cover.clone()).unwrap();
|
||
assert_eq!(parsed.cover.unwrap().data_base64, "AQID");
|
||
with_cover["cover"]["url"] = json!("https://example.test/image.png");
|
||
assert!(serde_json::from_value::<AdminUpdateAgcTemplateRequest>(with_cover).is_err());
|
||
}
|
||
|
||
#[test]
|
||
fn refund_manual_review_resolution_request_uses_camel_case_contract() {
|
||
let value = serde_json::to_value(AdminRechargeRefundManualReviewResolveRequest {
|
||
out_refund_no: "refund-1".to_string(),
|
||
reason: "已核对微信商户平台原始账单".to_string(),
|
||
expected_error_code: "provider_transaction_id_mismatch".to_string(),
|
||
})
|
||
.expect("request should serialize");
|
||
|
||
assert_eq!(value["outRefundNo"], json!("refund-1"));
|
||
assert_eq!(
|
||
value["expectedErrorCode"],
|
||
json!("provider_transaction_id_mismatch")
|
||
);
|
||
assert!(value.get("out_refund_no").is_none());
|
||
}
|
||
|
||
#[test]
|
||
fn editor_showcase_campaign_image_confirm_uses_camel_case_contract() {
|
||
let value = serde_json::to_value(AdminConfirmEditorShowcaseCampaignImageUploadRequest {
|
||
bucket: "genarrative-release".to_string(),
|
||
object_key: "generated-character-drafts/editor/showcase-campaign/current/card.png"
|
||
.to_string(),
|
||
content_type: "image/png".to_string(),
|
||
content_length: 1024,
|
||
})
|
||
.expect("request should serialize");
|
||
|
||
assert_eq!(
|
||
value["objectKey"],
|
||
json!("generated-character-drafts/editor/showcase-campaign/current/card.png")
|
||
);
|
||
assert_eq!(value["contentType"], json!("image/png"));
|
||
assert_eq!(value["contentLength"], json!(1024));
|
||
assert!(value.get("object_key").is_none());
|
||
}
|
||
|
||
#[test]
|
||
fn editor_showcase_asset_payload_serializes_author_fields_as_camel_case() {
|
||
let payload = AdminEditorShowcaseAssetPayload {
|
||
showcase_id: "showcase-1".to_string(),
|
||
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()),
|
||
label: "角色形象 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,
|
||
prompt: None,
|
||
actual_prompt: None,
|
||
model: None,
|
||
provider: None,
|
||
task_id: None,
|
||
source_resource_id: Some("source-resource-1".to_string()),
|
||
source_image_src: Some("/generated-character-drafts/editor/source.png".to_string()),
|
||
source_object_key: Some("generated-character-drafts/editor/source.png".to_string()),
|
||
source_asset_object_id: Some("source-asset-object-1".to_string()),
|
||
source_label: Some("来源素材".to_string()),
|
||
asset_kind: Some("character".to_string()),
|
||
generation_inputs: None,
|
||
thumbnail_src: Some("/generated-character-drafts/editor/spec-thumb.png".to_string()),
|
||
generation_cost_mud_points: 12,
|
||
refund_mud_points: 6,
|
||
review_status: "pending".to_string(),
|
||
display_enabled: false,
|
||
like_count: 0,
|
||
asset_deleted_while_pending: false,
|
||
reviewed_by_admin_user_id: None,
|
||
review_note: None,
|
||
refund_ledger_id: None,
|
||
refund_completed_at: None,
|
||
submitted_at: "2026-07-04T10:00:00.000Z".to_string(),
|
||
reviewed_at: None,
|
||
approved_at: None,
|
||
rejected_at: None,
|
||
updated_at: "2026-07-04T10:00:00.000Z".to_string(),
|
||
showcase_category: None,
|
||
image_sequence_frames: Some(vec![
|
||
AdminEditorImageSequenceFramePayload {
|
||
image_src: "/generated/action/frame-01.png".to_string(),
|
||
object_key: None,
|
||
asset_object_id: None,
|
||
width: 192,
|
||
height: 256,
|
||
},
|
||
AdminEditorImageSequenceFramePayload {
|
||
image_src: "/generated/action/frame-02.png".to_string(),
|
||
object_key: None,
|
||
asset_object_id: None,
|
||
width: 192,
|
||
height: 256,
|
||
},
|
||
]),
|
||
image_sequence_duration_ms: Some(5_000),
|
||
};
|
||
|
||
let value = serde_json::to_value(payload).expect("payload should serialize");
|
||
|
||
assert_eq!(value["authorDisplayName"], json!("作者昵称"));
|
||
assert_eq!(value["authorPublicUserCode"], json!("SY-00000042"));
|
||
assert_eq!(
|
||
value["thumbnailSrc"],
|
||
json!("/generated-character-drafts/editor/spec-thumb.png")
|
||
);
|
||
assert_eq!(value["sourceResourceId"], json!("source-resource-1"));
|
||
assert_eq!(
|
||
value["sourceImageSrc"],
|
||
json!("/generated-character-drafts/editor/source.png")
|
||
);
|
||
assert_eq!(
|
||
value["sourceObjectKey"],
|
||
json!("generated-character-drafts/editor/source.png")
|
||
);
|
||
assert_eq!(value["sourceAssetObjectId"], json!("source-asset-object-1"));
|
||
assert_eq!(value["sourceLabel"], json!("来源素材"));
|
||
assert_eq!(
|
||
value["imageSequenceFrames"].as_array().map(Vec::len),
|
||
Some(2)
|
||
);
|
||
assert_eq!(value["imageSequenceDurationMs"], json!(5_000));
|
||
assert!(value.get("author_display_name").is_none());
|
||
assert!(value.get("author_public_user_code").is_none());
|
||
assert!(value.get("thumbnail_src").is_none());
|
||
assert!(value.get("source_resource_id").is_none());
|
||
assert!(value.get("source_image_src").is_none());
|
||
assert!(value.get("source_object_key").is_none());
|
||
assert!(value.get("source_asset_object_id").is_none());
|
||
assert!(value.get("source_label").is_none());
|
||
assert!(value.get("image_sequence_frames").is_none());
|
||
assert!(value.get("image_sequence_duration_ms").is_none());
|
||
}
|
||
}
|
||
/// AGC 模型目录仅在后台返回实际模型名。
|
||
#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
|
||
#[serde(rename_all = "camelCase", deny_unknown_fields)]
|
||
pub struct AdminAgcModel {
|
||
pub id: String,
|
||
pub alias: String,
|
||
pub model_id: String,
|
||
pub enabled: bool,
|
||
}
|
||
|
||
#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
|
||
#[serde(rename_all = "camelCase", deny_unknown_fields)]
|
||
pub struct AdminAgcModelCatalog {
|
||
pub revision: u64,
|
||
pub default_model_id: String,
|
||
pub models: Vec<AdminAgcModel>,
|
||
}
|
||
|
||
// 客户端埋点明细;时间范围按发生时间,按 (event_time, event_id) 倒序分页。
|
||
// received_at 仅用于固定分页快照的入库时间上界。
|
||
#[derive(Clone, Debug, Default, serde::Deserialize, serde::Serialize)]
|
||
#[serde(rename_all = "camelCase", deny_unknown_fields)]
|
||
pub struct AdminAgcTrackingEventListQuery {
|
||
pub user_id: Option<String>,
|
||
pub project_id: Option<String>,
|
||
pub creative_task_id: Option<String>,
|
||
pub agent_run_id: Option<String>,
|
||
pub event_name: Option<String>,
|
||
pub client_version: Option<String>,
|
||
pub start_time: Option<String>,
|
||
pub end_time: Option<String>,
|
||
pub cursor: Option<String>,
|
||
pub limit: Option<u32>,
|
||
}
|
||
#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
|
||
#[serde(rename_all = "camelCase")]
|
||
pub struct AdminAgcTrackingEventEntry {
|
||
pub event_id: String,
|
||
pub schema_version: u32,
|
||
pub event_name: String,
|
||
pub event_time: String,
|
||
pub user_id: String,
|
||
pub editor_session_id: String,
|
||
pub project_id: Option<String>,
|
||
pub creative_task_id: Option<String>,
|
||
pub agent_run_id: Option<String>,
|
||
pub agent_turn_id: Option<String>,
|
||
pub status: Option<String>,
|
||
pub error_code: Option<String>,
|
||
pub source: String,
|
||
pub client_version: String,
|
||
pub properties: serde_json::Value,
|
||
pub batch_id: String,
|
||
pub received_at: String,
|
||
}
|
||
#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
|
||
#[serde(rename_all = "camelCase")]
|
||
pub struct AdminAgcTrackingEventListPayload {
|
||
pub entries: Vec<AdminAgcTrackingEventEntry>,
|
||
pub next_cursor: Option<String>,
|
||
}
|