From 80d9f20dae64ff580618aa36d9db4354a5e6ab74 Mon Sep 17 00:00:00 2001 From: Caleb Bourg Date: Mon, 24 Feb 2025 13:05:25 -0500 Subject: [PATCH 01/11] apply change in find_by_id pattern to other entities --- entity_api/src/action.rs | 41 +++-------------- entity_api/src/agreement.rs | 46 +++---------------- entity_api/src/overarching_goal.rs | 31 ++----------- web/src/controller/action_controller.rs | 4 +- web/src/controller/agreement_controller.rs | 4 +- .../controller/overarching_goal_controller.rs | 7 ++- 6 files changed, 28 insertions(+), 105 deletions(-) diff --git a/entity_api/src/action.rs b/entity_api/src/action.rs index 277fd164..924fe008 100644 --- a/entity_api/src/action.rs +++ b/entity_api/src/action.rs @@ -106,43 +106,16 @@ pub async fn update_status( pub async fn delete_by_id(db: &DatabaseConnection, id: Id) -> Result<(), Error> { let result = find_by_id(db, id).await?; - match result { - Some(action_model) => { - debug!("Existing Action model to be deleted: {:?}", action_model); + result.delete(db).await?; - action_model.delete(db).await?; - Ok(()) - } - None => Err(Error { - source: None, - error_kind: EntityApiErrorKind::RecordNotFound, - }), - } + Ok(()) } -pub async fn find_by_id(db: &DatabaseConnection, id: Id) -> Result, Error> { - match Entity::find_by_id(id).one(db).await { - Ok(Some(action)) => { - debug!("Action found: {:?}", action); - - Ok(Some(action)) - } - Ok(None) => { - error!("Action with id {} not found", id); - - Err(Error { - source: None, - error_kind: EntityApiErrorKind::RecordNotFound, - }) - } - Err(err) => { - error!("Action with id {} not found and returned error {}", id, err); - Err(Error { - source: None, - error_kind: EntityApiErrorKind::RecordNotFound, - }) - } - } +pub async fn find_by_id(db: &DatabaseConnection, id: Id) -> Result { + Entity::find_by_id(id).one(db).await?.ok_or_else(|| Error { + source: None, + error_kind: EntityApiErrorKind::RecordNotFound, + }) } pub async fn find_by( diff --git a/entity_api/src/agreement.rs b/entity_api/src/agreement.rs index 01fbc285..3dafa540 100644 --- a/entity_api/src/agreement.rs +++ b/entity_api/src/agreement.rs @@ -63,47 +63,15 @@ pub async fn update(db: &DatabaseConnection, id: Id, model: Model) -> Result Result<(), Error> { let result = find_by_id(db, id).await?; - match result { - Some(agreement_model) => { - debug!( - "Existing Agreement model to be deleted: {:?}", - agreement_model - ); - - agreement_model.delete(db).await?; - Ok(()) - } - None => Err(Error { - source: None, - error_kind: EntityApiErrorKind::RecordNotFound, - }), - } + result.delete(db).await?; + Ok(()) } -pub async fn find_by_id(db: &DatabaseConnection, id: Id) -> Result, Error> { - match Entity::find_by_id(id).one(db).await { - Ok(Some(agreement)) => { - debug!("Agreement found: {:?}", agreement); - - Ok(Some(agreement)) - } - Ok(None) => { - error!("Agreement with id {} not found", id); - - Err(Error { - source: None, - error_kind: EntityApiErrorKind::RecordNotFound, - }) - } - Err(err) => { - error!("Error finding Agreement with id {}: {:?}", id, err); - - Err(Error { - source: Some(err), - error_kind: EntityApiErrorKind::RecordNotFound, - }) - } - } +pub async fn find_by_id(db: &DatabaseConnection, id: Id) -> Result { + Entity::find_by_id(id).one(db).await?.ok_or_else(|| Error { + source: None, + error_kind: EntityApiErrorKind::RecordNotFound, + }) } pub async fn find_by( diff --git a/entity_api/src/overarching_goal.rs b/entity_api/src/overarching_goal.rs index 77770a5d..64d8a418 100644 --- a/entity_api/src/overarching_goal.rs +++ b/entity_api/src/overarching_goal.rs @@ -129,32 +129,11 @@ pub async fn update_status( } } -pub async fn find_by_id(db: &DatabaseConnection, id: Id) -> Result, Error> { - match Entity::find_by_id(id).one(db).await { - Ok(Some(overarching_goal)) => { - debug!("Overarching Goal found: {:?}", overarching_goal); - - Ok(Some(overarching_goal)) - } - Ok(None) => { - error!("Overarching Goal with id {} not found", id); - - Err(Error { - source: None, - error_kind: EntityApiErrorKind::RecordNotFound, - }) - } - Err(err) => { - error!( - "Overarching Goal with id {} not found and returned error {}", - id, err - ); - Err(Error { - source: None, - error_kind: EntityApiErrorKind::RecordNotFound, - }) - } - } +pub async fn find_by_id(db: &DatabaseConnection, id: Id) -> Result { + Entity::find_by_id(id).one(db).await?.ok_or_else(|| Error { + source: None, + error_kind: EntityApiErrorKind::RecordNotFound, + }) } pub async fn find_by( diff --git a/web/src/controller/action_controller.rs b/web/src/controller/action_controller.rs index 78aabaf4..f23b9f7b 100644 --- a/web/src/controller/action_controller.rs +++ b/web/src/controller/action_controller.rs @@ -71,9 +71,9 @@ pub async fn read( ) -> Result { debug!("GET Action by id: {}", id); - let note: Option = ActionApi::find_by_id(app_state.db_conn_ref(), id).await?; + let action = ActionApi::find_by_id(app_state.db_conn_ref(), id).await?; - Ok(Json(ApiResponse::new(StatusCode::OK.into(), note))) + Ok(Json(ApiResponse::new(StatusCode::OK.into(), action))) } #[utoipa::path( diff --git a/web/src/controller/agreement_controller.rs b/web/src/controller/agreement_controller.rs index 79f869bc..e231c263 100644 --- a/web/src/controller/agreement_controller.rs +++ b/web/src/controller/agreement_controller.rs @@ -77,9 +77,9 @@ pub async fn read( ) -> Result { debug!("GET Agreement by id: {}", id); - let note: Option = AgreementApi::find_by_id(app_state.db_conn_ref(), id).await?; + let agreement = AgreementApi::find_by_id(app_state.db_conn_ref(), id).await?; - Ok(Json(ApiResponse::new(StatusCode::OK.into(), note))) + Ok(Json(ApiResponse::new(StatusCode::OK.into(), agreement))) } #[utoipa::path( diff --git a/web/src/controller/overarching_goal_controller.rs b/web/src/controller/overarching_goal_controller.rs index d5c6149c..3da30e61 100644 --- a/web/src/controller/overarching_goal_controller.rs +++ b/web/src/controller/overarching_goal_controller.rs @@ -80,9 +80,12 @@ pub async fn read( ) -> Result { debug!("GET Overarching Goal by id: {}", id); - let note: Option = OverarchingGoalApi::find_by_id(app_state.db_conn_ref(), id).await?; + let overarching_goal = OverarchingGoalApi::find_by_id(app_state.db_conn_ref(), id).await?; - Ok(Json(ApiResponse::new(StatusCode::OK.into(), note))) + Ok(Json(ApiResponse::new( + StatusCode::OK.into(), + overarching_goal, + ))) } #[utoipa::path( From b827dcf6d6c10bd16118f2d3fe8621f8882c413c Mon Sep 17 00:00:00 2001 From: Caleb Bourg Date: Mon, 24 Feb 2025 14:02:51 -0500 Subject: [PATCH 02/11] remove web dependencies on entity and entity_api --- Cargo.lock | 3 --- domain/Cargo.toml | 1 - domain/src/action.rs | 1 + domain/src/agreement.rs | 2 +- domain/src/coaching_relationship.rs | 4 ++++ domain/src/coaching_session.rs | 24 ++++--------------- domain/src/jwt/mod.rs | 12 ++++------ domain/src/lib.rs | 15 +++++++++++- domain/src/note.rs | 1 + domain/src/organization.rs | 3 +++ domain/src/overarching_goal.rs | 1 + domain/src/user.rs | 1 + entity/src/{jwt.rs => jwts.rs} | 2 +- entity/src/lib.rs | 2 +- entity_api/src/lib.rs | 5 +++- entity_api/src/user.rs | 4 +--- web/Cargo.toml | 2 -- web/src/controller/action_controller.rs | 20 ++++++++-------- web/src/controller/agreement_controller.rs | 16 ++++++------- .../controller/coaching_session_controller.rs | 9 ++++--- web/src/controller/note_controller.rs | 15 ++++++------ .../coaching_relationship_controller.rs | 12 +++++----- web/src/controller/organization_controller.rs | 15 ++++++------ .../controller/overarching_goal_controller.rs | 4 ++-- web/src/controller/user_controller.rs | 7 +++--- web/src/controller/user_session_controller.rs | 12 +++++----- web/src/extractors/authenticated_user.rs | 5 ++-- web/src/lib.rs | 2 +- web/src/params/agreement.rs | 2 +- web/src/params/jwt.rs | 2 +- web/src/protect/actions.rs | 3 +-- web/src/protect/agreements.rs | 2 +- web/src/protect/coaching_relationships.rs | 3 +-- web/src/protect/coaching_sessions.rs | 3 +-- web/src/protect/jwt.rs | 2 +- web/src/protect/notes.rs | 3 +-- web/src/protect/overarching_goals.rs | 3 +-- web/src/router.rs | 24 +++++++++---------- 38 files changed, 119 insertions(+), 128 deletions(-) create mode 100644 domain/src/action.rs create mode 100644 domain/src/coaching_relationship.rs create mode 100644 domain/src/note.rs create mode 100644 domain/src/organization.rs create mode 100644 domain/src/overarching_goal.rs create mode 100644 domain/src/user.rs rename entity/src/{jwt.rs => jwts.rs} (96%) diff --git a/Cargo.lock b/Cargo.lock index 88b69fc3..376d0eed 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -838,7 +838,6 @@ name = "domain" version = "1.0.0-beta1" dependencies = [ "chrono", - "entity", "entity_api", "jsonwebtoken", "log", @@ -3914,8 +3913,6 @@ dependencies = [ "axum-login", "chrono", "domain", - "entity", - "entity_api", "log", "password-auth", "reqwest", diff --git a/domain/Cargo.toml b/domain/Cargo.toml index 2e15f571..35a50894 100644 --- a/domain/Cargo.toml +++ b/domain/Cargo.toml @@ -5,7 +5,6 @@ edition = "2021" [dependencies] chrono = { version = "0.4.38", features = ["serde"] } -entity = { path = "../entity" } entity_api = { path = "../entity_api" } jsonwebtoken = "9" service = { path = "../service" } diff --git a/domain/src/action.rs b/domain/src/action.rs new file mode 100644 index 00000000..d54420fd --- /dev/null +++ b/domain/src/action.rs @@ -0,0 +1 @@ +pub use entity_api::action::{create, delete_by_id, find_by, find_by_id, update, update_status}; diff --git a/domain/src/agreement.rs b/domain/src/agreement.rs index 7b2f07ae..d53d0b29 100644 --- a/domain/src/agreement.rs +++ b/domain/src/agreement.rs @@ -1,5 +1,5 @@ +use crate::agreements::Model; use crate::error::Error; -use entity::agreements::Model; pub use entity_api::agreement::{create, delete_by_id, find_by_id, update}; use entity_api::{agreement, IntoQueryFilterMap}; use sea_orm::DatabaseConnection; diff --git a/domain/src/coaching_relationship.rs b/domain/src/coaching_relationship.rs new file mode 100644 index 00000000..dc9ea228 --- /dev/null +++ b/domain/src/coaching_relationship.rs @@ -0,0 +1,4 @@ +pub use entity_api::coaching_relationship::{ + create, find_by, find_by_id, find_by_organization_with_user_names, find_by_user, + get_relationship_with_user_names, CoachingRelationshipWithUserNames, +}; diff --git a/domain/src/coaching_session.rs b/domain/src/coaching_session.rs index 5e361211..fff17fdb 100644 --- a/domain/src/coaching_session.rs +++ b/domain/src/coaching_session.rs @@ -1,13 +1,17 @@ +use crate::coaching_sessions::Model; use crate::error::{DomainErrorKind, Error, ExternalErrorKind, InternalErrorKind}; use crate::gateway::tiptap::client as tiptap_client; use chrono::{DurationRound, TimeDelta}; -use entity::coaching_sessions::Model; use entity_api::{coaching_relationship, coaching_session, organization}; use log::*; use sea_orm::DatabaseConnection; use serde_json::json; use service::config::Config; +pub use entity_api::coaching_session::{ + find_by, find_by_id, find_by_id_with_coaching_relationship, +}; + pub async fn create( db: &DatabaseConnection, config: &Config, @@ -81,21 +85,3 @@ pub async fn create( }) } } - -pub async fn find_by_id(db: &DatabaseConnection, id: entity::Id) -> Result { - Ok(coaching_session::find_by_id(db, id).await?) -} - -pub async fn find_by_id_with_coaching_relationship( - db: &DatabaseConnection, - id: entity::Id, -) -> Result<(Model, entity::coaching_relationships::Model), Error> { - Ok(coaching_session::find_by_id_with_coaching_relationship(db, id).await?) -} - -pub async fn find_by( - db: &DatabaseConnection, - params: std::collections::HashMap, -) -> Result, Error> { - Ok(coaching_session::find_by(db, params).await?) -} diff --git a/domain/src/jwt/mod.rs b/domain/src/jwt/mod.rs index b7010df6..0cb6e2bd 100644 --- a/domain/src/jwt/mod.rs +++ b/domain/src/jwt/mod.rs @@ -13,7 +13,7 @@ //! use domain::jwt::generate_collab_token; //! use sea_orm::DatabaseConnection; //! use service::config::Config; -//! use entity::Id; +//! use crate::Id; //! //! async fn example(db: &DatabaseConnection, config: &Config, coaching_session_id: Id) { //! match generate_collab_token(db, config, coaching_session_id).await { @@ -23,18 +23,14 @@ //! } //! ``` -use crate::coaching_session; use crate::error::{DomainErrorKind, Error, InternalErrorKind}; +use crate::{coaching_session, jwts::Jwts, Id}; use claims::TiptapCollabClaims; -use entity::Id; use jsonwebtoken::{encode, EncodingKey, Header}; use log::*; use sea_orm::DatabaseConnection; use service::config::Config; -// re-export the Jwt struct from the entity module -pub use entity::jwt::Jwt; - pub(crate) mod claims; /// Generates a collaboration token for a coaching session. @@ -47,7 +43,7 @@ pub async fn generate_collab_token( db: &DatabaseConnection, config: &Config, coaching_session_id: Id, -) -> Result { +) -> Result { let coaching_session = coaching_session::find_by_id(db, coaching_session_id).await?; let collab_document_name = coaching_session.collab_document_name.ok_or_else(|| { @@ -105,7 +101,7 @@ pub async fn generate_collab_token( &EncodingKey::from_secret(tiptap_jwt_signing_key.as_bytes()), )?; - Ok(Jwt { + Ok(Jwts { token, sub: collab_document_name, }) diff --git a/domain/src/lib.rs b/domain/src/lib.rs index 2ffb5bf8..e54522f3 100644 --- a/domain/src/lib.rs +++ b/domain/src/lib.rs @@ -1,4 +1,4 @@ -//! This module re-exports `IntoQueryFilterMap` and `QueryFilterMap` from the `entity_api` crate. +//! This module re-exports various items from the `entity_api` crate. //! //! The purpose of this re-export is to ensure that consumers of the `domain` crate do not need to //! directly depend on the `entity_api` crate. By re-exporting these items, we provide a clear and @@ -6,9 +6,22 @@ //! the underlying implementation details remain in the `entity_api` crate. pub use entity_api::{IntoQueryFilterMap, QueryFilterMap}; +// Re-exports from `entity` +pub use entity_api::user::{AuthSession, Backend, Credentials}; +pub use entity_api::{ + actions, agreements, coachees, coaches, coaching_relationships, coaching_sessions, jwts, notes, + organizations, overarching_goals, users, Id, +}; + +pub mod action; pub mod agreement; +pub mod coaching_relationship; pub mod coaching_session; pub mod error; pub mod jwt; +pub mod note; +pub mod organization; +pub mod overarching_goal; +pub mod user; pub(crate) mod gateway; diff --git a/domain/src/note.rs b/domain/src/note.rs new file mode 100644 index 00000000..c7141a8b --- /dev/null +++ b/domain/src/note.rs @@ -0,0 +1 @@ +pub use entity_api::note::{create, find_by, find_by_id, update}; diff --git a/domain/src/organization.rs b/domain/src/organization.rs new file mode 100644 index 00000000..3c76adc1 --- /dev/null +++ b/domain/src/organization.rs @@ -0,0 +1,3 @@ +pub use entity_api::organization::{ + create, delete_by_id, find_all, find_by, find_by_id, find_by_user, update, +}; diff --git a/domain/src/overarching_goal.rs b/domain/src/overarching_goal.rs new file mode 100644 index 00000000..49a34acb --- /dev/null +++ b/domain/src/overarching_goal.rs @@ -0,0 +1 @@ +pub use entity_api::overarching_goal::{create, find_by, find_by_id, update, update_status}; diff --git a/domain/src/user.rs b/domain/src/user.rs new file mode 100644 index 00000000..cd0a3c6d --- /dev/null +++ b/domain/src/user.rs @@ -0,0 +1 @@ +pub use entity_api::user::{create, find_by_email}; diff --git a/entity/src/jwt.rs b/entity/src/jwts.rs similarity index 96% rename from entity/src/jwt.rs rename to entity/src/jwts.rs index 6ffeecf9..1f8063b3 100644 --- a/entity/src/jwt.rs +++ b/entity/src/jwts.rs @@ -11,7 +11,7 @@ use utoipa::ToSchema; /// the subject without having to decode the JWT. #[derive(Serialize, Debug, ToSchema)] #[schema(as = jwt::Jwt)] // OpenAPI schema -pub struct Jwt { +pub struct Jwts { pub token: String, pub sub: String, } diff --git a/entity/src/lib.rs b/entity/src/lib.rs index 3410d88c..06772760 100644 --- a/entity/src/lib.rs +++ b/entity/src/lib.rs @@ -8,7 +8,7 @@ pub mod coachees; pub mod coaches; pub mod coaching_relationships; pub mod coaching_sessions; -pub mod jwt; +pub mod jwts; pub mod notes; pub mod organizations; pub mod overarching_goals; diff --git a/entity_api/src/lib.rs b/entity_api/src/lib.rs index 49d3c80b..5f1b09bc 100644 --- a/entity_api/src/lib.rs +++ b/entity_api/src/lib.rs @@ -3,7 +3,10 @@ use password_auth::generate_hash; use sea_orm::{ActiveModelTrait, DatabaseConnection, Set, Value}; use std::collections::HashMap; -use entity::{coaching_relationships, coaching_sessions, organizations, users, Id}; +pub use entity::{ + actions, agreements, coachees, coaches, coaching_relationships, coaching_sessions, jwts, notes, + organizations, overarching_goals, users, Id, +}; pub mod action; pub mod agreement; diff --git a/entity_api/src/user.rs b/entity_api/src/user.rs index cdc82753..01a7172b 100644 --- a/entity_api/src/user.rs +++ b/entity_api/src/user.rs @@ -2,7 +2,7 @@ use super::error::{EntityApiErrorKind, Error}; use async_trait::async_trait; use axum_login::{AuthnBackend, UserId}; use chrono::Utc; -use entity::users::*; +use entity::users::{ActiveModel, Column, Entity, Model}; use log::*; use password_auth::{generate_hash, verify_password}; use sea_orm::{entity::prelude::*, DatabaseConnection, Set}; @@ -10,8 +10,6 @@ use serde::Deserialize; use std::sync::Arc; use utoipa::ToSchema; -use crate::user::Entity; - pub async fn create(db: &DatabaseConnection, user_model: Model) -> Result { debug!( "New User Relationship Model to be inserted: {:?}", diff --git a/web/Cargo.toml b/web/Cargo.toml index 8bf1be02..9a414ecd 100644 --- a/web/Cargo.toml +++ b/web/Cargo.toml @@ -7,8 +7,6 @@ edition = "2021" [dependencies] domain = { path = "../domain" } -entity = { path = "../entity" } -entity_api = { path = "../entity_api" } service = { path = "../service" } axum = "0.7.7" diff --git a/web/src/controller/action_controller.rs b/web/src/controller/action_controller.rs index f23b9f7b..035c9b2b 100644 --- a/web/src/controller/action_controller.rs +++ b/web/src/controller/action_controller.rs @@ -7,8 +7,8 @@ use axum::extract::{Path, Query, State}; use axum::http::StatusCode; use axum::response::IntoResponse; use axum::Json; -use entity::{actions::Model, Id}; -use entity_api::action as ActionApi; +use domain::{action as ActionApi, actions::Model, Id}; + use serde_json::json; use service::config::ApiVersion; use std::collections::HashMap; @@ -20,9 +20,9 @@ use log::*; post, path = "/actions", params(ApiVersion), - request_body = entity::actions::Model, + request_body = actions::Model, responses( - (status = 201, description = "Successfully Created a New Action", body = [entity::actions::Model]), + (status = 201, description = "Successfully Created a New Action", body = [actions::Model]), (status= 422, description = "Unprocessable Entity"), (status = 401, description = "Unauthorized"), (status = 405, description = "Method not allowed") @@ -55,7 +55,7 @@ pub async fn create( ("id" = String, Path, description = "Action id to retrieve") ), responses( - (status = 200, description = "Successfully retrieved a specific Action by its id", body = [entity::notes::Model]), + (status = 200, description = "Successfully retrieved a specific Action by its id", body = [notes::Model]), (status = 401, description = "Unauthorized"), (status = 404, description = "Action not found"), (status = 405, description = "Method not allowed") @@ -83,9 +83,9 @@ pub async fn read( ApiVersion, ("id" = Id, Path, description = "Id of action to update"), ), - request_body = entity::actions::Model, + request_body = actions::Model, responses( - (status = 200, description = "Successfully Updated Action", body = [entity::actions::Model]), + (status = 200, description = "Successfully Updated Action", body = [actions::Model]), (status = 401, description = "Unauthorized"), (status = 405, description = "Method not allowed") ), @@ -119,9 +119,9 @@ pub async fn update( ("id" = Id, Path, description = "Id of action to update"), ("value" = Option, Query, description = "Status value to update"), ), - request_body = entity::actions::Model, + request_body = actions::Model, responses( - (status = 200, description = "Successfully Updated Action", body = [entity::actions::Model]), + (status = 200, description = "Successfully Updated Action", body = [actions::Model]), (status = 401, description = "Unauthorized"), (status = 405, description = "Method not allowed") ), @@ -154,7 +154,7 @@ pub async fn update_status( ("coaching_session_id" = Option, Query, description = "Filter by coaching_session_id") ), responses( - (status = 200, description = "Successfully retrieved all Actions", body = [entity::actions::Model]), + (status = 200, description = "Successfully retrieved all Actions", body = [actions::Model]), (status = 401, description = "Unauthorized"), (status = 405, description = "Method not allowed") ), diff --git a/web/src/controller/agreement_controller.rs b/web/src/controller/agreement_controller.rs index e231c263..95ae0d82 100644 --- a/web/src/controller/agreement_controller.rs +++ b/web/src/controller/agreement_controller.rs @@ -8,8 +8,8 @@ use axum::extract::{Path, Query, State}; use axum::http::StatusCode; use axum::response::IntoResponse; use axum::Json; -use domain::agreement as AgreementApi; -use entity::{agreements::Model, Id}; +use domain::{agreement as AgreementApi, agreements::Model, Id}; + use serde_json::json; use service::config::ApiVersion; @@ -20,9 +20,9 @@ use log::*; post, path = "/agreements", params(ApiVersion), - request_body = entity::agreements::Model, + request_body = agreements::Model, responses( - (status = 201, description = "Successfully Created a New Agreement", body = [entity::agreements::Model]), + (status = 201, description = "Successfully Created a New Agreement", body = [agreements::Model]), (status= 422, description = "Unprocessable Entity"), (status = 401, description = "Unauthorized"), (status = 405, description = "Method not allowed") @@ -61,7 +61,7 @@ pub async fn create( ("id" = String, Path, description = "Agreement id to retrieve") ), responses( - (status = 200, description = "Successfully retrieved a specific Agreement by its id", body = [entity::notes::Model]), + (status = 200, description = "Successfully retrieved a specific Agreement by its id", body = [notes::Model]), (status = 401, description = "Unauthorized"), (status = 404, description = "Agreement not found"), (status = 405, description = "Method not allowed") @@ -89,9 +89,9 @@ pub async fn read( ApiVersion, ("id" = Id, Path, description = "Id of agreement to update"), ), - request_body = entity::agreements::Model, + request_body = agreements::Model, responses( - (status = 200, description = "Successfully Updated Agreement", body = [entity::agreements::Model]), + (status = 200, description = "Successfully Updated Agreement", body = [agreements::Model]), (status = 401, description = "Unauthorized"), (status = 405, description = "Method not allowed") ), @@ -125,7 +125,7 @@ pub async fn update( ("coaching_session_id" = Id, Query, description = "Filter by coaching_session_id") ), responses( - (status = 200, description = "Successfully retrieved all Agreements", body = [entity::agreements::Model]), + (status = 200, description = "Successfully retrieved all Agreements", body = [agreements::Model]), (status = 401, description = "Unauthorized"), (status = 405, description = "Method not allowed") ), diff --git a/web/src/controller/coaching_session_controller.rs b/web/src/controller/coaching_session_controller.rs index 09cc757c..23a47523 100644 --- a/web/src/controller/coaching_session_controller.rs +++ b/web/src/controller/coaching_session_controller.rs @@ -7,8 +7,7 @@ use axum::extract::{Query, State}; use axum::http::StatusCode; use axum::response::IntoResponse; use axum::Json; -use domain::coaching_session as CoachingSessionApi; -use entity::coaching_sessions::Model; +use domain::{coaching_session as CoachingSessionApi, coaching_sessions::Model}; use service::config::ApiVersion; use std::collections::HashMap; @@ -24,7 +23,7 @@ use log::*; ("to_date" = Option, Query, description = "Filter by to_date") ), responses( - (status = 200, description = "Successfully retrieved all Coaching Sessions", body = [entity::coaching_sessions::Model]), + (status = 200, description = "Successfully retrieved all Coaching Sessions", body = [coaching_sessions::Model]), (status = 401, description = "Unauthorized"), (status = 405, description = "Method not allowed") ), @@ -58,9 +57,9 @@ pub async fn index( post, path = "/coaching_sessions", params(ApiVersion), - request_body = entity::coaching_sessions::Model, + request_body = coaching_sessions::Model, responses( - (status = 201, description = "Successfully Created a new Coaching Session", body = [entity::coaching_sessions::Model]), + (status = 201, description = "Successfully Created a new Coaching Session", body = [coaching_sessions::Model]), (status= 422, description = "Unprocessable Entity"), (status = 401, description = "Unauthorized"), (status = 405, description = "Method not allowed") diff --git a/web/src/controller/note_controller.rs b/web/src/controller/note_controller.rs index 1fe30014..45faca69 100644 --- a/web/src/controller/note_controller.rs +++ b/web/src/controller/note_controller.rs @@ -7,8 +7,7 @@ use axum::extract::{Path, Query, State}; use axum::http::StatusCode; use axum::response::IntoResponse; use axum::Json; -use entity::{notes, Id}; -use entity_api::note as NoteApi; +use domain::{note as NoteApi, notes, Id}; use service::config::ApiVersion; use std::collections::HashMap; @@ -19,9 +18,9 @@ use log::*; post, path = "/notes", params(ApiVersion), - request_body = entity::notes::Model, + request_body = notes::Model, responses( - (status = 201, description = "Successfully Created a New Note", body = [entity::notes::Model]), + (status = 201, description = "Successfully Created a New Note", body = [notes::Model]), (status= 422, description = "Unprocessable Entity"), (status = 401, description = "Unauthorized"), (status = 405, description = "Method not allowed") @@ -55,9 +54,9 @@ pub async fn create( ApiVersion, ("id" = Id, Path, description = "Id of note to update"), ), - request_body = entity::notes::Model, + request_body = notes::Model, responses( - (status = 200, description = "Successfully Updated Note", body = [entity::notes::Model]), + (status = 200, description = "Successfully Updated Note", body = [notes::Model]), (status = 401, description = "Unauthorized"), (status = 405, description = "Method not allowed") ), @@ -91,7 +90,7 @@ pub async fn update( ("coaching_session_id" = Option, Query, description = "Filter by coaching_session_id") ), responses( - (status = 200, description = "Successfully retrieved all Notes", body = [entity::coaching_sessions::Model]), + (status = 200, description = "Successfully retrieved all Notes", body = [coaching_sessions::Model]), (status = 401, description = "Unauthorized"), (status = 405, description = "Method not allowed") ), @@ -126,7 +125,7 @@ pub async fn index( ("id" = String, Path, description = "Note id to retrieve") ), responses( - (status = 200, description = "Successfully retrieved a certain Note by its id", body = [entity::notes::Model]), + (status = 200, description = "Successfully retrieved a certain Note by its id", body = [notes::Model]), (status = 401, description = "Unauthorized"), (status = 404, description = "Note not found"), (status = 405, description = "Method not allowed") diff --git a/web/src/controller/organization/coaching_relationship_controller.rs b/web/src/controller/organization/coaching_relationship_controller.rs index 0f639da0..2cd2cf54 100644 --- a/web/src/controller/organization/coaching_relationship_controller.rs +++ b/web/src/controller/organization/coaching_relationship_controller.rs @@ -7,8 +7,8 @@ use axum::extract::{Path, State}; use axum::http::StatusCode; use axum::response::IntoResponse; use axum::Json; -use entity::{coaching_relationships, Id}; -use entity_api::coaching_relationship as CoachingRelationshipApi; +use domain::coaching_relationship::CoachingRelationshipWithUserNames; +use domain::{coaching_relationship as CoachingRelationshipApi, coaching_relationships, Id}; use service::config::ApiVersion; use log::*; @@ -22,7 +22,7 @@ use log::*; ), request_body = entity::coaching_relationships::Model, responses( - (status = 200, description = "Successfully created a new Coaching Relationship", body = [entity::coaching_relationships::Model]), + (status = 200, description = "Successfully created a new Coaching Relationship", body = [coaching_relationships::Model]), (status = 401, description = "Unauthorized"), (status = 405, description = "Method not allowed") ), @@ -65,7 +65,7 @@ pub async fn create( ("relationship_id" = String, Path, description = "CoachingRelationship id to retrieve") ), responses( - (status = 200, description = "Successfully retrieved a certain CoachingRelationship by its id", body = [entity::coaching_relationships::Model]), + (status = 200, description = "Successfully retrieved a certain CoachingRelationship by its id", body = [coaching_relationships::Model]), (status = 401, description = "Unauthorized"), (status = 404, description = "CoachingRelationship not found"), (status = 405, description = "Method not allowed") @@ -84,7 +84,7 @@ pub async fn read( ) -> Result { debug!("GET CoachingRelationship by id: {}", relationship_id); - let relationship: Option = + let relationship: Option = CoachingRelationshipApi::get_relationship_with_user_names( app_state.db_conn_ref(), relationship_id, @@ -103,7 +103,7 @@ pub async fn read( ("organization_id" = Id, Path, description = "Organization id to retrieve CoachingRelationships") ), responses( - (status = 200, description = "Successfully retrieved all CoachingRelationships", body = [entity::coaching_relationships::Model]), + (status = 200, description = "Successfully retrieved all CoachingRelationships", body = [coaching_relationships::Model]), (status = 401, description = "Unauthorized"), (status = 405, description = "Method not allowed") ), diff --git a/web/src/controller/organization_controller.rs b/web/src/controller/organization_controller.rs index 36a8b504..38e14bab 100644 --- a/web/src/controller/organization_controller.rs +++ b/web/src/controller/organization_controller.rs @@ -7,8 +7,7 @@ use axum::extract::{Path, Query, State}; use axum::http::StatusCode; use axum::response::IntoResponse; use axum::Json; -use entity::{organizations, Id}; -use entity_api::organization as OrganizationApi; +use domain::{organization as OrganizationApi, organizations, Id}; use serde_json::json; use service::config::ApiVersion; @@ -25,7 +24,7 @@ use log::debug; ("user_id" = Option, Query, description = "Filter by user_id") ), responses( - (status = 200, description = "Successfully retrieved all Organizations", body = [entity::organizations::Model]), + (status = 200, description = "Successfully retrieved all Organizations", body = [organizations::Model]), (status = 401, description = "Unauthorized"), (status = 405, description = "Method not allowed") ), @@ -61,7 +60,7 @@ pub async fn index( ("id" = String, Path, description = "Organization id to retrieve") ), responses( - (status = 200, description = "Successfully retrieved a certain Organization by its id", body = [entity::organizations::Model]), + (status = 200, description = "Successfully retrieved a certain Organization by its id", body = [organizations::Model]), (status = 401, description = "Unauthorized"), (status = 404, description = "Organization not found"), (status = 405, description = "Method not allowed") @@ -89,9 +88,9 @@ pub async fn read( params( ApiVersion, ), - request_body = entity::organizations::Model, + request_body = organizations::Model, responses( - (status = 200, description = "Successfully created a new Organization", body = [entity::organizations::Model]), + (status = 200, description = "Successfully created a new Organization", body = [organizations::Model]), (status = 401, description = "Unauthorized"), (status = 405, description = "Method not allowed") ), @@ -125,9 +124,9 @@ pub async fn create( ApiVersion, ("id" = i32, Path, description = "Organization id to update") ), - request_body = entity::organizations::Model, + request_body = organizations::Model, responses( - (status = 200, description = "Successfully updated a certain Organization by its id", body = [entity::organizations::Model]), + (status = 200, description = "Successfully updated a certain Organization by its id", body = [organizations::Model]), (status = 401, description = "Unauthorized"), (status = 404, description = "Organization not found"), (status = 405, description = "Method not allowed") diff --git a/web/src/controller/overarching_goal_controller.rs b/web/src/controller/overarching_goal_controller.rs index 3da30e61..f74ef7c4 100644 --- a/web/src/controller/overarching_goal_controller.rs +++ b/web/src/controller/overarching_goal_controller.rs @@ -7,8 +7,8 @@ use axum::extract::{Path, Query, State}; use axum::http::StatusCode; use axum::response::IntoResponse; use axum::Json; -use entity::{overarching_goals::Model, Id}; -use entity_api::overarching_goal as OverarchingGoalApi; +use domain::overarching_goal as OverarchingGoalApi; +use domain::{overarching_goals::Model, Id}; use service::config::ApiVersion; use std::collections::HashMap; diff --git a/web/src/controller/user_controller.rs b/web/src/controller/user_controller.rs index 42e82283..40faf1b0 100644 --- a/web/src/controller/user_controller.rs +++ b/web/src/controller/user_controller.rs @@ -1,8 +1,7 @@ use crate::{controller::ApiResponse, extractors::compare_api_version::CompareApiVersion}; use crate::{AppState, Error}; use axum::{extract::State, http::StatusCode, response::IntoResponse, Json}; -use entity::users; -use entity_api::user as UserApi; +use domain::{user as UserApi, users}; use service::config::ApiVersion; use log::*; @@ -14,9 +13,9 @@ use log::*; params( ApiVersion, ), - request_body = entity::users::Model, + request_body = users::Model, responses( - (status = 200, description = "Successfully created a new User", body = [entity::users::Model]), + (status = 200, description = "Successfully created a new User", body = [users::Model]), (status = 401, description = "Unauthorized"), (status = 405, description = "Method not allowed") ), diff --git a/web/src/controller/user_session_controller.rs b/web/src/controller/user_session_controller.rs index c03575ab..a14ffc95 100644 --- a/web/src/controller/user_session_controller.rs +++ b/web/src/controller/user_session_controller.rs @@ -1,6 +1,6 @@ use crate::controller::ApiResponse; use axum::{http::StatusCode, response::IntoResponse, Form, Json}; -use entity_api::user as UserApi; +use domain::{AuthSession, Credentials}; use log::*; use serde::Deserialize; use serde_json::json; @@ -12,7 +12,7 @@ pub struct NextUrl { _next: Option, } -pub async fn protected(auth_session: UserApi::AuthSession) -> impl IntoResponse { +pub async fn protected(auth_session: AuthSession) -> impl IntoResponse { debug!("UserSessionController::protected()"); match auth_session.user { @@ -37,7 +37,7 @@ pub async fn protected(auth_session: UserApi::AuthSession) -> impl IntoResponse #[utoipa::path( post, path = "/login", - request_body(content = entity_api::user::Credentials, content_type = "application/x-www-form-urlencoded"), + request_body(content = Credentials, content_type = "application/x-www-form-urlencoded"), responses( (status = 200, description = "Logs in and returns session authentication cookie"), (status = 401, description = "Unauthorized"), @@ -48,8 +48,8 @@ pub async fn protected(auth_session: UserApi::AuthSession) -> impl IntoResponse ) )] pub async fn login( - mut auth_session: UserApi::AuthSession, - Form(creds): Form, + mut auth_session: AuthSession, + Form(creds): Form, ) -> impl IntoResponse { debug!("UserSessionController::login()"); let user = match auth_session.authenticate(creds.clone()).await { @@ -94,7 +94,7 @@ security( ("cookie_auth" = []) ) )] -pub async fn logout(mut auth_session: UserApi::AuthSession) -> impl IntoResponse { +pub async fn logout(mut auth_session: domain::AuthSession) -> impl IntoResponse { debug!("UserSessionController::logout()"); match auth_session.logout().await { Ok(_) => StatusCode::OK.into_response(), diff --git a/web/src/extractors/authenticated_user.rs b/web/src/extractors/authenticated_user.rs index 7a58d284..dd8644b1 100644 --- a/web/src/extractors/authenticated_user.rs +++ b/web/src/extractors/authenticated_user.rs @@ -5,8 +5,7 @@ use axum::{ http::{request::Parts, StatusCode}, }; use axum_login::AuthSession; -use entity::users; -use entity_api::user; +use domain::users; pub(crate) struct AuthenticatedUser(pub users::Model); @@ -20,7 +19,7 @@ where // This extractor wraps the AuthSession extractor from axum_login. It extracts the user from the AuthSession and returns an AuthenticatedUser. // If the user is authenticated. If the user is not authenticated, it returns an Unauthorized error. async fn from_request_parts(parts: &mut Parts, state: &S) -> Result { - let session: user::AuthSession = AuthSession::from_request_parts(parts, state) + let session: domain::AuthSession = AuthSession::from_request_parts(parts, state) .await .map_err(|(status, msg)| (status, msg.to_string()))?; match session.user { diff --git a/web/src/lib.rs b/web/src/lib.rs index 58f1cfd7..3ad1bdbe 100644 --- a/web/src/lib.rs +++ b/web/src/lib.rs @@ -6,7 +6,7 @@ use axum_login::{ tower_sessions::{Expiry, SessionManagerLayer}, AuthManagerLayerBuilder, }; -use entity_api::user::Backend; +use domain::Backend; use tower_sessions::session_store::ExpiredDeletion; use tower_sessions_sqlx_store::PostgresStore; diff --git a/web/src/params/agreement.rs b/web/src/params/agreement.rs index 81ac04ce..44f34040 100644 --- a/web/src/params/agreement.rs +++ b/web/src/params/agreement.rs @@ -1,4 +1,4 @@ -use entity::Id; +use domain::Id; use sea_orm::Value; use serde::Deserialize; use utoipa::IntoParams; diff --git a/web/src/params/jwt.rs b/web/src/params/jwt.rs index c117beac..84d78e79 100644 --- a/web/src/params/jwt.rs +++ b/web/src/params/jwt.rs @@ -1,4 +1,4 @@ -use entity::Id; +use domain::Id; use serde::Deserialize; use utoipa::IntoParams; diff --git a/web/src/protect/actions.rs b/web/src/protect/actions.rs index 057d9229..07979e4b 100644 --- a/web/src/protect/actions.rs +++ b/web/src/protect/actions.rs @@ -5,8 +5,7 @@ use axum::{ middleware::Next, response::IntoResponse, }; -use entity::Id; -use entity_api::coaching_session; +use domain::{coaching_session, Id}; use log::*; use serde::Deserialize; diff --git a/web/src/protect/agreements.rs b/web/src/protect/agreements.rs index 29483c68..f2edacb5 100644 --- a/web/src/protect/agreements.rs +++ b/web/src/protect/agreements.rs @@ -6,7 +6,7 @@ use axum::{ middleware::Next, response::IntoResponse, }; -use entity_api::coaching_session; +use domain::coaching_session; use log::*; /// Checks that coaching relationship record associated with the coaching session diff --git a/web/src/protect/coaching_relationships.rs b/web/src/protect/coaching_relationships.rs index 57d078ac..1536ac0e 100644 --- a/web/src/protect/coaching_relationships.rs +++ b/web/src/protect/coaching_relationships.rs @@ -6,8 +6,7 @@ use axum::{ response::IntoResponse, }; -use entity::Id; -use entity_api::organization; +use domain::{organization, Id}; use std::collections::HashSet; /// Checks that the organization record referenced by `organization_id` diff --git a/web/src/protect/coaching_sessions.rs b/web/src/protect/coaching_sessions.rs index 85ef5fdc..0eaa1331 100644 --- a/web/src/protect/coaching_sessions.rs +++ b/web/src/protect/coaching_sessions.rs @@ -7,8 +7,7 @@ use axum::{ }; use serde::Deserialize; -use entity::Id; -use entity_api::coaching_relationship; +use domain::{coaching_relationship, Id}; #[derive(Debug, Deserialize)] pub(crate) struct QueryParams { diff --git a/web/src/protect/jwt.rs b/web/src/protect/jwt.rs index 3e3624bd..241ea268 100644 --- a/web/src/protect/jwt.rs +++ b/web/src/protect/jwt.rs @@ -7,7 +7,7 @@ use axum::{ middleware::Next, response::IntoResponse, }; -use entity_api::coaching_session; +use domain::coaching_session; use log::*; /// Checks that coaching relationship record associated with the coaching session diff --git a/web/src/protect/notes.rs b/web/src/protect/notes.rs index 057d9229..07979e4b 100644 --- a/web/src/protect/notes.rs +++ b/web/src/protect/notes.rs @@ -5,8 +5,7 @@ use axum::{ middleware::Next, response::IntoResponse, }; -use entity::Id; -use entity_api::coaching_session; +use domain::{coaching_session, Id}; use log::*; use serde::Deserialize; diff --git a/web/src/protect/overarching_goals.rs b/web/src/protect/overarching_goals.rs index 057d9229..07979e4b 100644 --- a/web/src/protect/overarching_goals.rs +++ b/web/src/protect/overarching_goals.rs @@ -5,8 +5,7 @@ use axum::{ middleware::Next, response::IntoResponse, }; -use entity::Id; -use entity_api::coaching_session; +use domain::{coaching_session, Id}; use log::*; use serde::Deserialize; diff --git a/web/src/router.rs b/web/src/router.rs index 2a29db8a..6c8d7cbe 100644 --- a/web/src/router.rs +++ b/web/src/router.rs @@ -5,7 +5,7 @@ use axum::{ Router, }; use axum_login::login_required; -use entity_api::user::Backend; +use domain::Backend; use tower_http::services::ServeDir; use crate::controller::{ @@ -67,15 +67,15 @@ use self::organization::coaching_relationship_controller; ), components( schemas( - entity::actions::Model, - entity::agreements::Model, - entity::coaching_sessions::Model, - entity::coaching_relationships::Model, - entity::notes::Model, - entity::organizations::Model, - entity::overarching_goals::Model, - entity::users::Model, - entity_api::user::Credentials, + domain::actions::Model, + domain::agreements::Model, + domain::coaching_sessions::Model, + domain::coaching_relationships::Model, + domain::notes::Model, + domain::organizations::Model, + domain::overarching_goals::Model, + domain::users::Model, + domain::Credentials, ) ), modifiers(&SecurityAddon), @@ -326,8 +326,8 @@ mod organization_endpoints_tests { AuthManagerLayerBuilder, }; use chrono::Utc; - use entity::{organizations, users, Id}; - use entity_api::user::Backend; + use domain::Backend; + use domain::{organizations, users, Id}; use log::{debug, LevelFilter}; use password_auth::generate_hash; use reqwest::{header, header::HeaderValue, Url}; From 66243680fa3359f5821bd3e2b52f0cd5695fa8e2 Mon Sep 17 00:00:00 2001 From: Caleb Bourg Date: Mon, 24 Feb 2025 20:55:35 -0500 Subject: [PATCH 03/11] make find_by generic --- domain/src/agreement.rs | 9 +++++++-- entity_api/src/agreement.rs | 20 ++------------------ entity_api/src/lib.rs | 1 + entity_api/src/query.rs | 22 ++++++++++++++++++++++ 4 files changed, 32 insertions(+), 20 deletions(-) create mode 100644 entity_api/src/query.rs diff --git a/domain/src/agreement.rs b/domain/src/agreement.rs index d53d0b29..2979ef89 100644 --- a/domain/src/agreement.rs +++ b/domain/src/agreement.rs @@ -1,14 +1,19 @@ use crate::agreements::Model; use crate::error::Error; pub use entity_api::agreement::{create, delete_by_id, find_by_id, update}; -use entity_api::{agreement, IntoQueryFilterMap}; +use entity_api::IntoQueryFilterMap; +use entity_api::{agreements, query}; use sea_orm::DatabaseConnection; pub async fn find_by( db: &DatabaseConnection, params: impl IntoQueryFilterMap, ) -> Result, Error> { - let agreements = agreement::find_by(db, params.into_query_filter_map()).await?; + let agreements = query::find_by::( + db, + params.into_query_filter_map(), + ) + .await?; Ok(agreements) } diff --git a/entity_api/src/agreement.rs b/entity_api/src/agreement.rs index 3dafa540..482a9185 100644 --- a/entity_api/src/agreement.rs +++ b/entity_api/src/agreement.rs @@ -1,11 +1,10 @@ use super::error::{EntityApiErrorKind, Error}; -use crate::QueryFilterMap; -use entity::agreements::{self, ActiveModel, Entity, Model}; +use entity::agreements::{ActiveModel, Entity, Model}; use entity::Id; use sea_orm::{ entity::prelude::*, ActiveValue::{Set, Unchanged}, - DatabaseConnection, Iterable, TryIntoModel, + DatabaseConnection, TryIntoModel, }; use log::*; @@ -74,21 +73,6 @@ pub async fn find_by_id(db: &DatabaseConnection, id: Id) -> Result }) } -pub async fn find_by( - db: &DatabaseConnection, - query_filter_map: QueryFilterMap, -) -> Result, Error> { - let mut query = Entity::find(); - - for column in agreements::Column::iter() { - if let Some(value) = query_filter_map.get(&column.to_string()) { - query = query.filter(column.eq(value)); - } - } - - Ok(query.all(db).await?) -} - #[cfg(test)] // We need to gate seaORM's mock feature behind conditional compilation because // the feature removes the Clone trait implementation from seaORM's DatabaseConnection. diff --git a/entity_api/src/lib.rs b/entity_api/src/lib.rs index 5f1b09bc..72c063e1 100644 --- a/entity_api/src/lib.rs +++ b/entity_api/src/lib.rs @@ -16,6 +16,7 @@ pub mod error; pub mod note; pub mod organization; pub mod overarching_goal; +pub mod query; pub mod user; pub(crate) fn uuid_parse_str(uuid_str: &str) -> Result { diff --git a/entity_api/src/query.rs b/entity_api/src/query.rs new file mode 100644 index 00000000..8b98e10d --- /dev/null +++ b/entity_api/src/query.rs @@ -0,0 +1,22 @@ +use crate::{error::Error, QueryFilterMap}; +use sea_orm::strum::IntoEnumIterator; +use sea_orm::{ColumnTrait, DatabaseConnection, EntityTrait, QueryFilter}; + +pub async fn find_by( + db: &DatabaseConnection, + query_filter_map: QueryFilterMap, +) -> Result, Error> +where + E: EntityTrait, + C: ColumnTrait + IntoEnumIterator, +{ + let mut query = E::find(); + + for column in C::iter() { + if let Some(value) = query_filter_map.get(&column.to_string()) { + query = query.filter(column.eq(value)); + } + } + + Ok(query.all(db).await?) +} From 87e76dc5dce19f210e5731c370ab0b1f6ab5f740 Mon Sep 17 00:00:00 2001 From: Caleb Bourg Date: Tue, 25 Feb 2025 15:38:22 -0500 Subject: [PATCH 04/11] update actions to use new find_by --- domain/src/action.rs | 19 +++++++++++++++- entity_api/src/action.rs | 26 ---------------------- entity_api/src/query.rs | 3 +++ web/src/controller/action_controller.rs | 4 ++-- web/src/controller/agreement_controller.rs | 6 ++++- web/src/params/action.rs | 23 +++++++++++++++++++ web/src/params/mod.rs | 1 + 7 files changed, 52 insertions(+), 30 deletions(-) create mode 100644 web/src/params/action.rs diff --git a/domain/src/action.rs b/domain/src/action.rs index d54420fd..dab74a3c 100644 --- a/domain/src/action.rs +++ b/domain/src/action.rs @@ -1 +1,18 @@ -pub use entity_api::action::{create, delete_by_id, find_by, find_by_id, update, update_status}; +use crate::actions::Model; +use crate::error::Error; +use entity_api::IntoQueryFilterMap; +use entity_api::{actions, query}; +use sea_orm::DatabaseConnection; + +pub use entity_api::action::{create, delete_by_id, find_by_id, update, update_status}; + +pub async fn find_by( + db: &DatabaseConnection, + params: impl IntoQueryFilterMap, +) -> Result, Error> { + let actions = + query::find_by::(db, params.into_query_filter_map()) + .await?; + + Ok(actions) +} diff --git a/entity_api/src/action.rs b/entity_api/src/action.rs index 924fe008..7fde8ae0 100644 --- a/entity_api/src/action.rs +++ b/entity_api/src/action.rs @@ -7,7 +7,6 @@ use sea_orm::{ ActiveValue::{Set, Unchanged}, DatabaseConnection, TryIntoModel, }; -use std::collections::HashMap; use log::*; @@ -118,31 +117,6 @@ pub async fn find_by_id(db: &DatabaseConnection, id: Id) -> Result }) } -pub async fn find_by( - db: &DatabaseConnection, - query_params: HashMap, -) -> Result, Error> { - let mut query = Entity::find(); - - for (key, value) in query_params { - match key.as_str() { - "coaching_session_id" => { - let coaching_session_id = uuid_parse_str(&value)?; - - query = query.filter(actions::Column::CoachingSessionId.eq(coaching_session_id)); - } - _ => { - return Err(Error { - source: None, - error_kind: EntityApiErrorKind::InvalidQueryTerm, - }); - } - } - } - - Ok(query.all(db).await?) -} - #[cfg(test)] // We need to gate seaORM's mock feature behind conditional compilation because // the feature removes the Clone trait implementation from seaORM's DatabaseConnection. diff --git a/entity_api/src/query.rs b/entity_api/src/query.rs index 8b98e10d..9d0fe652 100644 --- a/entity_api/src/query.rs +++ b/entity_api/src/query.rs @@ -2,6 +2,7 @@ use crate::{error::Error, QueryFilterMap}; use sea_orm::strum::IntoEnumIterator; use sea_orm::{ColumnTrait, DatabaseConnection, EntityTrait, QueryFilter}; +/// Find all records of an entity by the given query filter map. pub async fn find_by( db: &DatabaseConnection, query_filter_map: QueryFilterMap, @@ -12,6 +13,8 @@ where { let mut query = E::find(); + // We iterate through the entity's defined columns so that we only attempt + // to filter by columns that exist. for column in C::iter() { if let Some(value) = query_filter_map.get(&column.to_string()) { query = query.filter(column.eq(value)); diff --git a/web/src/controller/action_controller.rs b/web/src/controller/action_controller.rs index 035c9b2b..a70a22fe 100644 --- a/web/src/controller/action_controller.rs +++ b/web/src/controller/action_controller.rs @@ -2,6 +2,7 @@ use crate::controller::ApiResponse; use crate::extractors::{ authenticated_user::AuthenticatedUser, compare_api_version::CompareApiVersion, }; +use crate::params::action::IndexParams; use crate::{AppState, Error}; use axum::extract::{Path, Query, State}; use axum::http::StatusCode; @@ -11,7 +12,6 @@ use domain::{action as ActionApi, actions::Model, Id}; use serde_json::json; use service::config::ApiVersion; -use std::collections::HashMap; use log::*; @@ -168,7 +168,7 @@ pub async fn index( // TODO: create a new Extractor to authorize the user to access // the data requested State(app_state): State, - Query(params): Query>, + Query(params): Query, ) -> Result { debug!("GET all Actions"); debug!("Filter Params: {:?}", params); diff --git a/web/src/controller/agreement_controller.rs b/web/src/controller/agreement_controller.rs index 95ae0d82..8ead6326 100644 --- a/web/src/controller/agreement_controller.rs +++ b/web/src/controller/agreement_controller.rs @@ -142,8 +142,12 @@ pub async fn index( Query(params): Query, ) -> Result { debug!("GET all Agreements"); - info!("Params: {:?}", params); + debug!("Filter Params: {:?}", params); + let agreements = AgreementApi::find_by(app_state.db_conn_ref(), params).await?; + + debug!("Found Agreements: {:?}", agreements); + Ok(Json(ApiResponse::new(StatusCode::OK.into(), agreements))) } diff --git a/web/src/params/action.rs b/web/src/params/action.rs new file mode 100644 index 00000000..44f34040 --- /dev/null +++ b/web/src/params/action.rs @@ -0,0 +1,23 @@ +use domain::Id; +use sea_orm::Value; +use serde::Deserialize; +use utoipa::IntoParams; + +use domain::{IntoQueryFilterMap, QueryFilterMap}; + +#[derive(Debug, Deserialize, IntoParams)] +pub(crate) struct IndexParams { + pub(crate) coaching_session_id: Id, +} + +impl IntoQueryFilterMap for IndexParams { + fn into_query_filter_map(self) -> QueryFilterMap { + let mut query_filter_map = QueryFilterMap::new(); + query_filter_map.insert( + "coaching_session_id".to_string(), + Some(Value::Uuid(Some(Box::new(self.coaching_session_id)))), + ); + + query_filter_map + } +} diff --git a/web/src/params/mod.rs b/web/src/params/mod.rs index f615806e..946b806e 100644 --- a/web/src/params/mod.rs +++ b/web/src/params/mod.rs @@ -11,5 +11,6 @@ // //! ``` +pub(crate) mod action; pub(crate) mod agreement; pub(crate) mod jwt; From 8643058198e79d27a020653e2c7374ca6d33910d Mon Sep 17 00:00:00 2001 From: Caleb Bourg Date: Tue, 25 Feb 2025 15:44:16 -0500 Subject: [PATCH 05/11] fix tests --- entity_api/src/action.rs | 30 ++--------------------- entity_api/src/agreement.rs | 47 +------------------------------------ 2 files changed, 3 insertions(+), 74 deletions(-) diff --git a/entity_api/src/action.rs b/entity_api/src/action.rs index 7fde8ae0..4996929e 100644 --- a/entity_api/src/action.rs +++ b/entity_api/src/action.rs @@ -1,6 +1,5 @@ use super::error::{EntityApiErrorKind, Error}; -use crate::uuid_parse_str; -use entity::actions::{self, ActiveModel, Entity, Model}; +use entity::actions::{ActiveModel, Entity, Model}; use entity::{status::Status, Id}; use sea_orm::{ entity::prelude::*, @@ -125,7 +124,7 @@ pub async fn find_by_id(db: &DatabaseConnection, id: Id) -> Result mod tests { use super::*; use entity::{actions::Model, Id}; - use sea_orm::{DatabaseBackend, MockDatabase, Transaction}; + use sea_orm::{DatabaseBackend, MockDatabase}; #[tokio::test] async fn create_returns_a_new_action_model() -> Result<(), Error> { @@ -233,29 +232,4 @@ mod tests { Ok(()) } - - #[tokio::test] - async fn find_by_returns_all_actions_associated_with_coaching_session() -> Result<(), Error> { - let db = MockDatabase::new(DatabaseBackend::Postgres).into_connection(); - let mut query_params = HashMap::new(); - let coaching_session_id = Id::new_v4(); - - query_params.insert( - "coaching_session_id".to_owned(), - coaching_session_id.to_string(), - ); - - let _ = find_by(&db, query_params).await; - - assert_eq!( - db.into_transaction_log(), - [Transaction::from_sql_and_values( - DatabaseBackend::Postgres, - r#"SELECT "actions"."id", "actions"."coaching_session_id", "actions"."user_id", "actions"."body", "actions"."due_by", CAST("actions"."status" AS text), "actions"."status_changed_at", "actions"."created_at", "actions"."updated_at" FROM "refactor_platform"."actions" WHERE "actions"."coaching_session_id" = $1"#, - [coaching_session_id.into()] - )] - ); - - Ok(()) - } } diff --git a/entity_api/src/agreement.rs b/entity_api/src/agreement.rs index 482a9185..e28b7c41 100644 --- a/entity_api/src/agreement.rs +++ b/entity_api/src/agreement.rs @@ -81,7 +81,7 @@ pub async fn find_by_id(db: &DatabaseConnection, id: Id) -> Result mod tests { use super::*; use entity::{agreements::Model, Id}; - use sea_orm::{DatabaseBackend, MockDatabase, Transaction, Value}; + use sea_orm::{DatabaseBackend, MockDatabase}; #[tokio::test] async fn create_returns_a_new_agreement_model() -> Result<(), Error> { @@ -133,49 +133,4 @@ mod tests { Ok(()) } - - #[tokio::test] - async fn find_by_id_returns_agreement_associated_with_id() -> Result<(), Error> { - let db = MockDatabase::new(DatabaseBackend::Postgres).into_connection(); - let agreement_id = Id::new_v4(); - - let _ = find_by_id(&db, agreement_id).await; - - assert_eq!( - db.into_transaction_log(), - [Transaction::from_sql_and_values( - DatabaseBackend::Postgres, - r#"SELECT "agreements"."id", "agreements"."coaching_session_id", "agreements"."body", "agreements"."user_id", "agreements"."created_at", "agreements"."updated_at" FROM "refactor_platform"."agreements" WHERE "agreements"."id" = $1 LIMIT $2"#, - [agreement_id.into(), sea_orm::Value::BigUnsigned(Some(1))] - )] - ); - - Ok(()) - } - - #[tokio::test] - async fn find_by_returns_all_agreements_associated_with_coaching_session() -> Result<(), Error> - { - let db = MockDatabase::new(DatabaseBackend::Postgres).into_connection(); - let mut query_filter_map = QueryFilterMap::new(); - let coaching_session_id = Id::new_v4(); - - query_filter_map.insert( - "coaching_session_id".to_owned(), - Some(Value::Uuid(Some(Box::new(coaching_session_id)))), - ); - - let _ = find_by(&db, query_filter_map).await; - - assert_eq!( - db.into_transaction_log(), - [Transaction::from_sql_and_values( - DatabaseBackend::Postgres, - r#"SELECT "agreements"."id", "agreements"."coaching_session_id", "agreements"."body", "agreements"."user_id", "agreements"."created_at", "agreements"."updated_at" FROM "refactor_platform"."agreements" WHERE "agreements"."coaching_session_id" = $1"#, - [coaching_session_id.into()] - )] - ); - - Ok(()) - } } From 1113faa9c15d452ff7d9a9f8677611f7cab69d03 Mon Sep 17 00:00:00 2001 From: Caleb Bourg Date: Tue, 25 Feb 2025 15:54:36 -0500 Subject: [PATCH 06/11] fixup agreement --- domain/src/agreement.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/domain/src/agreement.rs b/domain/src/agreement.rs index 2979ef89..ff59c85c 100644 --- a/domain/src/agreement.rs +++ b/domain/src/agreement.rs @@ -1,10 +1,11 @@ use crate::agreements::Model; use crate::error::Error; -pub use entity_api::agreement::{create, delete_by_id, find_by_id, update}; use entity_api::IntoQueryFilterMap; use entity_api::{agreements, query}; use sea_orm::DatabaseConnection; +pub use entity_api::agreement::{create, delete_by_id, find_by_id, update}; + pub async fn find_by( db: &DatabaseConnection, params: impl IntoQueryFilterMap, From 1e4789d64ac1c7a907e30db54d9694fde47c4404 Mon Sep 17 00:00:00 2001 From: Caleb Bourg Date: Tue, 25 Feb 2025 16:07:33 -0500 Subject: [PATCH 07/11] use new find_by for coaching_sessions --- domain/src/coaching_session.rs | 22 +++- entity_api/src/coaching_session.rs | 110 +----------------- entity_api/src/lib.rs | 21 ---- web/Cargo.toml | 1 + .../controller/coaching_session_controller.rs | 4 +- web/src/params/coaching_session.rs | 32 +++++ web/src/params/mod.rs | 1 + 7 files changed, 55 insertions(+), 136 deletions(-) create mode 100644 web/src/params/coaching_session.rs diff --git a/domain/src/coaching_session.rs b/domain/src/coaching_session.rs index fff17fdb..818c416f 100644 --- a/domain/src/coaching_session.rs +++ b/domain/src/coaching_session.rs @@ -2,15 +2,16 @@ use crate::coaching_sessions::Model; use crate::error::{DomainErrorKind, Error, ExternalErrorKind, InternalErrorKind}; use crate::gateway::tiptap::client as tiptap_client; use chrono::{DurationRound, TimeDelta}; -use entity_api::{coaching_relationship, coaching_session, organization}; +use entity_api::{ + coaching_relationship, coaching_session, coaching_sessions, organization, query, + IntoQueryFilterMap, +}; use log::*; use sea_orm::DatabaseConnection; use serde_json::json; use service::config::Config; -pub use entity_api::coaching_session::{ - find_by, find_by_id, find_by_id_with_coaching_relationship, -}; +pub use entity_api::coaching_session::{find_by_id, find_by_id_with_coaching_relationship}; pub async fn create( db: &DatabaseConnection, @@ -85,3 +86,16 @@ pub async fn create( }) } } + +pub async fn find_by( + db: &DatabaseConnection, + params: impl IntoQueryFilterMap, +) -> Result, Error> { + let coaching_sessions = query::find_by::( + db, + params.into_query_filter_map(), + ) + .await?; + + Ok(coaching_sessions) +} diff --git a/entity_api/src/coaching_session.rs b/entity_api/src/coaching_session.rs index df2558d9..09000810 100644 --- a/entity_api/src/coaching_session.rs +++ b/entity_api/src/coaching_session.rs @@ -1,13 +1,11 @@ use super::error::{EntityApiErrorKind, Error}; -use crate::{naive_date_parse_str, uuid_parse_str}; use entity::{ coaching_relationships, - coaching_sessions::{self, ActiveModel, Entity, Model}, + coaching_sessions::{ActiveModel, Entity, Model}, Id, }; use log::debug; use sea_orm::{entity::prelude::*, DatabaseConnection, Set, TryIntoModel}; -use std::collections::HashMap; pub async fn create( db: &DatabaseConnection, @@ -60,41 +58,6 @@ pub async fn find_by_id_with_coaching_relationship( error_kind: EntityApiErrorKind::RecordNotFound, }) } - -pub async fn find_by( - db: &DatabaseConnection, - params: HashMap, -) -> Result, Error> { - let mut query = Entity::find(); - - for (key, value) in params { - match key.as_str() { - "coaching_relationship_id" => { - let coaching_relationship_id = uuid_parse_str(&value)?; - query = query.filter( - coaching_sessions::Column::CoachingRelationshipId.eq(coaching_relationship_id), - ) - } - "from_date" => { - let from_date = naive_date_parse_str(&value)?; - query = query.filter(coaching_sessions::Column::Date.gt(from_date)); - } - "to_date" => { - let to_date = naive_date_parse_str(&value)?; - query = query.filter(coaching_sessions::Column::Date.lt(to_date)); - } - _ => { - return Err(Error { - source: None, - error_kind: EntityApiErrorKind::InvalidQueryTerm, - }); - } - } - } - - Ok(query.all(db).await?) -} - #[cfg(test)] // We need to gate seaORM's mock feature behind conditional compilation because // the feature removes the Clone trait implementation from seaORM's DatabaseConnection. @@ -102,7 +65,6 @@ pub async fn find_by( #[cfg(feature = "mock")] mod tests { use super::*; - use chrono::NaiveDate; use entity::Id; use sea_orm::{DatabaseBackend, MockDatabase, Transaction}; @@ -173,74 +135,4 @@ mod tests { Ok(()) } - - #[tokio::test] - async fn find_by_coaching_relationships_returns_all_records_associated_with_coaching_relationship( - ) -> Result<(), Error> { - let db = MockDatabase::new(DatabaseBackend::Postgres).into_connection(); - let mut query_params = HashMap::new(); - let coaching_relationship_id = Id::new_v4(); - - query_params.insert( - "coaching_relationship_id".to_owned(), - coaching_relationship_id.to_string(), - ); - - let _ = find_by(&db, query_params).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"."collab_document_name", "coaching_sessions"."date", "coaching_sessions"."created_at", "coaching_sessions"."updated_at" FROM "refactor_platform"."coaching_sessions" WHERE "coaching_sessions"."coaching_relationship_id" = $1"#, - [coaching_relationship_id.into()] - )] - ); - - Ok(()) - } - - #[tokio::test] - async fn find_by_from_date_returns_all_records_after_date() -> Result<(), Error> { - let db = MockDatabase::new(DatabaseBackend::Postgres).into_connection(); - let mut query_params = HashMap::new(); - let from_date = NaiveDate::from_ymd_opt(2021, 1, 1).unwrap(); - - query_params.insert("from_date".to_owned(), from_date.to_string()); - - let _ = find_by(&db, query_params).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"."collab_document_name", "coaching_sessions"."date", "coaching_sessions"."created_at", "coaching_sessions"."updated_at" FROM "refactor_platform"."coaching_sessions" WHERE "coaching_sessions"."date" > $1"#, - [from_date.into()] - )] - ); - - Ok(()) - } - - #[tokio::test] - async fn find_by_to_date_returns_all_records_before_date() -> Result<(), Error> { - let db = MockDatabase::new(DatabaseBackend::Postgres).into_connection(); - let mut query_params = HashMap::new(); - let to_date = NaiveDate::from_ymd_opt(2027, 1, 1).unwrap(); - - query_params.insert("to_date".to_owned(), to_date.to_string()); - - let _ = find_by(&db, query_params).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"."collab_document_name", "coaching_sessions"."date", "coaching_sessions"."created_at", "coaching_sessions"."updated_at" FROM "refactor_platform"."coaching_sessions" WHERE "coaching_sessions"."date" < $1"#, - [to_date.into()] - )] - ); - - Ok(()) - } } diff --git a/entity_api/src/lib.rs b/entity_api/src/lib.rs index 72c063e1..5aaafd3d 100644 --- a/entity_api/src/lib.rs +++ b/entity_api/src/lib.rs @@ -26,13 +26,6 @@ pub(crate) fn uuid_parse_str(uuid_str: &str) -> Result { }) } -pub(crate) fn naive_date_parse_str(date_str: &str) -> Result { - chrono::NaiveDate::parse_from_str(date_str, "%Y-%m-%d").map_err(|_| error::Error { - source: None, - error_kind: error::EntityApiErrorKind::InvalidQueryTerm, - }) -} - /// `QueryFilterMap` is a data structure that serves as a bridge for translating filter parameters /// between different layers of the application. It is essentially a wrapper around a `HashMap` /// where the keys are filter parameter names (as `String`) and the values are optional `Value` types @@ -400,18 +393,4 @@ mod tests { let result = uuid_parse_str(uuid_str); assert!(result.is_err()); } - - #[tokio::test] - async fn naive_date_parse_str_parses_valid_date() { - let date_str = "2021-08-01"; - let date = naive_date_parse_str(date_str).unwrap(); - assert_eq!(date.to_string(), date_str); - } - - #[tokio::test] - async fn naive_date_parse_str_returns_error_for_invalid_date() { - let date_str = "invalid"; - let result = naive_date_parse_str(date_str); - assert!(result.is_err()); - } } diff --git a/web/Cargo.toml b/web/Cargo.toml index 9a414ecd..25e539f3 100644 --- a/web/Cargo.toml +++ b/web/Cargo.toml @@ -11,6 +11,7 @@ service = { path = "../service" } axum = "0.7.7" axum-login = "0.16.0" +chrono = { version = "0.4.38", features = ["serde"] } log = "0.4.22" tower-http = { version = "0.6.1", features = ["fs", "cors"] } serde_json = "1.0.128" diff --git a/web/src/controller/coaching_session_controller.rs b/web/src/controller/coaching_session_controller.rs index 23a47523..6822a6c2 100644 --- a/web/src/controller/coaching_session_controller.rs +++ b/web/src/controller/coaching_session_controller.rs @@ -2,6 +2,7 @@ use crate::controller::ApiResponse; use crate::extractors::{ authenticated_user::AuthenticatedUser, compare_api_version::CompareApiVersion, }; +use crate::params::coaching_session::IndexParams; use crate::{AppState, Error}; use axum::extract::{Query, State}; use axum::http::StatusCode; @@ -9,7 +10,6 @@ use axum::response::IntoResponse; use axum::Json; use domain::{coaching_session as CoachingSessionApi, coaching_sessions::Model}; use service::config::ApiVersion; -use std::collections::HashMap; use log::*; @@ -37,7 +37,7 @@ pub async fn index( // TODO: create a new Extractor to authorize the user to access // the data requested State(app_state): State, - Query(params): Query>, + Query(params): Query, ) -> Result { debug!("GET all Coaching Sessions"); debug!("Filter Params: {:?}", params); diff --git a/web/src/params/coaching_session.rs b/web/src/params/coaching_session.rs new file mode 100644 index 00000000..e8082c7d --- /dev/null +++ b/web/src/params/coaching_session.rs @@ -0,0 +1,32 @@ +use chrono::NaiveDate; +use domain::Id; +use domain::{IntoQueryFilterMap, QueryFilterMap}; +use sea_orm::Value; +use serde::Deserialize; +use utoipa::IntoParams; + +#[derive(Debug, Deserialize, IntoParams)] +pub(crate) struct IndexParams { + pub(crate) coaching_relationship_id: Id, + pub(crate) from_date: NaiveDate, + pub(crate) to_date: NaiveDate, +} + +impl IntoQueryFilterMap for IndexParams { + fn into_query_filter_map(self) -> QueryFilterMap { + let mut query_filter_map = QueryFilterMap::new(); + query_filter_map.insert( + "coaching_relationship_id".to_string(), + Some(Value::Uuid(Some(Box::new(self.coaching_relationship_id)))), + ); + query_filter_map.insert( + "from_date".to_string(), + Some(Value::ChronoDate(Some(Box::new(self.from_date)))), + ); + query_filter_map.insert( + "to_date".to_string(), + Some(Value::ChronoDate(Some(Box::new(self.to_date)))), + ); + query_filter_map + } +} diff --git a/web/src/params/mod.rs b/web/src/params/mod.rs index 946b806e..c6f24d53 100644 --- a/web/src/params/mod.rs +++ b/web/src/params/mod.rs @@ -13,4 +13,5 @@ pub(crate) mod action; pub(crate) mod agreement; +pub(crate) mod coaching_session; pub(crate) mod jwt; From 660632dc62b5cf74f97643c34519ad281013e7e2 Mon Sep 17 00:00:00 2001 From: Caleb Bourg Date: Tue, 25 Feb 2025 16:15:15 -0500 Subject: [PATCH 08/11] use new find_by for coaching_relationships This function is not currently being used --- domain/src/coaching_relationship.rs | 21 ++++++++++++++++++++- entity_api/src/coaching_relationship.rs | 24 ------------------------ 2 files changed, 20 insertions(+), 25 deletions(-) diff --git a/domain/src/coaching_relationship.rs b/domain/src/coaching_relationship.rs index dc9ea228..4edf1891 100644 --- a/domain/src/coaching_relationship.rs +++ b/domain/src/coaching_relationship.rs @@ -1,4 +1,23 @@ +use crate::coaching_relationships::Model; +use crate::error::Error; +use entity_api::IntoQueryFilterMap; +use entity_api::{coaching_relationships, query}; +use sea_orm::DatabaseConnection; + pub use entity_api::coaching_relationship::{ - create, find_by, find_by_id, find_by_organization_with_user_names, find_by_user, + create, find_by_id, find_by_organization_with_user_names, find_by_user, get_relationship_with_user_names, CoachingRelationshipWithUserNames, }; + +pub async fn find_by( + db: &DatabaseConnection, + params: impl IntoQueryFilterMap, +) -> Result, Error> { + let coaching_relationships = query::find_by::< + coaching_relationships::Entity, + coaching_relationships::Column, + >(db, params.into_query_filter_map()) + .await?; + + Ok(coaching_relationships) +} diff --git a/entity_api/src/coaching_relationship.rs b/entity_api/src/coaching_relationship.rs index 01a77e1f..7a555ee5 100644 --- a/entity_api/src/coaching_relationship.rs +++ b/entity_api/src/coaching_relationship.rs @@ -1,5 +1,4 @@ use super::error::{EntityApiErrorKind, Error}; -use crate::uuid_parse_str; use chrono::Utc; use entity::{ coachees, coaches, @@ -143,29 +142,6 @@ pub async fn get_relationship_with_user_names( Ok(query.one(db).await?) } -pub async fn find_by( - db: &DatabaseConnection, - params: std::collections::HashMap, -) -> Result, Error> { - let mut query = coaching_relationships::Entity::find(); - - for (key, value) in params.iter() { - match key.as_str() { - "organization_id" => { - query = by_organization(query, uuid_parse_str(value)?).await; - } - _ => { - return Err(Error { - source: None, - error_kind: EntityApiErrorKind::InvalidQueryTerm, - }); - } - } - } - - Ok(query.all(db).await?) -} - pub async fn by_coaching_relationship( query: Select, id: Id, From 3e54d0b048299c5cc24d9dfe448643638ff743cf Mon Sep 17 00:00:00 2001 From: Caleb Bourg Date: Tue, 25 Feb 2025 16:31:25 -0500 Subject: [PATCH 09/11] new find_by for overarching goals --- domain/src/overarching_goal.rs | 21 ++++++- entity_api/src/overarching_goal.rs | 58 +------------------ .../controller/overarching_goal_controller.rs | 4 +- web/src/params/mod.rs | 1 + web/src/params/overarching_goal.rs | 23 ++++++++ 5 files changed, 48 insertions(+), 59 deletions(-) create mode 100644 web/src/params/overarching_goal.rs diff --git a/domain/src/overarching_goal.rs b/domain/src/overarching_goal.rs index 49a34acb..b14774d5 100644 --- a/domain/src/overarching_goal.rs +++ b/domain/src/overarching_goal.rs @@ -1 +1,20 @@ -pub use entity_api::overarching_goal::{create, find_by, find_by_id, update, update_status}; +use crate::error::Error; +use crate::overarching_goals::Model; +use entity_api::IntoQueryFilterMap; +use entity_api::{overarching_goals, query}; +use sea_orm::DatabaseConnection; + +pub use entity_api::overarching_goal::{create, find_by_id, update, update_status}; + +pub async fn find_by( + db: &DatabaseConnection, + params: impl IntoQueryFilterMap, +) -> Result, Error> { + let overarching_goals = query::find_by::( + db, + params.into_query_filter_map(), + ) + .await?; + + Ok(overarching_goals) +} diff --git a/entity_api/src/overarching_goal.rs b/entity_api/src/overarching_goal.rs index 64d8a418..3ab7dd1d 100644 --- a/entity_api/src/overarching_goal.rs +++ b/entity_api/src/overarching_goal.rs @@ -1,6 +1,5 @@ use super::error::{EntityApiErrorKind, Error}; -use crate::uuid_parse_str; -use entity::overarching_goals::{self, ActiveModel, Entity, Model}; +use entity::overarching_goals::{ActiveModel, Entity, Model}; use entity::{status::Status, Id}; use sea_orm::ActiveValue; use sea_orm::{ @@ -9,7 +8,6 @@ use sea_orm::{ ActiveValue::{Set, Unchanged}, DatabaseConnection, TryIntoModel, }; -use std::collections::HashMap; use log::*; @@ -136,32 +134,6 @@ pub async fn find_by_id(db: &DatabaseConnection, id: Id) -> Result }) } -pub async fn find_by( - db: &DatabaseConnection, - query_params: HashMap, -) -> Result, Error> { - let mut query = Entity::find(); - - for (key, value) in query_params { - match key.as_str() { - "coaching_session_id" => { - let coaching_session_id = uuid_parse_str(&value)?; - - query = query - .filter(overarching_goals::Column::CoachingSessionId.eq(coaching_session_id)); - } - _ => { - return Err(Error { - source: None, - error_kind: EntityApiErrorKind::InvalidQueryTerm, - }); - } - } - } - - Ok(query.all(db).await?) -} - #[cfg(test)] // We need to gate seaORM's mock feature behind conditional compilation because // the feature removes the Clone trait implementation from seaORM's DatabaseConnection. @@ -170,7 +142,7 @@ pub async fn find_by( mod tests { use super::*; use entity::{overarching_goals::Model, Id}; - use sea_orm::{DatabaseBackend, MockDatabase, Transaction}; + use sea_orm::{DatabaseBackend, MockDatabase}; #[tokio::test] async fn create_returns_a_new_overarching_goal_model() -> Result<(), Error> { @@ -292,30 +264,4 @@ mod tests { Ok(()) } - - #[tokio::test] - async fn find_by_returns_all_overarching_goals_associated_with_coaching_session( - ) -> Result<(), Error> { - let db = MockDatabase::new(DatabaseBackend::Postgres).into_connection(); - let mut query_params = HashMap::new(); - let coaching_session_id = Id::new_v4(); - - query_params.insert( - "coaching_session_id".to_owned(), - coaching_session_id.to_string(), - ); - - let _ = find_by(&db, query_params).await; - - assert_eq!( - db.into_transaction_log(), - [Transaction::from_sql_and_values( - DatabaseBackend::Postgres, - r#"SELECT "overarching_goals"."id", "overarching_goals"."coaching_session_id", "overarching_goals"."user_id", "overarching_goals"."title", "overarching_goals"."body", CAST("overarching_goals"."status" AS text), "overarching_goals"."status_changed_at", "overarching_goals"."completed_at", "overarching_goals"."created_at", "overarching_goals"."updated_at" FROM "refactor_platform"."overarching_goals" WHERE "overarching_goals"."coaching_session_id" = $1"#, - [coaching_session_id.into()] - )] - ); - - Ok(()) - } } diff --git a/web/src/controller/overarching_goal_controller.rs b/web/src/controller/overarching_goal_controller.rs index f74ef7c4..a95959ed 100644 --- a/web/src/controller/overarching_goal_controller.rs +++ b/web/src/controller/overarching_goal_controller.rs @@ -2,6 +2,7 @@ use crate::controller::ApiResponse; use crate::extractors::{ authenticated_user::AuthenticatedUser, compare_api_version::CompareApiVersion, }; +use crate::params::overarching_goal::IndexParams; use crate::{AppState, Error}; use axum::extract::{Path, Query, State}; use axum::http::StatusCode; @@ -10,7 +11,6 @@ use axum::Json; use domain::overarching_goal as OverarchingGoalApi; use domain::{overarching_goals::Model, Id}; use service::config::ApiVersion; -use std::collections::HashMap; use log::*; @@ -188,7 +188,7 @@ pub async fn index( // TODO: create a new Extractor to authorize the user to access // the data requested State(app_state): State, - Query(params): Query>, + Query(params): Query, ) -> Result { debug!("GET all Overarching Goals"); debug!("Filter Params: {:?}", params); diff --git a/web/src/params/mod.rs b/web/src/params/mod.rs index c6f24d53..3b589667 100644 --- a/web/src/params/mod.rs +++ b/web/src/params/mod.rs @@ -15,3 +15,4 @@ pub(crate) mod action; pub(crate) mod agreement; pub(crate) mod coaching_session; pub(crate) mod jwt; +pub(crate) mod overarching_goal; diff --git a/web/src/params/overarching_goal.rs b/web/src/params/overarching_goal.rs new file mode 100644 index 00000000..44f34040 --- /dev/null +++ b/web/src/params/overarching_goal.rs @@ -0,0 +1,23 @@ +use domain::Id; +use sea_orm::Value; +use serde::Deserialize; +use utoipa::IntoParams; + +use domain::{IntoQueryFilterMap, QueryFilterMap}; + +#[derive(Debug, Deserialize, IntoParams)] +pub(crate) struct IndexParams { + pub(crate) coaching_session_id: Id, +} + +impl IntoQueryFilterMap for IndexParams { + fn into_query_filter_map(self) -> QueryFilterMap { + let mut query_filter_map = QueryFilterMap::new(); + query_filter_map.insert( + "coaching_session_id".to_string(), + Some(Value::Uuid(Some(Box::new(self.coaching_session_id)))), + ); + + query_filter_map + } +} From 027b32d73e494e7166ea2b03b488ed1299f1e507 Mon Sep 17 00:00:00 2001 From: Caleb Bourg Date: Tue, 25 Feb 2025 21:06:40 -0500 Subject: [PATCH 10/11] add dependency diagram to docs --- docs/architecture/crate_dependency_graph.md | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 docs/architecture/crate_dependency_graph.md diff --git a/docs/architecture/crate_dependency_graph.md b/docs/architecture/crate_dependency_graph.md new file mode 100644 index 00000000..59384b53 --- /dev/null +++ b/docs/architecture/crate_dependency_graph.md @@ -0,0 +1,11 @@ + +This diagram represents the dependency structure of the crates in this project. Each arrow indicates a dependency relationship between the crates. For example, the `web` crate depends on both the `domain` and `service` crates, while the `entity_api` crate depends on the `entity` and `service` crates. + +```mermaid +graph TD; + web-->domain; + web-->service; + domain-->entity_api; + entity_api-->entity; + entity_api-->service; +``` \ No newline at end of file From 2d0b5871ae7c8336eeaeffcc689e6c71a3782c13 Mon Sep 17 00:00:00 2001 From: Caleb Bourg Date: Tue, 25 Feb 2025 21:10:52 -0500 Subject: [PATCH 11/11] consider Jwt struct singular --- domain/src/jwt/mod.rs | 6 +++--- entity/src/jwts.rs | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/domain/src/jwt/mod.rs b/domain/src/jwt/mod.rs index 0cb6e2bd..ec89f646 100644 --- a/domain/src/jwt/mod.rs +++ b/domain/src/jwt/mod.rs @@ -24,7 +24,7 @@ //! ``` use crate::error::{DomainErrorKind, Error, InternalErrorKind}; -use crate::{coaching_session, jwts::Jwts, Id}; +use crate::{coaching_session, jwts::Jwt, Id}; use claims::TiptapCollabClaims; use jsonwebtoken::{encode, EncodingKey, Header}; use log::*; @@ -43,7 +43,7 @@ pub async fn generate_collab_token( db: &DatabaseConnection, config: &Config, coaching_session_id: Id, -) -> Result { +) -> Result { let coaching_session = coaching_session::find_by_id(db, coaching_session_id).await?; let collab_document_name = coaching_session.collab_document_name.ok_or_else(|| { @@ -101,7 +101,7 @@ pub async fn generate_collab_token( &EncodingKey::from_secret(tiptap_jwt_signing_key.as_bytes()), )?; - Ok(Jwts { + Ok(Jwt { token, sub: collab_document_name, }) diff --git a/entity/src/jwts.rs b/entity/src/jwts.rs index 1f8063b3..6ffeecf9 100644 --- a/entity/src/jwts.rs +++ b/entity/src/jwts.rs @@ -11,7 +11,7 @@ use utoipa::ToSchema; /// the subject without having to decode the JWT. #[derive(Serialize, Debug, ToSchema)] #[schema(as = jwt::Jwt)] // OpenAPI schema -pub struct Jwts { +pub struct Jwt { pub token: String, pub sub: String, }