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; 18] = [ "dashboard", "overview", "tables", "debug", "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, pub limit: Option, /// 目标渠道;缺省用本部署渠道。 pub channel: Option, } /// 后台可查看的快照渠道列表。 #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] #[serde(rename_all = "camelCase")] pub struct AdminProjectSnapshotChannelsResponse { /// 本部署渠道:上传与默认查询都用它。 pub default_channel: String, /// 本部署渠道与远端已存在渠道的并集(升序,去重)。 pub channels: Vec, } /// 私有工程快照下载查询:只接受渠道。 #[derive(Clone, Debug, Default, Deserialize)] #[serde(rename_all = "camelCase")] pub struct AdminProjectSnapshotDownloadQuery { /// 目标渠道;缺省用本部署渠道。 pub channel: Option, } #[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, /// 项目归属用户陶泥号,与素材查询同口径;账号不可解析时为占位账号。 #[serde(default)] pub author_public_user_code: Option, } #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] #[serde(rename_all = "camelCase")] pub struct AdminProjectSnapshotsResponse { pub items: Vec, pub next_cursor: Option, } /// 不随一级 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, } /// 合并上架与下架条目的后台展示投影。 #[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, 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, pub enabled: bool, #[serde(default, skip_serializing_if = "Option::is_none")] pub cover: Option, } /// 待保存的封面原始图片;格式、字节和尺寸由后端验证。 #[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, } /// 单条导入模板: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, 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, pub imported: Vec, } /// 单条导入结果。 #[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, /// 底部加号创作入口页的后台公告列表。 pub event_banners: Vec, /// 公开作品详情页点赞 / 改造能力配置。 pub public_work_interactions: Vec, } /// 后台单个创作入口开关配置。 #[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, } /// 后台保存创作入口开关配置请求。 #[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, } /// 后台保存创作入口公告表单序列化结果请求。 #[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, } /// 后台灰度配置列表响应。 #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] #[serde(rename_all = "camelCase")] pub struct AdminFeatureGateConfigResponse { pub gates: Vec, } /// 后台单个灰度 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, pub allow_user_tags: Vec, pub deny_user_ids: Vec, 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, pub allow_user_tags: Vec, pub deny_user_ids: Vec, 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, 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, pub visible: bool, pub published_at_micros: Option, pub updated_at_micros: i64, } /// 后台作品可见性列表响应。 #[cfg(any())] #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] #[serde(rename_all = "camelCase")] pub struct AdminWorkVisibilityListResponse { pub entries: Vec, } /// 后台修改作品可见性请求。 #[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, pub owner_user_id: Option, pub keyword: Option, pub created_after: Option, pub created_before: Option, pub limit: Option, } #[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, #[serde(default, skip_serializing_if = "Option::is_none")] pub asset_object_id: Option, 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, pub author_public_user_code: Option, pub folder_id: String, pub label: String, pub asset_object_id: Option, pub image_src: String, pub object_key: Option, pub width: u32, pub height: u32, pub source_type: String, pub prompt: Option, pub actual_prompt: Option, pub model: Option, pub provider: Option, pub task_id: Option, pub group_task_id: Option, pub asset_kind: Option, pub generation_inputs: Option, pub source_resource_id: Option, pub source_image_src: Option, pub source_object_key: Option, pub source_asset_object_id: Option, pub source_label: Option, pub thumbnail_src: Option, 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, pub image_sequence_frames: Option>, pub image_sequence_duration_ms: Option, } #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] #[serde(rename_all = "camelCase")] pub struct AdminEditorAssetListResponse { pub entries: Vec, pub next_cursor: Option, } #[derive(Clone, Debug, Serialize, Deserialize, Default, PartialEq, Eq)] #[serde(rename_all = "camelCase")] pub struct AdminEditorShowcaseListQuery { pub cursor: Option, pub owner_user_id: Option, pub review_status: Option, pub submitted_after: Option, pub submitted_before: Option, pub limit: Option, } #[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, pub author_public_user_code: Option, pub label: String, pub image_src: String, pub object_key: Option, pub width: u32, pub height: u32, pub prompt: Option, pub actual_prompt: Option, pub model: Option, pub provider: Option, pub task_id: Option, pub source_resource_id: Option, pub source_image_src: Option, pub source_object_key: Option, pub source_asset_object_id: Option, pub source_label: Option, pub asset_kind: Option, pub generation_inputs: Option, pub thumbnail_src: Option, 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, pub review_note: Option, pub refund_ledger_id: Option, pub refund_completed_at: Option, pub submitted_at: String, pub reviewed_at: Option, pub approved_at: Option, pub rejected_at: Option, pub updated_at: String, pub showcase_category: Option, pub image_sequence_frames: Option>, pub image_sequence_duration_ms: Option, } #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] #[serde(rename_all = "camelCase")] pub struct AdminEditorShowcaseListResponse { pub entries: Vec, pub next_cursor: Option, } #[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, } #[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, } #[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, pub image_width: Option, pub image_height: Option, 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, } #[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, pub image_width: Option, pub image_height: Option, 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, } #[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, pub account_role: AdminAccountRole, pub tab_permissions: Vec, pub action_permissions: Vec, 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, pub action_permissions: Vec, 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, } #[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, #[serde(default)] pub action_permissions: Vec, 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, pub tab_permissions: Vec, pub action_permissions: Vec, 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, /// day / week / month 兼容旧查询口径,表示被选中的日期。 pub anchor: Option, /// period 自定义时段起始日期,闭区间。 pub start_date: Option, /// period 自定义时段终止日期,闭区间。 pub end_date: Option, } /// 后台 dashboard 返回运营指标、趋势图和读取告警。 #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] #[serde(rename_all = "camelCase")] pub struct AdminDashboardResponse { pub range: AdminDashboardRangePayload, pub metrics: AdminDashboardMetricsPayload, pub charts: Vec, pub operations: AdminDashboardOperationsPayload, pub warnings: Vec, 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, } /// 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, pub asset_kind_breakdown: Vec, pub module_visit_breakdown: Vec, } /// 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, pub owner_identity: Option, pub host_type: Option, pub schema_table_names: Vec, pub table_stats: Vec, pub fetch_errors: Vec, } // 单表统计允许成功和失败并存,避免某张表失败导致整页概览不可用。 #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] #[serde(rename_all = "camelCase")] pub struct AdminDatabaseTableStatPayload { pub table_name: String, pub row_count: Option, pub error_message: Option, } // 后台表清单独立用于“表查询”页,避免页面必须先拉完整总览。 #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] #[serde(rename_all = "camelCase")] pub struct AdminDatabaseTableListResponse { pub tables: Vec, pub fetch_errors: Vec, } // 后台通用表查询参数,用户输入不进入 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, pub page: Option, pub search: Option, pub filters: Option, pub sort_column: Option, pub sort_direction: Option, } // 后台通用表查询响应,cells 使用列名映射,raw 保留原始行便于详情排障。 #[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] #[serde(rename_all = "camelCase")] pub struct AdminDatabaseTableRowsResponse { pub table_name: String, pub columns: Vec, pub rows: Vec, 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, pub public_user_code: Option, pub key_id: Option, pub name: Option, pub key_prefix: Option, pub created_after: Option, pub created_before: Option, pub status: Option, pub limit: Option, pub offset: Option, pub sort_column: Option, pub sort_direction: Option, } /// 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, pub created_at: String, pub last_used_at: Option, pub revoked_at: Option, pub updated_at: String, pub status: String, } #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] #[serde(rename_all = "camelCase")] pub struct AdminExternalApiKeyListResponse { pub keys: Vec, 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>, pub body: Option, } // 调试请求头使用显式结构,避免页面直接塞任意对象。 #[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, pub body_text: String, pub body_json: Option, } // 后台埋点明细查询参数只保留运营筛选需要的只读字段。 #[derive(Clone, Debug, Serialize, Deserialize, Default, PartialEq, Eq)] #[serde(rename_all = "camelCase")] pub struct AdminTrackingEventListQuery { pub event_key: Option, pub user_id: Option, pub scope_kind: Option, pub scope_id: Option, /// 北京时间日历日期 YYYY-MM-DD,按 tracking_event.day_key 闭区间过滤。 pub start_date: Option, /// 北京时间日历日期 YYYY-MM-DD,按 tracking_event.day_key 闭区间过滤。 pub end_date: Option, pub limit: Option, /// 后台导出专用开关;列表查询仍使用较小上限,避免日常误刷拖慢数据库。 pub export_all: Option, } // 单条埋点原始事件明细,字段与 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, pub owner_user_id: Option, pub profile_id: Option, pub module_key: Option, 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, } // 后台埋点 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, } // 后台埋点 key 列表响应,任务配置页和埋点明细页共用。 #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] #[serde(rename_all = "camelCase")] pub struct AdminTrackingEventKeyListResponse { pub event_keys: Vec, } /// 后台充值订单筛选参数。陶泥号由 BFF 解析为内部用户 ID 后再访问运行时读模型。 #[derive(Clone, Debug, Serialize, Deserialize, Default, PartialEq, Eq)] #[serde(rename_all = "camelCase")] pub struct AdminRechargeOrderListQuery { pub order_id: Option, pub provider_transaction_id: Option, pub user_id: Option, pub public_user_code: Option, pub payment_channel: Option, pub status: Option, pub created_after: Option, pub created_before: Option, pub limit: Option, } #[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, } #[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, } #[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, 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, pub manual_review_resolved_by_admin_user_id: Option, pub manual_review_resolution_reason: Option, pub manual_review_resolved_at_micros: Option, pub manual_review_resolved_error_code: Option, } #[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, 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, pub provider_transaction_id: Option, 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, pub wallet: AdminProfileWalletPayload, pub refunds: Vec, pub active_hold: Option, pub remaining_refundable_cents: u64, pub refund_eligible: bool, pub refund_block_reason_code: Option, } #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] #[serde(rename_all = "camelCase")] pub struct AdminRechargeOrderListResponse { pub entries: Vec, } #[derive(Clone, Debug, Serialize, Deserialize, Default, PartialEq, Eq)] #[serde(rename_all = "camelCase")] pub struct AdminUserDetailQuery { pub user_id: Option, pub public_user_code: Option, } #[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, pub phone_number_masked: Option, 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, } #[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, 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, } #[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, pub amount_total_cents: Option, 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, } #[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::(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::(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, }