Handle create issue

This commit is contained in:
Adrian Wozniak 2020-04-08 20:26:28 +02:00
parent a003f1a60a
commit a019d56a59
6 changed files with 136 additions and 78 deletions

View File

@ -34,6 +34,9 @@ pub fn update(msg: &Msg, model: &mut crate::model::Model, orders: &mut impl Orde
match msg { match msg {
Msg::AddIssue => { 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 { let payload = jirs_data::CreateIssuePayload {
title: modal.title.clone(), title: modal.title.clone(),
issue_type: modal.issue_type.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(), estimate: modal.estimate.clone(),
time_spent: modal.time_spent.clone(), time_spent: modal.time_spent.clone(),
time_remaining: modal.time_remaining.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(), user_ids: modal.user_ids.clone(),
reporter_id: modal.reporter_id.unwrap_or_else(|| user_id),
}; };
send_ws_msg(jirs_data::WsMsg::IssueCreateRequest(payload)); send_ws_msg(jirs_data::WsMsg::IssueCreateRequest(payload));
} }

View File

@ -52,7 +52,7 @@ pub fn update(msg: &Msg, model: &mut model::Model, orders: &mut impl Orders<Msg>
} }
Msg::ChangePage(Page::AddIssue) => { Msg::ChangePage(Page::AddIssue) => {
let mut modal = AddIssueModal::default(); 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)); model.modals.push(ModalType::AddIssue(modal));
} }

View File

