//! Waitlist, invite code, and creator wave models. use chrono::{DateTime, Utc}; use serde::Serialize; use sqlx::FromRow; use super::super::id_types::*; use super::super::validated_types::*; /// Approved-entry state: fields that are always present when /// `status == Approved`. #[derive(Debug, Clone)] pub struct ApprovedEntryInfo { /// How this entry was selected (hand-picked or lottery). pub selection_method: super::super::SelectionMethod, /// Creator wave this entry was included in. pub wave_id: CreatorWaveId, /// When the admin approved this entry. pub reviewed_at: DateTime, } /// A creator invite code for controlled alpha growth. #[derive(Debug, Clone, FromRow, Serialize)] pub struct DbInviteCode { pub id: InviteCodeId, pub code: String, pub creator_id: UserId, pub redeemed_by_id: Option, pub redeemed_at: Option>, pub created_at: DateTime, } /// A creator waitlist application. /// /// **State invariant:** When `status == Approved`, `selection_method`, /// `wave_id`, and `reviewed_at` are all `Some`. When `status == Spam`, /// `reviewed_at` may be `Some` but `selection_method` and `wave_id` are /// `None`. When `status == Pending`, all three are `None`. #[derive(Debug, Clone, FromRow, Serialize)] pub struct DbWaitlistEntry { /// Database primary key. pub id: WaitlistEntryId, /// Applicant user ID. pub user_id: UserId, /// Free-text pitch/description from the applicant. NULL for invited users. pub pitch: Option, /// Current status. pub status: super::super::WaitlistStatus, /// How the entry was approved. Present only when `status == Approved`. pub selection_method: Option, /// Creator wave this entry was included in. Present only when `status == Approved`. pub wave_id: Option, /// Internal admin note. pub admin_note: Option, /// When the application was submitted. pub created_at: DateTime, /// When an admin reviewed this entry. Present when `status != Pending`. pub reviewed_at: Option>, /// User who invited this applicant (if invited). pub invited_by_user_id: Option, } impl DbWaitlistEntry { /// Extract approved-state fields as a coherent unit. /// /// Returns `Some` only for approved entries. pub fn approved_info(&self) -> Option { Some(ApprovedEntryInfo { selection_method: self.selection_method?, wave_id: self.wave_id?, reviewed_at: self.reviewed_at?, }) } } /// A batch ("wave") of creators granted access together. #[derive(Debug, Clone, FromRow, Serialize)] pub struct DbCreatorWave { /// Database primary key. pub id: CreatorWaveId, /// Sequential wave number (1, 2, 3, ...). pub wave_number: i32, /// Number of hand-picked creators in this wave. pub hand_picked_count: i32, /// Number of lottery-selected creators in this wave. pub lottery_count: i32, /// Total eligible applicants at the time of this wave. pub total_eligible: i32, /// Optional admin note about this wave. pub note: Option, /// When this wave was created. pub created_at: DateTime, } /// A waitlist entry enriched with user info for the admin dashboard. #[derive(Debug, Clone, FromRow)] #[allow(dead_code)] // Fields populated by sqlx query, read during type conversion pub struct DbAdminWaitlistRow { /// Waitlist entry primary key. pub id: WaitlistEntryId, /// Applicant user ID. pub user_id: UserId, /// Applicant's pitch text. NULL for invited users. pub pitch: Option, /// Current status. pub status: super::super::WaitlistStatus, /// Selection method if approved. pub selection_method: Option, /// Internal admin note. pub admin_note: Option, /// When the application was submitted. pub created_at: DateTime, /// When an admin reviewed this entry. pub reviewed_at: Option>, // Joined from users /// Applicant's username (joined from users). pub username: Username, /// Applicant's email (joined from users). pub email: Email, /// Whether the applicant's email is verified (joined from users). pub email_verified: bool, /// User who invited this applicant (if invited). pub invited_by_user_id: Option, /// Username of the inviter (joined from users). pub invited_by_username: Option, } /// Aggregate counts of waitlist entries by status. #[derive(Debug, Clone, FromRow)] pub struct DbWaitlistStats { /// Number of entries awaiting review. pub pending: i64, /// Number of approved entries. pub approved: i64, /// Number of entries marked as spam. pub spam: i64, } #[cfg(test)] mod tests { use super::*; fn make_waitlist_entry( status: super::super::super::WaitlistStatus, method: Option, wave: Option, reviewed: Option>, ) -> DbWaitlistEntry { DbWaitlistEntry { id: WaitlistEntryId::nil(), user_id: UserId::nil(), pitch: Some("test".to_string()), status, selection_method: method, wave_id: wave, admin_note: None, created_at: Utc::now(), reviewed_at: reviewed, invited_by_user_id: None, } } #[test] fn approved_info_for_approved_entry() { let wave = CreatorWaveId::new(); let now = Utc::now(); let e = make_waitlist_entry( super::super::super::WaitlistStatus::Approved, Some(super::super::super::SelectionMethod::HandPicked), Some(wave), Some(now), ); let info = e.approved_info().unwrap(); assert_eq!(info.selection_method, super::super::super::SelectionMethod::HandPicked); assert_eq!(info.wave_id, wave); assert_eq!(info.reviewed_at, now); } #[test] fn approved_info_none_for_pending() { let e = make_waitlist_entry( super::super::super::WaitlistStatus::Pending, None, None, None, ); assert!(e.approved_info().is_none()); } #[test] fn approved_info_none_for_spam() { // Spam has reviewed_at but no selection_method or wave_id let e = make_waitlist_entry( super::super::super::WaitlistStatus::Spam, None, None, Some(Utc::now()), ); assert!(e.approved_info().is_none()); } }