Skip to content

Commit

Permalink
add index, read, update for agreements
Browse files Browse the repository at this point in the history
  • Loading branch information
calebbourg committed Aug 1, 2024
1 parent 93db113 commit 911ca30
Show file tree
Hide file tree
Showing 6 changed files with 310 additions and 2 deletions.
7 changes: 6 additions & 1 deletion entity/src/agreements.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,22 @@
use crate::Id;
use sea_orm::entity::prelude::*;
use serde::{Deserialize, Serialize};
use utoipa::ToSchema;

#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize, Deserialize)]
#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize, Deserialize, ToSchema)]
#[schema(as = entity::agreements::Model)]
#[sea_orm(schema_name = "refactor_platform", table_name = "agreements")]
pub struct Model {
#[serde(skip_deserializing)]
#[sea_orm(primary_key)]
pub id: Id,
#[sea_orm(unique)]
pub coaching_session_id: Id,
pub details: Option<String>,
pub user_id: Id,
#[serde(skip_deserializing)]
pub created_at: DateTimeWithTimeZone,
#[serde(skip_deserializing)]
pub updated_at: DateTimeWithTimeZone,
}

Expand Down
167 changes: 167 additions & 0 deletions entity_api/src/agreement.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
use super::error::{EntityApiErrorCode, Error};
use crate::uuid_parse_str;
use entity::agreements::{self, ActiveModel, Entity, Model};
use entity::Id;
use sea_orm::{
entity::prelude::*,
ActiveValue::{Set, Unchanged},
DatabaseConnection, TryIntoModel,
};
use std::collections::HashMap;

use log::*;

pub async fn create(db: &DatabaseConnection, agreement_model: Model) -> Result<Model, Error> {
debug!("New Agreement Model to be inserted: {:?}", agreement_model);

let now = chrono::Utc::now();

let agreement_active_model: ActiveModel = ActiveModel {
coaching_session_id: Set(agreement_model.coaching_session_id),
details: Set(agreement_model.details),
user_id: Set(agreement_model.user_id),
created_at: Set(now.into()),
updated_at: Set(now.into()),
..Default::default()
};

Ok(agreement_active_model.save(db).await?.try_into_model()?)
}

pub async fn update(db: &DatabaseConnection, id: Id, model: Model) -> Result<Model, Error> {
let result = Entity::find_by_id(id).one(db).await?;

match result {
Some(agreement) => {
debug!("Existing Agreement model to be Updated: {:?}", agreement);

let active_model: ActiveModel = ActiveModel {
id: Unchanged(agreement.id),
coaching_session_id: Unchanged(agreement.coaching_session_id),
details: Set(model.details),
user_id: Unchanged(agreement.user_id),
updated_at: Set(chrono::Utc::now().into()),
created_at: Unchanged(agreement.created_at),
};

Ok(active_model.update(db).await?.try_into_model()?)
}
None => {
debug!("Agreement with id {} not found", id);

Err(Error {
inner: None,
error_code: EntityApiErrorCode::RecordNotFound,
})
}
}
}

pub async fn find_by(
db: &DatabaseConnection,
query_params: HashMap<String, String>,
) -> Result<Vec<Model>, 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(agreements::Column::CoachingSessionId.eq(coaching_session_id));
}
_ => {
return Err(Error {
inner: None,
error_code: EntityApiErrorCode::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.
// see https://github.com/SeaQL/sea-orm/issues/830
#[cfg(feature = "mock")]
mod tests {
use super::*;
use entity::{agreements::Model, Id};
use sea_orm::{DatabaseBackend, MockDatabase, Transaction};

#[tokio::test]
async fn create_returns_a_new_agreement_model() -> Result<(), Error> {
let now = chrono::Utc::now();

let agreement_model = Model {
id: Id::new_v4(),
coaching_session_id: Id::new_v4(),
details: Some("This is a agreement".to_owned()),
user_id: Id::new_v4(),
created_at: now.into(),
updated_at: now.into(),
};

let db = MockDatabase::new(DatabaseBackend::Postgres)
.append_query_results(vec![vec![agreement_model.clone()]])
.into_connection();

let agreement = create(&db, agreement_model.clone().into()).await?;

assert_eq!(agreement.id, agreement_model.id);

Ok(())
}

#[tokio::test]
async fn update_returns_an_updated_agreement_model() -> Result<(), Error> {
let now = chrono::Utc::now();

let agreement_model = Model {
id: Id::new_v4(),
coaching_session_id: Id::new_v4(),
details: Some("This is a agreement".to_owned()),
user_id: Id::new_v4(),
created_at: now.into(),
updated_at: now.into(),
};

let db = MockDatabase::new(DatabaseBackend::Postgres)
.append_query_results(vec![vec![agreement_model.clone()], vec![agreement_model.clone()]])
.into_connection();

let agreement = update(&db, agreement_model.id, agreement_model.clone()).await?;

assert_eq!(agreement.details, agreement_model.details);

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_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 "agreements"."id", "agreements"."coaching_session_id", "agreements"."details", "agreements"."user_id", "agreements"."created_at", "agreements"."updated_at" FROM "refactor_platform"."agreements" WHERE "agreements"."coaching_session_id" = $1"#,
[coaching_session_id.into()]
)]
);

Ok(())
}
}
1 change: 1 addition & 0 deletions entity_api/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ use sea_orm::{ActiveModelTrait, DatabaseConnection, Set};

