Skip to content

Commit e7a45c6

Browse files
committed
refactor coaching session code
1 parent 0451ab5 commit e7a45c6

File tree

2 files changed

+126
-99
lines changed

2 files changed

+126
-99
lines changed

domain/src/coaching_session.rs

Lines changed: 46 additions & 98 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,38 @@
11
use crate::coaching_sessions::Model;
2-
use crate::error::{DomainErrorKind, Error, ExternalErrorKind, InternalErrorKind};
3-
use crate::gateway::tiptap::client as tiptap_client;
2+
use crate::error::{DomainErrorKind, Error, InternalErrorKind};
3+
use crate::gateway::tiptap::TiptapDocument;
44
use crate::Id;
5-
use chrono::{DurationRound, TimeDelta};
5+
use chrono::{DurationRound, NaiveDateTime, TimeDelta};
66
use entity_api::{
77
coaching_relationship, coaching_session, coaching_sessions, mutate, organization, query,
88
query::IntoQueryFilterMap,
99
};
1010
use log::*;
1111
use sea_orm::{DatabaseConnection, IntoActiveModel};
12-
use serde_json::json;
1312
use service::config::Config;
1413

1514
pub use entity_api::coaching_session::{find_by_id, find_by_id_with_coaching_relationship};
1615

16+
#[derive(Debug, Clone)]
17+
struct SessionDate(NaiveDateTime);
18+
19+
impl SessionDate {
20+
fn new(date: NaiveDateTime) -> Result<Self, Error> {
21+
let truncated = date.duration_trunc(TimeDelta::minutes(1)).map_err(|err| {
22+
warn!("Failed to truncate date_time: {:?}", err);
23+
Error {
24+
source: Some(Box::new(err)),
25+
error_kind: DomainErrorKind::Internal(InternalErrorKind::Other),
26+
}
27+
})?;
28+
Ok(Self(truncated))
29+
}
30+
31+
fn into_inner(self) -> NaiveDateTime {
32+
self.0
33+
}
34+
}
35+
1736
pub async fn create(
1837
db: &DatabaseConnection,
1938
config: &Config,
@@ -23,68 +42,20 @@ pub async fn create(
2342
coaching_relationship::find_by_id(db, coaching_session_model.coaching_relationship_id)
2443
.await?;
2544
let organization = organization::find_by_id(db, coaching_relationship.organization_id).await?;
26-
// Remove seconds because all coaching_sessions will be scheduled by the minute
27-
// TODO: we might consider codifying this in the type system at some point.
28-
let date_time = coaching_session_model
29-
.date
30-
.duration_trunc(TimeDelta::minutes(1))
31-
.map_err(|err| {
32-
warn!("Failed to truncate date_time: {:?}", err);
33-
Error {
34-
source: Some(Box::new(err)),
35-
error_kind: DomainErrorKind::Internal(InternalErrorKind::Other),
36-
}
37-
})?;
38-
coaching_session_model.date = date_time;
39-
let document_name = format!(
40-
"{}.{}.{}-v0",
41-
organization.slug,
42-
coaching_relationship.slug,
43-
Id::new_v4()
44-
);
45+
46+
coaching_session_model.date = SessionDate::new(coaching_session_model.date)?.into_inner();
47+
48+
let document_name = generate_document_name(&organization.slug, &coaching_relationship.slug);
4549
info!(
4650
"Attempting to create Tiptap document with name: {}",
4751
document_name
4852
);
4953
coaching_session_model.collab_document_name = Some(document_name.clone());
50-
let tiptap_url = config.tiptap_url().ok_or_else(|| {
51-
warn!("Failed to get Tiptap URL from config");
52-
Error {
53-
source: None,
54-
error_kind: DomainErrorKind::Internal(InternalErrorKind::Other),
55-
}
56-
})?;
57-
let full_url = format!("{}/api/documents/{}?format=json", tiptap_url, document_name);
58-
let client = tiptap_client(config).await?;
59-
60-
let request = client
61-
.post(full_url)
62-
.json(&json!({"type": "doc", "content": []}));
63-
let response = match request.send().await {
64-
Ok(response) => {
65-
info!("Tiptap response: {:?}", response);
66-
response
67-
}
68-
Err(e) => {
69-
warn!("Failed to send request: {:?}", e);
70-
return Err(e.into());
71-
}
72-
};
73-
74-
// Tiptap's API will return a 200 for successful creation of a new document
75-
// and will return a 409 if the document already exists. We consider both "successful".
76-
if response.status().is_success() || response.status().as_u16() == 409 {
77-
Ok(coaching_session::create(db, coaching_session_model).await?)
78-
} else {
79-
warn!(
80-
"Failed to create Tiptap document: {}",
81-
response.text().await?
82-
);
83-
Err(Error {
84-
source: None,
85-
error_kind: DomainErrorKind::External(ExternalErrorKind::Network),
86-
})
87-
}
54+
55+
let tiptap = TiptapDocument::new(config).await?;
56+
tiptap.create(&document_name).await?;
57+
58+
Ok(coaching_session::create(db, coaching_session_model).await?)
8859
}
8960

9061
pub async fn find_by(
@@ -127,41 +98,18 @@ pub async fn delete(db: &DatabaseConnection, config: &Config, id: Id) -> Result<
12798
}
12899
})?;
129100

