This commit is contained in:
2026-05-10 22:20:54 +08:00
parent d6219f1a0c
commit 192accd796
92 changed files with 7045 additions and 1559 deletions

View File

@@ -715,6 +715,7 @@ pub fn build_runtime_profile_invite_code_record(
user_id: snapshot.user_id,
invite_code: snapshot.invite_code,
metadata_json: snapshot.metadata_json,
granted_user_tags: snapshot.granted_user_tags,
starts_at: snapshot.starts_at_micros.map(format_utc_micros),
starts_at_micros: snapshot.starts_at_micros,
expires_at: snapshot.expires_at_micros.map(format_utc_micros),

View File

@@ -13,6 +13,9 @@ use crate::domain::*;
use crate::errors::*;
use crate::{format_utc_micros, runtime_profile_recharge_product_by_id};
pub const PROFILE_USER_TAG_MAX_COUNT: usize = 8;
pub const PROFILE_USER_TAG_MAX_CHARS: usize = 16;
// 统一把共享必填字符串归一化映射到 runtime 各自的字段错误,避免输入构造函数重复 trim + 判空。
fn normalize_runtime_settings_user_id(
user_id: String,
@@ -425,6 +428,7 @@ pub fn build_runtime_profile_invite_code_admin_upsert_input(
admin_user_id: String,
invite_code: String,
metadata_json: String,
granted_user_tags: Vec<String>,
starts_at_micros: Option<i64>,
expires_at_micros: Option<i64>,
updated_at_micros: i64,
@@ -433,6 +437,7 @@ pub fn build_runtime_profile_invite_code_admin_upsert_input(
let invite_code =
normalize_invite_code(invite_code).ok_or(RuntimeProfileFieldError::MissingInviteCode)?;
let metadata_json = normalize_invite_code_metadata_json(metadata_json)?;
let granted_user_tags = normalize_profile_user_tags(granted_user_tags)?;
crate::commands::validate_runtime_profile_invite_code_validity_window(
starts_at_micros,
expires_at_micros,
@@ -442,6 +447,7 @@ pub fn build_runtime_profile_invite_code_admin_upsert_input(
admin_user_id,
invite_code,
metadata_json,
granted_user_tags,
starts_at_micros,
expires_at_micros,
updated_at_micros,
@@ -770,6 +776,27 @@ pub fn normalize_invite_code_metadata_json(
serde_json::to_string(&parsed).map_err(|_| RuntimeProfileFieldError::InvalidInviteCodeMetadata)
}
pub fn normalize_profile_user_tags(
values: Vec<String>,
) -> Result<Vec<String>, RuntimeProfileFieldError> {
let mut tags = Vec::new();
for value in values {
let Some(tag) = normalize_optional_string(Some(value)) else {
continue;
};
if tag.chars().count() > PROFILE_USER_TAG_MAX_CHARS {
return Err(RuntimeProfileFieldError::InvalidUserTag);
}
if !tags.iter().any(|existing| existing == &tag) {
tags.push(tag);
}
if tags.len() > PROFILE_USER_TAG_MAX_COUNT {
return Err(RuntimeProfileFieldError::InvalidUserTag);
}
}
Ok(tags)
}
pub fn validate_runtime_profile_invite_code_validity_window(
starts_at_micros: Option<i64>,
expires_at_micros: Option<i64>,

View File

@@ -1121,6 +1121,7 @@ pub struct RuntimeProfileInviteCodeAdminUpsertInput {
pub admin_user_id: String,
pub invite_code: String,
pub metadata_json: String,
pub granted_user_tags: Vec<String>,
pub starts_at_micros: Option<i64>,
pub expires_at_micros: Option<i64>,
pub updated_at_micros: i64,
@@ -1138,6 +1139,7 @@ pub struct RuntimeProfileInviteCodeSnapshot {
pub user_id: String,
pub invite_code: String,
pub metadata_json: String,
pub granted_user_tags: Vec<String>,
pub starts_at_micros: Option<i64>,
pub expires_at_micros: Option<i64>,
pub created_at_micros: i64,
@@ -1510,6 +1512,7 @@ pub struct RuntimeProfileInviteCodeRecord {
pub user_id: String,
pub invite_code: String,
pub metadata_json: String,
pub granted_user_tags: Vec<String>,
pub starts_at: Option<String>,
pub starts_at_micros: Option<i64>,
pub expires_at: Option<String>,

View File

@@ -57,6 +57,7 @@ pub enum RuntimeProfileFieldError {
InvalidRedeemCodeReward,
InvalidRedeemCodeMaxUses,
InvalidInviteCodeMetadata,
InvalidUserTag,
InvalidInviteCodeValidityWindow,
MissingTaskId,
MissingTaskTitle,
@@ -115,6 +116,7 @@ impl std::fmt::Display for RuntimeProfileFieldError {
Self::InvalidInviteCodeMetadata => {
f.write_str("邀请码 metadata 必须是合法 JSON object")
}
Self::InvalidUserTag => f.write_str("用户标签格式无效"),
Self::InvalidInviteCodeValidityWindow => f.write_str("邀请码开始时间不能晚于截止时间"),
Self::MissingTaskId => f.write_str("profile_task.task_id 不能为空"),
Self::MissingTaskTitle => f.write_str("profile_task.title 不能为空"),

View File

@@ -146,6 +146,13 @@ pub fn runtime_profile_recharge_product_by_id(
.find(|product| product.product_id == product_id)
}
pub fn visible_runtime_profile_user_tags(tags: &[String]) -> Vec<String> {
tags.iter()
.filter(|tag| tag.as_str() == "北科")
.cloned()
.collect()
}
fn build_points_recharge_product(
product_id: &str,
title: &str,

View File

@@ -48,6 +48,7 @@ fn invite_code_record_formats_window_and_status() {
user_id: "user-1".to_string(),
invite_code: "SY00000001".to_string(),
metadata_json: "{}".to_string(),
granted_user_tags: Vec::new(),
starts_at_micros: Some(0),
expires_at_micros: Some(1_000_000),
created_at_micros: 0,