use entity::{coaching_relationships, coaching_sessions, organizations, users, Id};

pub mod agreement;
pub mod coaching_relationship;
pub mod coaching_session;
pub mod error;
Expand Down
121 changes: 121 additions & 0 deletions web/src/controller/agreement_controller.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
use crate::controller::ApiResponse;
use crate::extractors::{
authenticated_user::AuthenticatedUser, compare_api_version::CompareApiVersion,
};
use crate::{AppState, Error};
use axum::extract::{Path, Query, State};
use axum::http::StatusCode;
use axum::response::IntoResponse;
use axum::Json;
use entity::{agreements::Model, Id};
use entity_api::agreement as AgreementApi;
use service::config::ApiVersion;
use std::collections::HashMap;

use log::*;

/// POST create a new Agreement
#[utoipa::path(
post,
path = "/agreements",
params(ApiVersion),
request_body = entity::agreements::Model,
responses(
(status = 201, description = "Successfully Created a New Agreement", body = [entity::agreements::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<AppState>,
Json(agreement_model): Json<Model>,
) -> Result<impl IntoResponse, Error> {
debug!("POST Create a New Agreement from: {:?}", agreement_model);

let agreement = AgreementApi::create(app_state.db_conn_ref(), agreement_model).await?;

debug!("New Agreement: {:?}", agreement);

Ok(Json(ApiResponse::new(StatusCode::CREATED.into(), agreement)))
}

#[utoipa::path(
put,
path = "/agreements/{id}",
params(
ApiVersion,
("id" = Id, Path, description = "Id of agreement to update"),
),
request_body = entity::agreements::Model,
responses(
(status = 200, description = "Successfully Updated Agreement", body = [entity::agreements::Model]),
(status = 401, description = "Unauthorized"),
(status = 405, description = "Method not allowed")
),
security(
("cookie_auth" = [])
)
)]
pub async fn update(
CompareApiVersion(_v): CompareApiVersion,
AuthenticatedUser(_user): AuthenticatedUser,
// TODO: create a new Extractor to authorize the user to access
// the data requested
State(app_state): State<AppState>,
Path(id): Path<Id>,
Json(agreement_model): Json<Model>,
) -> Result<impl IntoResponse, Error> {
debug!("PUT Update Agreement with id: {}", id);

let agreement = AgreementApi::update(app_state.db_conn_ref(), id, agreement_model).await?;

debug!("Updated Agreement: {:?}", agreement);

Ok(Json(ApiResponse::new(StatusCode::OK.into(), agreement)))
}

#[utoipa::path(
get,
path = "/agreements",
params(
ApiVersion,
("coaching_session_id" = Option<Id>, Query, description = "Filter by coaching_session_id")
),
responses(
(status = 200, description = "Successfully retrieved all Agreements", body = [entity::agreements::Model]),
(status = 401, description = "Unauthorized"),
(status = 405, description = "Method not allowed")
),
security(
("cookie_auth" = [])
)
)]
pub async fn index(
CompareApiVersion(_v): CompareApiVersion,
AuthenticatedUser(_user): AuthenticatedUser,
// TODO: create a new Extractor to authorize the user to access
// the data requested
State(app_state): State<AppState>,
Query(params): Query<HashMap<String, String>>,
) -> Result<impl IntoResponse, Error> {
debug!("GET all Agreements");
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,
)))
}
1 change: 1 addition & 0 deletions web/src/controller/mod.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use serde::Serialize;

pub(crate) mod agreement_controller;
pub(crate) mod coaching_session_controller;
pub(crate) mod note_controller;
pub(crate) mod organization;
Expand Down
15 changes: 14 additions & 1 deletion web/src/router.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ use entity_api::user::Backend;
use tower_http::services::ServeDir;

use crate::controller::{
coaching_session_controller, note_controller, organization, organization_controller,
agreement_controller, coaching_session_controller, note_controller, organization, organization_controller,
user_session_controller,
};

Expand All @@ -26,6 +26,9 @@ use utoipa_rapidoc::RapiDoc;
title = "Refactor Platform API"
),
paths(
agreement_controller::create,
agreement_controller::update,
agreement_controller::index,
note_controller::create,
note_controller::update,
note_controller::index,
Expand Down Expand Up @@ -76,6 +79,7 @@ impl Modify for SecurityAddon {

pub fn define_routes(app_state: AppState) -> Router {
Router::new()
.merge(agreement_routes(app_state.clone()))
.merge(organization_routes(app_state.clone()))
.merge(note_routes(app_state.clone()))
.merge(organization_coaching_relationship_routes(app_state.clone()))
Expand All @@ -97,6 +101,15 @@ fn organization_coaching_relationship_routes(app_state: AppState) -> Router {
.with_state(app_state)
}

fn agreement_routes(app_state: AppState) -> Router {
Router::new()
.route("/agreements", post(agreement_controller::create))
.route("/agreements/:id", put(agreement_controller::update))
.route("/agreements", get(agreement_controller::index))
.route_layer(login_required!(Backend, login_url = "/login"))
.with_state(app_state)
}

fn note_routes(app_state: AppState) -> Router {
Router::new()
.route("/notes", post(note_controller::create))
Expand Down

0 comments on commit 911ca30

Please sign in to comment.