SpacetimeDB 定价表追加 3D 两段列,记录形状先能表达端点 × 版本 × 贴图与加价项
- editor_generation_pricing_config 末尾追加 text_to_model_pricing / image_to_model_pricing 两个 Option 列并带显式默认值 - 新增模型版本底价、加价项、端点定价三个 SpacetimeType 行类型,模块侧按段校验空段、缺半段、重复键与非正价格 - upsert 输入、快照、生成绑定与客户端记录映射同步两段字段 - api-server 保存路径本次先写空值,读取仍走本地文件缓存;补模块段级归一化用例
This commit is contained in:
@@ -535,6 +535,9 @@ fn editor_generation_pricing_upsert_input(
|
||||
.spacetime_runtime_service_bootstrap_secret
|
||||
.clone()
|
||||
.unwrap_or_default(),
|
||||
// 3D 两段列在后续步骤接入真实值,当前保存保持空值,读取仍走本地文件缓存。
|
||||
text_to_model_pricing: None,
|
||||
image_to_model_pricing: None,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3013,6 +3016,8 @@ mod tests {
|
||||
updated_by_admin_user_id: Some("admin:test".to_string()),
|
||||
updated_at: "2026-07-10T00:00:00Z".to_string(),
|
||||
updated_at_micros: 1,
|
||||
text_to_model_pricing: None,
|
||||
image_to_model_pricing: None,
|
||||
};
|
||||
|
||||
let actual = editor_generation_pricing_from_record(record, &expected)
|
||||
@@ -3037,6 +3042,8 @@ mod tests {
|
||||
updated_by_admin_user_id: Some("admin:test".to_string()),
|
||||
updated_at: "2026-08-07T00:00:00Z".to_string(),
|
||||
updated_at_micros: 1,
|
||||
text_to_model_pricing: None,
|
||||
image_to_model_pricing: None,
|
||||
};
|
||||
|
||||
let actual = editor_generation_pricing_from_record(record, &fallback)
|
||||
@@ -3088,6 +3095,8 @@ mod tests {
|
||||
updated_by_admin_user_id: None,
|
||||
updated_at: "2026-07-10T00:00:00Z".to_string(),
|
||||
updated_at_micros: 1,
|
||||
text_to_model_pricing: None,
|
||||
image_to_model_pricing: None,
|
||||
};
|
||||
|
||||
let error = editor_generation_pricing_from_record(record, &config)
|
||||
|
||||
@@ -251,6 +251,27 @@ pub struct EditorGenerationPricingConfigRecord {
|
||||
pub updated_by_admin_user_id: Option<String>,
|
||||
pub updated_at: String,
|
||||
pub updated_at_micros: i64,
|
||||
pub text_to_model_pricing: Option<EditorGenerationModel3dPricingRecord>,
|
||||
pub image_to_model_pricing: Option<EditorGenerationModel3dPricingRecord>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
|
||||
pub struct EditorGenerationModel3dVersionPriceRecord {
|
||||
pub model_version: String,
|
||||
pub no_texture: u32,
|
||||
pub texture: u32,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
|
||||
pub struct EditorGenerationModel3dAddOnPriceRecord {
|
||||
pub add_on: String,
|
||||
pub price: u32,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
|
||||
pub struct EditorGenerationModel3dPricingRecord {
|
||||
pub version_prices: Vec<EditorGenerationModel3dVersionPriceRecord>,
|
||||
pub add_on_prices: Vec<EditorGenerationModel3dAddOnPriceRecord>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
|
||||
@@ -564,6 +585,8 @@ pub struct EditorGenerationPricingConfigUpsertRecordInput {
|
||||
pub models: Vec<EditorGenerationModelPricingRecord>,
|
||||
pub updated_at_micros: i64,
|
||||
pub bootstrap_secret: String,
|
||||
pub text_to_model_pricing: Option<EditorGenerationModel3dPricingRecord>,
|
||||
pub image_to_model_pricing: Option<EditorGenerationModel3dPricingRecord>,
|
||||
}
|
||||
|
||||
impl From<EditorProjectCreateRecordInput> for crate::module_bindings::EditorProjectCreateInput {
|
||||
@@ -1020,10 +1043,68 @@ impl From<EditorGenerationPricingConfigUpsertRecordInput>
|
||||
.collect(),
|
||||
updated_at_micros: input.updated_at_micros,
|
||||
bootstrap_secret: input.bootstrap_secret,
|
||||
text_to_model_pricing: input
|
||||
.text_to_model_pricing
|
||||
.map(map_editor_generation_model3d_pricing_record),
|
||||
image_to_model_pricing: input
|
||||
.image_to_model_pricing
|
||||
.map(map_editor_generation_model3d_pricing_record),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn map_editor_generation_model3d_pricing_record(
|
||||
pricing: EditorGenerationModel3dPricingRecord,
|
||||
) -> crate::module_bindings::EditorGenerationModel3DPricing {
|
||||
crate::module_bindings::EditorGenerationModel3DPricing {
|
||||
version_prices: pricing
|
||||
.version_prices
|
||||
.into_iter()
|
||||
.map(
|
||||
|entry| crate::module_bindings::EditorGenerationModel3DVersionPrice {
|
||||
model_version: entry.model_version,
|
||||
no_texture: entry.no_texture,
|
||||
texture: entry.texture,
|
||||
},
|
||||
)
|
||||
.collect(),
|
||||
add_on_prices: pricing
|
||||
.add_on_prices
|
||||
.into_iter()
|
||||
.map(
|
||||
|entry| crate::module_bindings::EditorGenerationModel3DAddOnPrice {
|
||||
add_on: entry.add_on,
|
||||
price: entry.price,
|
||||
},
|
||||
)
|
||||
.collect(),
|
||||
}
|
||||
}
|
||||
|
||||
fn map_editor_generation_model3d_pricing_snapshot(
|
||||
pricing: crate::module_bindings::EditorGenerationModel3DPricing,
|
||||
) -> EditorGenerationModel3dPricingRecord {
|
||||
EditorGenerationModel3dPricingRecord {
|
||||
version_prices: pricing
|
||||
.version_prices
|
||||
.into_iter()
|
||||
.map(|entry| EditorGenerationModel3dVersionPriceRecord {
|
||||
model_version: entry.model_version,
|
||||
no_texture: entry.no_texture,
|
||||
texture: entry.texture,
|
||||
})
|
||||
.collect(),
|
||||
add_on_prices: pricing
|
||||
.add_on_prices
|
||||
.into_iter()
|
||||
.map(|entry| EditorGenerationModel3dAddOnPriceRecord {
|
||||
add_on: entry.add_on,
|
||||
price: entry.price,
|
||||
})
|
||||
.collect(),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn map_editor_project_optional_procedure_result(
|
||||
result: EditorProjectProcedureResult,
|
||||
) -> Result<Option<EditorProjectRecord>, SpacetimeClientError> {
|
||||
@@ -1627,6 +1708,12 @@ fn map_editor_generation_pricing_config_snapshot(
|
||||
updated_by_admin_user_id: snapshot.updated_by_admin_user_id,
|
||||
updated_at: format_timestamp_micros(snapshot.updated_at_micros),
|
||||
updated_at_micros: snapshot.updated_at_micros,
|
||||
text_to_model_pricing: snapshot
|
||||
.text_to_model_pricing
|
||||
.map(map_editor_generation_model3d_pricing_snapshot),
|
||||
image_to_model_pricing: snapshot
|
||||
.image_to_model_pricing
|
||||
.map(map_editor_generation_model3d_pricing_snapshot),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -302,6 +302,9 @@ pub mod editor_canvas_table;
|
||||
pub mod editor_canvas_type;
|
||||
pub mod editor_character_animation_normalization_input_type;
|
||||
pub mod editor_character_animation_normalization_procedure_result_type;
|
||||
pub mod editor_generation_model_3_d_add_on_price_type;
|
||||
pub mod editor_generation_model_3_d_pricing_type;
|
||||
pub mod editor_generation_model_3_d_version_price_type;
|
||||
pub mod editor_generation_model_pricing_type;
|
||||
pub mod editor_generation_operation_table;
|
||||
pub mod editor_generation_operation_type;
|
||||
@@ -1213,6 +1216,9 @@ pub use editor_canvas_table::*;
|
||||
pub use editor_canvas_type::EditorCanvas;
|
||||
pub use editor_character_animation_normalization_input_type::EditorCharacterAnimationNormalizationInput;
|
||||
pub use editor_character_animation_normalization_procedure_result_type::EditorCharacterAnimationNormalizationProcedureResult;
|
||||
pub use editor_generation_model_3_d_add_on_price_type::EditorGenerationModel3DAddOnPrice;
|
||||
pub use editor_generation_model_3_d_pricing_type::EditorGenerationModel3DPricing;
|
||||
pub use editor_generation_model_3_d_version_price_type::EditorGenerationModel3DVersionPrice;
|
||||
pub use editor_generation_model_pricing_type::EditorGenerationModelPricing;
|
||||
pub use editor_generation_operation_table::*;
|
||||
pub use editor_generation_operation_type::EditorGenerationOperation;
|
||||
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE
|
||||
// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD.
|
||||
|
||||
#![allow(unused, clippy::all)]
|
||||
use spacetimedb_sdk::__codegen::{self as __sdk, __lib, __sats, __ws};
|
||||
|
||||
#[derive(__lib::ser::Serialize, __lib::de::Deserialize, Clone, PartialEq, Debug)]
|
||||
#[sats(crate = __lib)]
|
||||
pub struct EditorGenerationModel3DAddOnPrice {
|
||||
pub add_on: String,
|
||||
pub price: u32,
|
||||
}
|
||||
|
||||
impl __sdk::InModule for EditorGenerationModel3DAddOnPrice {
|
||||
type Module = super::RemoteModule;
|
||||
}
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE
|
||||
// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD.
|
||||
|
||||
#![allow(unused, clippy::all)]
|
||||
use spacetimedb_sdk::__codegen::{self as __sdk, __lib, __sats, __ws};
|
||||
|
||||
use super::editor_generation_model_3_d_add_on_price_type::EditorGenerationModel3DAddOnPrice;
|
||||
use super::editor_generation_model_3_d_version_price_type::EditorGenerationModel3DVersionPrice;
|
||||
|
||||
#[derive(__lib::ser::Serialize, __lib::de::Deserialize, Clone, PartialEq, Debug)]
|
||||
#[sats(crate = __lib)]
|
||||
pub struct EditorGenerationModel3DPricing {
|
||||
pub version_prices: Vec<EditorGenerationModel3DVersionPrice>,
|
||||
pub add_on_prices: Vec<EditorGenerationModel3DAddOnPrice>,
|
||||
}
|
||||
|
||||
impl __sdk::InModule for EditorGenerationModel3DPricing {
|
||||
type Module = super::RemoteModule;
|
||||
}
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE
|
||||
// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD.
|
||||
|
||||
#![allow(unused, clippy::all)]
|
||||
use spacetimedb_sdk::__codegen::{self as __sdk, __lib, __sats, __ws};
|
||||
|
||||
#[derive(__lib::ser::Serialize, __lib::de::Deserialize, Clone, PartialEq, Debug)]
|
||||
#[sats(crate = __lib)]
|
||||
pub struct EditorGenerationModel3DVersionPrice {
|
||||
pub model_version: String,
|
||||
pub no_texture: u32,
|
||||
pub texture: u32,
|
||||
}
|
||||
|
||||
impl __sdk::InModule for EditorGenerationModel3DVersionPrice {
|
||||
type Module = super::RemoteModule;
|
||||
}
|
||||
+3
@@ -4,6 +4,7 @@
|
||||
#![allow(unused, clippy::all)]
|
||||
use spacetimedb_sdk::__codegen::{self as __sdk, __lib, __sats, __ws};
|
||||
|
||||
use super::editor_generation_model_3_d_pricing_type::EditorGenerationModel3DPricing;
|
||||
use super::editor_generation_model_pricing_type::EditorGenerationModelPricing;
|
||||
|
||||
#[derive(__lib::ser::Serialize, __lib::de::Deserialize, Clone, PartialEq, Debug)]
|
||||
@@ -13,6 +14,8 @@ pub struct EditorGenerationPricingConfigSnapshot {
|
||||
pub models: Vec<EditorGenerationModelPricing>,
|
||||
pub updated_by_admin_user_id: Option<String>,
|
||||
pub updated_at_micros: i64,
|
||||
pub text_to_model_pricing: Option<EditorGenerationModel3DPricing>,
|
||||
pub image_to_model_pricing: Option<EditorGenerationModel3DPricing>,
|
||||
}
|
||||
|
||||
impl __sdk::InModule for EditorGenerationPricingConfigSnapshot {
|
||||
|
||||
+1
@@ -2,6 +2,7 @@
|
||||
// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD.
|
||||
|
||||
#![allow(unused, clippy::all)]
|
||||
use super::editor_generation_model_3_d_pricing_type::EditorGenerationModel3DPricing;
|
||||
use super::editor_generation_model_pricing_type::EditorGenerationModelPricing;
|
||||
use super::editor_generation_pricing_config_type::EditorGenerationPricingConfig;
|
||||
use spacetimedb_sdk::__codegen::{self as __sdk, __lib, __sats, __ws};
|
||||
|
||||
+19
@@ -4,6 +4,7 @@
|
||||
#![allow(unused, clippy::all)]
|
||||
use spacetimedb_sdk::__codegen::{self as __sdk, __lib, __sats, __ws};
|
||||
|
||||
use super::editor_generation_model_3_d_pricing_type::EditorGenerationModel3DPricing;
|
||||
use super::editor_generation_model_pricing_type::EditorGenerationModelPricing;
|
||||
|
||||
#[derive(__lib::ser::Serialize, __lib::de::Deserialize, Clone, PartialEq, Debug)]
|
||||
@@ -14,6 +15,8 @@ pub struct EditorGenerationPricingConfig {
|
||||
pub updated_by_admin_user_id: Option<String>,
|
||||
pub updated_at: __sdk::Timestamp,
|
||||
pub writer_identity: __sdk::Identity,
|
||||
pub text_to_model_pricing: Option<EditorGenerationModel3DPricing>,
|
||||
pub image_to_model_pricing: Option<EditorGenerationModel3DPricing>,
|
||||
}
|
||||
|
||||
impl __sdk::InModule for EditorGenerationPricingConfig {
|
||||
@@ -34,6 +37,14 @@ pub struct EditorGenerationPricingConfigCols {
|
||||
pub updated_at: __sdk::__query_builder::Col<EditorGenerationPricingConfig, __sdk::Timestamp>,
|
||||
pub writer_identity:
|
||||
__sdk::__query_builder::Col<EditorGenerationPricingConfig, __sdk::Identity>,
|
||||
pub text_to_model_pricing: __sdk::__query_builder::Col<
|
||||
EditorGenerationPricingConfig,
|
||||
Option<EditorGenerationModel3DPricing>,
|
||||
>,
|
||||
pub image_to_model_pricing: __sdk::__query_builder::Col<
|
||||
EditorGenerationPricingConfig,
|
||||
Option<EditorGenerationModel3DPricing>,
|
||||
>,
|
||||
}
|
||||
|
||||
impl __sdk::__query_builder::HasCols for EditorGenerationPricingConfig {
|
||||
@@ -48,6 +59,14 @@ impl __sdk::__query_builder::HasCols for EditorGenerationPricingConfig {
|
||||
),
|
||||
updated_at: __sdk::__query_builder::Col::new(table_name, "updated_at"),
|
||||
writer_identity: __sdk::__query_builder::Col::new(table_name, "writer_identity"),
|
||||
text_to_model_pricing: __sdk::__query_builder::Col::new(
|
||||
table_name,
|
||||
"text_to_model_pricing",
|
||||
),
|
||||
image_to_model_pricing: __sdk::__query_builder::Col::new(
|
||||
table_name,
|
||||
"image_to_model_pricing",
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+3
@@ -4,6 +4,7 @@
|
||||
#![allow(unused, clippy::all)]
|
||||
use spacetimedb_sdk::__codegen::{self as __sdk, __lib, __sats, __ws};
|
||||
|
||||
use super::editor_generation_model_3_d_pricing_type::EditorGenerationModel3DPricing;
|
||||
use super::editor_generation_model_pricing_type::EditorGenerationModelPricing;
|
||||
|
||||
#[derive(__lib::ser::Serialize, __lib::de::Deserialize, Clone, PartialEq, Debug)]
|
||||
@@ -13,6 +14,8 @@ pub struct EditorGenerationPricingConfigUpsertInput {
|
||||
pub models: Vec<EditorGenerationModelPricing>,
|
||||
pub updated_at_micros: i64,
|
||||
pub bootstrap_secret: String,
|
||||
pub text_to_model_pricing: Option<EditorGenerationModel3DPricing>,
|
||||
pub image_to_model_pricing: Option<EditorGenerationModel3DPricing>,
|
||||
}
|
||||
|
||||
impl __sdk::InModule for EditorGenerationPricingConfigUpsertInput {
|
||||
|
||||
@@ -108,6 +108,28 @@ pub struct EditorGenerationModelPricing {
|
||||
pub prices: Vec<EditorGenerationPricingTier>,
|
||||
}
|
||||
|
||||
/// 一个模型版本在「无贴图 / 带贴图」两种形态下的 3D 生成底价。
|
||||
#[derive(Clone, Debug, PartialEq, Eq, SpacetimeType)]
|
||||
pub struct EditorGenerationModel3dVersionPrice {
|
||||
pub model_version: String,
|
||||
pub no_texture: u32,
|
||||
pub texture: u32,
|
||||
}
|
||||
|
||||
/// 叠加在 3D 生成底价之上的加价项单价。
|
||||
#[derive(Clone, Debug, PartialEq, Eq, SpacetimeType)]
|
||||
pub struct EditorGenerationModel3dAddOnPrice {
|
||||
pub add_on: String,
|
||||
pub price: u32,
|
||||
}
|
||||
|
||||
/// 单个 3D 生成端点的整套定价:模型版本底价与该端点自己的加价项价目。
|
||||
#[derive(Clone, Debug, PartialEq, Eq, SpacetimeType)]
|
||||
pub struct EditorGenerationModel3dPricing {
|
||||
pub version_prices: Vec<EditorGenerationModel3dVersionPrice>,
|
||||
pub add_on_prices: Vec<EditorGenerationModel3dAddOnPrice>,
|
||||
}
|
||||
|
||||
#[spacetimedb::table(
|
||||
accessor = editor_project,
|
||||
index(accessor = by_editor_project_owner_user_id, btree(columns = [owner_user_id]))
|
||||
@@ -455,6 +477,10 @@ pub struct EditorGenerationPricingConfig {
|
||||
updated_by_admin_user_id: Option<String>,
|
||||
updated_at: Timestamp,
|
||||
writer_identity: Identity,
|
||||
#[default(None::<EditorGenerationModel3dPricing>)]
|
||||
text_to_model_pricing: Option<EditorGenerationModel3dPricing>,
|
||||
#[default(None::<EditorGenerationModel3dPricing>)]
|
||||
image_to_model_pricing: Option<EditorGenerationModel3dPricing>,
|
||||
}
|
||||
|
||||
#[spacetimedb::table(accessor = editor_generation_runtime_identity_rotation)]
|
||||
@@ -957,6 +983,8 @@ pub struct EditorGenerationPricingConfigSnapshot {
|
||||
pub models: Vec<EditorGenerationModelPricing>,
|
||||
pub updated_by_admin_user_id: Option<String>,
|
||||
pub updated_at_micros: i64,
|
||||
pub text_to_model_pricing: Option<EditorGenerationModel3dPricing>,
|
||||
pub image_to_model_pricing: Option<EditorGenerationModel3dPricing>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, SpacetimeType)]
|
||||
@@ -1304,6 +1332,8 @@ pub struct EditorGenerationPricingConfigUpsertInput {
|
||||
pub models: Vec<EditorGenerationModelPricing>,
|
||||
pub updated_at_micros: i64,
|
||||
pub bootstrap_secret: String,
|
||||
pub text_to_model_pricing: Option<EditorGenerationModel3dPricing>,
|
||||
pub image_to_model_pricing: Option<EditorGenerationModel3dPricing>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, SpacetimeType)]
|
||||
@@ -6786,6 +6816,14 @@ fn upsert_editor_generation_pricing_config(
|
||||
"editor_generation_pricing_config.updated_by_admin_user_id",
|
||||
)?;
|
||||
let models = normalize_editor_generation_pricing_models(input.models)?;
|
||||
let text_to_model_pricing = normalize_editor_generation_model3d_pricing(
|
||||
input.text_to_model_pricing,
|
||||
"editor_generation_pricing_config.text_to_model_pricing",
|
||||
)?;
|
||||
let image_to_model_pricing = normalize_editor_generation_model3d_pricing(
|
||||
input.image_to_model_pricing,
|
||||
"editor_generation_pricing_config.image_to_model_pricing",
|
||||
)?;
|
||||
let updated_at = Timestamp::from_micros_since_unix_epoch(input.updated_at_micros);
|
||||
let config_id = EDITOR_GENERATION_PRICING_CONFIG_ID.to_string();
|
||||
let writer_identity = resolve_editor_generation_pricing_writer_identity(
|
||||
@@ -6806,6 +6844,8 @@ fn upsert_editor_generation_pricing_config(
|
||||
updated_by_admin_user_id: Some(admin_user_id),
|
||||
updated_at,
|
||||
writer_identity,
|
||||
text_to_model_pricing,
|
||||
image_to_model_pricing,
|
||||
});
|
||||
ctx.db
|
||||
.editor_generation_pricing_config()
|
||||
@@ -6844,6 +6884,14 @@ fn initialize_editor_generation_pricing_config_if_missing(
|
||||
"editor_generation_pricing_config.updated_by_admin_user_id",
|
||||
)?;
|
||||
let models = normalize_editor_generation_pricing_models(input.models)?;
|
||||
let text_to_model_pricing = normalize_editor_generation_model3d_pricing(
|
||||
input.text_to_model_pricing,
|
||||
"editor_generation_pricing_config.text_to_model_pricing",
|
||||
)?;
|
||||
let image_to_model_pricing = normalize_editor_generation_model3d_pricing(
|
||||
input.image_to_model_pricing,
|
||||
"editor_generation_pricing_config.image_to_model_pricing",
|
||||
)?;
|
||||
let row = ctx
|
||||
.db
|
||||
.editor_generation_pricing_config()
|
||||
@@ -6853,6 +6901,8 @@ fn initialize_editor_generation_pricing_config_if_missing(
|
||||
updated_by_admin_user_id: Some(admin_user_id),
|
||||
updated_at: Timestamp::from_micros_since_unix_epoch(input.updated_at_micros),
|
||||
writer_identity: caller,
|
||||
text_to_model_pricing,
|
||||
image_to_model_pricing,
|
||||
});
|
||||
Ok(editor_generation_pricing_config_snapshot_from_row(row))
|
||||
}
|
||||
@@ -7923,9 +7973,52 @@ fn editor_generation_pricing_config_snapshot_from_row(
|
||||
models: row.models,
|
||||
updated_by_admin_user_id: row.updated_by_admin_user_id,
|
||||
updated_at_micros: row.updated_at.to_micros_since_unix_epoch(),
|
||||
text_to_model_pricing: row.text_to_model_pricing,
|
||||
image_to_model_pricing: row.image_to_model_pricing,
|
||||
}
|
||||
}
|
||||
|
||||
/// 3D 生成定价两个端点的段级校验:段允许整体缺失,非空时必须同时给出底价与加价项、
|
||||
/// 键唯一且价格为正。契约支持的模型版本与加价项集合由 api-server 侧的枚举负责,
|
||||
/// 模块这里只守存储形状,避免把模型版本清单复制成第二份真相。
|
||||
fn normalize_editor_generation_model3d_pricing(
|
||||
pricing: Option<EditorGenerationModel3dPricing>,
|
||||
label: &str,
|
||||
) -> Result<Option<EditorGenerationModel3dPricing>, String> {
|
||||
let Some(mut pricing) = pricing else {
|
||||
return Ok(None);
|
||||
};
|
||||
if pricing.version_prices.is_empty() || pricing.add_on_prices.is_empty() {
|
||||
return Err(format!("{label} 必须同时给出模型版本底价与加价项"));
|
||||
}
|
||||
|
||||
let mut seen_versions = BTreeSet::new();
|
||||
for entry in pricing.version_prices.iter_mut() {
|
||||
let model_version = normalize_required(&entry.model_version, label)?;
|
||||
if !seen_versions.insert(model_version.clone()) {
|
||||
return Err(format!("{label} 包含重复模型版本 {model_version}"));
|
||||
}
|
||||
if entry.no_texture == 0 || entry.texture == 0 {
|
||||
return Err(format!("{label} 的模型版本 {model_version} 价格必须大于 0"));
|
||||
}
|
||||
entry.model_version = model_version;
|
||||
}
|
||||
|
||||
let mut seen_add_ons = BTreeSet::new();
|
||||
for entry in pricing.add_on_prices.iter_mut() {
|
||||
let add_on = normalize_required(&entry.add_on, label)?;
|
||||
if !seen_add_ons.insert(add_on.clone()) {
|
||||
return Err(format!("{label} 包含重复加价项 {add_on}"));
|
||||
}
|
||||
if entry.price == 0 {
|
||||
return Err(format!("{label} 的加价项 {add_on} 价格必须大于 0"));
|
||||
}
|
||||
entry.add_on = add_on;
|
||||
}
|
||||
|
||||
Ok(Some(pricing))
|
||||
}
|
||||
|
||||
fn normalize_editor_generation_pricing_models(
|
||||
models: Vec<EditorGenerationModelPricing>,
|
||||
) -> Result<Vec<EditorGenerationModelPricing>, String> {
|
||||
@@ -20023,11 +20116,73 @@ mod tests {
|
||||
models: valid_editor_generation_pricing_models(),
|
||||
updated_by_admin_user_id: Some("admin:test".to_string()),
|
||||
updated_at_micros: 1,
|
||||
text_to_model_pricing: None,
|
||||
image_to_model_pricing: None,
|
||||
};
|
||||
|
||||
assert!(!format!("{snapshot:?}").contains("writer_identity"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn normalize_editor_generation_model3d_pricing_accepts_absent_and_complete_sections() {
|
||||
assert_eq!(
|
||||
normalize_editor_generation_model3d_pricing(None, "段"),
|
||||
Ok(None)
|
||||
);
|
||||
|
||||
let pricing = EditorGenerationModel3dPricing {
|
||||
version_prices: vec![EditorGenerationModel3dVersionPrice {
|
||||
model_version: "v3.1-20260211".to_string(),
|
||||
no_texture: 8,
|
||||
texture: 16,
|
||||
}],
|
||||
add_on_prices: vec![EditorGenerationModel3dAddOnPrice {
|
||||
add_on: " hdTexture ".to_string(),
|
||||
price: 8,
|
||||
}],
|
||||
};
|
||||
|
||||
let normalized = normalize_editor_generation_model3d_pricing(Some(pricing), "段")
|
||||
.expect("complete section should normalize");
|
||||
let normalized = normalized.expect("section should stay present");
|
||||
assert_eq!(normalized.add_on_prices[0].add_on, "hdTexture");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn normalize_editor_generation_model3d_pricing_rejects_partial_and_duplicate_keys() {
|
||||
let partial = EditorGenerationModel3dPricing {
|
||||
version_prices: vec![EditorGenerationModel3dVersionPrice {
|
||||
model_version: "v3.1-20260211".to_string(),
|
||||
no_texture: 8,
|
||||
texture: 16,
|
||||
}],
|
||||
add_on_prices: Vec::new(),
|
||||
};
|
||||
assert!(normalize_editor_generation_model3d_pricing(Some(partial), "段").is_err());
|
||||
|
||||
let duplicate = EditorGenerationModel3dPricing {
|
||||
version_prices: vec![
|
||||
EditorGenerationModel3dVersionPrice {
|
||||
model_version: "v3.1-20260211".to_string(),
|
||||
no_texture: 8,
|
||||
texture: 16,
|
||||
},
|
||||
EditorGenerationModel3dVersionPrice {
|
||||
model_version: "v3.1-20260211".to_string(),
|
||||
no_texture: 10,
|
||||
texture: 18,
|
||||
},
|
||||
],
|
||||
add_on_prices: vec![EditorGenerationModel3dAddOnPrice {
|
||||
add_on: "hdTexture".to_string(),
|
||||
price: 8,
|
||||
}],
|
||||
};
|
||||
let error = normalize_editor_generation_model3d_pricing(Some(duplicate), "段")
|
||||
.expect_err("duplicate model version should fail");
|
||||
assert!(error.contains("重复模型版本"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn normalize_editor_generation_pricing_models_rejects_missing_required_model() {
|
||||
let mut models = valid_editor_generation_pricing_models();
|
||||
|
||||
Reference in New Issue
Block a user