use anyhow::Context; use axum::{extract::FromRequestParts, http::request::Parts, RequestPartsExt}; use diesel::{ associations::Identifiable, deserialize::Queryable, dsl::{auto_type, insert_into, AsSelect, Eq, Select}, pg::Pg, prelude::*, Selectable, }; use uuid::Uuid; use crate::{ app_error::AppError, app_state::AppState, auth::AuthInfo, schema::{team_memberships, teams, users}, team_memberships::TeamMembership, teams::Team, }; #[derive(Clone, Debug, Identifiable, Insertable, Queryable, Selectable)] #[diesel(table_name = users)] #[diesel(check_for_backend(Pg))] pub struct User { pub id: Uuid, pub uid: String, pub email: String, } impl User { pub fn all() -> Select> { users::table.select(User::as_select()) } #[auto_type(no_type_alias)] pub fn with_uid(uid_value: &str) -> _ { users::uid.eq(uid_value) } #[auto_type(no_type_alias)] pub fn team_memberships(&self) -> _ { let user_id_filter: Eq = TeamMembership::with_user_id(&self.id); let select: AsSelect<(TeamMembership, Team), Pg> = <(TeamMembership, Team)>::as_select(); team_memberships::table .inner_join(teams::table) .filter(user_id_filter) .select(select) } } #[derive(Clone, Debug)] pub struct CurrentUser(pub User); impl FromRequestParts for CurrentUser { type Rejection = AppError; async fn from_request_parts( parts: &mut Parts, state: &AppState, ) -> Result>::Rejection> { let auth_info = parts .extract_with_state::(state) .await .map_err(|_| { AppError::auth_redirect_from_base_path(state.settings.base_path.clone()) })?; let current_user = state .db_pool .get() .await? .interact(move |conn| { let maybe_current_user = User::all() .filter(User::with_uid(&auth_info.sub)) .first(conn) .optional() .context("failed to load maybe_current_user")?; if let Some(current_user) = maybe_current_user { return Ok(current_user); } let new_user = User { id: Uuid::now_v7(), uid: auth_info.sub.clone(), email: auth_info.email, }; match insert_into(users::table) .values(new_user) .on_conflict(users::uid) .do_nothing() .returning(User::as_returning()) .get_result(conn) { QueryResult::Err(diesel::result::Error::NotFound) => { tracing::debug!("detected race to insert current user record"); User::all() .filter(User::with_uid(&auth_info.sub)) .first(conn) .context( "failed to load record after detecting race to insert current user", ) } QueryResult::Err(err) => { Err(err).context("failed to insert current user record") } QueryResult::Ok(result) => Ok(result), } }) .await .unwrap()?; Ok(CurrentUser(current_user)) } }