Skip to content

Commit

Permalink
refactor how query params are deserialized
Browse files Browse the repository at this point in the history
  • Loading branch information
calebbourg committed Dec 9, 2024
1 parent 84304b6 commit ac1113b
Show file tree
Hide file tree
Showing 5 changed files with 121 additions and 31 deletions.
3 changes: 2 additions & 1 deletion entity/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@ serde = { version = "1.0.210", features = ["derive"] }
sqlx = { version = "0.8.2", features = ["time", "runtime-tokio"] }
sqlx-sqlite = { version = "0.8.2" }
utoipa = { version = "4.2.0", features = ["axum_extras", "uuid"] }
uuid = { version = "1.11.0", features = ["v4"] }

uuid = { version = "1.11.0", features = ["v4", "serde"] }

[dependencies.sea-orm]
version = "1.1.0"
Expand Down
42 changes: 42 additions & 0 deletions entity_api/src/coaching_session.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use super::error::{EntityApiErrorCode, Error};
use crate::{naive_date_parse_str, uuid_parse_str};
use entity::{
coaching_relationships,
coaching_sessions::{self, ActiveModel, Entity, Model},
Id,
};
Expand Down Expand Up @@ -38,6 +39,25 @@ pub async fn find_by_id(db: &DatabaseConnection, id: Id) -> Result<Option<Model>
Ok(Entity::find_by_id(id).one(db).await?)
}

pub async fn find_by_id_with_coaching_relationship(
db: &DatabaseConnection,
id: Id,
) -> Result<(Model, coaching_relationships::Model), Error> {
if let Some(results) = Entity::find_by_id(id)
.find_also_related(coaching_relationships::Entity)
.one(db)
.await?
{
if let Some(coaching_relationship) = results.1 {
return Ok((results.0, coaching_relationship));
}
}
Err(Error {
inner: None,
error_code: EntityApiErrorCode::RecordNotFound,
})
}

