diff --git a/jirs-client/src/modal/add_issue.rs b/jirs-client/src/modal/add_issue.rs index 555ea5fc..8f194680 100644 --- a/jirs-client/src/modal/add_issue.rs +++ b/jirs-client/src/modal/add_issue.rs @@ -34,6 +34,9 @@ pub fn update(msg: &Msg, model: &mut crate::model::Model, orders: &mut impl Orde match msg { Msg::AddIssue => { + let user_id = model.user.as_ref().map(|u| u.id).unwrap_or_default(); + let project_id = model.project.as_ref().map(|p| p.id).unwrap_or_default(); + let payload = jirs_data::CreateIssuePayload { title: modal.title.clone(), issue_type: modal.issue_type.clone(), @@ -44,8 +47,9 @@ pub fn update(msg: &Msg, model: &mut crate::model::Model, orders: &mut impl Orde estimate: modal.estimate.clone(), time_spent: modal.time_spent.clone(), time_remaining: modal.time_remaining.clone(), - project_id: modal.project_id.clone(), + project_id: modal.project_id.unwrap_or(project_id), user_ids: modal.user_ids.clone(), + reporter_id: modal.reporter_id.unwrap_or_else(|| user_id), }; send_ws_msg(jirs_data::WsMsg::IssueCreateRequest(payload)); } diff --git a/jirs-client/src/modal/mod.rs b/jirs-client/src/modal/mod.rs index 4d24d3e0..633926f8 100644 --- a/jirs-client/src/modal/mod.rs +++ b/jirs-client/src/modal/mod.rs @@ -52,7 +52,7 @@ pub fn update(msg: &Msg, model: &mut model::Model, orders: &mut impl Orders } Msg::ChangePage(Page::AddIssue) => { let mut modal = AddIssueModal::default(); - modal.project_id = model.project.as_ref().map(|p| p.id).unwrap_or_default(); + modal.project_id = model.project.as_ref().map(|p| p.id); model.modals.push(ModalType::AddIssue(modal)); } diff --git a/jirs-client/src/model.rs b/jirs-client/src/model.rs index 52dbd7dd..201a76b5 100644 --- a/jirs-client/src/model.rs +++ b/jirs-client/src/model.rs @@ -37,7 +37,7 @@ pub struct AddIssueModal { pub estimate: Option, pub time_spent: Option, pub time_remaining: Option, - pub project_id: i32, + pub project_id: Option, pub user_ids: Vec, pub reporter_id: Option, diff --git a/jirs-data/src/lib.rs b/jirs-data/src/lib.rs index dd62d80d..e0f79b8c 100644 --- a/jirs-data/src/lib.rs +++ b/jirs-data/src/lib.rs @@ -193,13 +193,13 @@ impl Into for u32 { } } -#[derive(Clone, Serialize, Debug)] +#[derive(Clone, Serialize, Debug, PartialEq)] #[serde(rename_all = "camelCase")] pub struct ErrorResponse { pub errors: Vec, } -#[derive(Clone, Serialize, Deserialize, Debug)] +#[derive(Clone, Serialize, Deserialize, Debug, PartialEq)] #[serde(rename_all = "camelCase")] pub struct FullProject { pub id: i32, @@ -214,7 +214,7 @@ pub struct FullProject { pub users: Vec, } -#[derive(Clone, Serialize, Deserialize, Debug)] +#[derive(Clone, Serialize, Deserialize, Debug, PartialEq)] #[serde(rename_all = "camelCase")] pub struct FullIssue { pub id: i32, @@ -261,7 +261,7 @@ impl Into for FullIssue { } } -#[derive(Clone, Serialize, Deserialize, Debug)] +#[derive(Clone, Serialize, Deserialize, Debug, PartialEq)] #[serde(rename_all = "camelCase")] pub struct Project { pub id: i32, @@ -273,7 +273,7 @@ pub struct Project { pub updated_at: NaiveDateTime, } -#[derive(Clone, Serialize, Deserialize, Debug)] +#[derive(Clone, Serialize, Deserialize, Debug, PartialEq)] #[serde(rename_all = "camelCase")] pub struct Issue { pub id: i32, @@ -296,7 +296,7 @@ pub struct Issue { pub user_ids: Vec, } -#[derive(Clone, Debug, Serialize, Deserialize)] +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] #[serde(rename_all = "camelCase")] pub struct Comment { pub id: i32, @@ -321,7 +321,7 @@ pub struct User { pub updated_at: NaiveDateTime, } -#[derive(Clone, Debug, Serialize, Deserialize)] +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] #[serde(rename_all = "camelCase")] pub struct Token { pub id: i32, @@ -332,7 +332,7 @@ pub struct Token { pub updated_at: NaiveDateTime, } -#[derive(Clone, Debug, Serialize, Deserialize)] +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] #[serde(rename_all = "camelCase")] pub struct UpdateIssuePayload { pub title: Option, @@ -350,7 +350,7 @@ pub struct UpdateIssuePayload { pub user_ids: Option>, } -#[derive(Clone, Debug, Serialize, Deserialize)] +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] #[serde(rename_all = "camelCase")] pub struct CreateCommentPayload { pub user_id: Option, @@ -358,13 +358,13 @@ pub struct CreateCommentPayload { pub body: String, } -#[derive(Clone, Debug, Serialize, Deserialize)] +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] #[serde(rename_all = "camelCase")] pub struct UpdateCommentPayload { pub body: String, } -#[derive(Serialize, Deserialize, Debug, Clone)] +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)] #[serde(rename_all = "camelCase")] pub struct CreateIssuePayload { pub title: String, @@ -379,9 +379,10 @@ pub struct CreateIssuePayload { pub time_remaining: Option, pub project_id: i32, pub user_ids: Vec, + pub reporter_id: i32, } -#[derive(Serialize, Deserialize, Debug)] +#[derive(Serialize, Deserialize, Debug, PartialEq)] #[serde(rename_all = "camelCase")] pub struct UpdateProjectPayload { pub name: Option, @@ -390,7 +391,7 @@ pub struct UpdateProjectPayload { pub category: Option, } -#[derive(Serialize, Deserialize, Debug, Clone)] +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)] pub enum WsMsg { Ping, Pong, diff --git a/jirs-server/src/db/issues.rs b/jirs-server/src/db/issues.rs index 510cbade..4c24da26 100644 --- a/jirs-server/src/db/issues.rs +++ b/jirs-server/src/db/issues.rs @@ -255,7 +255,10 @@ impl Handler for DbExecutor { .values(form) .on_conflict_do_nothing() .get_result::(conn) - .map_err(|_| ServiceErrors::DatabaseConnectionLost)?; + .map_err(|e| { + error!("{}", e); + ServiceErrors::DatabaseConnectionLost + })?; let mut values = vec![]; for user_id in msg.user_ids.iter() { diff --git a/jirs-server/src/ws/mod.rs b/jirs-server/src/ws/mod.rs index a295ee2b..609eaa58 100644 --- a/jirs-server/src/ws/mod.rs +++ b/jirs-server/src/ws/mod.rs @@ -3,7 +3,7 @@ use actix_web::web::Data; use actix_web::{get, web, Error, HttpRequest, HttpResponse}; use actix_web_actors::ws; -use jirs_data::{Project, WsMsg}; +use jirs_data::WsMsg; use crate::db::authorize_user::AuthorizeUser; use crate::db::issues::{LoadProjectIssues, UpdateIssue}; @@ -11,6 +11,8 @@ use crate::db::projects::LoadCurrentProject; use crate::db::users::LoadProjectUsers; use crate::db::DbExecutor; +type WsResult = std::result::Result, WsMsg>; + trait WsMessageSender { fn send_msg(&mut self, msg: jirs_data::WsMsg); } @@ -31,91 +33,97 @@ impl WsMessageSender for ws::WebsocketContext { } impl WebSocketActor { - fn handle_msg(&mut self, msg: WsMsg) -> Option { + fn handle_ws_msg(&mut self, msg: WsMsg) -> WsResult { + // use futures::executor::LocalPool; use futures::executor::block_on; + // let mut pool = LocalPool::new(); + // pool.run_until(); - match msg { + if msg != WsMsg::Ping && msg != WsMsg::Pong { + info!("(2)incoming message: {:?}", msg); + } + + let msg = match msg { WsMsg::Ping => Some(WsMsg::Pong), WsMsg::Pong => Some(WsMsg::Ping), - WsMsg::ProjectRequest => match block_on(self.load_project()) { - Some(p) => Some(WsMsg::ProjectLoaded(p)), - _ => { - error!("Failed to load project"); - None - } - }, - WsMsg::AuthorizeRequest(uuid) => match block_on(self.authorize(uuid)) { - Some(user) => { - self.current_user = Some(user.clone()); - Some(WsMsg::AuthorizeLoaded(Ok(user))) - } - _ => Some(WsMsg::AuthorizeLoaded( - Err("Invalid auth token".to_string()), - )), - }, - WsMsg::ProjectIssuesRequest => block_on(self.load_issues()), - WsMsg::ProjectUsersRequest => block_on(self.load_project_users()), - WsMsg::IssueUpdateRequest(id, payload) => block_on(self.update_issue(id, payload)), + WsMsg::IssueUpdateRequest(id, payload) => block_on(self.update_issue(id, payload))?, + WsMsg::IssueCreateRequest(payload) => block_on(self.add_issue(payload))?, + WsMsg::IssueDeleteRequest(id) => block_on(self.delete_issue(id))?, + WsMsg::ProjectRequest => block_on(self.load_project())?, + WsMsg::AuthorizeRequest(uuid) => block_on(self.authorize(uuid))?, + WsMsg::ProjectIssuesRequest => block_on(self.load_issues())?, + WsMsg::ProjectUsersRequest => block_on(self.load_project_users())?, _ => { error!("Failed to resolve message"); None } - } + }; + Ok(msg) } - async fn authorize(&mut self, token: uuid::Uuid) -> Option { - match self + async fn authorize(&mut self, token: uuid::Uuid) -> WsResult { + let m = match self .db .send(AuthorizeUser { access_token: token, }) .await { - Ok(Ok(u)) => Some(u.into()), - _ => None, - } - } - - async fn load_project(&mut self) -> Option { - let project_id = self.current_user.as_ref().map(|u| u.project_id)?; - match self.db.send(LoadCurrentProject { project_id }).await { - Ok(Ok(p)) => Some(p.into()), - Ok(e) => { - error!("{:?}", e); - None + Ok(Ok(u)) => { + let user: jirs_data::User = u.into(); + self.current_user = Some(user.clone()); + Some(WsMsg::AuthorizeLoaded(Ok(user))) } - Err(e) => { - error!("{:?}", e); - None - } - } - } - - async fn load_issues(&mut self) -> Option { - let project_id = self.current_user.as_ref().map(|u| u.project_id)?; - match self.db.send(LoadProjectIssues { project_id }).await { - Ok(Ok(v)) => Some(WsMsg::ProjectIssuesLoaded( - v.into_iter().map(|i| i.into()).collect(), + Ok(Err(_)) => Some(WsMsg::AuthorizeLoaded( + Err("Invalid auth token".to_string()), )), - _ => None, + _ => Some(WsMsg::AuthorizeExpired), + }; + Ok(m) + } + + fn current_user(&mut self) -> Result<&jirs_data::User, WsMsg> { + self.current_user + .as_ref() + .map(|u| u) + .ok_or_else(|| WsMsg::AuthorizeExpired) + } + + async fn load_project(&mut self) -> WsResult { + let project_id = self.current_user().map(|u| u.project_id)?; + match self.db.send(LoadCurrentProject { project_id }).await { + Ok(Ok(p)) => Ok(Some(WsMsg::ProjectLoaded(p.into()))), + _ => Ok(None), } } - async fn load_project_users(&mut self) -> Option { - let project_id = self.current_user.as_ref().map(|u| u.project_id)?; - match self.db.send(LoadProjectUsers { project_id }).await { + async fn load_issues(&mut self) -> WsResult { + let project_id = self.current_user().map(|u| u.project_id)?; + match self.db.send(LoadProjectIssues { project_id }).await { + Ok(Ok(v)) => Ok(Some(WsMsg::ProjectIssuesLoaded( + v.into_iter().map(|i| i.into()).collect(), + ))), + _ => Ok(None), + } + } + + async fn load_project_users(&mut self) -> WsResult { + let project_id = self.current_user().map(|u| u.project_id)?; + let m = match self.db.send(LoadProjectUsers { project_id }).await { Ok(Ok(v)) => Some(WsMsg::ProjectUsersLoaded( v.into_iter().map(|i| i.into()).collect(), )), _ => None, - } + }; + Ok(m) } async fn update_issue( &mut self, issue_id: i32, payload: jirs_data::UpdateIssuePayload, - ) -> Option { + ) -> WsResult { + self.current_user()?; let jirs_data::UpdateIssuePayload { title, issue_type, @@ -130,7 +138,7 @@ impl WebSocketActor { project_id, user_ids, } = payload; - match self + let m = match self .db .send(UpdateIssue { issue_id, @@ -151,7 +159,44 @@ impl WebSocketActor { { Ok(Ok(issue)) => Some(WsMsg::IssueUpdated(issue.into())), _ => None, - } + }; + Ok(m) + } + + async fn add_issue(&mut self, payload: jirs_data::CreateIssuePayload) -> WsResult { + self.current_user()?; + let msg = crate::db::issues::CreateIssue { + title: payload.title, + issue_type: payload.issue_type, + status: payload.status, + priority: payload.priority, + description: payload.description, + description_text: payload.description_text, + estimate: payload.estimate, + time_spent: payload.time_spent, + time_remaining: payload.time_remaining, + project_id: payload.project_id, + reporter_id: payload.reporter_id, + user_ids: payload.user_ids, + }; + let m = match self.db.send(msg).await { + Ok(Ok(issue)) => Some(WsMsg::IssueCreated(issue.into())), + _ => None, + }; + Ok(m) + } + + async fn delete_issue(&mut self, id: i32) -> WsResult { + self.current_user()?; + let m = match self + .db + .send(crate::db::issues::DeleteIssue { issue_id: id }) + .await + { + Ok(Ok(_)) => Some(WsMsg::IssueDeleted(id)), + _ => None, + }; + Ok(m) } } @@ -165,12 +210,17 @@ impl StreamHandler> for WebSocketActor { let ws_msg: bincode::Result = bincode::deserialize(bin.to_vec().as_slice()); let msg = match ws_msg { - Ok(msg) => msg, + Ok(m) => m, _ => return, }; - match self.handle_msg(msg) { - Some(msg) => ctx.send_msg(msg), - _ => return, + if msg != WsMsg::Ping && msg != WsMsg::Pong { + info!("(1)incoming message: {:?}", msg); + } + let _x = 1; + match self.handle_ws_msg(msg) { + Ok(Some(msg)) => ctx.send_msg(msg), + Err(e) => ctx.send_msg(e), + _ => (), }; } _ => (),