From b6e89ebb1e1e5ced10bb6f75286013353ca6146b Mon Sep 17 00:00:00 2001 From: Jim Hodapp Date: Sat, 26 Oct 2024 16:14:24 -0500 Subject: [PATCH 1/3] Add a route to create a new coaching session entity --- entity_api/src/coaching_session.rs | 56 ++++++++++++++++++- .../controller/coaching_session_controller.rs | 42 ++++++++++++++ .../controller/overarching_goal_controller.rs | 1 - web/src/router.rs | 12 ++-- 4 files changed, 104 insertions(+), 7 deletions(-) diff --git a/entity_api/src/coaching_session.rs b/entity_api/src/coaching_session.rs index a41d19a..0fce16c 100644 --- a/entity_api/src/coaching_session.rs +++ b/entity_api/src/coaching_session.rs @@ -1,9 +1,36 @@ use super::error::{EntityApiErrorCode, Error}; use crate::{naive_date_parse_str, uuid_parse_str}; -use entity::coaching_sessions::{self, Entity, Model}; -use sea_orm::{entity::prelude::*, DatabaseConnection}; +use entity::coaching_sessions::{self, ActiveModel, Entity, Model}; +use log::debug; +use sea_orm::{entity::prelude::*, DatabaseConnection, Set, TryIntoModel}; use std::collections::HashMap; +pub async fn create( + db: &DatabaseConnection, + coaching_session_model: Model, +) -> Result { + debug!( + "New Coaching Session Model to be inserted: {:?}", + coaching_session_model + ); + + let now = chrono::Utc::now(); + + let coaching_session_active_model: ActiveModel = ActiveModel { + coaching_relationship_id: Set(coaching_session_model.coaching_relationship_id), + date: Set(coaching_session_model.date), + timezone: Set(coaching_session_model.timezone), + created_at: Set(now.into()), + updated_at: Set(now.into()), + ..Default::default() + }; + + Ok(coaching_session_active_model + .save(db) + .await? + .try_into_model()?) +} + pub async fn find_by( db: &DatabaseConnection, params: HashMap, @@ -49,6 +76,31 @@ mod tests { use entity::Id; use sea_orm::{DatabaseBackend, MockDatabase, Transaction}; + #[tokio::test] + async fn create_returns_a_new_coaching_session_model() -> Result<(), Error> { + let now = chrono::Utc::now(); + + let coaching_session_model = Model { + id: Id::new_v4(), + coaching_relationship_id: Id::new_v4(), + date: chrono::Local::now().naive_utc(), + timezone: "Americas/Chicago".to_owned(), + created_at: now.into(), + updated_at: now.into(), + }; + + let db = MockDatabase::new(DatabaseBackend::Postgres) + .append_query_results(vec![vec![coaching_session_model.clone()]]) + .into_connection(); + + let coaching_session = + create(&db, coaching_session_model.clone().into()).await?; + + assert_eq!(coaching_session.id, coaching_session_model.id); + + Ok(()) + } + #[tokio::test] async fn find_by_coaching_relationships_returns_all_records_associated_with_coaching_relationship( ) -> Result<(), Error> { diff --git a/web/src/controller/coaching_session_controller.rs b/web/src/controller/coaching_session_controller.rs index d589c7f..ae2d1a7 100644 --- a/web/src/controller/coaching_session_controller.rs +++ b/web/src/controller/coaching_session_controller.rs @@ -7,6 +7,7 @@ use axum::extract::{Query, State}; use axum::http::StatusCode; use axum::response::IntoResponse; use axum::Json; +use entity::coaching_sessions::Model; use entity_api::coaching_session as CoachingSessionApi; use service::config::ApiVersion; use std::collections::HashMap; @@ -51,3 +52,44 @@ pub async fn index( coaching_sessions, ))) } + +/// POST create a new Coaching Session +#[utoipa::path( + post, + path = "/coaching_sessions", + params(ApiVersion), + request_body = entity::coaching_sessions::Model, + responses( + (status = 201, description = "Successfully Created a new Coaching Session", body = [entity::coaching_sessions::Model]), + (status= 422, description = "Unprocessable Entity"), + (status = 401, description = "Unauthorized"), + (status = 405, description = "Method not allowed") + ), + security( + ("cookie_auth" = []) + ) +)] +pub async fn create( + CompareApiVersion(_v): CompareApiVersion, + AuthenticatedUser(_user): AuthenticatedUser, + // TODO: create a new Extractor to authorize the user to access + // the data requested + State(app_state): State, + Json(coaching_sessions_model): Json, +) -> Result { + debug!( + "POST Create a new Coaching Session from: {:?}", + coaching_sessions_model + ); + + let coaching_session = + CoachingSessionApi::create(app_state.db_conn_ref(), coaching_sessions_model) + .await?; + + debug!("New Coaching Session: {:?}", coaching_session); + + Ok(Json(ApiResponse::new( + StatusCode::CREATED.into(), + coaching_session, + ))) +} diff --git a/web/src/controller/overarching_goal_controller.rs b/web/src/controller/overarching_goal_controller.rs index 9cb568d..d5c6149 100644 --- a/web/src/controller/overarching_goal_controller.rs +++ b/web/src/controller/overarching_goal_controller.rs @@ -30,7 +30,6 @@ use log::*; ("cookie_auth" = []) ) )] - pub async fn create( CompareApiVersion(_v): CompareApiVersion, AuthenticatedUser(user): AuthenticatedUser, diff --git a/web/src/router.rs b/web/src/router.rs index d31d4cb..ef05f15 100644 --- a/web/src/router.rs +++ b/web/src/router.rs @@ -103,8 +103,8 @@ pub fn define_routes(app_state: AppState) -> Router { .merge(note_routes(app_state.clone())) .merge(organization_coaching_relationship_routes(app_state.clone())) .merge(overarching_goal_routes(app_state.clone())) - .merge(session_routes()) - .merge(protected_routes()) + .merge(user_session_routes()) + .merge(user_session_protected_routes()) .merge(coaching_sessions_routes(app_state.clone())) // FIXME: protect the OpenAPI web UI .merge(RapiDoc::with_openapi("/api-docs/openapi2.json", ApiDoc::openapi()).path("/rapidoc")) @@ -204,6 +204,10 @@ pub fn overarching_goal_routes(app_state: AppState) -> Router { pub fn coaching_sessions_routes(app_state: AppState) -> Router { Router::new() + .route( + "/coaching_sessions", + post(coaching_session_controller::create), + ) .route( "/coaching_sessions", get(coaching_session_controller::index), @@ -212,14 +216,14 @@ pub fn coaching_sessions_routes(app_state: AppState) -> Router { .with_state(app_state) } -pub fn protected_routes() -> Router { +pub fn user_session_protected_routes() -> Router { Router::new() .route("/protected", get(user_session_controller::protected)) .route("/logout", get(user_session_controller::logout)) .route_layer(login_required!(Backend, login_url = "/login")) } -pub fn session_routes() -> Router { +pub fn user_session_routes() -> Router { Router::new().route("/login", post(user_session_controller::login)) } From dec95907fcc8ee365e4790e8d968087c7c901c63 Mon Sep 17 00:00:00 2001 From: Jim Hodapp Date: Sat, 26 Oct 2024 16:20:00 -0500 Subject: [PATCH 2/3] Make sure the new create function shows up in RAPIdoc --- web/src/router.rs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/web/src/router.rs b/web/src/router.rs index ef05f15..47b3fcc 100644 --- a/web/src/router.rs +++ b/web/src/router.rs @@ -37,6 +37,8 @@ use utoipa_rapidoc::RapiDoc; agreement_controller::index, agreement_controller::read, agreement_controller::delete, + coaching_session_controller::index, + coaching_session_controller::create, note_controller::create, note_controller::update, note_controller::index, @@ -46,6 +48,8 @@ use utoipa_rapidoc::RapiDoc; organization_controller::create, organization_controller::update, organization_controller::delete, + organization::coaching_relationship_controller::index, + organization::coaching_relationship_controller::read, overarching_goal_controller::create, overarching_goal_controller::update, overarching_goal_controller::index, @@ -53,9 +57,6 @@ use utoipa_rapidoc::RapiDoc; overarching_goal_controller::update_status, user_session_controller::login, user_session_controller::logout, - organization::coaching_relationship_controller::index, - organization::coaching_relationship_controller::read, - coaching_session_controller::index, ), components( schemas( From a92694f16b5206831dadce62b643368c4727df33 Mon Sep 17 00:00:00 2001 From: Jim Hodapp Date: Sat, 26 Oct 2024 16:23:15 -0500 Subject: [PATCH 3/3] Add coaching_sessions as a schema for RAPIdoc --- entity/src/coaching_sessions.rs | 1 + entity_api/src/coaching_session.rs | 3 +-- web/src/controller/coaching_session_controller.rs | 3 +-- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/entity/src/coaching_sessions.rs b/entity/src/coaching_sessions.rs index 30dfae0..5bf25d2 100644 --- a/entity/src/coaching_sessions.rs +++ b/entity/src/coaching_sessions.rs @@ -6,6 +6,7 @@ use serde::{Deserialize, Serialize}; use utoipa::ToSchema; #[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize, Deserialize, ToSchema)] +#[schema(as = entity::coaching_sessions::Model)] #[sea_orm(schema_name = "refactor_platform", table_name = "coaching_sessions")] pub struct Model { #[sea_orm(primary_key)] diff --git a/entity_api/src/coaching_session.rs b/entity_api/src/coaching_session.rs index 0fce16c..c26d1b5 100644 --- a/entity_api/src/coaching_session.rs +++ b/entity_api/src/coaching_session.rs @@ -93,8 +93,7 @@ mod tests { .append_query_results(vec![vec![coaching_session_model.clone()]]) .into_connection(); - let coaching_session = - create(&db, coaching_session_model.clone().into()).await?; + let coaching_session = create(&db, coaching_session_model.clone().into()).await?; assert_eq!(coaching_session.id, coaching_session_model.id); diff --git a/web/src/controller/coaching_session_controller.rs b/web/src/controller/coaching_session_controller.rs index ae2d1a7..8ebedfd 100644 --- a/web/src/controller/coaching_session_controller.rs +++ b/web/src/controller/coaching_session_controller.rs @@ -83,8 +83,7 @@ pub async fn create( ); let coaching_session = - CoachingSessionApi::create(app_state.db_conn_ref(), coaching_sessions_model) - .await?; + CoachingSessionApi::create(app_state.db_conn_ref(), coaching_sessions_model).await?; debug!("New Coaching Session: {:?}", coaching_session);