shoutdotdev/src/users.rs

91 lines
2.7 KiB
Rust
Raw Normal View History

2024-11-01 00:07:33 -07:00
use axum::{
extract::FromRequestParts,
http::request::Parts,
response::{IntoResponse, Redirect, Response},
RequestPartsExt,
};
use diesel::{
associations::Identifiable, deserialize::Queryable, dsl::insert_into, pg::Pg, prelude::*,
Selectable,
};
use uuid::Uuid;
use crate::{
app_error::AppError,
app_state::AppState,
auth::AuthInfo,
schema::{self, users},
};
#[derive(Clone, Debug, Identifiable, Insertable, Queryable, Selectable)]
#[diesel(table_name = schema::users)]
#[diesel(check_for_backend(Pg))]
pub struct User {
pub id: Uuid,
pub uid: String,
pub email: String,
}
#[derive(Clone, Debug)]
pub struct CurrentUser(pub User);
impl FromRequestParts<AppState> for CurrentUser {
type Rejection = CurrentUserRejection;
async fn from_request_parts(
parts: &mut Parts,
state: &AppState,
) -> Result<Self, <Self as FromRequestParts<AppState>>::Rejection> {
let auth_info = parts
.extract_with_state::<AuthInfo, AppState>(state)
.await
.map_err(|_| CurrentUserRejection::AuthRequired(state.settings.base_path.clone()))?;
let current_user = state
.db_pool
.get()
.await
.map_err(|err| CurrentUserRejection::InternalServerError(err.into()))?
.interact(move |conn| {
let maybe_current_user = users::table
.filter(users::uid.eq(auth_info.sub.clone()))
.select(User::as_select())
.first(conn)
.optional()?;
if let Some(current_user) = maybe_current_user {
return Ok(current_user);
}
let new_user = User {
id: Uuid::now_v7(),
uid: auth_info.sub,
email: auth_info.email,
};
insert_into(users::table)
.values(&new_user)
.returning(User::as_returning())
.on_conflict(users::uid)
.do_nothing()
.get_result(conn)
})
.await
.unwrap()
.map_err(|err| CurrentUserRejection::InternalServerError(err.into()))?;
Ok(CurrentUser(current_user))
}
}
pub enum CurrentUserRejection {
AuthRequired(String),
InternalServerError(AppError),
}
impl IntoResponse for CurrentUserRejection {
fn into_response(self) -> Response {
match self {
Self::AuthRequired(base_path) => {
Redirect::to(&format!("{}/auth/login", base_path)).into_response()
}
Self::InternalServerError(err) => err.into_response(),
}
}
}