pub async fn find_by(
db: &DatabaseConnection,
params: HashMap<String, String>,
Expand Down Expand Up @@ -129,6 +149,28 @@ mod tests {
Ok(())
}

#[tokio::test]
async fn find_by_id_with_coaching_relationship_returns_a_single_record() -> Result<(), Error> {
let db = MockDatabase::new(DatabaseBackend::Postgres).into_connection();

let coaching_session_id = Id::new_v4();
let _ = find_by_id_with_coaching_relationship(&db, coaching_session_id).await;

assert_eq!(
db.into_transaction_log(),
[Transaction::from_sql_and_values(
DatabaseBackend::Postgres,
r#"SELECT "coaching_sessions"."id", "coaching_sessions"."coaching_relationship_id", "coaching_sessions"."date", "coaching_sessions"."timezone", "coaching_sessions"."created_at", "coaching_sessions"."updated_at", "coaching_relationships"."id", "coaching_relationships"."coach_id", "coaching_relationships"."coachee_id", "coaching_relationships"."created_at", "coaching_relationships"."updated_at" FROM "refactor_platform"."coaching_sessions" LEFT JOIN "refactor_platform"."coaching_relationships" ON "coaching_sessions"."coaching_relationship_id" = "coaching_relationships"."id" WHERE "coaching_sessions"."id" = $1 LIMIT $2"#,
[
coaching_session_id.into(),
sea_orm::Value::BigUnsigned(Some(1))
]
)]
);

Ok(())
}

#[tokio::test]
async fn find_by_coaching_relationships_returns_all_records_associated_with_coaching_relationship(
) -> Result<(), Error> {
Expand Down
54 changes: 24 additions & 30 deletions web/src/protect/coaching_sessions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,50 +5,44 @@ use axum::{
middleware::Next,
response::IntoResponse,
};
use serde::Deserialize;

use entity::Id;
use entity_api::coaching_relationship;
use std::collections::HashMap;
// use std::collections::HashMap;

#[derive(Debug, Deserialize)]
pub(crate) struct QueryParams {
coaching_relationship_id: Id,
}

/// Checks that coaching relationship record referenced by `coaching_relationship_id`
/// exists and that the authenticated user is associated with it.
/// Intended to be given to axum::middleware::from_fn_with_state in the router
pub(crate) async fn index(
State(app_state): State<AppState>,
AuthenticatedUser(user): AuthenticatedUser,
Query(params): Query<HashMap<String, String>>,
Query(params): Query<QueryParams>,
request: Request,
next: Next,
) -> impl IntoResponse {
if let Some(coaching_relationship_id) = params.get("coaching_relationship_id") {
let coaching_relationship_id = match Id::try_parse(coaching_relationship_id) {
Ok(id) => id,
Err(_) => {
// coaching relationship ID is not a parseable UUID
return (StatusCode::BAD_REQUEST, "BAD REQUEST").into_response();
}
};
let coaching_relationship =
coaching_relationship::find_by_id(app_state.db_conn_ref(), coaching_relationship_id)
.await
.unwrap_or(None);
match coaching_relationship {
Some(coaching_relationship) => {
if coaching_relationship.coach_id == user.id
|| coaching_relationship.coachee_id == user.id
{
// User has access to coaching relationship
next.run(request).await
} else {
// User does not have access to coaching relationship
(StatusCode::UNAUTHORIZED, "UNAUTHORIZED").into_response()
}
let coaching_relationship =
coaching_relationship::find_by_id(app_state.db_conn_ref(), params.coaching_relationship_id)
.await
.unwrap_or_default();
match coaching_relationship {
Some(coaching_relationship) => {
if coaching_relationship.coach_id == user.id
|| coaching_relationship.coachee_id == user.id
{
// User has access to coaching relationship
next.run(request).await
} else {
// User does not have access to coaching relationship
(StatusCode::UNAUTHORIZED, "UNAUTHORIZED").into_response()
}
// coaching relationship with given ID not found
None => (StatusCode::NOT_FOUND, "NOT FOUND").into_response(),
}
} else {
// No coaching relationship ID provided
(StatusCode::BAD_REQUEST, "BAD REQUEST").into_response()
// coaching relationship with given ID not found
None => (StatusCode::NOT_FOUND, "NOT FOUND").into_response(),
}
}
1 change: 1 addition & 0 deletions web/src/protect/mod.rs
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
pub(crate) mod coaching_relationships;
pub(crate) mod coaching_sessions;
pub(crate) mod overarching_goals;
52 changes: 52 additions & 0 deletions web/src/protect/overarching_goals.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
use crate::{extractors::authenticated_user::AuthenticatedUser, AppState};
use axum::{
extract::{Query, Request, State},
http::StatusCode,
middleware::Next,
response::IntoResponse,
};
use entity::Id;
use entity_api::coaching_session;
use log::*;
use serde::Deserialize;

#[derive(Debug, Deserialize)]
pub(crate) struct QueryParams {
coaching_session_id: Id,
}

/// Checks that coaching relationship record associated with the coaching session
/// referenced by `coaching_session_id exists and that the authenticated user is associated with it.
/// Intended to be given to axum::middleware::from_fn_with_state in the router
pub(crate) async fn index(
State(app_state): State<AppState>,
AuthenticatedUser(user): AuthenticatedUser,
Query(params): Query<QueryParams>,
request: Request,
next: Next,
) -> impl IntoResponse {
match coaching_session::find_by_id_with_coaching_relationship(
app_state.db_conn_ref(),
params.coaching_session_id,
)
.await
{
Ok((_coaching_session, coaching_relationship)) => {
if coaching_relationship.coach_id == user.id
|| coaching_relationship.coachee_id == user.id
{
// User has access to coaching relationship
next.run(request).await
} else {
// User does not have access to coaching relationship
(StatusCode::UNAUTHORIZED, "UNAUTHORIZED").into_response()
}
}
Err(e) => {
error!("Error authorizing overarching goals index{:?}", e);

(StatusCode::INTERNAL_SERVER_ERROR, "INTERNAL SERVER ERROR").into_response()
}

}
}

0 comments on commit ac1113b

Please sign in to comment.