后端重写提交

This commit is contained in:
2026-04-22 12:34:49 +08:00
parent cf8da3f50f
commit 997a8daada
438 changed files with 53355 additions and 865 deletions

View File

@@ -1,6 +1,10 @@
use std::{error::Error, fmt};
use serde::{Deserialize, Serialize};
use shared_kernel::{
build_prefixed_seed_id, format_timestamp_micros, normalize_optional_string,
normalize_required_string,
};
#[cfg(feature = "spacetime-types")]
use spacetimedb::SpacetimeType;
@@ -175,6 +179,28 @@ impl AssetObjectAccessPolicy {
}
}
// 资产核心对象字段需要继续保留模块自己的错误语义,但基础必填字符串归一化统一走 shared-kernel。
fn normalize_required_asset_field(
value: impl AsRef<str>,
error: AssetObjectFieldError,
) -> Result<String, AssetObjectFieldError> {
normalize_required_string(value).ok_or(error)
}
fn normalize_asset_object_key(value: impl AsRef<str>) -> Result<String, AssetObjectFieldError> {
let normalized = value.as_ref().trim();
let normalized = normalized.trim_start_matches('/');
normalize_required_string(normalized).ok_or(AssetObjectFieldError::MissingObjectKey)
}
fn validate_asset_object_version(version: u32) -> Result<(), AssetObjectFieldError> {
if version == 0 {
return Err(AssetObjectFieldError::InvalidVersion);
}
Ok(())
}
// bucket 与 object_key 是正式真相字段,因此这里只做字段校验,不回退成单字符串路径字段。
pub fn validate_asset_object_fields(
bucket: &str,
@@ -182,22 +208,10 @@ pub fn validate_asset_object_fields(
asset_kind: &str,
version: u32,
) -> Result<(), AssetObjectFieldError> {
if bucket.trim().is_empty() {
return Err(AssetObjectFieldError::MissingBucket);
}
if object_key.trim().trim_start_matches('/').is_empty() {
return Err(AssetObjectFieldError::MissingObjectKey);
}
if asset_kind.trim().is_empty() {
return Err(AssetObjectFieldError::MissingAssetKind);
}
if version == 0 {
return Err(AssetObjectFieldError::InvalidVersion);
}
normalize_required_asset_field(bucket, AssetObjectFieldError::MissingBucket)?;
normalize_asset_object_key(object_key)?;
normalize_required_asset_field(asset_kind, AssetObjectFieldError::MissingAssetKind)?;
validate_asset_object_version(version)?;
Ok(())
}
@@ -210,30 +224,12 @@ pub fn validate_asset_entity_binding_fields(
slot: &str,
asset_kind: &str,
) -> Result<(), AssetObjectFieldError> {
if binding_id.trim().is_empty() {
return Err(AssetObjectFieldError::MissingBindingId);
}
if asset_object_id.trim().is_empty() {
return Err(AssetObjectFieldError::MissingAssetObjectId);
}
if entity_kind.trim().is_empty() {
return Err(AssetObjectFieldError::MissingEntityKind);
}
if entity_id.trim().is_empty() {
return Err(AssetObjectFieldError::MissingEntityId);
}
if slot.trim().is_empty() {
return Err(AssetObjectFieldError::MissingSlot);
}
if asset_kind.trim().is_empty() {
return Err(AssetObjectFieldError::MissingAssetKind);
}
normalize_required_asset_field(binding_id, AssetObjectFieldError::MissingBindingId)?;
normalize_required_asset_field(asset_object_id, AssetObjectFieldError::MissingAssetObjectId)?;
normalize_required_asset_field(entity_kind, AssetObjectFieldError::MissingEntityKind)?;
normalize_required_asset_field(entity_id, AssetObjectFieldError::MissingEntityId)?;
normalize_required_asset_field(slot, AssetObjectFieldError::MissingSlot)?;
normalize_required_asset_field(asset_kind, AssetObjectFieldError::MissingAssetKind)?;
Ok(())
}
@@ -253,21 +249,20 @@ pub fn build_asset_object_upsert_input(
entity_id: Option<String>,
updated_at_micros: i64,
) -> Result<AssetObjectUpsertInput, AssetObjectFieldError> {
if asset_object_id.trim().is_empty() {
return Err(AssetObjectFieldError::MissingAssetObjectId);
}
validate_asset_object_fields(
&bucket,
&object_key,
&asset_kind,
INITIAL_ASSET_OBJECT_VERSION,
let asset_object_id = normalize_required_asset_field(
asset_object_id,
AssetObjectFieldError::MissingAssetObjectId,
)?;
let bucket = normalize_required_asset_field(bucket, AssetObjectFieldError::MissingBucket)?;
let object_key = normalize_asset_object_key(object_key)?;
let asset_kind =
normalize_required_asset_field(asset_kind, AssetObjectFieldError::MissingAssetKind)?;
validate_asset_object_version(INITIAL_ASSET_OBJECT_VERSION)?;
Ok(AssetObjectUpsertInput {
asset_object_id: asset_object_id.trim().to_string(),
bucket: bucket.trim().to_string(),
object_key: object_key.trim().trim_start_matches('/').to_string(),
asset_object_id,
bucket,
object_key,
access_policy,
content_type: normalize_optional_value(content_type),
content_length,
@@ -277,7 +272,7 @@ pub fn build_asset_object_upsert_input(
owner_user_id: normalize_optional_value(owner_user_id),
profile_id: normalize_optional_value(profile_id),
entity_id: normalize_optional_value(entity_id),
asset_kind: asset_kind.trim().to_string(),
asset_kind,
updated_at_micros,
})
}
@@ -314,22 +309,27 @@ pub fn build_asset_entity_binding_input(
profile_id: Option<String>,
updated_at_micros: i64,
) -> Result<AssetEntityBindingInput, AssetObjectFieldError> {
validate_asset_entity_binding_fields(
&binding_id,
&asset_object_id,
&entity_kind,
&entity_id,
&slot,
&asset_kind,
let binding_id =
normalize_required_asset_field(binding_id, AssetObjectFieldError::MissingBindingId)?;
let asset_object_id = normalize_required_asset_field(
asset_object_id,
AssetObjectFieldError::MissingAssetObjectId,
)?;
let entity_kind =
normalize_required_asset_field(entity_kind, AssetObjectFieldError::MissingEntityKind)?;
let entity_id =
normalize_required_asset_field(entity_id, AssetObjectFieldError::MissingEntityId)?;
let slot = normalize_required_asset_field(slot, AssetObjectFieldError::MissingSlot)?;
let asset_kind =
normalize_required_asset_field(asset_kind, AssetObjectFieldError::MissingAssetKind)?;
Ok(AssetEntityBindingInput {
binding_id: binding_id.trim().to_string(),
asset_object_id: asset_object_id.trim().to_string(),
entity_kind: entity_kind.trim().to_string(),
entity_id: entity_id.trim().to_string(),
slot: slot.trim().to_string(),
asset_kind: asset_kind.trim().to_string(),
binding_id,
asset_object_id,
entity_kind,
entity_id,
slot,
asset_kind,
owner_user_id: normalize_optional_value(owner_user_id),
profile_id: normalize_optional_value(profile_id),
updated_at_micros,
@@ -354,24 +354,15 @@ pub fn build_asset_entity_binding_record(
}
pub fn generate_asset_object_id(seed_micros: i64) -> String {
format!("{}{:x}", ASSET_OBJECT_ID_PREFIX, seed_micros)
build_prefixed_seed_id(ASSET_OBJECT_ID_PREFIX, seed_micros)
}
pub fn generate_asset_binding_id(seed_micros: i64) -> String {
format!("{}{:x}", ASSET_BINDING_ID_PREFIX, seed_micros)
build_prefixed_seed_id(ASSET_BINDING_ID_PREFIX, seed_micros)
}
pub fn normalize_optional_value(value: Option<String>) -> Option<String> {
value.and_then(|value| {
let value = value.trim().to_string();
if value.is_empty() { None } else { Some(value) }
})
}
fn format_timestamp_micros(micros: i64) -> String {
let seconds = micros.div_euclid(1_000_000);
let subsec_micros = micros.rem_euclid(1_000_000);
format!("{seconds}.{subsec_micros:06}Z")
normalize_optional_string(value)
}
impl fmt::Display for AssetObjectFieldError {