Load issue comments and struct cleanup
This commit is contained in:
parent
d72328764d
commit
1663d9a485
@ -1,7 +1,8 @@
|
|||||||
use seed::{prelude::*, *};
|
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::model::{AddIssueModal, EditIssueModal, ModalType, Model, Page};
|
||||||
use crate::shared::styled_editor::Mode;
|
use crate::shared::styled_editor::Mode;
|
||||||
use crate::shared::styled_modal::{StyledModal, Variant as ModalVariant};
|
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>
|
|||||||
|
|
||||||
Msg::ChangePage(Page::EditIssue(issue_id)) => {
|
Msg::ChangePage(Page::EditIssue(issue_id)) => {
|
||||||
push_edit_modal(issue_id, model);
|
push_edit_modal(issue_id, model);
|
||||||
|
send_ws_msg(WsMsg::IssueCommentsRequest(issue_id.clone()));
|
||||||
}
|
}
|
||||||
|
|
||||||
Msg::ChangePage(Page::AddIssue) => {
|
Msg::ChangePage(Page::AddIssue) => {
|
||||||
|
@ -152,6 +152,7 @@ pub struct Model {
|
|||||||
pub user: Option<User>,
|
pub user: Option<User>,
|
||||||
pub issues: Vec<Issue>,
|
pub issues: Vec<Issue>,
|
||||||
pub users: Vec<User>,
|
pub users: Vec<User>,
|
||||||
|
pub comments: Vec<Comment>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for Model {
|
impl Default for Model {
|
||||||
@ -180,6 +181,7 @@ impl Default for Model {
|
|||||||
},
|
},
|
||||||
modals: vec![],
|
modals: vec![],
|
||||||
project: None,
|
project: None,
|
||||||
|
comments: vec![],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -20,20 +20,28 @@ pub fn handle(msg: WsMsg) {
|
|||||||
|
|
||||||
pub fn update(msg: &Msg, model: &mut model::Model, orders: &mut impl Orders<Msg>) {
|
pub fn update(msg: &Msg, model: &mut model::Model, orders: &mut impl Orders<Msg>) {
|
||||||
match msg {
|
match msg {
|
||||||
Msg::WsMsg(WsMsg::ProjectLoaded(project)) => {
|
// auth
|
||||||
model.project = Some(project.clone());
|
|
||||||
}
|
|
||||||
Msg::WsMsg(WsMsg::AuthorizeLoaded(Ok(user))) => {
|
Msg::WsMsg(WsMsg::AuthorizeLoaded(Ok(user))) => {
|
||||||
model.user = Some(user.clone());
|
model.user = Some(user.clone());
|
||||||
}
|
}
|
||||||
|
// project
|
||||||
|
Msg::WsMsg(WsMsg::ProjectLoaded(project)) => {
|
||||||
|
model.project = Some(project.clone());
|
||||||
|
}
|
||||||
|
// issues
|
||||||
Msg::WsMsg(WsMsg::ProjectIssuesLoaded(v)) => {
|
Msg::WsMsg(WsMsg::ProjectIssuesLoaded(v)) => {
|
||||||
let mut v = v.clone();
|
let mut v = v.clone();
|
||||||
v.sort_by(|a, b| (a.list_position as i64).cmp(&(b.list_position as i64)));
|
v.sort_by(|a, b| (a.list_position as i64).cmp(&(b.list_position as i64)));
|
||||||
model.issues = v;
|
model.issues = v;
|
||||||
}
|
}
|
||||||
|
// users
|
||||||
Msg::WsMsg(WsMsg::ProjectUsersLoaded(v)) => {
|
Msg::WsMsg(WsMsg::ProjectUsersLoaded(v)) => {
|
||||||
model.users = v.clone();
|
model.users = v.clone();
|
||||||
}
|
}
|
||||||
|
// comments
|
||||||
|
Msg::WsMsg(WsMsg::IssueCommentsLoaded(comments)) => {
|
||||||
|
model.comments = comments.clone();
|
||||||
|
}
|
||||||
_ => (),
|
_ => (),
|
||||||
};
|
};
|
||||||
orders.render();
|
orders.render();
|
||||||
|
@ -17,6 +17,12 @@ pub trait ToVec {
|
|||||||
fn ordered() -> Vec<Self::Item>;
|
fn ordered() -> Vec<Self::Item>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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", derive(FromSqlRow, AsExpression))]
|
||||||
#[cfg_attr(feature = "backend", sql_type = "IssueTypeType")]
|
#[cfg_attr(feature = "backend", sql_type = "IssueTypeType")]
|
||||||
#[derive(Clone, Deserialize, Serialize, Debug, PartialOrd, PartialEq, Hash)]
|
#[derive(Clone, Deserialize, Serialize, Debug, PartialOrd, PartialEq, Hash)]
|
||||||
@ -260,68 +266,9 @@ pub struct ErrorResponse {
|
|||||||
pub errors: Vec<String>,
|
pub errors: Vec<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[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<Issue>,
|
|
||||||
pub users: Vec<User>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[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<String>,
|
|
||||||
pub description_text: Option<String>,
|
|
||||||
pub estimate: Option<i32>,
|
|
||||||
pub time_spent: Option<i32>,
|
|
||||||
pub time_remaining: Option<i32>,
|
|
||||||
pub reporter_id: i32,
|
|
||||||
pub project_id: i32,
|
|
||||||
pub created_at: NaiveDateTime,
|
|
||||||
pub updated_at: NaiveDateTime,
|
|
||||||
|
|
||||||
pub user_ids: Vec<i32>,
|
|
||||||
pub comments: Vec<Comment>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Into<Issue> 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)]
|
#[derive(Clone, Serialize, Deserialize, Debug, PartialEq)]
|
||||||
pub struct Project {
|
pub struct Project {
|
||||||
pub id: i32,
|
pub id: ProjectId,
|
||||||
pub name: String,
|
pub name: String,
|
||||||
pub url: String,
|
pub url: String,
|
||||||
pub description: String,
|
pub description: String,
|
||||||
@ -332,7 +279,7 @@ pub struct Project {
|
|||||||
|
|
||||||
#[derive(Clone, Serialize, Deserialize, Debug, PartialEq)]
|
#[derive(Clone, Serialize, Deserialize, Debug, PartialEq)]
|
||||||
pub struct Issue {
|
pub struct Issue {
|
||||||
pub id: i32,
|
pub id: IssueId,
|
||||||
pub title: String,
|
pub title: String,
|
||||||
pub issue_type: IssueType,
|
pub issue_type: IssueType,
|
||||||
pub status: IssueStatus,
|
pub status: IssueStatus,
|
||||||
@ -343,8 +290,8 @@ pub struct Issue {
|
|||||||
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 reporter_id: i32,
|
pub reporter_id: UserId,
|
||||||
pub project_id: i32,
|
pub project_id: ProjectId,
|
||||||
pub created_at: NaiveDateTime,
|
pub created_at: NaiveDateTime,
|
||||||
pub updated_at: NaiveDateTime,
|
pub updated_at: NaiveDateTime,
|
||||||
|
|
||||||
@ -353,31 +300,29 @@ pub struct Issue {
|
|||||||
|
|
||||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
||||||
pub struct Comment {
|
pub struct Comment {
|
||||||
pub id: i32,
|
pub id: CommentId,
|
||||||
pub body: String,
|
pub body: String,
|
||||||
pub user_id: i32,
|
pub user_id: UserId,
|
||||||
pub issue_id: i32,
|
pub issue_id: IssueId,
|
||||||
pub created_at: NaiveDateTime,
|
pub created_at: NaiveDateTime,
|
||||||
pub updated_at: NaiveDateTime,
|
pub updated_at: NaiveDateTime,
|
||||||
|
|
||||||
pub user: Option<User>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
||||||
pub struct User {
|
pub struct User {
|
||||||
pub id: i32,
|
pub id: UserId,
|
||||||
pub name: String,
|
pub name: String,
|
||||||
pub email: String,
|
pub email: String,
|
||||||
pub avatar_url: Option<String>,
|
pub avatar_url: Option<String>,
|
||||||
pub project_id: i32,
|
pub project_id: ProjectId,
|
||||||
pub created_at: NaiveDateTime,
|
pub created_at: NaiveDateTime,
|
||||||
pub updated_at: NaiveDateTime,
|
pub updated_at: NaiveDateTime,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
||||||
pub struct Token {
|
pub struct Token {
|
||||||
pub id: i32,
|
pub id: TokenId,
|
||||||
pub user_id: i32,
|
pub user_id: UserId,
|
||||||
pub access_token: Uuid,
|
pub access_token: Uuid,
|
||||||
pub refresh_token: Uuid,
|
pub refresh_token: Uuid,
|
||||||
pub created_at: NaiveDateTime,
|
pub created_at: NaiveDateTime,
|
||||||
@ -396,9 +341,9 @@ pub struct UpdateIssuePayload {
|
|||||||
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: ProjectId,
|
||||||
pub reporter_id: i32,
|
pub reporter_id: UserId,
|
||||||
pub user_ids: Vec<i32>,
|
pub user_ids: Vec<UserId>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<Issue> for UpdateIssuePayload {
|
impl From<Issue> for UpdateIssuePayload {
|
||||||
@ -423,8 +368,8 @@ impl From<Issue> for UpdateIssuePayload {
|
|||||||
|
|
||||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
||||||
pub struct CreateCommentPayload {
|
pub struct CreateCommentPayload {
|
||||||
pub user_id: Option<i32>,
|
pub user_id: Option<UserId>,
|
||||||
pub issue_id: i32,
|
pub issue_id: IssueId,
|
||||||
pub body: String,
|
pub body: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -444,9 +389,9 @@ pub struct CreateIssuePayload {
|
|||||||
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: ProjectId,
|
||||||
pub user_ids: Vec<i32>,
|
pub user_ids: Vec<UserId>,
|
||||||
pub reporter_id: i32,
|
pub reporter_id: UserId,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug, PartialEq)]
|
#[derive(Serialize, Deserialize, Debug, PartialEq)]
|
||||||
@ -476,10 +421,14 @@ pub enum WsMsg {
|
|||||||
ProjectUsersLoaded(Vec<User>),
|
ProjectUsersLoaded(Vec<User>),
|
||||||
|
|
||||||
// issue
|
// issue
|
||||||
IssueUpdateRequest(i32, UpdateIssuePayload),
|
IssueUpdateRequest(IssueId, UpdateIssuePayload),
|
||||||
IssueUpdated(Issue),
|
IssueUpdated(Issue),
|
||||||
IssueDeleteRequest(i32),
|
IssueDeleteRequest(IssueId),
|
||||||
IssueDeleted(i32),
|
IssueDeleted(IssueId),
|
||||||
IssueCreateRequest(CreateIssuePayload),
|
IssueCreateRequest(CreateIssuePayload),
|
||||||
IssueCreated(Issue),
|
IssueCreated(Issue),
|
||||||
|
|
||||||
|
// comments
|
||||||
|
IssueCommentsRequest(IssueId),
|
||||||
|
IssueCommentsLoaded(Vec<Comment>),
|
||||||
}
|
}
|
||||||
|
@ -48,4 +48,55 @@ insert into issues(
|
|||||||
2,
|
2,
|
||||||
1
|
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;
|
select * from tokens;
|
||||||
|
@ -34,14 +34,6 @@ async fn main() -> Result<(), String> {
|
|||||||
.data(db_addr.clone())
|
.data(db_addr.clone())
|
||||||
.data(crate::db::build_pool())
|
.data(crate::db::build_pool())
|
||||||
.service(crate::ws::index)
|
.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(
|
.service(
|
||||||
web::scope("/comments")
|
web::scope("/comments")
|
||||||
.service(crate::routes::comments::create)
|
.service(crate::routes::comments::create)
|
||||||
@ -49,11 +41,6 @@ async fn main() -> Result<(), String> {
|
|||||||
.service(crate::routes::comments::delete),
|
.service(crate::routes::comments::delete),
|
||||||
)
|
)
|
||||||
.service(web::scope("/currentUser").service(crate::routes::users::current_user))
|
.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)
|
.bind(addr)
|
||||||
.map_err(|e| format!("{}", e))?
|
.map_err(|e| format!("{}", e))?
|
||||||
|
@ -26,8 +26,6 @@ impl Into<jirs_data::Comment> for Comment {
|
|||||||
issue_id: self.issue_id,
|
issue_id: self.issue_id,
|
||||||
created_at: self.created_at,
|
created_at: self.created_at,
|
||||||
updated_at: self.updated_at,
|
updated_at: self.updated_at,
|
||||||
|
|
||||||
user: None,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -86,31 +84,6 @@ impl Into<jirs_data::Issue> for Issue {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Into<jirs_data::FullIssue> 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)]
|
#[derive(Debug, Serialize, Deserialize, Insertable)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
#[table_name = "issues"]
|
#[table_name = "issues"]
|
||||||
|
@ -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("<!DOCTYPE html><html><head><title>Issues</title></head><body>Foo</body></html>")
|
|
||||||
}
|
|
||||||
|
|
||||||
#[get("/{id}")]
|
|
||||||
pub async fn issue_with_users_and_comments(
|
|
||||||
req: HttpRequest,
|
|
||||||
path: Path<i32>,
|
|
||||||
db: Data<Addr<DbExecutor>>,
|
|
||||||
) -> 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<jirs_data::CreateIssuePayload>,
|
|
||||||
db: Data<Addr<DbExecutor>>,
|
|
||||||
) -> 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<jirs_data::UpdateIssuePayload>,
|
|
||||||
path: Path<i32>,
|
|
||||||
db: Data<Addr<DbExecutor>>,
|
|
||||||
) -> 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<i32>, db: Data<Addr<DbExecutor>>) -> 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<Addr<DbExecutor>>,
|
|
||||||
) -> Result<jirs_data::FullIssue, ServiceErrors> {
|
|
||||||
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)
|
|
||||||
}
|
|
@ -9,8 +9,6 @@ use crate::middleware::authorize::token_from_headers;
|
|||||||
use crate::models::User;
|
use crate::models::User;
|
||||||
|
|
||||||
pub mod comments;
|
pub mod comments;
|
||||||
pub mod issues;
|
|
||||||
pub mod projects;
|
|
||||||
pub mod users;
|
pub mod users;
|
||||||
|
|
||||||
pub async fn user_from_request(
|
pub async fn user_from_request(
|
||||||
|
@ -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<Addr<DbExecutor>>,
|
|
||||||
) -> 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<jirs_data::UpdateProjectPayload>,
|
|
||||||
path: Path<i32>,
|
|
||||||
db: Data<Addr<DbExecutor>>,
|
|
||||||
) -> 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(),
|
|
||||||
}
|
|
||||||
}
|
|
22
jirs-server/src/ws/comments.rs
Normal file
22
jirs-server/src/ws/comments.rs
Normal file
@ -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<Addr<DbExecutor>>,
|
||||||
|
user: &Option<jirs_data::User>,
|
||||||
|
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)))
|
||||||
|
}
|
136
jirs-server/src/ws/issues.rs
Normal file
136
jirs-server/src/ws/issues.rs
Normal file
@ -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<Addr<DbExecutor>>,
|
||||||
|
user: &Option<jirs_data::User>,
|
||||||
|
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<Addr<DbExecutor>>,
|
||||||
|
user: &Option<jirs_data::User>,
|
||||||
|
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<Addr<DbExecutor>>,
|
||||||
|
user: &Option<jirs_data::User>,
|
||||||
|
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<Addr<DbExecutor>>, user: &Option<jirs_data::User>) -> WsResult {
|
||||||
|
let project_id = current_user(user).map(|u| u.project_id)?;
|
||||||
|
|
||||||
|
let issues: Vec<jirs_data::Issue> = 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)))
|
||||||
|
}
|
@ -1,5 +1,3 @@
|
|||||||
use std::collections::HashMap;
|
|
||||||
|
|
||||||
use actix::{Actor, Addr, StreamHandler};
|
use actix::{Actor, Addr, StreamHandler};
|
||||||
use actix_web::web::Data;
|
use actix_web::web::Data;
|
||||||
use actix_web::{get, web, Error, HttpRequest, HttpResponse};
|
use actix_web::{get, web, Error, HttpRequest, HttpResponse};
|
||||||
@ -8,13 +6,21 @@ use actix_web_actors::ws;
|
|||||||
use jirs_data::WsMsg;
|
use jirs_data::WsMsg;
|
||||||
|
|
||||||
use crate::db::authorize_user::AuthorizeUser;
|
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;
|
use crate::db::DbExecutor;
|
||||||
|
|
||||||
type WsResult = std::result::Result<Option<WsMsg>, WsMsg>;
|
pub mod comments;
|
||||||
|
pub mod issues;
|
||||||
|
pub mod projects;
|
||||||
|
pub mod users;
|
||||||
|
|
||||||
|
pub type WsResult = std::result::Result<Option<WsMsg>, WsMsg>;
|
||||||
|
|
||||||
|
pub fn current_user(current_user: &Option<jirs_data::User>) -> Result<&jirs_data::User, WsMsg> {
|
||||||
|
current_user
|
||||||
|
.as_ref()
|
||||||
|
.map(|u| u)
|
||||||
|
.ok_or_else(|| WsMsg::AuthorizeExpired)
|
||||||
|
}
|
||||||
|
|
||||||
trait WsMessageSender {
|
trait WsMessageSender {
|
||||||
fn send_msg(&mut self, msg: jirs_data::WsMsg);
|
fn send_msg(&mut self, msg: jirs_data::WsMsg);
|
||||||
@ -46,13 +52,45 @@ impl WebSocketActor {
|
|||||||
let msg = match 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::IssueUpdateRequest(id, payload) => block_on(self.update_issue(id, payload))?,
|
|
||||||
WsMsg::IssueCreateRequest(payload) => block_on(self.add_issue(payload))?,
|
// Issues
|
||||||
WsMsg::IssueDeleteRequest(id) => block_on(self.delete_issue(id))?,
|
WsMsg::IssueUpdateRequest(id, payload) => block_on(issues::update_issue(
|
||||||
WsMsg::ProjectRequest => block_on(self.load_project())?,
|
&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::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);
|
error!("No handle for {:?} specified", msg);
|
||||||
None
|
None
|
||||||
@ -81,152 +119,6 @@ impl WebSocketActor {
|
|||||||
};
|
};
|
||||||
Ok(m)
|
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<jirs_data::Issue> =
|
|
||||||
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<Result<ws::Message, ws::ProtocolError>> for WebSocketActor {
|
impl StreamHandler<Result<ws::Message, ws::ProtocolError>> for WebSocketActor {
|
||||||
|
23
jirs-server/src/ws/projects.rs
Normal file
23
jirs-server/src/ws/projects.rs
Normal file
@ -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<Addr<DbExecutor>>,
|
||||||
|
user: &Option<jirs_data::User>,
|
||||||
|
) -> 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)
|
||||||
|
}
|
22
jirs-server/src/ws/users.rs
Normal file
22
jirs-server/src/ws/users.rs
Normal file
@ -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<Addr<DbExecutor>>,
|
||||||
|
user: &Option<jirs_data::User>,
|
||||||
|
) -> 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)
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user