Files
Genarrative/server-rs/crates/shared-contracts/src/admin.rs
T
suzmii 4f3f0f24ff 接入AGC后台模型目录与对话模型选择
新增后台 AGC 模型目录、别名、启停和默认项管理

客户端设置页恢复原状,对话框右下角按别名选择模型

服务端按稳定模型标识映射并校验实际模型白名单

修复 AGC 配套后端端口漂移、启动等待和 SpacetimeDB 版本检查

补充迁移、文档、启动与模型选择测试
2026-09-05 19:10:49 +08:00

1273 lines
44 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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; 16] = [
"dashboard",
"overview",
"tables",
"debug",
"tracking",
"gray-release",
"redeem",
"invite",
"profile-wallet",
"tasks",
"recharge-products",
"recharge-orders",
"editor-generation-pricing",
"editor-showcase",
"editor-assets",
"error-reports",
];
/// 不随一级 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,
}
// 登录成功后返回管理员访问令牌与基础会话信息。
/// 后台创作入口开关列表响应。
#[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..=100001 表示 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..=100001 表示 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::{
AdminConfirmEditorShowcaseCampaignImageUploadRequest, AdminEditorImageSequenceFramePayload,
AdminEditorShowcaseAssetPayload, AdminRechargeRefundManualReviewResolveRequest,
};
#[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>,
}