From 1663d9a485ce68d956db4ab61150600f7fa261f8 Mon Sep 17 00:00:00 2001 From: Adrian Wozniak Date: Sat, 11 Apr 2020 11:18:41 +0200 Subject: [PATCH] Load issue comments and struct cleanup --- jirs-client/src/modal/mod.rs | 4 +- jirs-client/src/model.rs | 2 + jirs-client/src/ws/mod.rs | 14 +- jirs-data/src/lib.rs | 115 +++++----------- jirs-server/seed.sql | 51 +++++++ jirs-server/src/main.rs | 13 -- jirs-server/src/models.rs | 27 ---- jirs-server/src/routes/issues.rs | 190 -------------------------- jirs-server/src/routes/mod.rs | 2 - jirs-server/src/routes/projects.rs | 100 -------------- jirs-server/src/ws/comments.rs | 22 +++ jirs-server/src/ws/issues.rs | 136 +++++++++++++++++++ jirs-server/src/ws/mod.rs | 210 +++++++---------------------- jirs-server/src/ws/projects.rs | 23 ++++ jirs-server/src/ws/users.rs | 22 +++ 15 files changed, 353 insertions(+), 578 deletions(-) delete mode 100644 jirs-server/src/routes/issues.rs delete mode 100644 jirs-server/src/routes/projects.rs create mode 100644 jirs-server/src/ws/comments.rs create mode 100644 jirs-server/src/ws/issues.rs create mode 100644 jirs-server/src/ws/projects.rs create mode 100644 jirs-server/src/ws/users.rs diff --git a/jirs-client/src/modal/mod.rs b/jirs-client/src/modal/mod.rs index 32045036..63c7a5a4 100644 --- a/jirs-client/src/modal/mod.rs +++ b/jirs-client/src/modal/mod.rs @@ -1,7 +1,8 @@ use seed::{prelude::*, *}; -use jirs_data::UpdateIssuePayload; +use jirs_data::{UpdateIssuePayload, WsMsg}; +use crate::api::send_ws_msg; use crate::model::{AddIssueModal, EditIssueModal, ModalType, Model, Page}; use crate::shared::styled_editor::Mode; use crate::shared::styled_modal::{StyledModal, Variant as ModalVariant}; @@ -44,6 +45,7 @@ pub fn update(msg: &Msg, model: &mut model::Model, orders: &mut impl Orders Msg::ChangePage(Page::EditIssue(issue_id)) => { push_edit_modal(issue_id, model); + send_ws_msg(WsMsg::IssueCommentsRequest(issue_id.clone())); } Msg::ChangePage(Page::AddIssue) => { diff --git a/jirs-client/src/model.rs b/jirs-client/src/model.rs index 7ebaae1d..06aaadd0 100644 --- a/jirs-client/src/model.rs +++ b/jirs-client/src/model.rs @@ -152,6 +152,7 @@ pub struct Model { pub user: Option, pub issues: Vec, pub users: Vec, + pub comments: Vec, } impl Default for Model { @@ -180,6 +181,7 @@ impl Default for Model { }, modals: vec![], project: None, + comments: vec![], } } } diff --git a/jirs-client/src/ws/mod.rs b/jirs-client/src/ws/mod.rs index 46190b75..9083ed28 100644 --- a/jirs-client/src/ws/mod.rs +++ b/jirs-client/src/ws/mod.rs @@ -20,20 +20,28 @@ pub fn handle(msg: WsMsg) { pub fn update(msg: &Msg, model: &mut model::Model, orders: &mut impl Orders) { match msg { - Msg::WsMsg(WsMsg::ProjectLoaded(project)) => { - model.project = Some(project.clone()); - } + // auth Msg::WsMsg(WsMsg::AuthorizeLoaded(Ok(user))) => { model.user = Some(user.clone()); } + // project + Msg::WsMsg(WsMsg::ProjectLoaded(project)) => { + model.project = Some(project.clone()); + } + // issues Msg::WsMsg(WsMsg::ProjectIssuesLoaded(v)) => { let mut v = v.clone(); v.sort_by(|a, b| (a.list_position as i64).cmp(&(b.list_position as i64))); model.issues = v; } + // users Msg::WsMsg(WsMsg::ProjectUsersLoaded(v)) => { model.users = v.clone(); } + // comments + Msg::WsMsg(WsMsg::IssueCommentsLoaded(comments)) => { + model.comments = comments.clone(); + } _ => (), }; orders.render(); diff --git a/jirs-data/src/lib.rs b/jirs-data/src/lib.rs index 26f1dd4a..28dab0a3 100644 --- a/jirs-data/src/lib.rs +++ b/jirs-data/src/lib.rs @@ -17,6 +17,12 @@ pub trait ToVec { fn ordered() -> Vec; } +pub type IssueId = i32; +pub type ProjectId = i32; +pub type UserId = i32; +pub type CommentId = i32; +pub type TokenId = i32; + #[cfg_attr(feature = "backend", derive(FromSqlRow, AsExpression))] #[cfg_attr(feature = "backend", sql_type = "IssueTypeType")] #[derive(Clone, Deserialize, Serialize, Debug, PartialOrd, PartialEq, Hash)] @@ -260,68 +266,9 @@ pub struct ErrorResponse { pub errors: Vec, } -#[derive(Clone, Serialize, Deserialize, Debug, PartialEq)] -pub struct FullProject { - pub id: i32, - pub name: String, - pub url: String, - pub description: String, - pub category: String, - pub created_at: NaiveDateTime, - pub updated_at: NaiveDateTime, - - pub issues: Vec, - pub users: Vec, -} - -#[derive(Clone, Serialize, Deserialize, Debug, PartialEq)] -pub struct FullIssue { - pub id: i32, - pub title: String, - pub issue_type: IssueType, - pub status: IssueStatus, - pub priority: IssuePriority, - pub list_position: i32, - pub description: Option, - pub description_text: Option, - pub estimate: Option, - pub time_spent: Option, - pub time_remaining: Option, - pub reporter_id: i32, - pub project_id: i32, - pub created_at: NaiveDateTime, - pub updated_at: NaiveDateTime, - - pub user_ids: Vec, - pub comments: Vec, -} - -impl Into for FullIssue { - fn into(self) -> Issue { - Issue { - id: self.id, - title: self.title, - issue_type: self.issue_type, - status: self.status, - priority: self.priority, - list_position: self.list_position, - description: self.description, - description_text: self.description_text, - estimate: self.estimate, - time_spent: self.time_spent, - time_remaining: self.time_remaining, - reporter_id: self.reporter_id, - project_id: self.project_id, - created_at: self.created_at, - updated_at: self.updated_at, - user_ids: self.user_ids, - } - } -} - #[derive(Clone, Serialize, Deserialize, Debug, PartialEq)] pub struct Project { - pub id: i32, + pub id: ProjectId, pub name: String, pub url: String, pub description: String, @@ -332,7 +279,7 @@ pub struct Project { #[derive(Clone, Serialize, Deserialize, Debug, PartialEq)] pub struct Issue { - pub id: i32, + pub id: IssueId, pub title: String, pub issue_type: IssueType, pub status: IssueStatus, @@ -343,8 +290,8 @@ pub struct Issue { pub estimate: Option, pub time_spent: Option, pub time_remaining: Option, - pub reporter_id: i32, - pub project_id: i32, + pub reporter_id: UserId, + pub project_id: ProjectId, pub created_at: NaiveDateTime, pub updated_at: NaiveDateTime, @@ -353,31 +300,29 @@ pub struct Issue { #[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] pub struct Comment { - pub id: i32, + pub id: CommentId, pub body: String, - pub user_id: i32, - pub issue_id: i32, + pub user_id: UserId, + pub issue_id: IssueId, pub created_at: NaiveDateTime, pub updated_at: NaiveDateTime, - - pub user: Option, } #[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] pub struct User { - pub id: i32, + pub id: UserId, pub name: String, pub email: String, pub avatar_url: Option, - pub project_id: i32, + pub project_id: ProjectId, pub created_at: NaiveDateTime, pub updated_at: NaiveDateTime, } #[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] pub struct Token { - pub id: i32, - pub user_id: i32, + pub id: TokenId, + pub user_id: UserId, pub access_token: Uuid, pub refresh_token: Uuid, pub created_at: NaiveDateTime, @@ -396,9 +341,9 @@ pub struct UpdateIssuePayload { pub estimate: Option, pub time_spent: Option, pub time_remaining: Option, - pub project_id: i32, - pub reporter_id: i32, - pub user_ids: Vec, + pub project_id: ProjectId, + pub reporter_id: UserId, + pub user_ids: Vec, } impl From for UpdateIssuePayload { @@ -423,8 +368,8 @@ impl From for UpdateIssuePayload { #[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] pub struct CreateCommentPayload { - pub user_id: Option, - pub issue_id: i32, + pub user_id: Option, + pub issue_id: IssueId, pub body: String, } @@ -444,9 +389,9 @@ pub struct CreateIssuePayload { pub estimate: Option, pub time_spent: Option, pub time_remaining: Option, - pub project_id: i32, - pub user_ids: Vec, - pub reporter_id: i32, + pub project_id: ProjectId, + pub user_ids: Vec, + pub reporter_id: UserId, } #[derive(Serialize, Deserialize, Debug, PartialEq)] @@ -476,10 +421,14 @@ pub enum WsMsg { ProjectUsersLoaded(Vec), // issue - IssueUpdateRequest(i32, UpdateIssuePayload), + IssueUpdateRequest(IssueId, UpdateIssuePayload), IssueUpdated(Issue), - IssueDeleteRequest(i32), - IssueDeleted(i32), + IssueDeleteRequest(IssueId), + IssueDeleted(IssueId), IssueCreateRequest(CreateIssuePayload), IssueCreated(Issue), + + // comments + IssueCommentsRequest(IssueId), + IssueCommentsLoaded(Vec), } diff --git a/jirs-server/seed.sql b/jirs-server/seed.sql index 0bddc62d..c0badd1f 100644 --- a/jirs-server/seed.sql +++ b/jirs-server/seed.sql @@ -48,4 +48,55 @@ insert into issues( 2, 1 ); + +insert into comments (user_id, issue_id, body) values ( + 1, 1, 'Vestibulum non neque at dui maximus porttitor fermentum consectetur eros.' + ), +( + 1, 2, 'Fusce varius ligula ut nisl porttitor, in gravida dolor rhoncus.' + ), +( + 1, 3, 'Cras viverra urna at urna convallis maximus.' + ), +( + 2, 1, 'Phasellus sollicitudin nisi eget arcu sollicitudin aliquam.' + ), +( + 2, 2, 'Duis sodales felis in maximus tincidunt.' + ), +( + 2, 3, 'Aenean sit amet sem sit amet dolor pellentesque rutrum.' + ), +( + 3, 1, 'Phasellus placerat dui vitae odio mattis convallis.' + ), +( + 3, 2, 'Suspendisse quis est eu neque vehicula sagittis.' + ), +( + 3, 3, 'Duis rutrum quam eget maximus laoreet.' + ), +( + 1, 1, 'Vestibulum eu ipsum a dui fringilla tristique.' + ), +( + 1, 2, 'Phasellus porttitor dolor vitae urna aliquam porta.' + ), +( + 2, 1, 'Curabitur volutpat mauris pretium urna laoreet, eget scelerisque neque fringilla.' + ), +( + 1, 3, 'Curabitur ac arcu eu eros auctor elementum.' + ), +( + 3, 1, 'Duis facilisis ipsum nec mi porta ultricies.' + ), +( + 1, 1, 'In elementum orci nec mi porta imperdiet ut ac ante.' + ), +( + 2, 3, 'Praesent et orci ut metus interdum sollicitudin.' +); + + select * from tokens; diff --git a/jirs-server/src/main.rs b/jirs-server/src/main.rs index 79e041f4..ef89e913 100644 --- a/jirs-server/src/main.rs +++ b/jirs-server/src/main.rs @@ -34,14 +34,6 @@ async fn main() -> Result<(), String> { .data(db_addr.clone()) .data(crate::db::build_pool()) .service(crate::ws::index) - .service( - web::scope("/issues") - .service(crate::routes::issues::project_issues) - .service(crate::routes::issues::issue_with_users_and_comments) - .service(crate::routes::issues::create) - .service(crate::routes::issues::update) - .service(crate::routes::issues::delete), - ) .service( web::scope("/comments") .service(crate::routes::comments::create) @@ -49,11 +41,6 @@ async fn main() -> Result<(), String> { .service(crate::routes::comments::delete), ) .service(web::scope("/currentUser").service(crate::routes::users::current_user)) - .service( - web::scope("/project") - .service(crate::routes::projects::project_with_users_and_issues) - .service(crate::routes::projects::update), - ) }) .bind(addr) .map_err(|e| format!("{}", e))? diff --git a/jirs-server/src/models.rs b/jirs-server/src/models.rs index 297a158c..1c5d8236 100644 --- a/jirs-server/src/models.rs +++ b/jirs-server/src/models.rs @@ -26,8 +26,6 @@ impl Into for Comment { issue_id: self.issue_id, created_at: self.created_at, updated_at: self.updated_at, - - user: None, } } } @@ -86,31 +84,6 @@ impl Into for Issue { } } -impl Into for Issue { - fn into(self) -> jirs_data::FullIssue { - jirs_data::FullIssue { - id: self.id, - title: self.title, - issue_type: self.issue_type, - status: self.status, - priority: self.priority, - list_position: self.list_position, - description: self.description, - description_text: self.description_text, - estimate: self.estimate, - time_spent: self.time_spent, - time_remaining: self.time_remaining, - reporter_id: self.reporter_id, - project_id: self.project_id, - created_at: self.created_at, - updated_at: self.updated_at, - - user_ids: vec![], - comments: vec![], - } - } -} - #[derive(Debug, Serialize, Deserialize, Insertable)] #[serde(rename_all = "camelCase")] #[table_name = "issues"] diff --git a/jirs-server/src/routes/issues.rs b/jirs-server/src/routes/issues.rs deleted file mode 100644 index d9143053..00000000 --- a/jirs-server/src/routes/issues.rs +++ /dev/null @@ -1,190 +0,0 @@ -use std::collections::HashMap; - -use actix::Addr; -use actix_web::web::{Data, Json, Path}; -use actix_web::{delete, get, post, put, HttpRequest, HttpResponse}; - -use crate::db::authorize_user::AuthorizeUser; -use crate::db::comments::LoadIssueComments; -use crate::db::issues::{CreateIssue, DeleteIssue, LoadIssue, UpdateIssue}; -use crate::db::users::{LoadIssueAssignees, LoadProjectUsers}; -use crate::db::DbExecutor; -use crate::errors::ServiceErrors; -use crate::middleware::authorize::token_from_headers; -use crate::routes::user_from_request; - -#[get("")] -pub async fn project_issues() -> HttpResponse { - HttpResponse::Ok() - .content_type("text/html") - .body("IssuesFoo") -} - -#[get("/{id}")] -pub async fn issue_with_users_and_comments( - req: HttpRequest, - path: Path, - db: Data>, -) -> HttpResponse { - let issue_id = path.into_inner(); - let token = match token_from_headers(req.headers()) { - Ok(uuid) => uuid, - _ => return crate::errors::ServiceErrors::Unauthorized.into_http_response(), - }; - let _user = match db - .send(AuthorizeUser { - access_token: token, - }) - .await - { - Ok(Ok(user)) => user, - _ => return crate::errors::ServiceErrors::Unauthorized.into_http_response(), - }; - - match load_issue(issue_id, db).await { - Ok(full_issue) => HttpResponse::Ok().json(full_issue), - Err(e) => e.into_http_response(), - } -} - -#[post("")] -pub async fn create( - req: HttpRequest, - payload: Json, - db: Data>, -) -> HttpResponse { - let user = match user_from_request(req, &db).await { - Ok(user) => user, - Err(response) => return response, - }; - let msg = CreateIssue { - title: payload.title.clone(), - issue_type: payload.issue_type.clone(), - status: payload.status.clone(), - priority: payload.priority.clone(), - description: payload.description.clone(), - description_text: payload.description_text.clone(), - estimate: payload.estimate.clone(), - time_spent: payload.time_spent.clone(), - time_remaining: payload.time_remaining.clone(), - project_id: payload.project_id, - reporter_id: user.id, - user_ids: payload.user_ids.clone(), - }; - match db.send(msg).await { - Ok(Ok(issue)) => HttpResponse::Ok().json(issue), - Ok(Err(e)) => e.into_http_response(), - _ => ServiceErrors::DatabaseConnectionLost.into_http_response(), - } -} - -#[put("/{id}")] -pub async fn update( - req: HttpRequest, - payload: Json, - path: Path, - db: Data>, -) -> HttpResponse { - let issue_id = path.into_inner(); - let token = match token_from_headers(req.headers()) { - Ok(uuid) => uuid, - _ => return crate::errors::ServiceErrors::Unauthorized.into_http_response(), - }; - match db - .send(AuthorizeUser { - access_token: token, - }) - .await - { - Ok(Ok(_)) => (), - _ => return crate::errors::ServiceErrors::Unauthorized.into_http_response(), - }; - let msg = UpdateIssue { - issue_id, - title: Some(payload.title.clone()), - issue_type: Some(payload.issue_type.clone()), - status: Some(payload.status.clone()), - priority: Some(payload.priority.clone()), - list_position: Some(payload.list_position.clone()), - description: Some(payload.description.clone()), - description_text: Some(payload.description_text.clone()), - estimate: Some(payload.estimate.clone()), - time_spent: Some(payload.time_spent.clone()), - time_remaining: Some(payload.time_remaining.clone()), - project_id: Some(payload.project_id.clone()), - user_ids: Some(payload.user_ids.clone()), - reporter_id: Some(payload.reporter_id), - }; - match db.send(msg).await { - Ok(Ok(_)) => (), - Ok(Err(e)) => return e.into_http_response(), - _ => return ServiceErrors::DatabaseConnectionLost.into_http_response(), - }; - match load_issue(issue_id, db).await { - Ok(full_issue) => HttpResponse::Ok().json(full_issue), - Err(e) => e.into_http_response(), - } -} - -#[delete("/{id}")] -pub async fn delete(req: HttpRequest, path: Path, db: Data>) -> HttpResponse { - let _user = match user_from_request(req, &db).await { - Ok(user) => user, - Err(response) => return response, - }; - let issue_id = path.into_inner(); - let msg = DeleteIssue { issue_id }; - match db.send(msg).await { - Ok(Ok(_)) => HttpResponse::NoContent().body(""), - Ok(Err(e)) => e.into_http_response(), - _ => ServiceErrors::DatabaseConnectionLost.into_http_response(), - } -} - -async fn load_issue( - issue_id: i32, - db: Data>, -) -> Result { - let issue_future = db.send(LoadIssue { issue_id }); - let assignees_future = db.send(LoadIssueAssignees { issue_id }); - let comments_future = db.send(LoadIssueComments { issue_id }); - let issue_result = issue_future.await; - let issue = match issue_result { - Ok(Ok(issue)) => issue, - _ => return Err(ServiceErrors::DatabaseConnectionLost), - }; - let users = match db - .send(LoadProjectUsers { - project_id: issue.project_id, - }) - .await - { - Ok(Ok(users)) => users, - _ => return Err(ServiceErrors::DatabaseConnectionLost), - }; - let mut full_issue: jirs_data::FullIssue = issue.into(); - let assignees_result = assignees_future.await; - let assignees = match assignees_result { - Ok(Ok(assignees)) => assignees, - _ => return Err(ServiceErrors::DatabaseConnectionLost), - }; - let mut user_map = HashMap::new(); - for user in users.into_iter() { - user_map.insert(user.id, user); - } - let comments_result = comments_future.await; - let comments = match comments_result { - Ok(Ok(comments)) => comments, - _ => return Err(ServiceErrors::DatabaseConnectionLost), - }; - full_issue.user_ids = assignees.iter().map(|u| u.id).collect(); - full_issue.comments = comments - .into_iter() - .map(|c| { - let mut comment: jirs_data::Comment = c.into(); - comment.user = user_map.get(&comment.user_id).map(|user| user.into()); - comment - }) - .collect(); - Ok(full_issue) -} diff --git a/jirs-server/src/routes/mod.rs b/jirs-server/src/routes/mod.rs index aaca148b..ed3d62ba 100644 --- a/jirs-server/src/routes/mod.rs +++ b/jirs-server/src/routes/mod.rs @@ -9,8 +9,6 @@ use crate::middleware::authorize::token_from_headers; use crate::models::User; pub mod comments; -pub mod issues; -pub mod projects; pub mod users; pub async fn user_from_request( diff --git a/jirs-server/src/routes/projects.rs b/jirs-server/src/routes/projects.rs deleted file mode 100644 index 60dd1350..00000000 --- a/jirs-server/src/routes/projects.rs +++ /dev/null @@ -1,100 +0,0 @@ -use actix::Addr; -use actix_web::web::{Data, Json, Path}; -use actix_web::{get, put, HttpRequest, HttpResponse}; - -use crate::db::authorize_user::AuthorizeUser; -use crate::db::issues::LoadProjectIssues; -use crate::db::projects::{LoadCurrentProject, UpdateProject}; -use crate::db::users::LoadProjectUsers; -use crate::db::DbExecutor; -use crate::errors::ServiceErrors; -use crate::middleware::authorize::token_from_headers; -use crate::routes::user_from_request; - -#[get("")] -pub async fn project_with_users_and_issues( - req: HttpRequest, - db: Data>, -) -> HttpResponse { - let token = match token_from_headers(req.headers()) { - Ok(uuid) => uuid, - _ => return crate::errors::ServiceErrors::Unauthorized.into_http_response(), - }; - let user = match db - .send(AuthorizeUser { - access_token: token, - }) - .await - { - Ok(Ok(user)) => user, - _ => return crate::errors::ServiceErrors::Unauthorized.into_http_response(), - }; - let issues_future = db.send(LoadProjectIssues { - project_id: user.project_id, - }); - let project_future = db.send(LoadCurrentProject { - project_id: user.project_id, - }); - let users_future = db.send(LoadProjectUsers { - project_id: user.project_id, - }); - let issues_result = issues_future.await; - let issues = match issues_result { - Ok(Ok(issues)) => issues, - _ => return ServiceErrors::DatabaseConnectionLost.into_http_response(), - }; - let project_result = project_future.await; - let project = match project_result { - Ok(Ok(project)) => project, - _ => return ServiceErrors::DatabaseConnectionLost.into_http_response(), - }; - let users_result = users_future.await; - let users = match users_result { - Ok(Ok(users)) => users, - _ => return ServiceErrors::DatabaseConnectionLost.into_http_response(), - }; - let res = jirs_data::FullProject { - id: project.id, - name: project.name, - url: project.url, - description: project.description, - category: project.category, - created_at: project.created_at, - updated_at: project.updated_at, - issues: issues - .into_iter() - .map(|i| { - let mut issue: jirs_data::Issue = i.into(); - issue.user_ids = users.iter().map(|u| u.id).collect(); - issue - }) - .collect(), - users: users.into_iter().map(|u| u.into()).collect(), - }; - HttpResponse::Ok().json(res) -} - -#[put("/{id}")] -pub async fn update( - req: HttpRequest, - payload: Json, - path: Path, - db: Data>, -) -> HttpResponse { - let _user = match user_from_request(req, &db).await { - Ok(user) => user, - Err(response) => return response, - }; - let msg = UpdateProject { - project_id: path.into_inner(), - name: payload.name.clone(), - url: payload.url.clone(), - description: payload.description.clone(), - category: payload.category.clone(), - }; - match db.send(msg).await { - Ok(Ok(project)) => HttpResponse::Ok().json(project), - Ok(Err(e)) => e.into_http_response(), - _ => ServiceErrors::DatabaseConnectionLost.into_http_response(), - } -} diff --git a/jirs-server/src/ws/comments.rs b/jirs-server/src/ws/comments.rs new file mode 100644 index 00000000..327d1f49 --- /dev/null +++ b/jirs-server/src/ws/comments.rs @@ -0,0 +1,22 @@ +use actix::Addr; +use actix_web::web::Data; + +use jirs_data::{IssueId, WsMsg}; + +use crate::db::comments::LoadIssueComments; +use crate::db::DbExecutor; +use crate::ws::{current_user, WsResult}; + +pub async fn load_issues( + db: &Data>, + user: &Option, + issue_id: IssueId, +) -> WsResult { + current_user(user)?; + let comments = match db.send(LoadIssueComments { issue_id }).await { + Ok(Ok(comments)) => comments.into_iter().map(|c| c.into()).collect(), + _ => return Ok(None), + }; + + Ok(Some(WsMsg::IssueCommentsLoaded(comments))) +} diff --git a/jirs-server/src/ws/issues.rs b/jirs-server/src/ws/issues.rs new file mode 100644 index 00000000..e90837a0 --- /dev/null +++ b/jirs-server/src/ws/issues.rs @@ -0,0 +1,136 @@ +use std::collections::HashMap; + +use actix::Addr; +use actix_web::web::Data; + +use jirs_data::WsMsg; + +use crate::db::issue_assignees::LoadAssignees; +use crate::db::issues::{LoadProjectIssues, UpdateIssue}; +use crate::db::DbExecutor; +use crate::ws::{current_user, WsResult}; + +pub async fn update_issue( + db: &Data>, + user: &Option, + issue_id: i32, + payload: jirs_data::UpdateIssuePayload, +) -> WsResult { + current_user(user)?; + let mut issue: jirs_data::Issue = match db + .send(UpdateIssue { + issue_id, + title: Some(payload.title), + issue_type: Some(payload.issue_type), + status: Some(payload.status), + priority: Some(payload.priority), + list_position: Some(payload.list_position), + description: Some(payload.description), + description_text: Some(payload.description_text), + estimate: Some(payload.estimate), + time_spent: Some(payload.time_spent), + time_remaining: Some(payload.time_remaining), + project_id: Some(payload.project_id), + user_ids: Some(payload.user_ids), + reporter_id: Some(payload.reporter_id), + }) + .await + { + Ok(Ok(issue)) => issue.into(), + _ => return Ok(None), + }; + + let assignees = match db + .send(LoadAssignees { + issue_id: issue.id.clone(), + }) + .await + { + Ok(Ok(v)) => v, + _ => vec![], + }; + for assignee in assignees { + issue.user_ids.push(assignee.user_id); + } + + Ok(Some(WsMsg::IssueUpdated(issue.into()))) +} + +pub async fn add_issue( + db: &Data>, + user: &Option, + payload: jirs_data::CreateIssuePayload, +) -> WsResult { + current_user(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 db.send(msg).await { + Ok(Ok(issue)) => Some(WsMsg::IssueCreated(issue.into())), + _ => None, + }; + Ok(m) +} + +pub async fn delete_issue( + db: &Data>, + user: &Option, + id: i32, +) -> WsResult { + current_user(user)?; + let m = match db + .send(crate::db::issues::DeleteIssue { issue_id: id }) + .await + { + Ok(Ok(_)) => Some(WsMsg::IssueDeleted(id)), + _ => None, + }; + Ok(m) +} + +pub async fn load_issues(db: &Data>, user: &Option) -> WsResult { + let project_id = current_user(user).map(|u| u.project_id)?; + + let issues: Vec = match db.send(LoadProjectIssues { project_id }).await { + Ok(Ok(v)) => v.into_iter().map(|i| i.into()).collect(), + _ => return Ok(None), + }; + let mut issue_map = HashMap::new(); + let mut queue = vec![]; + for issue in issues.into_iter() { + let f = db.send(LoadAssignees { + issue_id: issue.id.clone(), + }); + queue.push(f); + issue_map.insert(issue.id.clone(), issue); + } + for f in queue { + match f.await { + Ok(Ok(assignees)) => { + for assignee in assignees { + if let Some(issue) = issue_map.get_mut(&assignee.issue_id) { + issue.user_ids.push(assignee.user_id); + } + } + } + _ => {} + }; + } + let mut issues = vec![]; + for (_, issue) in issue_map.into_iter() { + issues.push(issue); + } + + Ok(Some(WsMsg::ProjectIssuesLoaded(issues))) +} diff --git a/jirs-server/src/ws/mod.rs b/jirs-server/src/ws/mod.rs index 2d37a37a..97a82375 100644 --- a/jirs-server/src/ws/mod.rs +++ b/jirs-server/src/ws/mod.rs @@ -1,5 +1,3 @@ -use std::collections::HashMap; - use actix::{Actor, Addr, StreamHandler}; use actix_web::web::Data; use actix_web::{get, web, Error, HttpRequest, HttpResponse}; @@ -8,13 +6,21 @@ use actix_web_actors::ws; use jirs_data::WsMsg; use crate::db::authorize_user::AuthorizeUser; -use crate::db::issue_assignees::LoadAssignees; -use crate::db::issues::{LoadProjectIssues, UpdateIssue}; -use crate::db::projects::LoadCurrentProject; -use crate::db::users::LoadProjectUsers; use crate::db::DbExecutor; -type WsResult = std::result::Result, WsMsg>; +pub mod comments; +pub mod issues; +pub mod projects; +pub mod users; + +pub type WsResult = std::result::Result, WsMsg>; + +pub fn current_user(current_user: &Option) -> Result<&jirs_data::User, WsMsg> { + current_user + .as_ref() + .map(|u| u) + .ok_or_else(|| WsMsg::AuthorizeExpired) +} trait WsMessageSender { fn send_msg(&mut self, msg: jirs_data::WsMsg); @@ -46,13 +52,45 @@ impl WebSocketActor { let msg = match msg { WsMsg::Ping => Some(WsMsg::Pong), WsMsg::Pong => Some(WsMsg::Ping), - 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())?, + + // Issues + WsMsg::IssueUpdateRequest(id, payload) => block_on(issues::update_issue( + &self.db, + &self.current_user, + id, + payload, + ))?, + WsMsg::IssueCreateRequest(payload) => { + block_on(issues::add_issue(&self.db, &self.current_user, payload))? + } + WsMsg::IssueDeleteRequest(id) => { + block_on(issues::delete_issue(&self.db, &self.current_user, id))? + } + WsMsg::ProjectIssuesRequest => { + block_on(issues::load_issues(&self.db, &self.current_user))? + } + + // projects + WsMsg::ProjectRequest => { + block_on(projects::current_project(&self.db, &self.current_user))? + } + + // auth WsMsg::AuthorizeRequest(uuid) => block_on(self.authorize(uuid))?, - WsMsg::ProjectIssuesRequest => block_on(self.load_issues())?, - WsMsg::ProjectUsersRequest => block_on(self.load_project_users())?, + + // users + WsMsg::ProjectUsersRequest => { + block_on(users::load_project_users(&self.db, &self.current_user))? + } + + // comments + WsMsg::IssueCommentsRequest(issue_id) => block_on(comments::load_issues( + &self.db, + &self.current_user, + issue_id, + ))?, + + // else fail _ => { error!("No handle for {:?} specified", msg); None @@ -81,152 +119,6 @@ impl WebSocketActor { }; 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_issues(&mut self) -> WsResult { - let project_id = self.current_user().map(|u| u.project_id)?; - - let issues: Vec = - match self.db.send(LoadProjectIssues { project_id }).await { - Ok(Ok(v)) => v.into_iter().map(|i| i.into()).collect(), - _ => return Ok(None), - }; - let mut issue_map = HashMap::new(); - let mut queue = vec![]; - for issue in issues.into_iter() { - let f = self.db.send(LoadAssignees { - issue_id: issue.id.clone(), - }); - queue.push(f); - issue_map.insert(issue.id.clone(), issue); - } - for f in queue { - match f.await { - Ok(Ok(assignees)) => { - for assignee in assignees { - if let Some(issue) = issue_map.get_mut(&assignee.issue_id) { - issue.user_ids.push(assignee.user_id); - } - } - } - _ => {} - }; - } - let mut issues = vec![]; - for (_, issue) in issue_map.into_iter() { - issues.push(issue); - } - - Ok(Some(WsMsg::ProjectIssuesLoaded(issues))) - } - - 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, - ) -> WsResult { - self.current_user()?; - let mut issue: jirs_data::Issue = match self - .db - .send(UpdateIssue { - issue_id, - title: Some(payload.title), - issue_type: Some(payload.issue_type), - status: Some(payload.status), - priority: Some(payload.priority), - list_position: Some(payload.list_position), - description: Some(payload.description), - description_text: Some(payload.description_text), - estimate: Some(payload.estimate), - time_spent: Some(payload.time_spent), - time_remaining: Some(payload.time_remaining), - project_id: Some(payload.project_id), - user_ids: Some(payload.user_ids), - reporter_id: Some(payload.reporter_id), - }) - .await - { - Ok(Ok(issue)) => issue.into(), - _ => return Ok(None), - }; - - let assignees = match self - .db - .send(LoadAssignees { - issue_id: issue.id.clone(), - }) - .await - { - Ok(Ok(v)) => v, - _ => vec![], - }; - for assignee in assignees { - issue.user_ids.push(assignee.user_id); - } - - Ok(Some(WsMsg::IssueUpdated(issue.into()))) - } - - 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) - } } impl StreamHandler> for WebSocketActor { diff --git a/jirs-server/src/ws/projects.rs b/jirs-server/src/ws/projects.rs new file mode 100644 index 00000000..e4bcf652 --- /dev/null +++ b/jirs-server/src/ws/projects.rs @@ -0,0 +1,23 @@ +use actix::Addr; +use actix_web::web::Data; + +use jirs_data::WsMsg; + +use crate::db::users::LoadProjectUsers; +use crate::db::DbExecutor; +use crate::ws::{current_user, WsResult}; + +pub async fn current_project( + db: &Data>, + user: &Option, +) -> WsResult { + let project_id = current_user(user).map(|u| u.project_id)?; + + let m = match db.send(LoadProjectUsers { project_id }).await { + Ok(Ok(v)) => Some(WsMsg::ProjectUsersLoaded( + v.into_iter().map(|i| i.into()).collect(), + )), + _ => None, + }; + Ok(m) +} diff --git a/jirs-server/src/ws/users.rs b/jirs-server/src/ws/users.rs new file mode 100644 index 00000000..24089bc5 --- /dev/null +++ b/jirs-server/src/ws/users.rs @@ -0,0 +1,22 @@ +use actix::Addr; +use actix_web::web::Data; + +use jirs_data::WsMsg; + +use crate::db::users::LoadProjectUsers; +use crate::db::DbExecutor; +use crate::ws::{current_user, WsResult}; + +pub async fn load_project_users( + db: &Data>, + user: &Option, +) -> WsResult { + let project_id = current_user(user).map(|u| u.project_id)?; + let m = match db.send(LoadProjectUsers { project_id }).await { + Ok(Ok(v)) => Some(WsMsg::ProjectUsersLoaded( + v.into_iter().map(|i| i.into()).collect(), + )), + _ => None, + }; + Ok(m) +}