@ -37,7 +37,7 @@ pub struct AddIssueModal {
pub estimate: Option<i32>, pub estimate: Option<i32>,
pub time_spent: Option<i32>, pub time_spent: Option<i32>,
pub time_remaining: Option<i32>, pub time_remaining: Option<i32>,
pub project_id: i32, pub project_id: Option<i32>,
pub user_ids: Vec<i32>, pub user_ids: Vec<i32>,
pub reporter_id: Option<i32>, pub reporter_id: Option<i32>,

View File

@ -193,13 +193,13 @@ impl Into<IssuePriority> for u32 {
} }
} }
#[derive(Clone, Serialize, Debug)] #[derive(Clone, Serialize, Debug, PartialEq)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct ErrorResponse { pub struct ErrorResponse {
pub errors: Vec<String>, pub errors: Vec<String>,
} }
#[derive(Clone, Serialize, Deserialize, Debug)] #[derive(Clone, Serialize, Deserialize, Debug, PartialEq)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct FullProject { pub struct FullProject {
pub id: i32, pub id: i32,
@ -214,7 +214,7 @@ pub struct FullProject {
pub users: Vec<User>, pub users: Vec<User>,
} }
#[derive(Clone, Serialize, Deserialize, Debug)] #[derive(Clone, Serialize, Deserialize, Debug, PartialEq)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct FullIssue { pub struct FullIssue {
pub id: i32, pub id: i32,
@ -261,7 +261,7 @@ impl Into<Issue> for FullIssue {
} }
} }
#[derive(Clone, Serialize, Deserialize, Debug)] #[derive(Clone, Serialize, Deserialize, Debug, PartialEq)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct Project { pub struct Project {
pub id: i32, pub id: i32,
@ -273,7 +273,7 @@ pub struct Project {
pub updated_at: NaiveDateTime, pub updated_at: NaiveDateTime,
} }
#[derive(Clone, Serialize, Deserialize, Debug)] #[derive(Clone, Serialize, Deserialize, Debug, PartialEq)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct Issue { pub struct Issue {
pub id: i32, pub id: i32,
@ -296,7 +296,7 @@ pub struct Issue {
pub user_ids: Vec<i32>, pub user_ids: Vec<i32>,
} }
#[derive(Clone, Debug, Serialize, Deserialize)] #[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct Comment { pub struct Comment {
pub id: i32, pub id: i32,
@ -321,7 +321,7 @@ pub struct User {
pub updated_at: NaiveDateTime, pub updated_at: NaiveDateTime,
} }
#[derive(Clone, Debug, Serialize, Deserialize)] #[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct Token { pub struct Token {
pub id: i32, pub id: i32,
@ -332,7 +332,7 @@ pub struct Token {
pub updated_at: NaiveDateTime, pub updated_at: NaiveDateTime,
} }
#[derive(Clone, Debug, Serialize, Deserialize)] #[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct UpdateIssuePayload { pub struct UpdateIssuePayload {
pub title: Option<String>, pub title: Option<String>,
@ -350,7 +350,7 @@ pub struct UpdateIssuePayload {
pub user_ids: Option<Vec<i32>>, pub user_ids: Option<Vec<i32>>,
} }
#[derive(Clone, Debug, Serialize, Deserialize)] #[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct CreateCommentPayload { pub struct CreateCommentPayload {
pub user_id: Option<i32>, pub user_id: Option<i32>,
@ -358,13 +358,13 @@ pub struct CreateCommentPayload {
pub body: String, pub body: String,
} }
#[derive(Clone, Debug, Serialize, Deserialize)] #[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct UpdateCommentPayload { pub struct UpdateCommentPayload {
pub body: String, pub body: String,
} }
#[derive(Serialize, Deserialize, Debug, Clone)] #[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct CreateIssuePayload { pub struct CreateIssuePayload {
pub title: String, pub title: String,
@ -379,9 +379,10 @@ pub struct CreateIssuePayload {
pub time_remaining: Option<i32>, pub time_remaining: Option<i32>,
pub project_id: i32, pub project_id: i32,
pub user_ids: Vec<i32>, pub user_ids: Vec<i32>,
pub reporter_id: i32,
} }
#[derive(Serialize, Deserialize, Debug)] #[derive(Serialize, Deserialize, Debug, PartialEq)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct UpdateProjectPayload { pub struct UpdateProjectPayload {
pub name: Option<String>, pub name: Option<String>,
@ -390,7 +391,7 @@ pub struct UpdateProjectPayload {
pub category: Option<String>, pub category: Option<String>,
} }
#[derive(Serialize, Deserialize, Debug, Clone)] #[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
pub enum WsMsg { pub enum WsMsg {
Ping, Ping,
Pong, Pong,

View File

@ -255,7 +255,10 @@ impl Handler<CreateIssue> for DbExecutor {
.values(form) .values(form)
.on_conflict_do_nothing() .on_conflict_do_nothing()
.get_result::<Issue>(conn) .get_result::<Issue>(conn)
.map_err(|_| ServiceErrors::DatabaseConnectionLost)?; .map_err(|e| {
error!("{}", e);
ServiceErrors::DatabaseConnectionLost
})?;
let mut values = vec![]; let mut values = vec![];
for user_id in msg.user_ids.iter() { for user_id in msg.user_ids.iter() {

View File

@ -3,7 +3,7 @@ use actix_web::web::Data;
use actix_web::{get, web, Error, HttpRequest, HttpResponse}; use actix_web::{get, web, Error, HttpRequest, HttpResponse};
use actix_web_actors::ws; use actix_web_actors::ws;
use jirs_data::{Project, WsMsg}; use jirs_data::WsMsg;
use crate::db::authorize_user::AuthorizeUser; use crate::db::authorize_user::AuthorizeUser;
use crate::db::issues::{LoadProjectIssues, UpdateIssue}; use crate::db::issues::{LoadProjectIssues, UpdateIssue};
@ -11,6 +11,8 @@ use crate::db::projects::LoadCurrentProject;
use crate::db::users::LoadProjectUsers; use crate::db::users::LoadProjectUsers;
use crate::db::DbExecutor; use crate::db::DbExecutor;
type WsResult = std::result::Result<Option<WsMsg>, WsMsg>;
trait WsMessageSender { trait WsMessageSender {
fn send_msg(&mut self, msg: jirs_data::WsMsg); fn send_msg(&mut self, msg: jirs_data::WsMsg);
} }
@ -31,91 +33,97 @@ impl WsMessageSender for ws::WebsocketContext<WebSocketActor> {
} }
impl WebSocketActor { impl WebSocketActor {
fn handle_msg(&mut self, msg: WsMsg) -> Option<WsMsg> { fn handle_ws_msg(&mut self, msg: WsMsg) -> WsResult {
// use futures::executor::LocalPool;
use futures::executor::block_on; 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::Ping => Some(WsMsg::Pong),
WsMsg::Pong => Some(WsMsg::Ping), WsMsg::Pong => Some(WsMsg::Ping),
WsMsg::ProjectRequest => match block_on(self.load_project()) { WsMsg::IssueUpdateRequest(id, payload) => block_on(self.update_issue(id, payload))?,
Some(p) => Some(WsMsg::ProjectLoaded(p)), WsMsg::IssueCreateRequest(payload) => block_on(self.add_issue(payload))?,
_ => { WsMsg::IssueDeleteRequest(id) => block_on(self.delete_issue(id))?,
error!("Failed to load project"); WsMsg::ProjectRequest => block_on(self.load_project())?,
None WsMsg::AuthorizeRequest(uuid) => block_on(self.authorize(uuid))?,
} WsMsg::ProjectIssuesRequest => block_on(self.load_issues())?,
}, WsMsg::ProjectUsersRequest => block_on(self.load_project_users())?,
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)),
_ => { _ => {
error!("Failed to resolve message"); error!("Failed to resolve message");
None None
} }
} };
Ok(msg)
} }
async fn authorize(&mut self, token: uuid::Uuid) -> Option<jirs_data::User> { async fn authorize(&mut self, token: uuid::Uuid) -> WsResult {
match self let m = match self
.db .db
.send(AuthorizeUser { .send(AuthorizeUser {
access_token: token, access_token: token,
}) })
.await .await
{ {
Ok(Ok(u)) => Some(u.into()), Ok(Ok(u)) => {
_ => None, let user: jirs_data::User = u.into();
self.current_user = Some(user.clone());
Some(WsMsg::AuthorizeLoaded(Ok(user)))
} }
} Ok(Err(_)) => Some(WsMsg::AuthorizeLoaded(
Err("Invalid auth token".to_string()),
async fn load_project(&mut self) -> Option<Project> {
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
}
Err(e) => {
error!("{:?}", e);
None
}
}
}
async fn load_issues(&mut self) -> Option<WsMsg> {
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(),
)), )),
_ => 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<WsMsg> { async fn load_issues(&mut self) -> WsResult {
let project_id = self.current_user.as_ref().map(|u| u.project_id)?; let project_id = self.current_user().map(|u| u.project_id)?;
match self.db.send(LoadProjectUsers { project_id }).await { 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( Ok(Ok(v)) => Some(WsMsg::ProjectUsersLoaded(
v.into_iter().map(|i| i.into()).collect(), v.into_iter().map(|i| i.into()).collect(),
)), )),
_ => None, _ => None,
} };
Ok(m)
} }
async fn update_issue( async fn update_issue(
&mut self, &mut self,
issue_id: i32, issue_id: i32,
payload: jirs_data::UpdateIssuePayload, payload: jirs_data::UpdateIssuePayload,
) -> Option<WsMsg> { ) -> WsResult {
self.current_user()?;
let jirs_data::UpdateIssuePayload { let jirs_data::UpdateIssuePayload {
title, title,
issue_type, issue_type,
@ -130,7 +138,7 @@ impl WebSocketActor {
project_id, project_id,
user_ids, user_ids,
} = payload; } = payload;
match self let m = match self
.db .db
.send(UpdateIssue { .send(UpdateIssue {
issue_id, issue_id,
@ -151,7 +159,44 @@ impl WebSocketActor {
{ {
Ok(Ok(issue)) => Some(WsMsg::IssueUpdated(issue.into())), Ok(Ok(issue)) => Some(WsMsg::IssueUpdated(issue.into())),
_ => None, _ => 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<Result<ws::Message, ws::ProtocolError>> for WebSocketActor {
let ws_msg: bincode::Result<jirs_data::WsMsg> = let ws_msg: bincode::Result<jirs_data::WsMsg> =
bincode::deserialize(bin.to_vec().as_slice()); bincode::deserialize(bin.to_vec().as_slice());
let msg = match ws_msg { let msg = match ws_msg {
Ok(msg) => msg, Ok(m) => m,
_ => return, _ => return,
}; };
match self.handle_msg(msg) { if msg != WsMsg::Ping && msg != WsMsg::Pong {
Some(msg) => ctx.send_msg(msg), info!("(1)incoming message: {:?}", msg);
_ => return, }
let _x = 1;
match self.handle_ws_msg(msg) {
Ok(Some(msg)) => ctx.send_msg(msg),
Err(e) => ctx.send_msg(e),
_ => (),
}; };
} }
_ => (), _ => (),