Skip to content

Commit 252858c

Browse files
committed
update actions and agreements
1 parent b3f4155 commit 252858c

File tree

11 files changed

+440
-21
lines changed

11 files changed

+440
-21
lines changed

docs/db/refactor_platform_rs.dbml

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,9 +44,10 @@ Table refactor_platform.coaching_sessions {
4444

4545
Table refactor_platform.overarching_goals {
4646
id uuid [primary key, unique, not null, default: `gen_random_uuid()`]
47+
user_id uuid [not null, note: 'User that created (owns) the overarching goal']
4748
coaching_session_id uuid [note: 'The coaching session that an overarching goal is associated with']
4849
title varchar [note: 'A short description of an overarching goal']
49-
details varchar [note: 'A long description of an overarching goal']
50+
body varchar [note: 'Main text of the overarching goal supporting Markdown']
5051
completed_at timestamptz [note: 'The date and time an overarching goal was completed']
5152
created_at timestamptz [not null, default: `now()`]
5253
updated_at timestamptz [not null, default: `now()`, note: 'The last date and time fields were changed']
@@ -64,7 +65,7 @@ Table refactor_platform.notes {
6465
Table refactor_platform.agreements {
6566
id uuid [primary key, unique, not null, default: `gen_random_uuid()`]
6667
coaching_session_id uuid [not null]
67-
details varchar [note: 'Either a short or long description of an agreement reached between coach and coachee in a coaching session']
68+
body varchar [note: 'Either a short or long description of an agreement reached between coach and coachee in a coaching session']
6869
user_id uuid [not null, note: 'User that created (owns) the agreement']
6970
status status [not null]
7071
status_changed_at timestamptz
@@ -78,6 +79,8 @@ Table refactor_platform.actions {
7879
// It will carry forward to every future session until
7980
// its due_by is passed or it was completed by the coachee
8081
coaching_session_id uuid [not null]
82+
body varchar [note: 'Main text of the action supporting Markdown']
83+
user_id uuid [not null, note: 'User that created (owns) the action']
8184
due_by timestamptz
8285
status status [not null]
8386
status_changed_at timestamptz

entity/src/actions.rs

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,19 +3,25 @@
33
use crate::{status, Id};
44
use sea_orm::entity::prelude::*;
55
use serde::{Deserialize, Serialize};
6+
use utoipa::ToSchema;
67

7-
#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize, Deserialize)]
8+
#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize, Deserialize, ToSchema)]
89
#[sea_orm(schema_name = "refactor_platform", table_name = "actions")]
910
pub struct Model {
11+
#[serde(skip_deserializing)]
1012
#[sea_orm(primary_key)]
1113
pub id: Id,
1214
pub coaching_session_id: Id,
15+
pub user_id: Id,
16+
pub body: Option<String>,
1317
pub due_by: Option<DateTimeWithTimeZone>,
14-
pub completed: Option<bool>,
15-
pub completed_at: Option<DateTimeWithTimeZone>,
18+
#[serde(skip_deserializing)]
1619
pub status: status::Status,
20+
#[serde(skip_deserializing)]
1721
pub status_changed_at: Option<DateTimeWithTimeZone>,
22+
#[serde(skip_deserializing)]
1823
pub created_at: DateTimeWithTimeZone,
24+
#[serde(skip_deserializing)]
1925
pub updated_at: DateTimeWithTimeZone,
2026
}
2127

@@ -29,6 +35,14 @@ pub enum Relation {
2935
on_delete = "NoAction"
3036
)]
3137
CoachingSessions,
38+
#[sea_orm(
39+
belongs_to = "super::users::Entity",
40+
from = "Column::UserId",
41+
to = "super::users::Column::Id",
42+
on_update = "NoAction",
43+
on_delete = "NoAction"
44+
)]
45+
Users,
3246
}
3347

3448
impl Related<super::coaching_sessions::Entity> for Entity {
@@ -37,4 +51,10 @@ impl Related<super::coaching_sessions::Entity> for Entity {
3751
}
3852
}
3953

54+
impl Related<super::users::Entity> for Entity {
55+
fn to() -> RelationDef {
56+
Relation::Users.def()
57+
}
58+
}
59+
4060
impl ActiveModelBehavior for ActiveModel {}

entity/src/agreements.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ pub struct Model {
1414
pub id: Id,
1515
#[sea_orm(unique)]
1616
pub coaching_session_id: Id,
17-
pub details: Option<String>,
17+
pub body: Option<String>,
1818
pub user_id: Id,
1919
#[serde(skip_deserializing)]
2020
pub status: status::Status,

entity/src/overarching_goals.rs

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,9 @@ pub struct Model {
1010
#[sea_orm(primary_key)]
1111
pub id: Id,
1212
pub coaching_session_id: Option<Id>,
13+
pub user_id: Id,
1314
pub title: Option<String>,
14-
pub details: Option<String>,
15+
pub body: Option<String>,
1516
pub completed_at: Option<DateTimeWithTimeZone>,
1617
pub created_at: DateTimeWithTimeZone,
1718
pub updated_at: DateTimeWithTimeZone,
@@ -27,6 +28,14 @@ pub enum Relation {
2728
on_delete = "NoAction"
2829
)]
2930
CoachingSessions,
31+
#[sea_orm(
32+
belongs_to = "super::users::Entity",
33+
from = "Column::UserId",
34+
to = "super::users::Column::Id",
35+
on_update = "NoAction",
36+
on_delete = "NoAction"
37+
)]
38+
Users,
3039
}
3140

