Files
gitks/models/users/user_queries.rs
T
2026-06-07 11:30:56 +08:00

100 lines
3.5 KiB
Rust

//! 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<Option<Self>, 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<Option<Self>, 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<Option<Self>, 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<Uuid>,
) -> Result<bool, sqlx::Error> {
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(())
}
}