2025-02-26 13:10:47 -08:00
|
|
|
use anyhow::Context;
|
2025-03-14 13:04:57 -07:00
|
|
|
use axum::{extract::FromRequestParts, http::request::Parts, RequestPartsExt};
|
2025-02-26 13:10:50 -08:00
|
|
|
use diesel::{
|
2025-02-26 13:10:48 -08:00
|
|
|
associations::Identifiable,
|
|
|
|
deserialize::Queryable,
|
2025-02-26 13:10:47 -08:00
|
|
|
dsl::{auto_type, insert_into, AsSelect, Eq, Select},
|
2025-02-26 13:10:48 -08:00
|
|
|
pg::Pg,
|
|
|
|
prelude::*,
|
2025-02-26 13:10:50 -08:00
|
|
|
Selectable,
|
|
|
|
};
|
|
|
|
use uuid::Uuid;
|
|
|
|
|
2025-02-26 13:10:47 -08:00
|
|
|
use crate::{
|
|
|
|
app_error::AppError,
|
|
|
|
app_state::AppState,
|
|
|
|
auth::AuthInfo,
|
|
|
|
schema::{team_memberships, teams, users},
|
|
|
|
team_memberships::TeamMembership,
|
|
|
|
teams::Team,
|
|
|
|
};
|
2025-02-26 13:10:50 -08:00
|
|
|
|
|
|
|
#[derive(Clone, Debug, Identifiable, Insertable, Queryable, Selectable)]
|
2025-02-26 13:10:47 -08:00
|
|
|
#[diesel(table_name = users)]
|
2025-02-26 13:10:50 -08:00
|
|
|
#[diesel(check_for_backend(Pg))]
|
|
|
|
pub struct User {
|
|
|
|
pub id: Uuid,
|
|
|
|
pub uid: String,
|
|
|
|
pub email: String,
|
|
|
|
}
|
|
|
|
|
2025-02-26 13:10:48 -08:00
|
|
|
impl User {
|
2025-02-26 13:10:47 -08:00
|
|
|
pub fn all() -> Select<users::table, AsSelect<User, Pg>> {
|
|
|
|
users::table.select(User::as_select())
|
|
|
|
}
|
|
|
|
|
2025-03-14 13:04:57 -07:00
|
|
|
#[auto_type(no_type_alias)]
|
|
|
|
pub fn with_uid(uid_value: &str) -> _ {
|
2025-02-26 13:10:47 -08:00
|
|
|
users::uid.eq(uid_value)
|
2025-02-26 13:10:48 -08:00
|
|
|
}
|
|
|
|
|
2025-02-26 13:10:47 -08:00
|
|
|
#[auto_type(no_type_alias)]
|
2025-03-14 13:04:57 -07:00
|
|
|
pub fn team_memberships(&self) -> _ {
|
|
|
|
let user_id_filter: Eq<team_memberships::user_id, &Uuid> =
|
|
|
|
TeamMembership::with_user_id(&self.id);
|
2025-02-26 13:10:47 -08:00
|
|
|
let select: AsSelect<(TeamMembership, Team), Pg> = <(TeamMembership, Team)>::as_select();
|
|
|
|
team_memberships::table
|
|
|
|
.inner_join(teams::table)
|
|
|
|
.filter(user_id_filter)
|
|
|
|
.select(select)
|
2025-02-26 13:10:48 -08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2025-02-26 13:10:50 -08:00
|
|
|
#[derive(Clone, Debug)]
|
|
|
|
pub struct CurrentUser(pub User);
|
|
|
|
|
2025-03-25 11:20:24 -07:00
|
|
|
impl<S> FromRequestParts<S> for CurrentUser
|
|
|
|
where
|
|
|
|
S: Into<AppState> + Clone + Sync,
|
|
|
|
{
|
2025-03-14 13:04:57 -07:00
|
|
|
type Rejection = AppError;
|
2025-02-26 13:10:50 -08:00
|
|
|
|
2025-03-25 11:20:24 -07:00
|
|
|
async fn from_request_parts(parts: &mut Parts, state: &S) -> Result<Self, Self::Rejection> {
|
|
|
|
let state: AppState = state.clone().into();
|
2025-02-26 13:10:50 -08:00
|
|
|
let auth_info = parts
|
2025-03-25 11:20:24 -07:00
|
|
|
.extract_with_state::<AuthInfo, AppState>(&state)
|
2025-02-26 13:10:50 -08:00
|
|
|
.await
|
2025-03-14 13:04:57 -07:00
|
|
|
.map_err(|_| {
|
|
|
|
AppError::auth_redirect_from_base_path(state.settings.base_path.clone())
|
|
|
|
})?;
|
2025-02-26 13:10:50 -08:00
|
|
|
let current_user = state
|
|
|
|
.db_pool
|
|
|
|
.get()
|
2025-03-14 13:04:57 -07:00
|
|
|
.await?
|
2025-02-26 13:10:50 -08:00
|
|
|
.interact(move |conn| {
|
2025-02-26 13:10:48 -08:00
|
|
|
let maybe_current_user = User::all()
|
|
|
|
.filter(User::with_uid(&auth_info.sub))
|
2025-02-26 13:10:50 -08:00
|
|
|
.first(conn)
|
2025-02-26 13:10:47 -08:00
|
|
|
.optional()
|
|
|
|
.context("failed to load maybe_current_user")?;
|
2025-02-26 13:10:50 -08:00
|
|
|
if let Some(current_user) = maybe_current_user {
|
|
|
|
return Ok(current_user);
|
|
|
|
}
|
|
|
|
let new_user = User {
|
|
|
|
id: Uuid::now_v7(),
|
2025-02-26 13:10:47 -08:00
|
|
|
uid: auth_info.sub.clone(),
|
2025-02-26 13:10:50 -08:00
|
|
|
email: auth_info.email,
|
|
|
|
};
|
2025-02-26 13:10:47 -08:00
|
|
|
match insert_into(users::table)
|
2025-02-26 13:10:48 -08:00
|
|
|
.values(new_user)
|
2025-02-26 13:10:47 -08:00
|
|
|
.on_conflict(users::uid)
|
2025-02-26 13:10:50 -08:00
|
|
|
.do_nothing()
|
2025-02-26 13:10:48 -08:00
|
|
|
.returning(User::as_returning())
|
2025-02-26 13:10:50 -08:00
|
|
|
.get_result(conn)
|
2025-02-26 13:10:47 -08:00
|
|
|
{
|
|
|
|
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),
|
|
|
|
}
|
2025-02-26 13:10:50 -08:00
|
|
|
})
|
|
|
|
.await
|
2025-03-14 13:04:57 -07:00
|
|
|
.unwrap()?;
|
2025-02-26 13:10:50 -08:00
|
|
|
Ok(CurrentUser(current_user))
|
|
|
|
}
|
|
|
|
}
|