130-
let tiptap_url = config.tiptap_url().ok_or_else(|| {
131-
warn!("Failed to get Tiptap URL from config");
132-
Error {
133-
source: None,
134-
error_kind: DomainErrorKind::Internal(InternalErrorKind::Other),
135-
}
136-
})?;
137-
let full_url = format!("{}/api/documents/{}?format=json", tiptap_url, document_name);
138-
let client = tiptap_client(config).await?;
139-
140-
let request = client.delete(full_url);
141-
let response = match request.send().await {
142-
Ok(response) => {
143-
info!("Tiptap response: {:?}", response);
144-
response
145-
}
146-
Err(e) => {
147-
warn!("Failed to send request: {:?}", e);
148-
return Err(e.into());
149-
}
150-
};
151-
152-
// Tiptap's API will return a 204 for successful deletion of a document
153-
// and will return a 404 if the document does not exist.
154-
let status = response.status();
155-
if status.is_success() {
156-
Ok(coaching_session::delete(db, id).await?)
157-
} else {
158-
warn!(
159-
"Failed to delete Tiptap document: {}, with status: {}",
160-
document_name, status
161-
);
162-
Err(Error {
163-
source: None,
164-
error_kind: DomainErrorKind::External(ExternalErrorKind::Network),
165-
})
166-
}
101+
let tiptap = TiptapDocument::new(config).await?;
102+
tiptap.delete(&document_name).await?;
103+
104+
coaching_session::delete(db, id).await?;
105+
Ok(())
106+
}
107+
108+
fn generate_document_name(organization_slug: &str, relationship_slug: &str) -> String {
109+
format!(
110+
"{}.{}.{}-v0",
111+
organization_slug,
112+
relationship_slug,
113+
Id::new_v4()
114+
)
167115
}

domain/src/gateway/tiptap.rs

Lines changed: 80 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
1-
use crate::error::{DomainErrorKind, Error, InternalErrorKind};
1+
use crate::error::{DomainErrorKind, Error, ExternalErrorKind, InternalErrorKind};
22
use log::*;
3+
use serde_json::json;
34
use service::config::Config;
45

56
/// HTTP client for making requests to Tiptap. This client is configured with the necessary
@@ -33,3 +34,81 @@ async fn build_auth_headers(config: &Config) -> Result<reqwest::header::HeaderMa
3334
headers.insert(reqwest::header::AUTHORIZATION, auth_value);
3435
Ok(headers)
3536
}
37+
38+
pub struct TiptapDocument {
39+
client: reqwest::Client,
40+
base_url: String,
41+
}
42+
43+
impl TiptapDocument {
44+
pub async fn new(config: &Config) -> Result<Self, Error> {
45+
let client = client(config).await?;
46+
let base_url = config.tiptap_url().ok_or_else(|| {
47+
warn!("Failed to get Tiptap URL from config");
48+
Error {
49+
source: None,
50+
error_kind: DomainErrorKind::Internal(InternalErrorKind::Other),
51+
}
52+
})?;
53+
Ok(Self { client, base_url })
54+
}
55+
56+
pub async fn create(&self, document_name: &str) -> Result<(), Error> {
57+
let url = format!(
58+
"{}/api/documents/{}?format=json",
59+
self.base_url, document_name
60+
);
61+
let response = self
62+
.client
63+
.post(url)
64+
.json(&json!({"type": "doc", "content": []}))
65+
.send()
66+
.await
67+
.map_err(|e| {
68+
warn!("Failed to send request: {:?}", e);
69+
Error {
70+
source: Some(Box::new(e)),
71+
error_kind: DomainErrorKind::External(ExternalErrorKind::Network),
72+
}
73+
})?;
74+
75+
if response.status().is_success() || response.status().as_u16() == 409 {
76+
Ok(())
77+
} else {
78+
let error_text = response.text().await.unwrap_or_default();
79+
warn!("Failed to create Tiptap document: {}", error_text);
80+
Err(Error {
81+
source: None,
82+
error_kind: DomainErrorKind::External(ExternalErrorKind::Network),
83+
})
84+
}
85+
}
86+
87+
pub async fn delete(&self, document_name: &str) -> Result<(), Error> {
88+
let url = format!(
89+
"{}/api/documents/{}?format=json",
90+
self.base_url, document_name
91+
);
92+
let response = self.client.delete(url).send().await.map_err(|e| {
93+
warn!("Failed to send request: {:?}", e);
94+
Error {
95+
source: Some(Box::new(e)),
96+
error_kind: DomainErrorKind::External(ExternalErrorKind::Network),
97+
}
98+
})?;
99+
100+
let status = response.status();
101+
if status.is_success() || status.as_u16() == 404 {
102+
Ok(())
103+
} else {
104+
warn!(
105+
"Failed to delete Tiptap document: {}, with status: {}",
106+
document_name, status
107+
);
108+
Err(Error {
109+
source: None,
110+
error_kind: DomainErrorKind::External(ExternalErrorKind::Network),
111+
})
112+
}
113+
}
114+
}

0 commit comments

Comments
 (0)