use sea_orm::*; use rand::distr::Alphanumeric; use rand::Rng; use crate::models::invite_link::{self, Entity as InviteLink, Model as InviteLinkModel}; use crate::models::{user, User}; pub struct InviteLinkService; impl InviteLinkService { pub fn generate_token() -> String { rand::rng() .sample_iter(&Alphanumeric) .take(32) .map(char::from) .collect() } pub async fn create( db: &DatabaseConnection, family_id: i32, created_by: i32, expires_at: Option, max_uses: Option, ) -> Result { let token = Self::generate_token(); let invite = invite_link::ActiveModel { family_id: Set(family_id), token: Set(token), created_by: Set(created_by), expires_at: Set(expires_at), max_uses: Set(max_uses), ..Default::default() }; invite.insert(db).await } pub async fn find_by_token( db: &DatabaseConnection, token: &str, ) -> Result, DbErr> { InviteLink::find() .filter(invite_link::Column::Token.eq(token)) .one(db) .await } pub async fn find_by_family( db: &DatabaseConnection, family_id: i32, ) -> Result, DbErr> { InviteLink::find() .filter(invite_link::Column::FamilyId.eq(family_id)) .all(db) .await } pub async fn validate_and_use( db: &DatabaseConnection, token: &str, user_id: i32, ) -> Result { let invite = InviteLink::find() .filter(invite_link::Column::Token.eq(token)) .one(db) .await? .ok_or(DbErr::RecordNotFound("Invite link not found".to_string()))?; if let Some(expires_at) = invite.expires_at { let now = chrono::Utc::now().naive_utc(); if now > expires_at { return Err(DbErr::Custom("Invite link has expired".to_string())); } } if let Some(max_uses) = invite.max_uses { if invite.uses_count >= max_uses { return Err(DbErr::Custom("Invite link has reached max uses".to_string())); } } let user = User::find_by_id(user_id) .one(db) .await? .ok_or(DbErr::RecordNotFound("User not found".to_string()))?; if user.family_id.is_some() { return Err(DbErr::Custom("User already belongs to a family".to_string())); } let mut active_user: user::ActiveModel = user.into(); active_user.family_id = Set(Some(invite.family_id)); active_user.update(db).await?; let mut active_invite: invite_link::ActiveModel = invite.clone().into(); active_invite.uses_count = Set(invite.uses_count + 1); active_invite.update(db).await } pub async fn delete(db: &DatabaseConnection, id: i32) -> Result { let invite = InviteLink::find_by_id(id) .one(db) .await? .ok_or(DbErr::RecordNotFound("Invite link not found".to_string()))?; let invite: invite_link::ActiveModel = invite.into(); invite.delete(db).await } pub async fn delete_by_token(db: &DatabaseConnection, token: &str) -> Result { let invite = InviteLink::find() .filter(invite_link::Column::Token.eq(token)) .one(db) .await? .ok_or(DbErr::RecordNotFound("Invite link not found".to_string()))?; let invite: invite_link::ActiveModel = invite.into(); invite.delete(db).await } }