use anyhow::{Context, Result};
use sqlx::PgPool;
use uuid::Uuid;
use crate::db::models::User;
pub async fn find_by_email(pool: &PgPool, email: &str) -> Result<Option<User>> {
let user = sqlx::query_as!(
User,
r#"
SELECT id, email, created_at, updated_at
FROM users
WHERE email = $1
"#,
email
)
.fetch_optional(pool)
.await
.context("Failed to find user by email")?;
Ok(user)
}
pub async fn find_by_id(
executor: impl sqlx::Executor<'_, Database = sqlx::Postgres>,
id: Uuid,
) -> Result<Option<User>> {
let user = sqlx::query_as!(
User,
r#"
SELECT id, email, created_at, updated_at
FROM users
WHERE id = $1
"#,
id
)
.fetch_optional(executor)
.await
.context("Failed to find user by ID")?;
Ok(user)
}
pub async fn create(pool: &PgPool, email: &str) -> Result<User> {
let user = sqlx::query_as!(
User,
r#"
INSERT INTO users (email)
VALUES ($1)
RETURNING id, email, created_at, updated_at
"#,
email
)
.fetch_one(pool)
.await
.context("Failed to create user")?;
Ok(user)
}
pub async fn find_or_create(pool: &PgPool, email: &str) -> Result<User> {
if let Some(user) = find_by_email(pool, email).await? {
return Ok(user);
}
create(pool, email).await
}
pub async fn get_emails_batch(
pool: &PgPool,
user_ids: &[Uuid],
) -> Result<std::collections::HashMap<Uuid, String>> {
let records = sqlx::query!(
r#"
SELECT id, email
FROM users
WHERE id = ANY($1)
"#,
user_ids
)
.fetch_all(pool)
.await
.context("Failed to batch fetch user emails")?;
Ok(records.into_iter().map(|r| (r.id, r.email)).collect())
}
pub async fn get_users_batch(
pool: &PgPool,
user_ids: &[Uuid],
) -> Result<std::collections::HashMap<Uuid, User>> {
let users = sqlx::query_as!(
User,
r#"
SELECT id, email, created_at, updated_at
FROM users
WHERE id = ANY($1)
"#,
user_ids
)
.fetch_all(pool)
.await
.context("Failed to batch fetch users")?;
Ok(users.into_iter().map(|u| (u.id, u)).collect())
}
#[cfg(test)]
mod tests {
use super::*;
#[sqlx::test]
async fn test_create_user(pool: PgPool) -> Result<()> {
let user = create(&pool, "test@example.com").await?;
assert_eq!(user.email, "test@example.com");
Ok(())
}
#[sqlx::test]
async fn test_find_by_email(pool: PgPool) -> Result<()> {
let created = create(&pool, "test@example.com").await?;
let found = find_by_email(&pool, "test@example.com").await?;
assert!(found.is_some());
assert_eq!(found.unwrap().id, created.id);
Ok(())
}
#[sqlx::test]
async fn test_find_by_id(pool: PgPool) -> Result<()> {
let created = create(&pool, "test@example.com").await?;
let found = find_by_id(&pool, created.id).await?;
assert!(found.is_some());
assert_eq!(found.unwrap().email, "test@example.com");
Ok(())
}
#[sqlx::test]
async fn test_find_or_create_existing(pool: PgPool) -> Result<()> {
let created = create(&pool, "test@example.com").await?;
let found = find_or_create(&pool, "test@example.com").await?;
assert_eq!(found.id, created.id);
Ok(())
}
#[sqlx::test]
async fn test_find_or_create_new(pool: PgPool) -> Result<()> {
let user = find_or_create(&pool, "new@example.com").await?;
assert_eq!(user.email, "new@example.com");
Ok(())
}
}