3241
impl Related<super::coaching_sessions::Entity> for Entity {
@@ -35,4 +44,10 @@ impl Related<super::coaching_sessions::Entity> for Entity {
3544
}
3645
}
3746

47+
impl Related<super::users::Entity> for Entity {
48+
fn to() -> RelationDef {
49+
Relation::Users.def()
50+
}
51+
}
52+
3853
impl ActiveModelBehavior for ActiveModel {}

entity_api/src/action.rs

Lines changed: 202 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,202 @@
1+
use super::error::{EntityApiErrorCode, Error};
2+
use crate::uuid_parse_str;
3+
use entity::actions::{self, ActiveModel, Entity, Model};
4+
use entity::Id;
5+
use sea_orm::{
6+
entity::prelude::*,
7+
ActiveValue::{Set, Unchanged},
8+
DatabaseConnection, TryIntoModel,
9+
};
10+
use std::collections::HashMap;
11+
12+
use log::*;
13+
14+
pub async fn create(db: &DatabaseConnection, action_model: Model) -> Result<Model, Error> {
15+
debug!("New Action Model to be inserted: {:?}", action_model);
16+
17+
let now = chrono::Utc::now();
18+
19+
let action_active_model: ActiveModel = ActiveModel {
20+
coaching_session_id: Set(action_model.coaching_session_id),
21+
user_id: Set(action_model.user_id),
22+
due_by: Set(action_model.due_by),
23+
body: Set(action_model.body),
24+
created_at: Set(now.into()),
25+
updated_at: Set(now.into()),
26+
..Default::default()
27+
};
28+
29+
Ok(action_active_model.save(db).await?.try_into_model()?)
30+
}
31+
32+
pub async fn update(db: &DatabaseConnection, id: Id, model: Model) -> Result<Model, Error> {
33+
let result = Entity::find_by_id(id).one(db).await?;
34+
35+
match result {
36+
Some(action) => {
37+
debug!("Existing Action model to be Updated: {:?}", action);
38+
39+
let active_model: ActiveModel = ActiveModel {
40+
id: Unchanged(model.id),
41+
coaching_session_id: Unchanged(model.coaching_session_id),
42+
user_id: Unchanged(model.user_id),
43+
body: Set(model.body),
44+
due_by: Set(model.due_by),
45+
status: Set(model.status),
46+
status_changed_at: Set(model.status_changed_at),
47+
updated_at: Set(chrono::Utc::now().into()),
48+
created_at: Unchanged(model.created_at),
49+
};
50+
51+
Ok(active_model.update(db).await?.try_into_model()?)
52+
}
53+
None => {
54+
error!("Action with id {} not found", id);
55+
56+
Err(Error {
57+
inner: None,
58+
error_code: EntityApiErrorCode::RecordNotFound,
59+
})
60+
}
61+
}
62+
}
63+
64+
pub async fn find_by_id(db: &DatabaseConnection, id: Id) -> Result<Option<Model>, Error> {
65+
match Entity::find_by_id(id).one(db).await {
66+
Ok(Some(action)) => {
67+
debug!("Action found: {:?}", action);
68+
69+
Ok(Some(action))
70+
}
71+
Ok(None) => {
72+
error!("Action with id {} not found", id);
73+
74+
Err(Error {
75+
inner: None,
76+
error_code: EntityApiErrorCode::RecordNotFound,
77+
})
78+
}
79+
Err(err) => {
80+
error!("Action with id {} not found and returned error {}", id, err);
81+
Err(Error {
82+
inner: None,
83+
error_code: EntityApiErrorCode::RecordNotFound,
84+
})
85+
}
86+
}
87+
}
88+
89+
pub async fn find_by(
90+
db: &DatabaseConnection,
91+
query_params: HashMap<String, String>,
92+
) -> Result<Vec<Model>, Error> {
93+
let mut query = Entity::find();
94+
95+
for (key, value) in query_params {
96+
match key.as_str() {
97+
"coaching_session_id" => {
98+
let coaching_session_id = uuid_parse_str(&value)?;
99+
100+
query = query.filter(actions::Column::CoachingSessionId.eq(coaching_session_id));
101+
}
102+
_ => {
103+
return Err(Error {
104+
inner: None,
105+
error_code: EntityApiErrorCode::InvalidQueryTerm,
106+
});
107+
}
108+
}
109+
}
110+
111+
Ok(query.all(db).await?)
112+
}
113+
114+
#[cfg(test)]
115+
// We need to gate seaORM's mock feature behind conditional compilation because
116+
// the feature removes the Clone trait implementation from seaORM's DatabaseConnection.
117+
// see https://github.com/SeaQL/sea-orm/issues/830
118+
#[cfg(feature = "mock")]
119+
mod tests {
120+
use super::*;
121+
use entity::{actions::Model, Id};
122+
use sea_orm::{DatabaseBackend, MockDatabase, Transaction};
123+
124+
#[tokio::test]
125+
async fn create_returns_a_new_action_model() -> Result<(), Error> {
126+
let now = chrono::Utc::now();
127+
128+
let action_model = Model {
129+
id: Id::new_v4(),
130+
coaching_session_id: Id::new_v4(),
131+
body: Some("This is a action".to_owned()),
132+
due_by: Some(now.into()),
133+
user_id: Id::new_v4(),
134+
status_changed_at: None,
135+
status: Default::default(),
136+
created_at: now.into(),
137+
updated_at: now.into(),
138+
};
139+
140+
let db = MockDatabase::new(DatabaseBackend::Postgres)
141+
.append_query_results(vec![vec![action_model.clone()]])
142+
.into_connection();
143+
144+
let action = create(&db, action_model.clone().into()).await?;
145+
146+
assert_eq!(action.id, action_model.id);
147+
148+
Ok(())
149+
}
150+
151+
#[tokio::test]
152+
async fn update_returns_an_updated_action_model() -> Result<(), Error> {
153+
let now = chrono::Utc::now();
154+
155+
let action_model = Model {
156+
id: Id::new_v4(),
157+
coaching_session_id: Id::new_v4(),
158+
due_by: Some(now.into()),
159+
body: Some("This is a action".to_owned()),
160+
user_id: Id::new_v4(),
161+
status_changed_at: None,
162+
status: Default::default(),
163+
created_at: now.into(),
164+
updated_at: now.into(),
165+
};
166+
167+
let db = MockDatabase::new(DatabaseBackend::Postgres)
168+
.append_query_results(vec![vec![action_model.clone()], vec![action_model.clone()]])
169+
.into_connection();
170+
171+
let action = update(&db, action_model.id, action_model.clone()).await?;
172+
173+
assert_eq!(action.body, action_model.body);
174+
175+
Ok(())
176+
}
177+
178+
#[tokio::test]
179+
async fn find_by_returns_all_actions_associated_with_coaching_session() -> Result<(), Error> {
180+
let db = MockDatabase::new(DatabaseBackend::Postgres).into_connection();
181+
let mut query_params = HashMap::new();
182+
let coaching_session_id = Id::new_v4();
183+
184+
query_params.insert(
185+
"coaching_session_id".to_owned(),
186+
coaching_session_id.to_string(),
187+
);
188+
189+
let _ = find_by(&db, query_params).await;
190+
191+
assert_eq!(
192+
db.into_transaction_log(),
193+
[Transaction::from_sql_and_values(
194+
DatabaseBackend::Postgres,
195+
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"#,
196+
[coaching_session_id.into()]
197+
)]
198+
);
199+
200+
Ok(())
201+
}
202+
}

0 commit comments

Comments
 (0)