//! SQL query methods for the `User` entity. //! //! These methods operate on the database directly, returning `sqlx::Error`. //! The service layer is responsible for converting errors into `AppError`. use sqlx::PgPool; use uuid::Uuid; use super::user::User; impl User { /// Find an active, non-deleted user by primary key. pub async fn find_by_id(pool: &PgPool, id: Uuid) -> Result, sqlx::Error> { sqlx::query_as::<_, User>( r#"SELECT id, username, display_name, avatar_url, bio, status, role, visibility, is_active, is_bot, last_login_at, created_at, updated_at, deleted_at FROM "user" WHERE id = $1 AND is_active = true AND status = 'active' AND deleted_at IS NULL"#, ) .bind(id) .fetch_optional(pool) .await } /// Find an active user by username (case-insensitive). pub async fn find_by_username( pool: &PgPool, username: &str, ) -> Result, sqlx::Error> { sqlx::query_as::<_, User>( r#"SELECT id, username, display_name, avatar_url, bio, status, role, visibility, is_active, is_bot, last_login_at, created_at, updated_at, deleted_at FROM "user" WHERE lower(username) = lower($1) AND is_active = true AND status = 'active' AND deleted_at IS NULL"#, ) .bind(username) .fetch_optional(pool) .await } /// Find an active user by verified email (case-insensitive). pub async fn find_by_email(pool: &PgPool, email: &str) -> Result, sqlx::Error> { sqlx::query_as::<_, User>( r#"SELECT u.id, u.username, u.display_name, u.avatar_url, u.bio, u.status, u.role, u.visibility, u.is_active, u.is_bot, u.last_login_at, u.created_at, u.updated_at, u.deleted_at FROM "user" u INNER JOIN user_mail e ON e.user_id = u.id WHERE lower(e.email) = lower($1) AND e.is_verified = true AND u.is_active = true AND u.status = 'active' AND u.deleted_at IS NULL"#, ) .bind(email) .fetch_optional(pool) .await } /// Check if a username already exists (excluding a given user id). pub async fn username_exists( pool: &PgPool, username: &str, exclude_id: Option, ) -> Result { let exists = if let Some(id) = exclude_id { sqlx::query_scalar::<_, bool>( r#"SELECT EXISTS( SELECT 1 FROM "user" WHERE lower(username) = lower($1) AND id <> $2 AND deleted_at IS NULL )"#, ) .bind(username) .bind(id) .fetch_one(pool) .await? } else { sqlx::query_scalar::<_, bool>( r#"SELECT EXISTS( SELECT 1 FROM "user" WHERE lower(username) = lower($1) AND deleted_at IS NULL )"#, ) .bind(username) .fetch_one(pool) .await? }; Ok(exists) } /// Update the last_login_at timestamp. pub async fn touch_login(pool: &PgPool, id: Uuid) -> Result<(), sqlx::Error> { sqlx::query(r#"UPDATE "user" SET last_login_at = $1, updated_at = $1 WHERE id = $2"#) .bind(chrono::Utc::now()) .bind(id) .execute(pool) .await?; Ok(()) } }