Files
Pac-Man/pacman-server/src/data/user.rs
Ryan Walters 0b5aeceb51 feat: improve test reliability and add request tracing
- Add retry configuration for flaky tests (2 retries for default, 3 for
OAuth)
- Configure test groups with proper concurrency limits (serial: 1,
integration: 4)
- Add tower-http tracing layer with custom span formatting for HTTP
requests
- Simplify database pool handling by removing unnecessary Arc wrapper
- Improve test context setup with better logging and error handling
- Refactor user creation parameters for better clarity and consistency
- Add debug logging for OAuth cookie handling
2025-09-19 17:35:53 -05:00

163 lines
4.3 KiB
Rust

use serde::Serialize;
use sqlx::FromRow;
#[derive(Debug, Clone, Serialize, FromRow)]
pub struct User {
pub id: i64,
pub email: Option<String>,
pub created_at: chrono::DateTime<chrono::Utc>,
pub updated_at: chrono::DateTime<chrono::Utc>,
}
#[derive(Debug, Clone, Serialize, FromRow)]
pub struct OAuthAccount {
pub id: i64,
pub user_id: i64,
pub provider: String,
pub provider_user_id: String,
pub email: Option<String>,
pub username: Option<String>,
pub display_name: Option<String>,
pub avatar_url: Option<String>,
pub created_at: chrono::DateTime<chrono::Utc>,
pub updated_at: chrono::DateTime<chrono::Utc>,
}
pub async fn find_user_by_email(pool: &sqlx::PgPool, email: &str) -> Result<Option<User>, sqlx::Error> {
sqlx::query_as::<_, User>(
r#"
SELECT id, email, created_at, updated_at
FROM users WHERE email = $1
"#,
)
.bind(email)
.fetch_optional(pool)
.await
}
#[allow(clippy::too_many_arguments)]
pub async fn link_oauth_account(
pool: &sqlx::PgPool,
user_id: i64,
provider: &str,
provider_user_id: &str,
email: Option<&str>,
username: Option<&str>,
display_name: Option<&str>,
avatar_url: Option<&str>,
) -> Result<OAuthAccount, sqlx::Error> {
sqlx::query_as::<_, OAuthAccount>(
r#"
INSERT INTO oauth_accounts (user_id, provider, provider_user_id, email, username, display_name, avatar_url)
VALUES ($1, $2, $3, $4, $5, $6, $7)
ON CONFLICT (provider, provider_user_id)
DO UPDATE SET email = EXCLUDED.email, username = EXCLUDED.username, display_name = EXCLUDED.display_name, avatar_url = EXCLUDED.avatar_url, user_id = EXCLUDED.user_id, updated_at = NOW()
RETURNING id, user_id, provider, provider_user_id, email, username, display_name, avatar_url, created_at, updated_at
"#,
)
.bind(user_id)
.bind(provider)
.bind(provider_user_id)
.bind(email)
.bind(username)
.bind(display_name)
.bind(avatar_url)
.fetch_one(pool)
.await
}
pub async fn create_user(
pool: &sqlx::PgPool,
provider_username: &str,
provider_display_name: Option<&str>,
provider_email: Option<&str>,
provider_avatar_url: Option<&str>,
provider: &str,
provider_user_id: &str,
) -> Result<User, sqlx::Error> {
let user = sqlx::query_as::<_, User>(
r#"
INSERT INTO users (email)
VALUES ($1)
RETURNING id, email, created_at, updated_at
"#,
)
.bind(provider_email)
.fetch_one(pool)
.await?;
// Create oauth link
let _linked = link_oauth_account(
pool,
user.id,
provider,
provider_user_id,
provider_email,
Some(provider_username),
provider_display_name,
provider_avatar_url,
)
.await?;
Ok(user)
}
pub async fn get_oauth_account_count_for_user(pool: &sqlx::PgPool, user_id: i64) -> Result<i64, sqlx::Error> {
let rec: (i64,) = sqlx::query_as(
r#"
SELECT COUNT(*)::BIGINT AS count
FROM oauth_accounts
WHERE user_id = $1
"#,
)
.bind(user_id)
.fetch_one(pool)
.await?;
Ok(rec.0)
}
pub async fn find_user_by_provider_id(
pool: &sqlx::PgPool,
provider: &str,
provider_user_id: &str,
) -> Result<Option<User>, sqlx::Error> {
let rec = sqlx::query_as::<_, User>(
r#"
SELECT u.id, u.email, u.created_at, u.updated_at
FROM users u
JOIN oauth_accounts oa ON oa.user_id = u.id
WHERE oa.provider = $1 AND oa.provider_user_id = $2
"#,
)
.bind(provider)
.bind(provider_user_id)
.fetch_optional(pool)
.await?;
Ok(rec)
}
#[derive(Debug, Clone, Serialize, FromRow)]
pub struct ProviderPublic {
pub provider: String,
pub provider_user_id: String,
pub email: Option<String>,
pub username: Option<String>,
pub display_name: Option<String>,
pub avatar_url: Option<String>,
}
pub async fn list_user_providers(pool: &sqlx::PgPool, user_id: i64) -> Result<Vec<ProviderPublic>, sqlx::Error> {
let recs = sqlx::query_as::<_, ProviderPublic>(
r#"
SELECT provider, provider_user_id, email, username, display_name, avatar_url
FROM oauth_accounts
WHERE user_id = $1
ORDER BY provider
"#,
)
.bind(user_id)
.fetch_all(pool)
.await?;
Ok(recs)
}