use chrono::{DateTime, Utc}; use serde::{Deserialize, Serialize}; use sqlx::PgPool; use uuid::Uuid; #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, sqlx::Type)] #[sqlx(type_name = "user_role", rename_all = "lowercase")] pub enum UserRole { Guest, Host, Admin, } #[derive(Debug, sqlx::FromRow)] pub struct User { pub id: Uuid, pub event_id: Uuid, pub display_name: String, pub role: UserRole, pub is_banned: bool, pub uploads_hidden: bool, pub recovery_pin_hash: String, pub total_upload_bytes: i64, pub failed_pin_attempts: i16, pub pin_locked_until: Option>, pub created_at: DateTime, } impl User { pub async fn create( pool: &PgPool, event_id: Uuid, display_name: &str, pin_hash: &str, ) -> Result { sqlx::query_as::<_, Self>( "INSERT INTO \"user\" (event_id, display_name, recovery_pin_hash) VALUES ($1, $2, $3) RETURNING *", ) .bind(event_id) .bind(display_name) .bind(pin_hash) .fetch_one(pool) .await } pub async fn find_by_id(pool: &PgPool, id: Uuid) -> Result, sqlx::Error> { sqlx::query_as::<_, Self>("SELECT * FROM \"user\" WHERE id = $1") .bind(id) .fetch_optional(pool) .await } pub async fn find_by_event_and_name( pool: &PgPool, event_id: Uuid, display_name: &str, ) -> Result, sqlx::Error> { sqlx::query_as::<_, Self>( "SELECT * FROM \"user\" WHERE event_id = $1 AND display_name = $2", ) .bind(event_id) .bind(display_name) .fetch_all(pool) .await } pub async fn increment_failed_pin(pool: &PgPool, id: Uuid) -> Result { let row: (i16,) = sqlx::query_as( "UPDATE \"user\" SET failed_pin_attempts = failed_pin_attempts + 1 WHERE id = $1 RETURNING failed_pin_attempts", ) .bind(id) .fetch_one(pool) .await?; Ok(row.0) } pub async fn lock_pin(pool: &PgPool, id: Uuid, until: DateTime) -> Result<(), sqlx::Error> { sqlx::query( "UPDATE \"user\" SET pin_locked_until = $2 WHERE id = $1", ) .bind(id) .bind(until) .execute(pool) .await?; Ok(()) } pub async fn reset_pin_attempts(pool: &PgPool, id: Uuid) -> Result<(), sqlx::Error> { sqlx::query( "UPDATE \"user\" SET failed_pin_attempts = 0, pin_locked_until = NULL WHERE id = $1", ) .bind(id) .execute(pool) .await?; Ok(()) } }