feat: add invite code validity controls

- Add invite code starts/expires fields across contracts, API, Spacetime bindings, and admin UI
- Enforce pending/expired invite code redemption behavior and expose admin status
- Add admin write-operation confirmation guard and documentation
- Add invite code contract/runtime tests
This commit is contained in:
2026-05-04 12:29:33 +08:00
parent 1142e90a35
commit 9f3e34e81a
27 changed files with 1465 additions and 97 deletions

View File

@@ -21,6 +21,10 @@ pub const PROFILE_INVITE_CODE_METADATA_DEFAULT_JSON: &str = "{}";
pub const PROFILE_INVITE_CODE_METADATA_MAX_BYTES: usize = 4096;
pub const PROFILE_RUNTIME_DAY_MICROS: i64 = 86_400_000_000;
pub const PROFILE_TASK_BEIJING_OFFSET_MICROS: i64 = 28_800_000_000;
pub const ANALYTICS_DATE_DIMENSION_MAX_SEED_DAYS: i64 = 3_660;
// 中文注释:日期维表当前只预置运营统计可接受的业务日期范围,避免裸 date_key 极值进入日历算法。
pub const ANALYTICS_DATE_DIMENSION_MIN_DATE: &str = "2000-01-01";
pub const ANALYTICS_DATE_DIMENSION_MAX_DATE: &str = "2100-12-31";
pub const PROFILE_TASK_ID_DAILY_LOGIN: &str = "daily_login";
pub const PROFILE_TASK_EVENT_KEY_DAILY_LOGIN: &str = "daily_login";
pub const PROFILE_TASK_DEFAULT_TITLE_DAILY_LOGIN: &str = "每日登录";
@@ -30,6 +34,30 @@ pub const SAVE_SNAPSHOT_VERSION: u32 = 2;
pub const DEFAULT_SAVE_ARCHIVE_SUMMARY_TEXT: &str = "继续推进上一次保存的故事。";
pub const PROFILE_RECHARGE_PAYMENT_CHANNEL_MOCK: &str = "mock";
/// 分析日期维表的纯领域快照。
///
/// date_key 沿用现有北京时间自然日桶floor((occurred_at_micros + 8h) / 1d)。
/// calendar_date 使用该业务日对应的公历日期,格式固定为 YYYY-MM-DD。
#[cfg_attr(feature = "spacetime-types", derive(SpacetimeType))]
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct AnalyticsDateDimensionSnapshot {
pub date_key: i64,
pub calendar_date: String,
pub weekday: u8,
pub iso_week_key: i32,
pub week_start_date_key: i64,
pub week_end_date_key: i64,
pub month_key: i32,
pub month_start_date_key: i64,
pub month_end_date_key: i64,
pub quarter_key: i32,
pub quarter_start_date_key: i64,
pub quarter_end_date_key: i64,
pub year_key: i32,
pub year_start_date_key: i64,
pub year_end_date_key: i64,
}
/// 运行时平台主题。
///
/// 当前只冻结 light/dark 两种主题,避免各层散落字符串字面量。
@@ -411,6 +439,24 @@ impl RuntimeProfileTaskStatus {
}
}
#[cfg_attr(feature = "spacetime-types", derive(SpacetimeType))]
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub enum RuntimeProfileInviteCodeStatus {
Pending,
Active,
Expired,
}
impl RuntimeProfileInviteCodeStatus {
pub fn as_str(&self) -> &'static str {
match self {
Self::Pending => "pending",
Self::Active => "active",
Self::Expired => "expired",
}
}
}
#[cfg_attr(feature = "spacetime-types", derive(SpacetimeType))]
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct RuntimeTrackingEventInput {
@@ -904,6 +950,8 @@ pub struct RuntimeProfileInviteCodeAdminUpsertInput {
pub admin_user_id: String,
pub invite_code: String,
pub metadata_json: String,
pub starts_at_micros: Option<i64>,
pub expires_at_micros: Option<i64>,
pub updated_at_micros: i64,
}
@@ -919,6 +967,8 @@ pub struct RuntimeProfileInviteCodeSnapshot {
pub user_id: String,
pub invite_code: String,
pub metadata_json: String,
pub starts_at_micros: Option<i64>,
pub expires_at_micros: Option<i64>,
pub created_at_micros: i64,
pub updated_at_micros: i64,
}
@@ -1289,6 +1339,11 @@ pub struct RuntimeProfileInviteCodeRecord {
pub user_id: String,
pub invite_code: String,
pub metadata_json: String,
pub starts_at: Option<String>,
pub starts_at_micros: Option<i64>,
pub expires_at: Option<String>,
pub expires_at_micros: Option<i64>,
pub status: RuntimeProfileInviteCodeStatus,
pub created_at: String,
pub created_at_micros: i64,
pub updated_at: String,