Add more verbose errors. Fix register
This commit is contained in:
parent
8e939faebd
commit
d3f30e03a1
12
Cargo.lock
generated
12
Cargo.lock
generated
@ -3116,9 +3116,9 @@ checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde"
|
name = "serde"
|
||||||
version = "1.0.116"
|
version = "1.0.117"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "96fe57af81d28386a513cbc6858332abc6117cfdb5999647c6444b8f43a370a5"
|
checksum = "b88fa983de7720629c9387e9f517353ed404164b1e482c970a90c1a4aaf7dc1a"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"serde_derive",
|
"serde_derive",
|
||||||
]
|
]
|
||||||
@ -3135,9 +3135,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde_derive"
|
name = "serde_derive"
|
||||||
version = "1.0.116"
|
version = "1.0.117"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f630a6370fd8e457873b4bd2ffdae75408bc291ba72be773772a4c2a065d9ae8"
|
checksum = "cbd1ae72adb44aab48f325a02444a5fc079349a8d804c1fc922aed3f7454c74e"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
@ -3329,9 +3329,9 @@ checksum = "2d67a5a62ba6e01cb2192ff309324cb4875d0c451d55fe2319433abe7a05a8ee"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "syn"
|
name = "syn"
|
||||||
version = "1.0.44"
|
version = "1.0.45"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "e03e57e4fcbfe7749842d53e24ccb9aa12b7252dbe5e91d2acad31834c8b8fdd"
|
checksum = "ea9c5432ff16d6152371f808fb5a871cd67368171b09bb21b43df8e4a47a3556"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
|
15
Dockerfile.build
Normal file
15
Dockerfile.build
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
FROM ubuntu:18.04
|
||||||
|
|
||||||
|
WORKDIR /app/
|
||||||
|
|
||||||
|
RUN apt-get update && apt-get install -y curl git openssl libpq-dev gcc openssl1.0 make cmake
|
||||||
|
RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- --default-toolchain nightly -y
|
||||||
|
RUN . $HOME/.cargo/env && \
|
||||||
|
rustup toolchain install nightly && rustup default nightly
|
||||||
|
|
||||||
|
RUN ls -al /app
|
||||||
|
CMD . $HOME/.cargo/env && \
|
||||||
|
cd ./jirs-server && \
|
||||||
|
rm -Rf ./target/debug/jirs_server && \
|
||||||
|
cargo build --bin jirs_server --release --no-default-features --features local-storage && \
|
||||||
|
cp /app/target/release/jirs_server /app/build/
|
@ -7,6 +7,24 @@ services:
|
|||||||
- POSTGRES_USER=postgres
|
- POSTGRES_USER=postgres
|
||||||
- POSTGRES_HOST_AUTH_METHOD=trust
|
- POSTGRES_HOST_AUTH_METHOD=trust
|
||||||
|
|
||||||
|
build-server:
|
||||||
|
build:
|
||||||
|
dockerfile: ./Dockerfile.build
|
||||||
|
context: .
|
||||||
|
stdin_open: true
|
||||||
|
tty: true
|
||||||
|
volumes:
|
||||||
|
- ./build:/app/build
|
||||||
|
- type: volume
|
||||||
|
source: rs-target
|
||||||
|
target: /app/jirs-server/target
|
||||||
|
- ./Cargo.toml:/app/Cargo.toml
|
||||||
|
- ./Cargo.lock:/app/Cargo.lock
|
||||||
|
- ./jirs-server:/app/jirs-server
|
||||||
|
- ./jirs-data:/app/jirs-data
|
||||||
|
- ./jirs-cli:/app/jirs-cli
|
||||||
|
- ./jirs-client:/app/jirs-client
|
||||||
|
- ./jirs-css:/app/jirs-css
|
||||||
server:
|
server:
|
||||||
build:
|
build:
|
||||||
dockerfile: ./jirs-server/Dockerfile
|
dockerfile: ./jirs-server/Dockerfile
|
||||||
@ -57,3 +75,5 @@ services:
|
|||||||
|
|
||||||
volumes:
|
volumes:
|
||||||
assets:
|
assets:
|
||||||
|
build:
|
||||||
|
rs-target:
|
||||||
|
@ -29,7 +29,7 @@ lto = true
|
|||||||
opt-level = 's'
|
opt-level = 's'
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
jirs-data = { path = "../jirs-data" }
|
jirs-data = { path = "../jirs-data", features = ["frontend"] }
|
||||||
|
|
||||||
wee_alloc = "*"
|
wee_alloc = "*"
|
||||||
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
use syntect::easy::HighlightLines;
|
// use syntect::easy::HighlightLines;
|
||||||
use syntect::highlighting::{FontStyle, Style};
|
// use syntect::highlighting::{FontStyle, Style};
|
||||||
use syntect::parsing::SyntaxReference;
|
// use syntect::parsing::SyntaxReference;
|
||||||
use wasm_bindgen::prelude::*;
|
use wasm_bindgen::prelude::*;
|
||||||
|
|
||||||
#[wasm_bindgen(final, js_name = JirsCodeBuilder)]
|
#[wasm_bindgen(final, js_name = JirsCodeBuilder)]
|
||||||
@ -15,51 +15,52 @@ impl JirsCodeBuilder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[wasm_bindgen]
|
#[wasm_bindgen]
|
||||||
pub fn hi_code(&mut self, lang: &str, code: &str) -> String {
|
pub fn hi_code(&mut self, _lang: &str, code: &str) -> String {
|
||||||
let syntax = {
|
// let syntax = {
|
||||||
match crate::hi::syntax_set::load().find_syntax_by_name(lang) {
|
// match crate::hi::syntax_set::load().find_syntax_by_name(lang) {
|
||||||
Some(s) => s.clone(),
|
// Some(s) => s.clone(),
|
||||||
_ => {
|
// _ => {
|
||||||
return code.to_string();
|
// return code.to_string();
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
};
|
// };
|
||||||
let mut buffer: Vec<String> = Vec::with_capacity(code.lines().count() * 2);
|
// let mut buffer: Vec<String> = Vec::with_capacity(code.lines().count() * 2);
|
||||||
for line in code.lines() {
|
// for line in code.lines() {
|
||||||
self.hi(&syntax, line, &mut buffer);
|
// self.hi(&syntax, line, &mut buffer);
|
||||||
buffer.push("<br />".to_string());
|
// buffer.push("<br />".to_string());
|
||||||
}
|
// }
|
||||||
buffer.join("")
|
// buffer.join("")
|
||||||
|
code.to_string()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn hi<'l>(&mut self, syntax: &SyntaxReference, line: &'l str, buffer: &mut Vec<String>) {
|
// fn hi<'l>(&mut self, syntax: &SyntaxReference, line: &'l str, buffer: &mut Vec<String>) {
|
||||||
let mut h = HighlightLines::new(syntax, &crate::hi::THEME_SET.themes["base16-ocean-dark"]); // inspired-github
|
// let mut h = HighlightLines::new(syntax, &crate::hi::THEME_SET.themes["base16-ocean-dark"]); // inspired-github
|
||||||
let tokens = { h.highlight(line, &crate::hi::syntax_set::load()) };
|
// let tokens = { h.highlight(line, &crate::hi::syntax_set::load()) };
|
||||||
|
//
|
||||||
for (style, token) in tokens.into_iter() {
|
// for (style, token) in tokens.into_iter() {
|
||||||
let Style {
|
// let Style {
|
||||||
foreground: f,
|
// foreground: f,
|
||||||
background: b,
|
// background: b,
|
||||||
font_style,
|
// font_style,
|
||||||
} = style;
|
// } = style;
|
||||||
let fs = if font_style == FontStyle::BOLD {
|
// let fs = if font_style == FontStyle::BOLD {
|
||||||
"font-weight: bold"
|
// "font-weight: bold"
|
||||||
} else if font_style == FontStyle::ITALIC {
|
// } else if font_style == FontStyle::ITALIC {
|
||||||
"font-style: italic"
|
// "font-style: italic"
|
||||||
} else if font_style == FontStyle::UNDERLINE {
|
// } else if font_style == FontStyle::UNDERLINE {
|
||||||
"text-decoration: underline"
|
// "text-decoration: underline"
|
||||||
} else {
|
// } else {
|
||||||
""
|
// ""
|
||||||
};
|
// };
|
||||||
|
//
|
||||||
buffer.push(format!(
|
// buffer.push(format!(
|
||||||
r#"<span style="color: rgba({f_r}, {f_g}, {f_b}, {f_a});background:rgba({b_r}, {b_g}, {b_b}, {b_a}); {fs}">{t}</span>"#,
|
// r#"<span style="color: rgba({f_r}, {f_g}, {f_b}, {f_a});background:rgba({b_r}, {b_g}, {b_b}, {b_a}); {fs}">{t}</span>"#,
|
||||||
t = if token.is_empty() { " " } else { token },
|
// t = if token.is_empty() { " " } else { token },
|
||||||
f_r = f.r, f_g = f.g, f_b = f.b, f_a = f.a, b_r = b.r, b_g = b.g, b_b = b.b, b_a = b.a,
|
// f_r = f.r, f_g = f.g, f_b = f.b, f_a = f.a, b_r = b.r, b_g = b.g, b_b = b.b, b_a = b.a,
|
||||||
fs = fs
|
// fs = fs
|
||||||
));
|
// ));
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn define() {
|
pub fn define() {
|
||||||
|
@ -9,10 +9,10 @@ use jirs_data::*;
|
|||||||
|
|
||||||
use crate::model::{ModalType, Model, Page};
|
use crate::model::{ModalType, Model, Page};
|
||||||
use crate::shared::styled_date_time_input::StyledDateTimeChanged;
|
use crate::shared::styled_date_time_input::StyledDateTimeChanged;
|
||||||
use crate::shared::styled_rte::RteMsg;
|
use crate::shared::{go_to_board, go_to_login, styled_tooltip};
|
||||||
|
// use crate::shared::styled_rte::RteMsg;
|
||||||
use crate::shared::styled_select::StyledSelectChanged;
|
use crate::shared::styled_select::StyledSelectChanged;
|
||||||
use crate::shared::styled_tooltip::{Variant as StyledTooltip, Variant};
|
use crate::shared::styled_tooltip::{Variant as StyledTooltip, Variant};
|
||||||
use crate::shared::{go_to_board, go_to_login, styled_tooltip};
|
|
||||||
use crate::ws::{flush_queue, open_socket, read_incoming, send_ws_msg};
|
use crate::ws::{flush_queue, open_socket, read_incoming, send_ws_msg};
|
||||||
|
|
||||||
mod changes;
|
mod changes;
|
||||||
@ -85,7 +85,7 @@ pub enum Msg {
|
|||||||
StrInputChanged(FieldId, String),
|
StrInputChanged(FieldId, String),
|
||||||
U32InputChanged(FieldId, u32),
|
U32InputChanged(FieldId, u32),
|
||||||
FileInputChanged(FieldId, Vec<File>),
|
FileInputChanged(FieldId, Vec<File>),
|
||||||
Rte(FieldId, RteMsg),
|
// Rte(FieldId, RteMsg),
|
||||||
|
|
||||||
// issues
|
// issues
|
||||||
AddIssue,
|
AddIssue,
|
||||||
@ -279,9 +279,9 @@ pub fn render(host_url: String, ws_url: String) {
|
|||||||
HOST_URL = host_url;
|
HOST_URL = host_url;
|
||||||
WS_URL = ws_url;
|
WS_URL = ws_url;
|
||||||
}
|
}
|
||||||
if !cfg!(debug_assertions) {
|
// if !cfg!(debug_assertions) {
|
||||||
crate::hi::syntax_set::load();
|
// crate::hi::syntax_set::load();
|
||||||
}
|
// }
|
||||||
elements::define();
|
elements::define();
|
||||||
|
|
||||||
let _app = seed::App::builder(update, view)
|
let _app = seed::App::builder(update, view)
|
||||||
|
@ -14,7 +14,7 @@ use crate::{
|
|||||||
drag::DragState, styled_checkbox::StyledCheckboxState,
|
drag::DragState, styled_checkbox::StyledCheckboxState,
|
||||||
styled_date_time_input::StyledDateTimeInputState, styled_editor::Mode,
|
styled_date_time_input::StyledDateTimeInputState, styled_editor::Mode,
|
||||||
styled_image_input::StyledImageInputState, styled_input::StyledInputState,
|
styled_image_input::StyledImageInputState, styled_input::StyledInputState,
|
||||||
styled_rte::StyledRteState, styled_select::StyledSelectState,
|
/*styled_rte::StyledRteState,*/ styled_select::StyledSelectState,
|
||||||
},
|
},
|
||||||
EditIssueModalSection, FieldId, Msg, ProjectFieldId,
|
EditIssueModalSection, FieldId, Msg, ProjectFieldId,
|
||||||
};
|
};
|
||||||
@ -371,7 +371,7 @@ pub struct ProjectSettingsPage {
|
|||||||
pub edit_column_id: Option<IssueStatusId>,
|
pub edit_column_id: Option<IssueStatusId>,
|
||||||
pub creating_issue_status: bool,
|
pub creating_issue_status: bool,
|
||||||
pub name: StyledInputState,
|
pub name: StyledInputState,
|
||||||
pub description_rte: StyledRteState,
|
// pub description_rte: StyledRteState,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ProjectSettingsPage {
|
impl ProjectSettingsPage {
|
||||||
@ -411,9 +411,9 @@ impl ProjectSettingsPage {
|
|||||||
FieldId::ProjectSettings(ProjectFieldId::IssueStatusName),
|
FieldId::ProjectSettings(ProjectFieldId::IssueStatusName),
|
||||||
"",
|
"",
|
||||||
),
|
),
|
||||||
description_rte: StyledRteState::new(FieldId::ProjectSettings(
|
// description_rte: StyledRteState::new(FieldId::ProjectSettings(
|
||||||
ProjectFieldId::Description,
|
// ProjectFieldId::Description,
|
||||||
)),
|
// )),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -54,7 +54,7 @@ pub fn update(msg: Msg, model: &mut Model, orders: &mut impl Orders<Msg>) {
|
|||||||
page.project_category_state.update(&msg, orders);
|
page.project_category_state.update(&msg, orders);
|
||||||
page.time_tracking.update(&msg);
|
page.time_tracking.update(&msg);
|
||||||
page.name.update(&msg);
|
page.name.update(&msg);
|
||||||
page.description_rte.update(&msg, orders);
|
// page.description_rte.update(&msg, orders);
|
||||||
|
|
||||||
match msg {
|
match msg {
|
||||||
Msg::StrInputChanged(FieldId::ProjectSettings(ProjectFieldId::Name), text) => {
|
Msg::StrInputChanged(FieldId::ProjectSettings(ProjectFieldId::Name), text) => {
|
||||||
|
@ -12,11 +12,11 @@ use crate::shared::styled_field::StyledField;
|
|||||||
use crate::shared::styled_form::StyledForm;
|
use crate::shared::styled_form::StyledForm;
|
||||||
use crate::shared::styled_icon::{Icon, StyledIcon};
|
use crate::shared::styled_icon::{Icon, StyledIcon};
|
||||||
use crate::shared::styled_input::StyledInput;
|
use crate::shared::styled_input::StyledInput;
|
||||||
use crate::shared::styled_rte::StyledRte;
|
|
||||||
use crate::shared::styled_select::StyledSelect;
|
|
||||||
use crate::shared::styled_textarea::StyledTextarea;
|
|
||||||
use crate::shared::{inner_layout, ToChild, ToNode};
|
use crate::shared::{inner_layout, ToChild, ToNode};
|
||||||
use crate::{model, FieldId, Msg, PageChanged, ProjectFieldId, ProjectPageChange};
|
use crate::{model, FieldId, Msg, PageChanged, ProjectFieldId, ProjectPageChange};
|
||||||
|
// use crate::shared::styled_rte::StyledRte;
|
||||||
|
use crate::shared::styled_select::StyledSelect;
|
||||||
|
use crate::shared::styled_textarea::StyledTextarea;
|
||||||
|
|
||||||
static TIME_TRACKING_FIBONACCI: &str = include_str!("./time_tracking_fibonacci.txt");
|
static TIME_TRACKING_FIBONACCI: &str = include_str!("./time_tracking_fibonacci.txt");
|
||||||
static TIME_TRACKING_HOURLY: &str = include_str!("./time_tracking_hourly.txt");
|
static TIME_TRACKING_HOURLY: &str = include_str!("./time_tracking_hourly.txt");
|
||||||
@ -32,15 +32,15 @@ pub fn view(model: &model::Model) -> Node<Msg> {
|
|||||||
|
|
||||||
let description_field = description_field(page);
|
let description_field = description_field(page);
|
||||||
|
|
||||||
let desc_rte = StyledField::build()
|
// let desc_rte = StyledField::build()
|
||||||
.input(
|
// .input(
|
||||||
StyledRte::build(FieldId::ProjectSettings(ProjectFieldId::Description))
|
// StyledRte::build(FieldId::ProjectSettings(ProjectFieldId::Description))
|
||||||
.state(&page.description_rte)
|
// .state(&page.description_rte)
|
||||||
.build()
|
// .build()
|
||||||
.into_node(),
|
// .into_node(),
|
||||||
)
|
// )
|
||||||
.build()
|
// .build()
|
||||||
.into_node();
|
// .into_node();
|
||||||
|
|
||||||
let category_field = category_field(page);
|
let category_field = category_field(page);
|
||||||
|
|
||||||
@ -89,7 +89,7 @@ pub fn view(model: &model::Model) -> Node<Msg> {
|
|||||||
}))
|
}))
|
||||||
.add_field(name_field)
|
.add_field(name_field)
|
||||||
.add_field(url_field)
|
.add_field(url_field)
|
||||||
.add_field(desc_rte)
|
// .add_field(desc_rte)
|
||||||
.add_field(description_field)
|
.add_field(description_field)
|
||||||
.add_field(category_field)
|
.add_field(category_field)
|
||||||
.add_field(time_tracking_field)
|
.add_field(time_tracking_field)
|
||||||
|
@ -24,7 +24,7 @@ pub mod styled_image_input;
|
|||||||
pub mod styled_input;
|
pub mod styled_input;
|
||||||
pub mod styled_link;
|
pub mod styled_link;
|
||||||
pub mod styled_modal;
|
pub mod styled_modal;
|
||||||
pub mod styled_rte;
|
// pub mod styled_rte;
|
||||||
pub mod styled_select;
|
pub mod styled_select;
|
||||||
pub mod styled_select_child;
|
pub mod styled_select_child;
|
||||||
pub mod styled_textarea;
|
pub mod styled_textarea;
|
||||||
|
@ -27,26 +27,13 @@ impl IntoNavItemIcon for Icon {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn update(msg: &Msg, model: &mut Model, orders: &mut impl Orders<Msg>) {
|
pub fn update(msg: &Msg, model: &mut Model, orders: &mut impl Orders<Msg>) {
|
||||||
match msg {
|
let m = match msg {
|
||||||
Msg::MessageInvitationApproved(token) => {
|
Msg::MessageInvitationApproved(token) => WsMsg::InvitationAcceptRequest(*token),
|
||||||
send_ws_msg(
|
Msg::MessageInvitationDismiss(token) => WsMsg::InvitationRejectRequest(*token),
|
||||||
WsMsg::InvitationAcceptRequest(*token),
|
Msg::MessageSeen(id) => WsMsg::MessageMarkSeen(*id),
|
||||||
model.ws.as_ref(),
|
_ => return,
|
||||||
orders,
|
};
|
||||||
);
|
send_ws_msg(m, model.ws.as_ref(), orders);
|
||||||
}
|
|
||||||
Msg::MessageInvitationDismiss(token) => {
|
|
||||||
send_ws_msg(
|
|
||||||
WsMsg::InvitationRejectRequest(*token),
|
|
||||||
model.ws.as_ref(),
|
|
||||||
orders,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
Msg::MessageSeen(id) => {
|
|
||||||
send_ws_msg(WsMsg::MessageMarkSeen(*id), model.ws.as_ref(), orders);
|
|
||||||
}
|
|
||||||
_ => (),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn render(model: &Model) -> Vec<Node<Msg>> {
|
pub fn render(model: &Model) -> Vec<Node<Msg>> {
|
||||||
@ -82,6 +69,15 @@ pub fn render(model: &Model) -> Vec<Node<Msg>> {
|
|||||||
)
|
)
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let issue_nav = if model.issue_statuses.is_empty() {
|
||||||
|
vec![]
|
||||||
|
} else {
|
||||||
|
vec![
|
||||||
|
navbar_left_item("Search issues", Icon::Search, None, None),
|
||||||
|
navbar_left_item("Create Issue", Icon::Plus, Some("/add-issue"), None),
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
vec![
|
vec![
|
||||||
about_tooltip_popup(model),
|
about_tooltip_popup(model),
|
||||||
messages_tooltip_popup(model),
|
messages_tooltip_popup(model),
|
||||||
@ -92,8 +88,7 @@ pub fn render(model: &Model) -> Vec<Node<Msg>> {
|
|||||||
attrs![At::Href => "/"],
|
attrs![At::Href => "/"],
|
||||||
div![class!["styledLogo"], logo_svg]
|
div![class!["styledLogo"], logo_svg]
|
||||||
],
|
],
|
||||||
navbar_left_item("Search issues", Icon::Search, None, None),
|
issue_nav,
|
||||||
navbar_left_item("Create Issue", Icon::Plus, Some("/add-issue"), None),
|
|
||||||
div![
|
div![
|
||||||
class!["bottom"],
|
class!["bottom"],
|
||||||
navbar_left_item("Profile", user_icon, Some("/profile"), None),
|
navbar_left_item("Profile", user_icon, Some("/profile"), None),
|
||||||
@ -158,14 +153,11 @@ fn messages_tooltip_popup(model: &Model) -> Node<Msg> {
|
|||||||
fn message_ui(model: &Model, message: &Message) -> Option<Node<Msg>> {
|
fn message_ui(model: &Model, message: &Message) -> Option<Node<Msg>> {
|
||||||
let Message {
|
let Message {
|
||||||
id,
|
id,
|
||||||
receiver_id: _,
|
|
||||||
sender_id: _,
|
|
||||||
summary,
|
summary,
|
||||||
description,
|
description,
|
||||||
message_type,
|
message_type,
|
||||||
hyper_link,
|
hyper_link,
|
||||||
created_at: _,
|
..
|
||||||
updated_at: _,
|
|
||||||
} = message;
|
} = message;
|
||||||
let message_id = *id;
|
let message_id = *id;
|
||||||
|
|
||||||
@ -203,10 +195,7 @@ fn message_ui(model: &Model, message: &Message) -> Option<Node<Msg>> {
|
|||||||
|
|
||||||
let node = match message_type {
|
let node = match message_type {
|
||||||
MessageType::ReceivedInvitation => {
|
MessageType::ReceivedInvitation => {
|
||||||
let token: InvitationToken = match hyper_link.trim_start_matches('#').parse() {
|
let token: InvitationToken = hyper_link.trim_start_matches('#').parse().ok()?;
|
||||||
Err(_) => return None,
|
|
||||||
Ok(n) => n,
|
|
||||||
};
|
|
||||||
let accept = StyledButton::build()
|
let accept = StyledButton::build()
|
||||||
.primary()
|
.primary()
|
||||||
.text("Accept")
|
.text("Accept")
|
||||||
|
@ -70,10 +70,10 @@ impl<'l> StyledButtonBuilder<'l> {
|
|||||||
self.variant(Variant::Empty)
|
self.variant(Variant::Empty)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn button_id(mut self, button_id: ButtonId) -> Self {
|
// pub fn button_id(mut self, button_id: ButtonId) -> Self {
|
||||||
self.button_id = Some(button_id);
|
// self.button_id = Some(button_id);
|
||||||
self
|
// self
|
||||||
}
|
// }
|
||||||
|
|
||||||
pub fn disabled(mut self, value: bool) -> Self {
|
pub fn disabled(mut self, value: bool) -> Self {
|
||||||
self.disabled = Some(value);
|
self.disabled = Some(value);
|
||||||
|
@ -173,13 +173,13 @@ impl<'l> StyledSelectBuilder<'l> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn try_state<'state: 'l>(self, state: Option<&'state StyledSelectState>) -> Self {
|
// pub fn try_state<'state: 'l>(self, state: Option<&'state StyledSelectState>) -> Self {
|
||||||
if let Some(s) = state {
|
// if let Some(s) = state {
|
||||||
self.state(s)
|
// self.state(s)
|
||||||
} else {
|
// } else {
|
||||||
self
|
// self
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
pub fn state<'state: 'l>(self, state: &'state StyledSelectState) -> Self {
|
pub fn state<'state: 'l>(self, state: &'state StyledSelectState) -> Self {
|
||||||
self.opened(state.opened)
|
self.opened(state.opened)
|
||||||
|
@ -89,15 +89,15 @@ impl<'l> StyledTooltipBuilder<'l> {
|
|||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn table_tooltip(mut self) -> Self {
|
// pub fn table_tooltip(mut self) -> Self {
|
||||||
self.variant = Variant::TableBuilder;
|
// self.variant = Variant::TableBuilder;
|
||||||
self
|
// self
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
pub fn code_tooltip(mut self) -> Self {
|
// pub fn code_tooltip(mut self) -> Self {
|
||||||
self.variant = Variant::CodeBuilder;
|
// self.variant = Variant::CodeBuilder;
|
||||||
self
|
// self
|
||||||
}
|
// }
|
||||||
|
|
||||||
pub fn date_time_picker(mut self) -> Self {
|
pub fn date_time_picker(mut self) -> Self {
|
||||||
self.variant = Variant::DateTimeBuilder;
|
self.variant = Variant::DateTimeBuilder;
|
||||||
|
@ -13,7 +13,8 @@ name = "jirs_data"
|
|||||||
path = "./src/lib.rs"
|
path = "./src/lib.rs"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
backend = [ "diesel" ]
|
backend = ["diesel"]
|
||||||
|
frontend = []
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
serde = "*"
|
serde = "*"
|
||||||
|
@ -14,7 +14,7 @@ pub use payloads::*;
|
|||||||
pub use sql::*;
|
pub use sql::*;
|
||||||
|
|
||||||
mod fields;
|
mod fields;
|
||||||
mod msg;
|
pub mod msg;
|
||||||
mod payloads;
|
mod payloads;
|
||||||
|
|
||||||
#[cfg(feature = "backend")]
|
#[cfg(feature = "backend")]
|
||||||
@ -80,6 +80,7 @@ impl IssueType {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "frontend")]
|
||||||
impl Into<u32> for IssueType {
|
impl Into<u32> for IssueType {
|
||||||
fn into(self) -> u32 {
|
fn into(self) -> u32 {
|
||||||
match self {
|
match self {
|
||||||
@ -90,6 +91,7 @@ impl Into<u32> for IssueType {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "frontend")]
|
||||||
impl Into<IssueType> for u32 {
|
impl Into<IssueType> for u32 {
|
||||||
fn into(self) -> IssueType {
|
fn into(self) -> IssueType {
|
||||||
match self {
|
match self {
|
||||||
@ -102,7 +104,7 @@ impl Into<IssueType> for u32 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl IssueType {
|
impl IssueType {
|
||||||
pub fn to_str(&self) -> &'static str {
|
pub fn to_str<'l>(&self) -> &'l str {
|
||||||
match self {
|
match self {
|
||||||
IssueType::Task => "task",
|
IssueType::Task => "task",
|
||||||
IssueType::Bug => "bug",
|
IssueType::Bug => "bug",
|
||||||
|
@ -9,6 +9,114 @@ use crate::{
|
|||||||
UsernameString,
|
UsernameString,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
|
||||||
|
#[repr(C)]
|
||||||
|
pub enum WsError {
|
||||||
|
InvalidLoginPair,
|
||||||
|
InvalidSignInToken,
|
||||||
|
|
||||||
|
// Issue status
|
||||||
|
NoIssueStatuses,
|
||||||
|
FailedToFetchIssueStatuses,
|
||||||
|
|
||||||
|
// tokens
|
||||||
|
FailedToDisableBindToken,
|
||||||
|
BindTokenNotExists,
|
||||||
|
NoBindToken,
|
||||||
|
FailedToCreateBindToken,
|
||||||
|
AccessTokenNotExists,
|
||||||
|
|
||||||
|
// users
|
||||||
|
UserNotExists(UserId),
|
||||||
|
NoMatchingPair(UsernameString, EmailString),
|
||||||
|
InvalidPair(UsernameString, EmailString),
|
||||||
|
TakenPair(UsernameString, EmailString),
|
||||||
|
FailedToLoadProjectUsers,
|
||||||
|
FailedToLoadAssignees,
|
||||||
|
FailedToChangeAvatar,
|
||||||
|
FailedToLoadInvitedUsers,
|
||||||
|
|
||||||
|
// user projects
|
||||||
|
InvalidUserProject,
|
||||||
|
|
||||||
|
// comments
|
||||||
|
FailedToLoadComments,
|
||||||
|
InvalidComment,
|
||||||
|
FailedToUpdateComment,
|
||||||
|
UnableToDeleteComment,
|
||||||
|
|
||||||
|
// epics
|
||||||
|
FailedToLoadEpics,
|
||||||
|
InvalidEpic,
|
||||||
|
FailedToUpdateEpic,
|
||||||
|
UnableToDeleteEpic,
|
||||||
|
|
||||||
|
// invitations
|
||||||
|
FailedToLoadInvitations,
|
||||||
|
InvalidInvitation,
|
||||||
|
FailedToUpdateInvitation,
|
||||||
|
UnableToDeleteInvitation,
|
||||||
|
InvitationRevoked,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl WsError {
|
||||||
|
pub fn to_str<'l>(&self) -> &'l str {
|
||||||
|
match self {
|
||||||
|
WsError::InvalidLoginPair => "E-Mail and Login does not match",
|
||||||
|
WsError::InvalidSignInToken => "Given token is not valid",
|
||||||
|
WsError::NoIssueStatuses => {
|
||||||
|
"Failed to fetch first issue status. Are you sure there is any?"
|
||||||
|
}
|
||||||
|
WsError::FailedToFetchIssueStatuses => "Failed to load issue statuses",
|
||||||
|
WsError::FailedToDisableBindToken => "Failed to disable one use token",
|
||||||
|
WsError::BindTokenNotExists => "Used single use bind token does not exists in database",
|
||||||
|
WsError::NoBindToken => "Current user does not have any active tokens",
|
||||||
|
WsError::FailedToCreateBindToken => {
|
||||||
|
"Something went wrong when creating bind token. Please try later"
|
||||||
|
}
|
||||||
|
WsError::AccessTokenNotExists => "Token used for authentication does not exists",
|
||||||
|
WsError::UserNotExists(_) => "User does not exists",
|
||||||
|
WsError::NoMatchingPair(_, _) => "User for given pair does not exists",
|
||||||
|
WsError::FailedToLoadProjectUsers => {
|
||||||
|
"There was problem while loading project users. Please try later"
|
||||||
|
}
|
||||||
|
WsError::FailedToLoadAssignees => {
|
||||||
|
"There was problem while loading issue assignees. Please try later"
|
||||||
|
}
|
||||||
|
WsError::InvalidPair(_, _) => "Given sign up pair is not valid.",
|
||||||
|
WsError::TakenPair(_, _) => "Given sign up pair is already taken.",
|
||||||
|
WsError::InvalidUserProject => "Unable to connect user to project",
|
||||||
|
WsError::FailedToChangeAvatar => "Unable to change user avatar",
|
||||||
|
WsError::FailedToLoadInvitedUsers => "Failed to load invited users. Please try later",
|
||||||
|
|
||||||
|
// comments
|
||||||
|
WsError::FailedToLoadComments => "Failed to load comments. Please try later",
|
||||||
|
WsError::InvalidComment => "There is something wrong with given comment data",
|
||||||
|
WsError::FailedToUpdateComment => {
|
||||||
|
"There was problem when updating comment. Please try later"
|
||||||
|
}
|
||||||
|
WsError::UnableToDeleteComment => "Unable to delete comment",
|
||||||
|
|
||||||
|
// epics
|
||||||
|
WsError::FailedToLoadEpics => "Failed to load epics. Please try later",
|
||||||
|
WsError::InvalidEpic => "There is something wrong with given epic data",
|
||||||
|
WsError::FailedToUpdateEpic => {
|
||||||
|
"There was problem when updating comment. Please try later"
|
||||||
|
}
|
||||||
|
WsError::UnableToDeleteEpic => "Unable to delete epic",
|
||||||
|
|
||||||
|
// invitations
|
||||||
|
WsError::InvalidInvitation => "Given invitation contains problems",
|
||||||
|
WsError::FailedToLoadInvitations => "Failed to load invitations. Please try later",
|
||||||
|
WsError::FailedToUpdateInvitation => {
|
||||||
|
"There was problem when updating invitation. Please try later"
|
||||||
|
}
|
||||||
|
WsError::UnableToDeleteInvitation => "Unable to delete invitation",
|
||||||
|
WsError::InvitationRevoked => "This invitation is no longer valid",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
|
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
|
||||||
pub enum WsMsg {
|
pub enum WsMsg {
|
||||||
Ping,
|
Ping,
|
||||||
@ -124,4 +232,7 @@ pub enum WsMsg {
|
|||||||
EpicUpdated(Epic),
|
EpicUpdated(Epic),
|
||||||
EpicDelete(EpicId),
|
EpicDelete(EpicId),
|
||||||
EpicDeleted(EpicId),
|
EpicDeleted(EpicId),
|
||||||
|
|
||||||
|
// errors
|
||||||
|
Error(WsError),
|
||||||
}
|
}
|
||||||
|
@ -41,7 +41,6 @@ dotenv = { version = "*" }
|
|||||||
|
|
||||||
byteorder = "1.0"
|
byteorder = "1.0"
|
||||||
chrono = { version = "0.4", features = ["serde"] }
|
chrono = { version = "0.4", features = ["serde"] }
|
||||||
libc = { version = "0.2.0" }
|
|
||||||
time = { version = "0.1" }
|
time = { version = "0.1" }
|
||||||
url = { version = "2.1.0" }
|
url = { version = "2.1.0" }
|
||||||
percent-encoding = { version = "2.1.0" }
|
percent-encoding = { version = "2.1.0" }
|
||||||
@ -66,6 +65,7 @@ async-trait = { version = "*" }
|
|||||||
|
|
||||||
futures = { version = "*" }
|
futures = { version = "*" }
|
||||||
openssl-sys = { version = "*", features = ["vendored"] }
|
openssl-sys = { version = "*", features = ["vendored"] }
|
||||||
|
libc = { version = "0.2.0", default-features = false }
|
||||||
|
|
||||||
lettre = { version = "*" }
|
lettre = { version = "*" }
|
||||||
lettre_email = { version = "*" }
|
lettre_email = { version = "*" }
|
||||||
|
@ -1,16 +1,13 @@
|
|||||||
use actix::{Handler, Message};
|
use actix::{Handler, Message};
|
||||||
use diesel::pg::Pg;
|
|
||||||
use diesel::prelude::*;
|
|
||||||
use serde::{Deserialize, Serialize};
|
|
||||||
|
|
||||||
use jirs_data::{Token, User};
|
use jirs_data::User;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
db::{DbExecutor, DbPool, SyncQuery},
|
db::{tokens::FindAccessToken, DbExecutor, DbPool, DbPooledConn, SyncQuery},
|
||||||
|
db_pool,
|
||||||
errors::ServiceErrors,
|
errors::ServiceErrors,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug)]
|
|
||||||
pub struct AuthorizeUser {
|
pub struct AuthorizeUser {
|
||||||
pub access_token: uuid::Uuid,
|
pub access_token: uuid::Uuid,
|
||||||
}
|
}
|
||||||
@ -19,30 +16,26 @@ impl Message for AuthorizeUser {
|
|||||||
type Result = Result<User, ServiceErrors>;
|
type Result = Result<User, ServiceErrors>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl AuthorizeUser {
|
||||||
|
pub fn execute(&self, conn: &DbPooledConn) -> Result<User, ServiceErrors> {
|
||||||
|
let token = FindAccessToken {
|
||||||
|
token: self.access_token,
|
||||||
|
}
|
||||||
|
.execute(conn)?;
|
||||||
|
|
||||||
|
crate::db::users::FindUser {
|
||||||
|
user_id: token.user_id,
|
||||||
|
}
|
||||||
|
.execute(conn)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Handler<AuthorizeUser> for DbExecutor {
|
impl Handler<AuthorizeUser> for DbExecutor {
|
||||||
type Result = Result<User, ServiceErrors>;
|
type Result = Result<User, ServiceErrors>;
|
||||||
|
|
||||||
fn handle(&mut self, msg: AuthorizeUser, _: &mut Self::Context) -> Self::Result {
|
fn handle(&mut self, msg: AuthorizeUser, _: &mut Self::Context) -> Self::Result {
|
||||||
use crate::schema::tokens::dsl::{access_token, tokens};
|
let conn = db_pool!(self);
|
||||||
use crate::schema::users::dsl::{id, users};
|
msg.execute(conn)
|
||||||
|
|
||||||
let conn = &self
|
|
||||||
.pool
|
|
||||||
.get()
|
|
||||||
.map_err(|_| ServiceErrors::DatabaseConnectionLost)?;
|
|
||||||
|
|
||||||
let token = tokens
|
|
||||||
.filter(access_token.eq(msg.access_token))
|
|
||||||
.first::<Token>(conn)
|
|
||||||
.map_err(|_e| {
|
|
||||||
ServiceErrors::RecordNotFound(format!("token for {}", msg.access_token))
|
|
||||||
})?;
|
|
||||||
|
|
||||||
let user_query = users.filter(id.eq(token.user_id));
|
|
||||||
debug!("{}", diesel::debug_query::<Pg, _>(&user_query));
|
|
||||||
user_query
|
|
||||||
.first::<User>(conn)
|
|
||||||
.map_err(|_e| ServiceErrors::RecordNotFound(format!("user {}", token.user_id)))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -50,21 +43,10 @@ impl SyncQuery for AuthorizeUser {
|
|||||||
type Result = std::result::Result<User, crate::errors::ServiceErrors>;
|
type Result = std::result::Result<User, crate::errors::ServiceErrors>;
|
||||||
|
|
||||||
fn handle(&self, pool: &DbPool) -> Self::Result {
|
fn handle(&self, pool: &DbPool) -> Self::Result {
|
||||||
use crate::schema::tokens::dsl::{access_token, tokens};
|
let conn = pool.get().map_err(|e| {
|
||||||
use crate::schema::users::dsl::{id, users};
|
error!("{:?}", e);
|
||||||
|
crate::errors::ServiceErrors::DatabaseConnectionLost
|
||||||
let conn = pool
|
})?;
|
||||||
.get()
|
self.execute(&conn)
|
||||||
.map_err(|_| crate::errors::ServiceErrors::DatabaseConnectionLost)?;
|
|
||||||
let token = tokens
|
|
||||||
.filter(access_token.eq(self.access_token))
|
|
||||||
.first::<Token>(&conn)
|
|
||||||
.map_err(|_| crate::errors::ServiceErrors::Unauthorized)?;
|
|
||||||
|
|
||||||
let user_query = users.filter(id.eq(token.user_id));
|
|
||||||
debug!("{}", diesel::debug_query::<Pg, _>(&user_query));
|
|
||||||
user_query
|
|
||||||
.first::<User>(&conn)
|
|
||||||
.map_err(|_| crate::errors::ServiceErrors::Unauthorized)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,17 +1,32 @@
|
|||||||
use actix::{Handler, Message};
|
use actix::{Handler, Message};
|
||||||
use diesel::pg::Pg;
|
|
||||||
use diesel::prelude::*;
|
use diesel::prelude::*;
|
||||||
use serde::{Deserialize, Serialize};
|
|
||||||
|
|
||||||
use jirs_data::Comment;
|
use jirs_data::{msg::WsError, Comment};
|
||||||
|
|
||||||
use crate::{db::DbExecutor, errors::ServiceErrors};
|
use crate::{
|
||||||
|
db::{DbExecutor, DbPooledConn},
|
||||||
|
db_pool,
|
||||||
|
errors::ServiceErrors,
|
||||||
|
q,
|
||||||
|
};
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize)]
|
|
||||||
pub struct LoadIssueComments {
|
pub struct LoadIssueComments {
|
||||||
pub issue_id: i32,
|
pub issue_id: i32,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl LoadIssueComments {
|
||||||
|
pub fn execute(self, conn: &DbPooledConn) -> Result<Vec<Comment>, ServiceErrors> {
|
||||||
|
use crate::schema::comments::dsl::*;
|
||||||
|
|
||||||
|
q!(comments.distinct_on(id).filter(issue_id.eq(self.issue_id)))
|
||||||
|
.load(conn)
|
||||||
|
.map_err(|e| {
|
||||||
|
error!("{:?}", e);
|
||||||
|
ServiceErrors::Error(WsError::FailedToLoadComments)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Message for LoadIssueComments {
|
impl Message for LoadIssueComments {
|
||||||
type Result = Result<Vec<Comment>, ServiceErrors>;
|
type Result = Result<Vec<Comment>, ServiceErrors>;
|
||||||
}
|
}
|
||||||
@ -20,28 +35,33 @@ impl Handler<LoadIssueComments> for DbExecutor {
|
|||||||
type Result = Result<Vec<Comment>, ServiceErrors>;
|
type Result = Result<Vec<Comment>, ServiceErrors>;
|
||||||
|
|
||||||
fn handle(&mut self, msg: LoadIssueComments, _ctx: &mut Self::Context) -> Self::Result {
|
fn handle(&mut self, msg: LoadIssueComments, _ctx: &mut Self::Context) -> Self::Result {
|
||||||
use crate::schema::comments::dsl::*;
|
let conn = db_pool!(self);
|
||||||
|
msg.execute(conn)
|
||||||
let conn = &self
|
|
||||||
.pool
|
|
||||||
.get()
|
|
||||||
.map_err(|_| ServiceErrors::DatabaseConnectionLost)?;
|
|
||||||
|
|
||||||
let comments_query = comments.distinct_on(id).filter(issue_id.eq(msg.issue_id));
|
|
||||||
debug!("{}", diesel::debug_query::<Pg, _>(&comments_query));
|
|
||||||
comments_query
|
|
||||||
.load(conn)
|
|
||||||
.map_err(|_| ServiceErrors::RecordNotFound("issue comments".to_string()))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize)]
|
|
||||||
pub struct CreateComment {
|
pub struct CreateComment {
|
||||||
pub user_id: i32,
|
pub user_id: i32,
|
||||||
pub issue_id: i32,
|
pub issue_id: i32,
|
||||||
pub body: String,
|
pub body: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl CreateComment {
|
||||||
|
pub fn execute(self, conn: &DbPooledConn) -> Result<Comment, ServiceErrors> {
|
||||||
|
use crate::schema::comments::dsl::*;
|
||||||
|
q!(diesel::insert_into(comments).values((
|
||||||
|
body.eq(self.body),
|
||||||
|
user_id.eq(self.user_id),
|
||||||
|
issue_id.eq(self.issue_id),
|
||||||
|
)))
|
||||||
|
.get_result::<Comment>(conn)
|
||||||
|
.map_err(|e| {
|
||||||
|
error!("{:?}", e);
|
||||||
|
ServiceErrors::Error(WsError::InvalidComment)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Message for CreateComment {
|
impl Message for CreateComment {
|
||||||
type Result = Result<Comment, ServiceErrors>;
|
type Result = Result<Comment, ServiceErrors>;
|
||||||
}
|
}
|
||||||
@ -50,35 +70,35 @@ impl Handler<CreateComment> for DbExecutor {
|
|||||||
type Result = Result<Comment, ServiceErrors>;
|
type Result = Result<Comment, ServiceErrors>;
|
||||||
|
|
||||||
fn handle(&mut self, msg: CreateComment, _ctx: &mut Self::Context) -> Self::Result {
|
fn handle(&mut self, msg: CreateComment, _ctx: &mut Self::Context) -> Self::Result {
|
||||||
use crate::models::CommentForm;
|
let conn = db_pool!(self);
|
||||||
use crate::schema::comments::dsl::*;
|
msg.execute(conn)
|
||||||
|
|
||||||
let form = CommentForm {
|
|
||||||
body: msg.body,
|
|
||||||
user_id: msg.user_id,
|
|
||||||
issue_id: msg.issue_id,
|
|
||||||
};
|
|
||||||
|
|
||||||
let conn = &self
|
|
||||||
.pool
|
|
||||||
.get()
|
|
||||||
.map_err(|_| ServiceErrors::DatabaseConnectionLost)?;
|
|
||||||
|
|
||||||
let comment_query = diesel::insert_into(comments).values(form);
|
|
||||||
debug!("{}", diesel::debug_query::<Pg, _>(&comment_query));
|
|
||||||
comment_query
|
|
||||||
.get_result::<Comment>(conn)
|
|
||||||
.map_err(|_| ServiceErrors::RecordNotFound("issue comments".to_string()))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize)]
|
|
||||||
pub struct UpdateComment {
|
pub struct UpdateComment {
|
||||||
pub comment_id: i32,
|
pub comment_id: i32,
|
||||||
pub user_id: i32,
|
pub user_id: i32,
|
||||||
pub body: String,
|
pub body: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl UpdateComment {
|
||||||
|
pub fn execute(self, conn: &DbPooledConn) -> Result<Comment, ServiceErrors> {
|
||||||
|
use crate::schema::comments::dsl::*;
|
||||||
|
|
||||||
|
q!(diesel::update(
|
||||||
|
comments
|
||||||
|
.filter(user_id.eq(self.user_id))
|
||||||
|
.find(self.comment_id),
|
||||||
|
)
|
||||||
|
.set(body.eq(self.body)))
|
||||||
|
.get_result::<Comment>(conn)
|
||||||
|
.map_err(|e| {
|
||||||
|
error!("{:?}", e);
|
||||||
|
ServiceErrors::Error(WsError::FailedToUpdateComment)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Message for UpdateComment {
|
impl Message for UpdateComment {
|
||||||
type Result = Result<Comment, ServiceErrors>;
|
type Result = Result<Comment, ServiceErrors>;
|
||||||
}
|
}
|
||||||
@ -87,32 +107,32 @@ impl Handler<UpdateComment> for DbExecutor {
|
|||||||
type Result = Result<Comment, ServiceErrors>;
|
type Result = Result<Comment, ServiceErrors>;
|
||||||
|
|
||||||
fn handle(&mut self, msg: UpdateComment, _ctx: &mut Self::Context) -> Self::Result {
|
fn handle(&mut self, msg: UpdateComment, _ctx: &mut Self::Context) -> Self::Result {
|
||||||
use crate::schema::comments::dsl::*;
|
let conn = db_pool!(self);
|
||||||
|
msg.execute(conn)
|
||||||
let conn = &self
|
|
||||||
.pool
|
|
||||||
.get()
|
|
||||||
.map_err(|_| ServiceErrors::DatabaseConnectionLost)?;
|
|
||||||
let query = diesel::update(
|
|
||||||
comments
|
|
||||||
.filter(user_id.eq(msg.user_id))
|
|
||||||
.find(msg.comment_id),
|
|
||||||
)
|
|
||||||
.set(body.eq(msg.body));
|
|
||||||
info!("{}", diesel::debug_query::<Pg, _>(&query));
|
|
||||||
let row: Comment = query
|
|
||||||
.get_result::<Comment>(conn)
|
|
||||||
.map_err(|_| ServiceErrors::RecordNotFound("issue comments".to_string()))?;
|
|
||||||
Ok(row)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize)]
|
|
||||||
pub struct DeleteComment {
|
pub struct DeleteComment {
|
||||||
pub comment_id: i32,
|
pub comment_id: i32,
|
||||||
pub user_id: i32,
|
pub user_id: i32,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl DeleteComment {
|
||||||
|
pub fn execute(self, conn: &DbPooledConn) -> Result<usize, ServiceErrors> {
|
||||||
|
use crate::schema::comments::dsl::*;
|
||||||
|
q!(diesel::delete(
|
||||||
|
comments
|
||||||
|
.filter(user_id.eq(self.user_id))
|
||||||
|
.find(self.comment_id),
|
||||||
|
))
|
||||||
|
.execute(conn)
|
||||||
|
.map_err(|e| {
|
||||||
|
error!("{:?}", e);
|
||||||
|
ServiceErrors::Error(WsError::UnableToDeleteComment)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Message for DeleteComment {
|
impl Message for DeleteComment {
|
||||||
type Result = Result<(), ServiceErrors>;
|
type Result = Result<(), ServiceErrors>;
|
||||||
}
|
}
|
||||||
@ -121,22 +141,8 @@ impl Handler<DeleteComment> for DbExecutor {
|
|||||||
type Result = Result<(), ServiceErrors>;
|
type Result = Result<(), ServiceErrors>;
|
||||||
|
|
||||||
fn handle(&mut self, msg: DeleteComment, _ctx: &mut Self::Context) -> Self::Result {
|
fn handle(&mut self, msg: DeleteComment, _ctx: &mut Self::Context) -> Self::Result {
|
||||||
use crate::schema::comments::dsl::*;
|
let conn = db_pool!(self);
|
||||||
|
msg.execute(conn)?;
|
||||||
let conn = &self
|
|
||||||
.pool
|
|
||||||
.get()
|
|
||||||
.map_err(|_| ServiceErrors::DatabaseConnectionLost)?;
|
|
||||||
|
|
||||||
let comment_query = diesel::delete(
|
|
||||||
comments
|
|
||||||
.filter(user_id.eq(msg.user_id))
|
|
||||||
.find(msg.comment_id),
|
|
||||||
);
|
|
||||||
debug!("{}", diesel::debug_query::<Pg, _>(&comment_query));
|
|
||||||
comment_query
|
|
||||||
.execute(conn)
|
|
||||||
.map_err(|_| ServiceErrors::RecordNotFound("issue comments".to_string()))?;
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,13 +1,10 @@
|
|||||||
use actix::{Handler, Message};
|
use actix::{Handler, Message};
|
||||||
use diesel::pg::Pg;
|
|
||||||
use diesel::prelude::*;
|
use diesel::prelude::*;
|
||||||
use serde::{Deserialize, Serialize};
|
|
||||||
|
|
||||||
use jirs_data::Epic;
|
use jirs_data::{msg::WsError, Epic};
|
||||||
|
|
||||||
use crate::{db::DbExecutor, errors::ServiceErrors};
|
use crate::{db::DbExecutor, db_pool, errors::ServiceErrors, q};
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize)]
|
|
||||||
pub struct LoadEpics {
|
pub struct LoadEpics {
|
||||||
pub project_id: i32,
|
pub project_id: i32,
|
||||||
}
|
}
|
||||||
@ -22,20 +19,17 @@ impl Handler<LoadEpics> for DbExecutor {
|
|||||||
fn handle(&mut self, msg: LoadEpics, _ctx: &mut Self::Context) -> Self::Result {
|
fn handle(&mut self, msg: LoadEpics, _ctx: &mut Self::Context) -> Self::Result {
|
||||||
use crate::schema::epics::dsl::*;
|
use crate::schema::epics::dsl::*;
|
||||||
|
|
||||||
let conn = &self
|
let conn = db_pool!(self);
|
||||||
.pool
|
|
||||||
.get()
|
|
||||||
.map_err(|_| ServiceErrors::DatabaseConnectionLost)?;
|
|
||||||
|
|
||||||
let epics_query = epics.distinct_on(id).filter(project_id.eq(msg.project_id));
|
q!(epics.distinct_on(id).filter(project_id.eq(msg.project_id)))
|
||||||
debug!("{}", diesel::debug_query::<Pg, _>(&epics_query));
|
|
||||||
epics_query
|
|
||||||
.load(conn)
|
.load(conn)
|
||||||
.map_err(|_| ServiceErrors::RecordNotFound("epics".to_string()))
|
.map_err(|e| {
|
||||||
|
error!("{:?}", e);
|
||||||
|
ServiceErrors::Error(WsError::FailedToLoadEpics)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize)]
|
|
||||||
pub struct CreateEpic {
|
pub struct CreateEpic {
|
||||||
pub user_id: i32,
|
pub user_id: i32,
|
||||||
pub project_id: i32,
|
pub project_id: i32,
|
||||||
@ -52,24 +46,21 @@ impl Handler<CreateEpic> for DbExecutor {
|
|||||||
fn handle(&mut self, msg: CreateEpic, _ctx: &mut Self::Context) -> Self::Result {
|
fn handle(&mut self, msg: CreateEpic, _ctx: &mut Self::Context) -> Self::Result {
|
||||||
use crate::schema::epics::dsl::*;
|
use crate::schema::epics::dsl::*;
|
||||||
|
|
||||||
let conn = &self
|
let conn = db_pool!(self);
|
||||||
.pool
|
|
||||||
.get()
|
|
||||||
.map_err(|_| ServiceErrors::DatabaseConnectionLost)?;
|
|
||||||
|
|
||||||
let epic_query = diesel::insert_into(epics).values((
|
q!(diesel::insert_into(epics).values((
|
||||||
name.eq(msg.name.as_str()),
|
name.eq(msg.name.as_str()),
|
||||||
user_id.eq(msg.user_id),
|
user_id.eq(msg.user_id),
|
||||||
project_id.eq(msg.project_id),
|
project_id.eq(msg.project_id),
|
||||||
));
|
)))
|
||||||
debug!("{}", diesel::debug_query::<Pg, _>(&epic_query));
|
.get_result::<Epic>(conn)
|
||||||
epic_query
|
.map_err(|e| {
|
||||||
.get_result::<Epic>(conn)
|
error!("{:?}", e);
|
||||||
.map_err(|_| ServiceErrors::RecordNotFound("epics".to_string()))
|
ServiceErrors::Error(WsError::InvalidEpic)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize)]
|
|
||||||
pub struct UpdateEpic {
|
pub struct UpdateEpic {
|
||||||
pub epic_id: i32,
|
pub epic_id: i32,
|
||||||
pub project_id: i32,
|
pub project_id: i32,
|
||||||
@ -86,25 +77,22 @@ impl Handler<UpdateEpic> for DbExecutor {
|
|||||||
fn handle(&mut self, msg: UpdateEpic, _ctx: &mut Self::Context) -> Self::Result {
|
fn handle(&mut self, msg: UpdateEpic, _ctx: &mut Self::Context) -> Self::Result {
|
||||||
use crate::schema::epics::dsl::*;
|
use crate::schema::epics::dsl::*;
|
||||||
|
|
||||||
let conn = &self
|
let conn = db_pool!(self);
|
||||||
.pool
|
|
||||||
.get()
|
q!(diesel::update(
|
||||||
.map_err(|_| ServiceErrors::DatabaseConnectionLost)?;
|
|
||||||
let query = diesel::update(
|
|
||||||
epics
|
epics
|
||||||
.filter(project_id.eq(msg.project_id))
|
.filter(project_id.eq(msg.project_id))
|
||||||
.find(msg.epic_id),
|
.find(msg.epic_id),
|
||||||
)
|
)
|
||||||
.set(name.eq(msg.name));
|
.set(name.eq(msg.name)))
|
||||||
info!("{}", diesel::debug_query::<Pg, _>(&query));
|
.get_result::<Epic>(conn)
|
||||||
let row: Epic = query
|
.map_err(|e| {
|
||||||
.get_result::<Epic>(conn)
|
error!("{:?}", e);
|
||||||
.map_err(|_| ServiceErrors::RecordNotFound("epics".to_string()))?;
|
ServiceErrors::Error(WsError::FailedToUpdateEpic)
|
||||||
Ok(row)
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize)]
|
|
||||||
pub struct DeleteEpic {
|
pub struct DeleteEpic {
|
||||||
pub epic_id: i32,
|
pub epic_id: i32,
|
||||||
pub user_id: i32,
|
pub user_id: i32,
|
||||||
@ -120,16 +108,17 @@ impl Handler<DeleteEpic> for DbExecutor {
|
|||||||
fn handle(&mut self, msg: DeleteEpic, _ctx: &mut Self::Context) -> Self::Result {
|
fn handle(&mut self, msg: DeleteEpic, _ctx: &mut Self::Context) -> Self::Result {
|
||||||
use crate::schema::epics::dsl::*;
|
use crate::schema::epics::dsl::*;
|
||||||
|
|
||||||
let conn = &self
|
let conn = db_pool!(self);
|
||||||
.pool
|
|
||||||
.get()
|
q!(diesel::delete(
|
||||||
.map_err(|_| ServiceErrors::DatabaseConnectionLost)?;
|
epics.filter(user_id.eq(msg.user_id)).find(msg.epic_id)
|
||||||
|
))
|
||||||
|
.execute(conn)
|
||||||
|
.map_err(|e| {
|
||||||
|
error!("{:?}", e);
|
||||||
|
ServiceErrors::Error(WsError::UnableToDeleteEpic)
|
||||||
|
})?;
|
||||||
|
|
||||||
let comment_query = diesel::delete(epics.filter(user_id.eq(msg.user_id)).find(msg.epic_id));
|
|
||||||
debug!("{}", diesel::debug_query::<Pg, _>(&comment_query));
|
|
||||||
comment_query
|
|
||||||
.execute(conn)
|
|
||||||
.map_err(|_| ServiceErrors::RecordNotFound("epics".to_string()))?;
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,22 +1,49 @@
|
|||||||
use actix::{Handler, Message};
|
use actix::{Handler, Message};
|
||||||
use diesel::connection::TransactionManager;
|
|
||||||
use diesel::pg::Pg;
|
|
||||||
use diesel::prelude::*;
|
use diesel::prelude::*;
|
||||||
|
|
||||||
use jirs_data::{
|
use jirs_data::{
|
||||||
EmailString, Invitation, InvitationId, InvitationState, InvitationToken, ProjectId, Token,
|
msg::WsError, EmailString, Invitation, InvitationId, InvitationState, InvitationToken,
|
||||||
User, UserId, UserRole, UsernameString,
|
ProjectId, Token, User, UserId, UserRole, UsernameString,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use crate::db::DbPooledConn;
|
||||||
use crate::{
|
use crate::{
|
||||||
db::{
|
db::{
|
||||||
tokens::CreateBindToken,
|
tokens::CreateBindToken,
|
||||||
users::{LookupUser, Register},
|
users::{LookupUser, Register},
|
||||||
DbExecutor,
|
DbExecutor,
|
||||||
},
|
},
|
||||||
|
db_pool,
|
||||||
errors::ServiceErrors,
|
errors::ServiceErrors,
|
||||||
|
q,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
pub struct FindByBindToken {
|
||||||
|
pub token: InvitationToken,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FindByBindToken {
|
||||||
|
pub fn execute(self, conn: &DbPooledConn) -> Result<Invitation, ServiceErrors> {
|
||||||
|
use crate::schema::invitations::dsl::*;
|
||||||
|
q!(invitations.filter(bind_token.eq(self.token)))
|
||||||
|
.first(conn)
|
||||||
|
.map_err(|e| ServiceErrors::DatabaseQueryFailed(format!("{}", e)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Message for FindByBindToken {
|
||||||
|
type Result = Result<Invitation, ServiceErrors>;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Handler<FindByBindToken> for DbExecutor {
|
||||||
|
type Result = Result<Invitation, ServiceErrors>;
|
||||||
|
|
||||||
|
fn handle(&mut self, msg: FindByBindToken, _ctx: &mut Self::Context) -> Self::Result {
|
||||||
|
let conn = db_pool!(self);
|
||||||
|
msg.execute(conn)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub struct ListInvitation {
|
pub struct ListInvitation {
|
||||||
pub user_id: UserId,
|
pub user_id: UserId,
|
||||||
}
|
}
|
||||||
@ -31,20 +58,18 @@ impl Handler<ListInvitation> for DbExecutor {
|
|||||||
fn handle(&mut self, msg: ListInvitation, _ctx: &mut Self::Context) -> Self::Result {
|
fn handle(&mut self, msg: ListInvitation, _ctx: &mut Self::Context) -> Self::Result {
|
||||||
use crate::schema::invitations::dsl::*;
|
use crate::schema::invitations::dsl::*;
|
||||||
|
|
||||||
let conn = &self
|
let conn = db_pool!(self);
|
||||||
.pool
|
|
||||||
.get()
|
|
||||||
.map_err(|_| ServiceErrors::DatabaseConnectionLost)?;
|
|
||||||
|
|
||||||
let query = invitations
|
q!(invitations
|
||||||
.filter(invited_by_id.eq(msg.user_id))
|
.filter(invited_by_id.eq(msg.user_id))
|
||||||
.filter(state.ne(InvitationState::Accepted))
|
.filter(state.ne(InvitationState::Accepted))
|
||||||
.order_by(state.asc())
|
.order_by(state.asc())
|
||||||
.then_order_by(updated_at.desc());
|
.then_order_by(updated_at.desc()))
|
||||||
debug!("{}", diesel::debug_query::<Pg, _>(&query).to_string());
|
.load(conn)
|
||||||
query
|
.map_err(|e| {
|
||||||
.load(conn)
|
error!("{:?}", e);
|
||||||
.map_err(|_| ServiceErrors::DatabaseConnectionLost)
|
ServiceErrors::Error(WsError::FailedToLoadInvitations)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -56,6 +81,25 @@ pub struct CreateInvitation {
|
|||||||
pub role: UserRole,
|
pub role: UserRole,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl CreateInvitation {
|
||||||
|
pub fn execute(self, conn: &DbPooledConn) -> Result<Invitation, ServiceErrors> {
|
||||||
|
use crate::schema::invitations::dsl::*;
|
||||||
|
q!(diesel::insert_into(invitations).values((
|
||||||
|
name.eq(self.name),
|
||||||
|
email.eq(self.email),
|
||||||
|
state.eq(InvitationState::Sent),
|
||||||
|
project_id.eq(self.project_id),
|
||||||
|
invited_by_id.eq(self.user_id),
|
||||||
|
role.eq(self.role),
|
||||||
|
)))
|
||||||
|
.get_result(conn)
|
||||||
|
.map_err(|e| {
|
||||||
|
error!("{:?}", e);
|
||||||
|
ServiceErrors::Error(WsError::InvalidInvitation)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Message for CreateInvitation {
|
impl Message for CreateInvitation {
|
||||||
type Result = Result<Invitation, ServiceErrors>;
|
type Result = Result<Invitation, ServiceErrors>;
|
||||||
}
|
}
|
||||||
@ -64,25 +108,8 @@ impl Handler<CreateInvitation> for DbExecutor {
|
|||||||
type Result = Result<Invitation, ServiceErrors>;
|
type Result = Result<Invitation, ServiceErrors>;
|
||||||
|
|
||||||
fn handle(&mut self, msg: CreateInvitation, _ctx: &mut Self::Context) -> Self::Result {
|
fn handle(&mut self, msg: CreateInvitation, _ctx: &mut Self::Context) -> Self::Result {
|
||||||
use crate::schema::invitations::dsl::*;
|
let conn = db_pool!(self);
|
||||||
|
msg.execute(conn)
|
||||||
let conn = &self
|
|
||||||
.pool
|
|
||||||
.get()
|
|
||||||
.map_err(|e| ServiceErrors::DatabaseQueryFailed(format!("{}", e)))?;
|
|
||||||
|
|
||||||
let query = diesel::insert_into(invitations).values((
|
|
||||||
name.eq(msg.name),
|
|
||||||
email.eq(msg.email),
|
|
||||||
state.eq(InvitationState::Sent),
|
|
||||||
project_id.eq(msg.project_id),
|
|
||||||
invited_by_id.eq(msg.user_id),
|
|
||||||
role.eq(msg.role),
|
|
||||||
));
|
|
||||||
debug!("{}", diesel::debug_query::<Pg, _>(&query).to_string());
|
|
||||||
query
|
|
||||||
.get_result(conn)
|
|
||||||
.map_err(|e| ServiceErrors::DatabaseQueryFailed(format!("{}", e)))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -90,6 +117,18 @@ pub struct DeleteInvitation {
|
|||||||
pub id: InvitationId,
|
pub id: InvitationId,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl DeleteInvitation {
|
||||||
|
pub fn execute(self, conn: &DbPooledConn) -> Result<usize, ServiceErrors> {
|
||||||
|
use crate::schema::invitations::dsl::*;
|
||||||
|
q!(diesel::delete(invitations).filter(id.eq(self.id)))
|
||||||
|
.execute(conn)
|
||||||
|
.map_err(|e| {
|
||||||
|
error!("{:?}", e);
|
||||||
|
ServiceErrors::Error(WsError::UnableToDeleteInvitation)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Message for DeleteInvitation {
|
impl Message for DeleteInvitation {
|
||||||
type Result = Result<(), ServiceErrors>;
|
type Result = Result<(), ServiceErrors>;
|
||||||
}
|
}
|
||||||
@ -98,17 +137,8 @@ impl Handler<DeleteInvitation> for DbExecutor {
|
|||||||
type Result = Result<(), ServiceErrors>;
|
type Result = Result<(), ServiceErrors>;
|
||||||
|
|
||||||
fn handle(&mut self, msg: DeleteInvitation, _ctx: &mut Self::Context) -> Self::Result {
|
fn handle(&mut self, msg: DeleteInvitation, _ctx: &mut Self::Context) -> Self::Result {
|
||||||
use crate::schema::invitations::dsl::*;
|
let conn = db_pool!(self);
|
||||||
|
msg.execute(conn)?;
|
||||||
let conn = &self
|
|
||||||
.pool
|
|
||||||
.get()
|
|
||||||
.map_err(|_| ServiceErrors::DatabaseConnectionLost)?;
|
|
||||||
let query = diesel::delete(invitations).filter(id.eq(msg.id));
|
|
||||||
debug!("{}", diesel::debug_query::<Pg, _>(&query).to_string());
|
|
||||||
query
|
|
||||||
.execute(conn)
|
|
||||||
.map_err(|e| ServiceErrors::DatabaseQueryFailed(format!("{}", e)))?;
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -118,6 +148,24 @@ struct UpdateInvitationState {
|
|||||||
pub state: InvitationState,
|
pub state: InvitationState,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl UpdateInvitationState {
|
||||||
|
pub fn execute(self, conn: &DbPooledConn) -> Result<usize, ServiceErrors> {
|
||||||
|
use crate::schema::invitations::dsl::*;
|
||||||
|
|
||||||
|
q!(diesel::update(invitations)
|
||||||
|
.set((
|
||||||
|
state.eq(self.state),
|
||||||
|
updated_at.eq(chrono::Utc::now().naive_utc()),
|
||||||
|
))
|
||||||
|
.filter(id.eq(self.id)))
|
||||||
|
.execute(conn)
|
||||||
|
.map_err(|e| {
|
||||||
|
error!("{:?}", e);
|
||||||
|
ServiceErrors::Error(WsError::FailedToUpdateInvitation)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Message for UpdateInvitationState {
|
impl Message for UpdateInvitationState {
|
||||||
type Result = Result<(), ServiceErrors>;
|
type Result = Result<(), ServiceErrors>;
|
||||||
}
|
}
|
||||||
@ -126,23 +174,8 @@ impl Handler<UpdateInvitationState> for DbExecutor {
|
|||||||
type Result = Result<(), ServiceErrors>;
|
type Result = Result<(), ServiceErrors>;
|
||||||
|
|
||||||
fn handle(&mut self, msg: UpdateInvitationState, _ctx: &mut Self::Context) -> Self::Result {
|
fn handle(&mut self, msg: UpdateInvitationState, _ctx: &mut Self::Context) -> Self::Result {
|
||||||
use crate::schema::invitations::dsl::*;
|
let conn = db_pool!(self);
|
||||||
|
msg.execute(conn)?;
|
||||||
let conn = &self
|
|
||||||
.pool
|
|
||||||
.get()
|
|
||||||
.map_err(|_| ServiceErrors::DatabaseConnectionLost)?;
|
|
||||||
|
|
||||||
let query = diesel::update(invitations)
|
|
||||||
.set((
|
|
||||||
state.eq(msg.state),
|
|
||||||
updated_at.eq(chrono::Utc::now().naive_utc()),
|
|
||||||
))
|
|
||||||
.filter(id.eq(msg.id));
|
|
||||||
debug!("{}", diesel::debug_query::<Pg, _>(&query).to_string());
|
|
||||||
query
|
|
||||||
.execute(conn)
|
|
||||||
.map_err(|e| ServiceErrors::DatabaseQueryFailed(format!("{}", e)))?;
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -158,14 +191,14 @@ impl Message for RevokeInvitation {
|
|||||||
impl Handler<RevokeInvitation> for DbExecutor {
|
impl Handler<RevokeInvitation> for DbExecutor {
|
||||||
type Result = Result<(), ServiceErrors>;
|
type Result = Result<(), ServiceErrors>;
|
||||||
|
|
||||||
fn handle(&mut self, msg: RevokeInvitation, ctx: &mut Self::Context) -> Self::Result {
|
fn handle(&mut self, msg: RevokeInvitation, _ctx: &mut Self::Context) -> Self::Result {
|
||||||
self.handle(
|
let conn = db_pool!(self);
|
||||||
UpdateInvitationState {
|
UpdateInvitationState {
|
||||||
id: msg.id,
|
id: msg.id,
|
||||||
state: InvitationState::Revoked,
|
state: InvitationState::Revoked,
|
||||||
},
|
}
|
||||||
ctx,
|
.execute(conn)?;
|
||||||
)
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -173,6 +206,85 @@ pub struct AcceptInvitation {
|
|||||||
pub invitation_token: InvitationToken,
|
pub invitation_token: InvitationToken,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl AcceptInvitation {
|
||||||
|
pub fn execute(self, conn: &DbPooledConn) -> Result<Token, ServiceErrors> {
|
||||||
|
use crate::schema::invitations::dsl::*;
|
||||||
|
|
||||||
|
crate::db::Guard::new(conn)?.run::<Token, _>(|_guard| {
|
||||||
|
let invitation = crate::db::invitations::FindByBindToken {
|
||||||
|
token: self.invitation_token,
|
||||||
|
}
|
||||||
|
.execute(conn)?;
|
||||||
|
|
||||||
|
if invitation.state == InvitationState::Revoked {
|
||||||
|
return Err(ServiceErrors::Error(WsError::InvitationRevoked));
|
||||||
|
}
|
||||||
|
|
||||||
|
crate::db::invitations::UpdateInvitationState {
|
||||||
|
id: invitation.id,
|
||||||
|
state: InvitationState::Accepted,
|
||||||
|
}
|
||||||
|
.execute(conn)?;
|
||||||
|
|
||||||
|
q!(diesel::update(invitations)
|
||||||
|
.set((
|
||||||
|
state.eq(InvitationState::Accepted),
|
||||||
|
updated_at.eq(chrono::Utc::now().naive_utc()),
|
||||||
|
))
|
||||||
|
.filter(id.eq(invitation.id))
|
||||||
|
.filter(state.eq(InvitationState::Sent)))
|
||||||
|
.execute(conn)
|
||||||
|
.map_err(|e| {
|
||||||
|
ServiceErrors::DatabaseQueryFailed(format!(
|
||||||
|
"update invitation {} {}",
|
||||||
|
invitation.id, e
|
||||||
|
))
|
||||||
|
})?;
|
||||||
|
|
||||||
|
match {
|
||||||
|
Register {
|
||||||
|
name: invitation.name.clone(),
|
||||||
|
email: invitation.email.clone(),
|
||||||
|
project_id: Some(invitation.project_id),
|
||||||
|
role: UserRole::User,
|
||||||
|
}
|
||||||
|
.execute(conn)
|
||||||
|
} {
|
||||||
|
Ok(_) => (),
|
||||||
|
Err(ServiceErrors::Error(WsError::InvalidPair(..))) => (),
|
||||||
|
Err(e) => return Err(e),
|
||||||
|
};
|
||||||
|
|
||||||
|
let user: User = LookupUser {
|
||||||
|
name: invitation.name.clone(),
|
||||||
|
email: invitation.email.clone(),
|
||||||
|
}
|
||||||
|
.execute(conn)?;
|
||||||
|
CreateBindToken { user_id: user.id }.execute(conn)?;
|
||||||
|
|
||||||
|
self.bind_to_default_project(conn, &invitation, &user)?;
|
||||||
|
|
||||||
|
crate::db::tokens::FindUserId { user_id: user.id }.execute(conn)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn bind_to_default_project(
|
||||||
|
&self,
|
||||||
|
conn: &DbPooledConn,
|
||||||
|
invitation: &Invitation,
|
||||||
|
user: &User,
|
||||||
|
) -> Result<usize, ServiceErrors> {
|
||||||
|
crate::db::user_projects::CreateUserProject {
|
||||||
|
user_id: user.id,
|
||||||
|
project_id: invitation.project_id,
|
||||||
|
is_current: false,
|
||||||
|
is_default: false,
|
||||||
|
role: invitation.role,
|
||||||
|
}
|
||||||
|
.execute(conn)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Message for AcceptInvitation {
|
impl Message for AcceptInvitation {
|
||||||
type Result = Result<Token, ServiceErrors>;
|
type Result = Result<Token, ServiceErrors>;
|
||||||
}
|
}
|
||||||
@ -180,107 +292,9 @@ impl Message for AcceptInvitation {
|
|||||||
impl Handler<AcceptInvitation> for DbExecutor {
|
impl Handler<AcceptInvitation> for DbExecutor {
|
||||||
type Result = Result<Token, ServiceErrors>;
|
type Result = Result<Token, ServiceErrors>;
|
||||||
|
|
||||||
fn handle(&mut self, msg: AcceptInvitation, ctx: &mut Self::Context) -> Self::Result {
|
fn handle(&mut self, msg: AcceptInvitation, _ctx: &mut Self::Context) -> Self::Result {
|
||||||
use crate::schema::invitations::dsl::*;
|
let conn = db_pool!(self);
|
||||||
|
|
||||||
let conn = &self
|
msg.execute(conn)
|
||||||
.pool
|
|
||||||
.get()
|
|
||||||
.map_err(|_| ServiceErrors::DatabaseConnectionLost)?;
|
|
||||||
|
|
||||||
let tm = conn.transaction_manager();
|
|
||||||
|
|
||||||
tm.begin_transaction(conn)
|
|
||||||
.map_err(|_| ServiceErrors::DatabaseConnectionLost)?;
|
|
||||||
|
|
||||||
let query = invitations.filter(bind_token.eq(msg.invitation_token));
|
|
||||||
debug!("{}", diesel::debug_query::<Pg, _>(&query).to_string());
|
|
||||||
let invitation: Invitation = query.first(conn).map_err(|e| {
|
|
||||||
if tm.rollback_transaction(conn).is_err() {
|
|
||||||
return ServiceErrors::DatabaseConnectionLost;
|
|
||||||
}
|
|
||||||
ServiceErrors::DatabaseQueryFailed(format!("{}", e))
|
|
||||||
})?;
|
|
||||||
|
|
||||||
if invitation.state == InvitationState::Revoked {
|
|
||||||
if tm.rollback_transaction(conn).is_err() {
|
|
||||||
return Err(ServiceErrors::DatabaseConnectionLost);
|
|
||||||
}
|
|
||||||
return Err(ServiceErrors::DatabaseQueryFailed(
|
|
||||||
"This invitation is no longer valid".to_string(),
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
let query = diesel::update(invitations)
|
|
||||||
.set((
|
|
||||||
state.eq(InvitationState::Accepted),
|
|
||||||
updated_at.eq(chrono::Utc::now().naive_utc()),
|
|
||||||
))
|
|
||||||
.filter(id.eq(invitation.id))
|
|
||||||
.filter(state.eq(InvitationState::Sent));
|
|
||||||
debug!("{}", diesel::debug_query::<Pg, _>(&query).to_string());
|
|
||||||
query.execute(conn).map_err(|e| {
|
|
||||||
if tm.rollback_transaction(conn).is_err() {
|
|
||||||
return ServiceErrors::DatabaseConnectionLost;
|
|
||||||
}
|
|
||||||
ServiceErrors::DatabaseQueryFailed(format!("update invitation {} {}", invitation.id, e))
|
|
||||||
})?;
|
|
||||||
|
|
||||||
match self.handle(
|
|
||||||
Register {
|
|
||||||
name: invitation.name.clone(),
|
|
||||||
email: invitation.email.clone(),
|
|
||||||
project_id: Some(invitation.project_id),
|
|
||||||
},
|
|
||||||
ctx,
|
|
||||||
) {
|
|
||||||
Ok(_) => (),
|
|
||||||
Err(ServiceErrors::RegisterCollision) => (),
|
|
||||||
Err(e) => return Err(e),
|
|
||||||
};
|
|
||||||
|
|
||||||
let user: User = self.handle(
|
|
||||||
LookupUser {
|
|
||||||
name: invitation.name.clone(),
|
|
||||||
email: invitation.email.clone(),
|
|
||||||
},
|
|
||||||
ctx,
|
|
||||||
)?;
|
|
||||||
self.handle(CreateBindToken { user_id: user.id }, ctx)?;
|
|
||||||
|
|
||||||
{
|
|
||||||
use crate::schema::user_projects::dsl::*;
|
|
||||||
|
|
||||||
let query = diesel::insert_into(user_projects).values((
|
|
||||||
user_id.eq(user.id),
|
|
||||||
project_id.eq(invitation.project_id),
|
|
||||||
role.eq(invitation.role),
|
|
||||||
));
|
|
||||||
debug!("{}", diesel::debug_query::<Pg, _>(&query));
|
|
||||||
query.execute(conn).map_err(|e| {
|
|
||||||
if tm.rollback_transaction(conn).is_err() {
|
|
||||||
return ServiceErrors::DatabaseConnectionLost;
|
|
||||||
}
|
|
||||||
ServiceErrors::DatabaseQueryFailed(format!("{}", e))
|
|
||||||
})?;
|
|
||||||
};
|
|
||||||
|
|
||||||
let token = {
|
|
||||||
use crate::schema::tokens::dsl::*;
|
|
||||||
|
|
||||||
let query = tokens.filter(user_id.eq(user.id)).order_by(id.desc());
|
|
||||||
debug!("{}", diesel::debug_query::<Pg, _>(&query));
|
|
||||||
query.first(conn).map_err(|e| {
|
|
||||||
if tm.rollback_transaction(conn).is_err() {
|
|
||||||
return ServiceErrors::DatabaseConnectionLost;
|
|
||||||
}
|
|
||||||
ServiceErrors::DatabaseQueryFailed(format!("token for user {} {}", user.id, e))
|
|
||||||
})?
|
|
||||||
};
|
|
||||||
|
|
||||||
tm.commit_transaction(conn)
|
|
||||||
.map_err(|_| ServiceErrors::DatabaseConnectionLost)?;
|
|
||||||
|
|
||||||
Ok(token)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,17 +1,34 @@
|
|||||||
use actix::{Handler, Message};
|
use actix::{Handler, Message};
|
||||||
use diesel::pg::Pg;
|
|
||||||
use diesel::prelude::*;
|
use diesel::prelude::*;
|
||||||
use serde::{Deserialize, Serialize};
|
|
||||||
|
|
||||||
use jirs_data::IssueAssignee;
|
use jirs_data::IssueAssignee;
|
||||||
|
|
||||||
use crate::{db::DbExecutor, errors::ServiceErrors};
|
use crate::{
|
||||||
|
db::{DbExecutor, DbPooledConn},
|
||||||
|
db_pool,
|
||||||
|
errors::ServiceErrors,
|
||||||
|
q,
|
||||||
|
};
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize)]
|
|
||||||
pub struct LoadAssignees {
|
pub struct LoadAssignees {
|
||||||
pub issue_id: i32,
|
pub issue_id: i32,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl LoadAssignees {
|
||||||
|
pub fn execute(self, conn: &DbPooledConn) -> Result<Vec<IssueAssignee>, ServiceErrors> {
|
||||||
|
use crate::schema::issue_assignees::dsl::*;
|
||||||
|
|
||||||
|
q!(issue_assignees
|
||||||
|
.distinct_on(id)
|
||||||
|
.filter(issue_id.eq(self.issue_id)))
|
||||||
|
.load::<IssueAssignee>(conn)
|
||||||
|
.map_err(|e| {
|
||||||
|
error!("{:?}", e);
|
||||||
|
ServiceErrors::RecordNotFound("issue users".to_string())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Message for LoadAssignees {
|
impl Message for LoadAssignees {
|
||||||
type Result = Result<Vec<IssueAssignee>, ServiceErrors>;
|
type Result = Result<Vec<IssueAssignee>, ServiceErrors>;
|
||||||
}
|
}
|
||||||
@ -20,18 +37,8 @@ impl Handler<LoadAssignees> for DbExecutor {
|
|||||||
type Result = Result<Vec<IssueAssignee>, ServiceErrors>;
|
type Result = Result<Vec<IssueAssignee>, ServiceErrors>;
|
||||||
|
|
||||||
fn handle(&mut self, msg: LoadAssignees, _ctx: &mut Self::Context) -> Self::Result {
|
fn handle(&mut self, msg: LoadAssignees, _ctx: &mut Self::Context) -> Self::Result {
|
||||||
use crate::schema::issue_assignees::dsl::*;
|
let conn = db_pool!(self);
|
||||||
|
|
||||||
let conn = &self
|
msg.execute(conn)
|
||||||
.pool
|
|
||||||
.get()
|
|
||||||
.map_err(|_| ServiceErrors::DatabaseConnectionLost)?;
|
|
||||||
let issue_assignees_query = issue_assignees
|
|
||||||
.distinct_on(id)
|
|
||||||
.filter(issue_id.eq(msg.issue_id));
|
|
||||||
debug!("{}", diesel::debug_query::<Pg, _>(&issue_assignees_query));
|
|
||||||
issue_assignees_query
|
|
||||||
.load::<IssueAssignee>(conn)
|
|
||||||
.map_err(|_| ServiceErrors::RecordNotFound("issue users".to_string()))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,15 +1,30 @@
|
|||||||
use actix::{Handler, Message};
|
use actix::{Handler, Message};
|
||||||
use diesel::pg::Pg;
|
|
||||||
use diesel::prelude::*;
|
use diesel::prelude::*;
|
||||||
|
|
||||||
use jirs_data::{IssueStatus, IssueStatusId, Position, ProjectId, TitleString};
|
use jirs_data::{IssueStatus, IssueStatusId, Position, ProjectId, TitleString};
|
||||||
|
|
||||||
use crate::{db::DbExecutor, errors::ServiceErrors};
|
use crate::db::DbPooledConn;
|
||||||
|
use crate::{db::DbExecutor, db_pool, errors::ServiceErrors, q};
|
||||||
|
|
||||||
pub struct LoadIssueStatuses {
|
pub struct LoadIssueStatuses {
|
||||||
pub project_id: ProjectId,
|
pub project_id: ProjectId,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl LoadIssueStatuses {
|
||||||
|
pub fn execute(self, conn: &DbPooledConn) -> Result<Vec<IssueStatus>, ServiceErrors> {
|
||||||
|
use crate::schema::issue_statuses::dsl::{id, issue_statuses, project_id};
|
||||||
|
|
||||||
|
q!(issue_statuses
|
||||||
|
.distinct_on(id)
|
||||||
|
.filter(project_id.eq(self.project_id)))
|
||||||
|
.load::<IssueStatus>(conn)
|
||||||
|
.map_err(|e| {
|
||||||
|
error!("{:?}", e);
|
||||||
|
ServiceErrors::RecordNotFound("issue users".to_string())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Message for LoadIssueStatuses {
|
impl Message for LoadIssueStatuses {
|
||||||
type Result = Result<Vec<IssueStatus>, ServiceErrors>;
|
type Result = Result<Vec<IssueStatus>, ServiceErrors>;
|
||||||
}
|
}
|
||||||
@ -18,20 +33,9 @@ impl Handler<LoadIssueStatuses> for DbExecutor {
|
|||||||
type Result = Result<Vec<IssueStatus>, ServiceErrors>;
|
type Result = Result<Vec<IssueStatus>, ServiceErrors>;
|
||||||
|
|
||||||
fn handle(&mut self, msg: LoadIssueStatuses, _ctx: &mut Self::Context) -> Self::Result {
|
fn handle(&mut self, msg: LoadIssueStatuses, _ctx: &mut Self::Context) -> Self::Result {
|
||||||
use crate::schema::issue_statuses::dsl::{id, issue_statuses, project_id};
|
let conn = db_pool!(self);
|
||||||
|
|
||||||
let conn = &self
|
msg.execute(conn)
|
||||||
.pool
|
|
||||||
.get()
|
|
||||||
.map_err(|_| ServiceErrors::DatabaseConnectionLost)?;
|
|
||||||
|
|
||||||
let issue_assignees_query = issue_statuses
|
|
||||||
.distinct_on(id)
|
|
||||||
.filter(project_id.eq(msg.project_id));
|
|
||||||
debug!("{}", diesel::debug_query::<Pg, _>(&issue_assignees_query));
|
|
||||||
issue_assignees_query
|
|
||||||
.load::<IssueStatus>(conn)
|
|
||||||
.map_err(|_| ServiceErrors::RecordNotFound("issue users".to_string()))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -41,6 +45,22 @@ pub struct CreateIssueStatus {
|
|||||||
pub name: TitleString,
|
pub name: TitleString,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl CreateIssueStatus {
|
||||||
|
pub fn execute(self, conn: &DbPooledConn) -> Result<IssueStatus, ServiceErrors> {
|
||||||
|
use crate::schema::issue_statuses::dsl::{issue_statuses, name, position, project_id};
|
||||||
|
q!(diesel::insert_into(issue_statuses).values((
|
||||||
|
project_id.eq(self.project_id),
|
||||||
|
name.eq(self.name),
|
||||||
|
position.eq(self.position),
|
||||||
|
)))
|
||||||
|
.get_result::<IssueStatus>(conn)
|
||||||
|
.map_err(|e| {
|
||||||
|
error!("{:?}", e);
|
||||||
|
ServiceErrors::RecordNotFound("issue users".to_string())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Message for CreateIssueStatus {
|
impl Message for CreateIssueStatus {
|
||||||
type Result = Result<IssueStatus, ServiceErrors>;
|
type Result = Result<IssueStatus, ServiceErrors>;
|
||||||
}
|
}
|
||||||
@ -49,22 +69,9 @@ impl Handler<CreateIssueStatus> for DbExecutor {
|
|||||||
type Result = Result<IssueStatus, ServiceErrors>;
|
type Result = Result<IssueStatus, ServiceErrors>;
|
||||||
|
|
||||||
fn handle(&mut self, msg: CreateIssueStatus, _ctx: &mut Self::Context) -> Self::Result {
|
fn handle(&mut self, msg: CreateIssueStatus, _ctx: &mut Self::Context) -> Self::Result {
|
||||||
use crate::schema::issue_statuses::dsl::{issue_statuses, name, position, project_id};
|
let conn = db_pool!(self);
|
||||||
|
|
||||||
let conn = &self
|
msg.execute(conn)
|
||||||
.pool
|
|
||||||
.get()
|
|
||||||
.map_err(|_| ServiceErrors::DatabaseConnectionLost)?;
|
|
||||||
|
|
||||||
let issue_assignees_query = diesel::insert_into(issue_statuses).values((
|
|
||||||
project_id.eq(msg.project_id),
|
|
||||||
name.eq(msg.name),
|
|
||||||
position.eq(msg.position),
|
|
||||||
));
|
|
||||||
debug!("{}", diesel::debug_query::<Pg, _>(&issue_assignees_query));
|
|
||||||
issue_assignees_query
|
|
||||||
.get_result::<IssueStatus>(conn)
|
|
||||||
.map_err(|_| ServiceErrors::RecordNotFound("issue users".to_string()))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -83,18 +90,16 @@ impl Handler<DeleteIssueStatus> for DbExecutor {
|
|||||||
fn handle(&mut self, msg: DeleteIssueStatus, _ctx: &mut Self::Context) -> Self::Result {
|
fn handle(&mut self, msg: DeleteIssueStatus, _ctx: &mut Self::Context) -> Self::Result {
|
||||||
use crate::schema::issue_statuses::dsl::{id, issue_statuses, project_id};
|
use crate::schema::issue_statuses::dsl::{id, issue_statuses, project_id};
|
||||||
|
|
||||||
let conn = &self
|
let conn = db_pool!(self);
|
||||||
.pool
|
|
||||||
.get()
|
|
||||||
.map_err(|_| ServiceErrors::DatabaseConnectionLost)?;
|
|
||||||
|
|
||||||
let issue_assignees_query = diesel::delete(issue_statuses)
|
q!(diesel::delete(issue_statuses)
|
||||||
.filter(id.eq(msg.issue_status_id))
|
.filter(id.eq(msg.issue_status_id))
|
||||||
.filter(project_id.eq(msg.project_id));
|
.filter(project_id.eq(msg.project_id)))
|
||||||
debug!("{}", diesel::debug_query::<Pg, _>(&issue_assignees_query));
|
.execute(conn)
|
||||||
issue_assignees_query
|
.map_err(|e| {
|
||||||
.execute(conn)
|
error!("{:?}", e);
|
||||||
.map_err(|_| ServiceErrors::RecordNotFound("issue users".to_string()))?;
|
ServiceErrors::RecordNotFound("issue users".to_string())
|
||||||
|
})?;
|
||||||
Ok(msg.issue_status_id)
|
Ok(msg.issue_status_id)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -118,22 +123,20 @@ impl Handler<UpdateIssueStatus> for DbExecutor {
|
|||||||
id, issue_statuses, name, position, project_id, updated_at,
|
id, issue_statuses, name, position, project_id, updated_at,
|
||||||
};
|
};
|
||||||
|
|
||||||
let conn = &self
|
let conn = db_pool!(self);
|
||||||
.pool
|
|
||||||
.get()
|
|
||||||
.map_err(|_| ServiceErrors::DatabaseConnectionLost)?;
|
|
||||||
|
|
||||||
let issue_assignees_query = diesel::update(issue_statuses)
|
q!(diesel::update(issue_statuses)
|
||||||
.set((
|
.set((
|
||||||
name.eq(msg.name),
|
name.eq(msg.name),
|
||||||
position.eq(msg.position),
|
position.eq(msg.position),
|
||||||
updated_at.eq(chrono::Utc::now().naive_utc()),
|
updated_at.eq(chrono::Utc::now().naive_utc()),
|
||||||
))
|
))
|
||||||
.filter(id.eq(msg.issue_status_id))
|
.filter(id.eq(msg.issue_status_id))
|
||||||
.filter(project_id.eq(msg.project_id));
|
.filter(project_id.eq(msg.project_id)))
|
||||||
debug!("{}", diesel::debug_query::<Pg, _>(&issue_assignees_query));
|
.get_result::<IssueStatus>(conn)
|
||||||
issue_assignees_query
|
.map_err(|e| {
|
||||||
.get_result::<IssueStatus>(conn)
|
error!("{:?}", e);
|
||||||
.map_err(|_| ServiceErrors::RecordNotFound("issue users".to_string()))
|
ServiceErrors::RecordNotFound("issue users".to_string())
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,9 +4,10 @@ use diesel::expression::sql_literal::sql;
|
|||||||
use diesel::prelude::*;
|
use diesel::prelude::*;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
use jirs_data::msg::WsError;
|
||||||
use jirs_data::{IssuePriority, IssueStatusId, IssueType};
|
use jirs_data::{IssuePriority, IssueStatusId, IssueType};
|
||||||
|
|
||||||
use crate::{db::DbExecutor, errors::ServiceErrors, models::Issue};
|
use crate::{db::DbExecutor, db_pool, errors::ServiceErrors, models::Issue};
|
||||||
|
|
||||||
const FAILED_CONNECT_USER_AND_ISSUE: &str = "Failed to create connection between user and issue";
|
const FAILED_CONNECT_USER_AND_ISSUE: &str = "Failed to create connection between user and issue";
|
||||||
|
|
||||||
@ -24,19 +25,18 @@ impl Handler<LoadIssue> for DbExecutor {
|
|||||||
|
|
||||||
fn handle(&mut self, msg: LoadIssue, _ctx: &mut Self::Context) -> Self::Result {
|
fn handle(&mut self, msg: LoadIssue, _ctx: &mut Self::Context) -> Self::Result {
|
||||||
use crate::schema::issues::dsl::{id, issues};
|
use crate::schema::issues::dsl::{id, issues};
|
||||||
let conn = &self
|
|
||||||
.pool
|
let conn = db_pool!(self);
|
||||||
.get()
|
|
||||||
.map_err(|_| ServiceErrors::DatabaseConnectionLost)?;
|
|
||||||
|
|
||||||
let query = issues.filter(id.eq(msg.issue_id)).distinct();
|
let query = issues.filter(id.eq(msg.issue_id)).distinct();
|
||||||
debug!(
|
debug!(
|
||||||
"{}",
|
"{}",
|
||||||
diesel::debug_query::<diesel::pg::Pg, _>(&query).to_string()
|
diesel::debug_query::<diesel::pg::Pg, _>(&query).to_string()
|
||||||
);
|
);
|
||||||
query
|
query.first::<Issue>(conn).map_err(|e| {
|
||||||
.first::<Issue>(conn)
|
error!("{:?}", e);
|
||||||
.map_err(|_| ServiceErrors::RecordNotFound("project issues".to_string()))
|
ServiceErrors::RecordNotFound("project issues".to_string())
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -54,18 +54,18 @@ impl Handler<LoadProjectIssues> for DbExecutor {
|
|||||||
|
|
||||||
fn handle(&mut self, msg: LoadProjectIssues, _ctx: &mut Self::Context) -> Self::Result {
|
fn handle(&mut self, msg: LoadProjectIssues, _ctx: &mut Self::Context) -> Self::Result {
|
||||||
use crate::schema::issues::dsl::{issues, project_id};
|
use crate::schema::issues::dsl::{issues, project_id};
|
||||||
let conn = &self
|
|
||||||
.pool
|
let conn = db_pool!(self);
|
||||||
.get()
|
|
||||||
.map_err(|_| ServiceErrors::DatabaseConnectionLost)?;
|
|
||||||
let chain = issues.filter(project_id.eq(msg.project_id)).distinct();
|
let chain = issues.filter(project_id.eq(msg.project_id)).distinct();
|
||||||
debug!(
|
debug!(
|
||||||
"{}",
|
"{}",
|
||||||
diesel::debug_query::<diesel::pg::Pg, _>(&chain).to_string()
|
diesel::debug_query::<diesel::pg::Pg, _>(&chain).to_string()
|
||||||
);
|
);
|
||||||
let vec = chain
|
let vec = chain.load::<Issue>(conn).map_err(|e| {
|
||||||
.load::<Issue>(conn)
|
error!("{:?}", e);
|
||||||
.map_err(|_| ServiceErrors::RecordNotFound("project issues".to_string()))?;
|
ServiceErrors::RecordNotFound("project issues".to_string())
|
||||||
|
})?;
|
||||||
Ok(vec)
|
Ok(vec)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -98,10 +98,8 @@ impl Handler<UpdateIssue> for DbExecutor {
|
|||||||
|
|
||||||
fn handle(&mut self, msg: UpdateIssue, _ctx: &mut Self::Context) -> Self::Result {
|
fn handle(&mut self, msg: UpdateIssue, _ctx: &mut Self::Context) -> Self::Result {
|
||||||
use crate::schema::issues::dsl::{self, issues};
|
use crate::schema::issues::dsl::{self, issues};
|
||||||
let conn = &self
|
|
||||||
.pool
|
let conn = db_pool!(self);
|
||||||
.get()
|
|
||||||
.map_err(|_| ServiceErrors::DatabaseConnectionLost)?;
|
|
||||||
|
|
||||||
let current_issue_id = msg.issue_id;
|
let current_issue_id = msg.issue_id;
|
||||||
|
|
||||||
@ -133,7 +131,8 @@ impl Handler<UpdateIssue> for DbExecutor {
|
|||||||
"{}",
|
"{}",
|
||||||
diesel::debug_query::<diesel::pg::Pg, _>(&chain).to_string()
|
diesel::debug_query::<diesel::pg::Pg, _>(&chain).to_string()
|
||||||
);
|
);
|
||||||
chain.get_result::<Issue>(conn).map_err(|_| {
|
chain.get_result::<Issue>(conn).map_err(|e| {
|
||||||
|
error!("{:?}", e);
|
||||||
ServiceErrors::DatabaseQueryFailed("Failed to update issue".to_string())
|
ServiceErrors::DatabaseQueryFailed("Failed to update issue".to_string())
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
@ -142,12 +141,18 @@ impl Handler<UpdateIssue> for DbExecutor {
|
|||||||
diesel::delete(dsl::issue_assignees)
|
diesel::delete(dsl::issue_assignees)
|
||||||
.filter(not(dsl::user_id.eq_any(user_ids)).and(dsl::issue_id.eq(current_issue_id)))
|
.filter(not(dsl::user_id.eq_any(user_ids)).and(dsl::issue_id.eq(current_issue_id)))
|
||||||
.execute(conn)
|
.execute(conn)
|
||||||
.map_err(|_| ServiceErrors::DatabaseConnectionLost)?;
|
.map_err(|e| {
|
||||||
|
error!("{:?}", e);
|
||||||
|
ServiceErrors::DatabaseConnectionLost
|
||||||
|
})?;
|
||||||
let existing: Vec<i32> = dsl::issue_assignees
|
let existing: Vec<i32> = dsl::issue_assignees
|
||||||
.select(dsl::user_id)
|
.select(dsl::user_id)
|
||||||
.filter(dsl::issue_id.eq(current_issue_id))
|
.filter(dsl::issue_id.eq(current_issue_id))
|
||||||
.get_results::<i32>(conn)
|
.get_results::<i32>(conn)
|
||||||
.map_err(|_| ServiceErrors::DatabaseConnectionLost)?;
|
.map_err(|e| {
|
||||||
|
error!("{:?}", e);
|
||||||
|
ServiceErrors::DatabaseConnectionLost
|
||||||
|
})?;
|
||||||
let mut values = vec![];
|
let mut values = vec![];
|
||||||
for user_id in user_ids.iter() {
|
for user_id in user_ids.iter() {
|
||||||
if !existing.contains(user_id) {
|
if !existing.contains(user_id) {
|
||||||
@ -160,15 +165,16 @@ impl Handler<UpdateIssue> for DbExecutor {
|
|||||||
diesel::insert_into(dsl::issue_assignees)
|
diesel::insert_into(dsl::issue_assignees)
|
||||||
.values(values)
|
.values(values)
|
||||||
.execute(conn)
|
.execute(conn)
|
||||||
.map_err(|_| {
|
.map_err(|e| {
|
||||||
|
error!("{:?}", e);
|
||||||
ServiceErrors::DatabaseQueryFailed(FAILED_CONNECT_USER_AND_ISSUE.to_string())
|
ServiceErrors::DatabaseQueryFailed(FAILED_CONNECT_USER_AND_ISSUE.to_string())
|
||||||
})?;
|
})?;
|
||||||
}
|
}
|
||||||
|
|
||||||
issues
|
issues.find(msg.issue_id).first::<Issue>(conn).map_err(|e| {
|
||||||
.find(msg.issue_id)
|
error!("{:?}", e);
|
||||||
.first::<Issue>(conn)
|
ServiceErrors::DatabaseConnectionLost
|
||||||
.map_err(|_| ServiceErrors::DatabaseConnectionLost)
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -188,10 +194,7 @@ impl Handler<DeleteIssue> for DbExecutor {
|
|||||||
use crate::schema::issue_assignees::dsl::{issue_assignees, issue_id};
|
use crate::schema::issue_assignees::dsl::{issue_assignees, issue_id};
|
||||||
use crate::schema::issues::dsl::issues;
|
use crate::schema::issues::dsl::issues;
|
||||||
|
|
||||||
let conn = &self
|
let conn = db_pool!(self);
|
||||||
.pool
|
|
||||||
.get()
|
|
||||||
.map_err(|_| ServiceErrors::DatabaseConnectionLost)?;
|
|
||||||
|
|
||||||
diesel::delete(issue_assignees.filter(issue_id.eq(msg.issue_id)))
|
diesel::delete(issue_assignees.filter(issue_id.eq(msg.issue_id)))
|
||||||
.execute(conn)
|
.execute(conn)
|
||||||
@ -231,16 +234,16 @@ impl Handler<CreateIssue> for DbExecutor {
|
|||||||
use crate::schema::issue_assignees::dsl;
|
use crate::schema::issue_assignees::dsl;
|
||||||
use crate::schema::issues::dsl::issues;
|
use crate::schema::issues::dsl::issues;
|
||||||
|
|
||||||
let conn = &self
|
let conn = db_pool!(self);
|
||||||
.pool
|
|
||||||
.get()
|
|
||||||
.map_err(|_| ServiceErrors::DatabaseConnectionLost)?;
|
|
||||||
|
|
||||||
let list_position = issues
|
let list_position = issues
|
||||||
// .filter(issue_status_id.eq(IssueStatus::Backlog))
|
// .filter(issue_status_id.eq(IssueStatus::Backlog))
|
||||||
.select(sql("max(list_position) + 1"))
|
.select(sql("COALESCE(max(list_position), 0) + 1"))
|
||||||
.get_result::<i32>(conn)
|
.get_result::<i32>(conn)
|
||||||
.map_err(|_| ServiceErrors::DatabaseConnectionLost)?;
|
.map_err(|e| {
|
||||||
|
error!("resolve new issue position failed {}", e);
|
||||||
|
ServiceErrors::DatabaseConnectionLost
|
||||||
|
})?;
|
||||||
|
|
||||||
info!("{:?}", msg.issue_type);
|
info!("{:?}", msg.issue_type);
|
||||||
info!("msg.issue_status_id {:?}", msg.issue_status_id);
|
info!("msg.issue_status_id {:?}", msg.issue_status_id);
|
||||||
@ -252,9 +255,12 @@ impl Handler<CreateIssue> for DbExecutor {
|
|||||||
},
|
},
|
||||||
ctx,
|
ctx,
|
||||||
)
|
)
|
||||||
.map_err(|_| ServiceErrors::DatabaseConnectionLost)?
|
.map_err(|e| {
|
||||||
|
error!("{:?}", e);
|
||||||
|
ServiceErrors::Error(WsError::FailedToFetchIssueStatuses)
|
||||||
|
})?
|
||||||
.get(0)
|
.get(0)
|
||||||
.ok_or_else(|| ServiceErrors::DatabaseConnectionLost)?
|
.ok_or_else(|| ServiceErrors::Error(WsError::NoIssueStatuses))?
|
||||||
.id
|
.id
|
||||||
} else {
|
} else {
|
||||||
msg.issue_status_id
|
msg.issue_status_id
|
||||||
@ -302,7 +308,10 @@ impl Handler<CreateIssue> for DbExecutor {
|
|||||||
diesel::insert_into(dsl::issue_assignees)
|
diesel::insert_into(dsl::issue_assignees)
|
||||||
.values(values)
|
.values(values)
|
||||||
.execute(conn)
|
.execute(conn)
|
||||||
.map_err(|_| ServiceErrors::DatabaseConnectionLost)?;
|
.map_err(|e| {
|
||||||
|
error!("{:?}", e);
|
||||||
|
ServiceErrors::DatabaseConnectionLost
|
||||||
|
})?;
|
||||||
|
|
||||||
Ok(issue)
|
Ok(issue)
|
||||||
}
|
}
|
||||||
|
@ -8,7 +8,9 @@ use crate::{
|
|||||||
users::{FindUser, LookupUser},
|
users::{FindUser, LookupUser},
|
||||||
DbExecutor,
|
DbExecutor,
|
||||||
},
|
},
|
||||||
|
db_pool,
|
||||||
errors::ServiceErrors,
|
errors::ServiceErrors,
|
||||||
|
q,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
@ -26,19 +28,14 @@ impl Handler<LoadMessages> for DbExecutor {
|
|||||||
fn handle(&mut self, msg: LoadMessages, _ctx: &mut Self::Context) -> Self::Result {
|
fn handle(&mut self, msg: LoadMessages, _ctx: &mut Self::Context) -> Self::Result {
|
||||||
use crate::schema::messages::dsl::*;
|
use crate::schema::messages::dsl::*;
|
||||||
|
|
||||||
let conn = &self
|
let conn = db_pool!(self);
|
||||||
.pool
|
|
||||||
.get()
|
|
||||||
.map_err(|_| ServiceErrors::DatabaseConnectionLost)?;
|
|
||||||
|
|
||||||
let query = messages.filter(receiver_id.eq(msg.user_id));
|
q!(messages.filter(receiver_id.eq(msg.user_id)))
|
||||||
debug!(
|
|
||||||
"{}",
|
|
||||||
diesel::debug_query::<diesel::pg::Pg, _>(&query).to_string()
|
|
||||||
);
|
|
||||||
query
|
|
||||||
.load(conn)
|
.load(conn)
|
||||||
.map_err(|_| ServiceErrors::DatabaseQueryFailed("load user messages".to_string()))
|
.map_err(|e| {
|
||||||
|
error!("{:?}", e);
|
||||||
|
ServiceErrors::DatabaseQueryFailed("load user messages".to_string())
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -58,20 +55,18 @@ impl Handler<MarkMessageSeen> for DbExecutor {
|
|||||||
fn handle(&mut self, msg: MarkMessageSeen, _ctx: &mut Self::Context) -> Self::Result {
|
fn handle(&mut self, msg: MarkMessageSeen, _ctx: &mut Self::Context) -> Self::Result {
|
||||||
use crate::schema::messages::dsl::*;
|
use crate::schema::messages::dsl::*;
|
||||||
|
|
||||||
let conn = &self
|
let conn = db_pool!(self);
|
||||||
.pool
|
|
||||||
.get()
|
|
||||||
.map_err(|_| ServiceErrors::DatabaseConnectionLost)?;
|
|
||||||
|
|
||||||
let query = diesel::delete(
|
let size = q!(diesel::delete(
|
||||||
messages
|
messages
|
||||||
.find(msg.message_id)
|
.find(msg.message_id)
|
||||||
.filter(receiver_id.eq(msg.user_id)),
|
.filter(receiver_id.eq(msg.user_id)),
|
||||||
);
|
))
|
||||||
debug!("{}", diesel::debug_query::<diesel::pg::Pg, _>(&query));
|
.execute(conn)
|
||||||
let size = query
|
.map_err(|e| {
|
||||||
.execute(conn)
|
error!("{:?}", e);
|
||||||
.map_err(|_| ServiceErrors::DatabaseQueryFailed("load user messages".to_string()))?;
|
ServiceErrors::DatabaseQueryFailed("load user messages".to_string())
|
||||||
|
})?;
|
||||||
|
|
||||||
if size > 0 {
|
if size > 0 {
|
||||||
Ok(msg.message_id)
|
Ok(msg.message_id)
|
||||||
@ -110,10 +105,7 @@ impl Handler<CreateMessage> for DbExecutor {
|
|||||||
fn handle(&mut self, msg: CreateMessage, ctx: &mut Self::Context) -> Self::Result {
|
fn handle(&mut self, msg: CreateMessage, ctx: &mut Self::Context) -> Self::Result {
|
||||||
use crate::schema::messages::dsl::*;
|
use crate::schema::messages::dsl::*;
|
||||||
|
|
||||||
let conn = &self
|
let conn = db_pool!(self);
|
||||||
.pool
|
|
||||||
.get()
|
|
||||||
.map_err(|_| ServiceErrors::DatabaseConnectionLost)?;
|
|
||||||
|
|
||||||
let user: User = match {
|
let user: User = match {
|
||||||
match msg.receiver {
|
match msg.receiver {
|
||||||
@ -143,9 +135,10 @@ impl Handler<CreateMessage> for DbExecutor {
|
|||||||
"{}",
|
"{}",
|
||||||
diesel::debug_query::<diesel::pg::Pg, _>(&query).to_string()
|
diesel::debug_query::<diesel::pg::Pg, _>(&query).to_string()
|
||||||
);
|
);
|
||||||
query
|
query.get_result(conn).map_err(|e| {
|
||||||
.get_result(conn)
|
error!("{:?}", e);
|
||||||
.map_err(|_| ServiceErrors::DatabaseQueryFailed("create message failed".to_string()))
|
ServiceErrors::DatabaseQueryFailed("create message failed".to_string())
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -165,22 +158,17 @@ impl Handler<LookupMessagesByToken> for DbExecutor {
|
|||||||
fn handle(&mut self, msg: LookupMessagesByToken, _ctx: &mut Self::Context) -> Self::Result {
|
fn handle(&mut self, msg: LookupMessagesByToken, _ctx: &mut Self::Context) -> Self::Result {
|
||||||
use crate::schema::messages::dsl::*;
|
use crate::schema::messages::dsl::*;
|
||||||
|
|
||||||
let conn = &self
|
let conn = db_pool!(self);
|
||||||
.pool
|
|
||||||
.get()
|
|
||||||
.map_err(|_| ServiceErrors::DatabaseConnectionLost)?;
|
|
||||||
|
|
||||||
let query = messages.filter(
|
q!(messages.filter(
|
||||||
hyper_link
|
hyper_link
|
||||||
.eq(format!("#{}", msg.token))
|
.eq(format!("#{}", msg.token))
|
||||||
.and(receiver_id.eq(msg.user_id)),
|
.and(receiver_id.eq(msg.user_id)),
|
||||||
);
|
))
|
||||||
debug!(
|
.load(conn)
|
||||||
"{}",
|
.map_err(|e| {
|
||||||
diesel::debug_query::<diesel::pg::Pg, _>(&query).to_string()
|
error!("{:?}", e);
|
||||||
);
|
ServiceErrors::DatabaseQueryFailed("create message failed".to_string())
|
||||||
query
|
})
|
||||||
.load(conn)
|
|
||||||
.map_err(|_| ServiceErrors::DatabaseQueryFailed("create message failed".to_string()))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -5,6 +5,8 @@ use diesel::pg::PgConnection;
|
|||||||
use diesel::r2d2::{self, ConnectionManager};
|
use diesel::r2d2::{self, ConnectionManager};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
use crate::errors::ServiceErrors;
|
||||||
|
|
||||||
pub mod authorize_user;
|
pub mod authorize_user;
|
||||||
pub mod comments;
|
pub mod comments;
|
||||||
pub mod epics;
|
pub mod epics;
|
||||||
@ -105,3 +107,68 @@ impl Configuration {
|
|||||||
"db.test.toml"
|
"db.test.toml"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[macro_export]
|
||||||
|
macro_rules! db_pool {
|
||||||
|
($self: expr) => {
|
||||||
|
&$self.pool.get().map_err(|e| {
|
||||||
|
error!("{:?}", e);
|
||||||
|
ServiceErrors::DatabaseConnectionLost
|
||||||
|
})?
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#[macro_export]
|
||||||
|
macro_rules! q {
|
||||||
|
($q: expr) => {{
|
||||||
|
let q = $q;
|
||||||
|
debug!(
|
||||||
|
"{}",
|
||||||
|
diesel::debug_query::<diesel::pg::Pg, _>(&q).to_string()
|
||||||
|
);
|
||||||
|
q
|
||||||
|
}};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Guard<'l> {
|
||||||
|
conn: &'l crate::db::DbPooledConn,
|
||||||
|
tm: &'l diesel::connection::AnsiTransactionManager,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'l> Guard<'l> {
|
||||||
|
pub fn new(conn: &'l DbPooledConn) -> Result<Self, ServiceErrors> {
|
||||||
|
use diesel::{connection::TransactionManager, prelude::*};
|
||||||
|
let tm = conn.transaction_manager();
|
||||||
|
tm.begin_transaction(conn).map_err(|e| {
|
||||||
|
log::error!("{:?}", e);
|
||||||
|
ServiceErrors::DatabaseConnectionLost
|
||||||
|
})?;
|
||||||
|
Ok(Self { conn, tm })
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn run<R, F: FnOnce(&Guard) -> Result<R, ServiceErrors>>(
|
||||||
|
&self,
|
||||||
|
f: F,
|
||||||
|
) -> Result<R, ServiceErrors> {
|
||||||
|
use diesel::connection::TransactionManager;
|
||||||
|
|
||||||
|
let r = f(self);
|
||||||
|
match r {
|
||||||
|
Ok(r) => {
|
||||||
|
self.tm.commit_transaction(self.conn).map_err(|e| {
|
||||||
|
log::error!("{:?}", e);
|
||||||
|
ServiceErrors::DatabaseConnectionLost
|
||||||
|
})?;
|
||||||
|
Ok(r)
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
log::error!("{:?}", e);
|
||||||
|
self.tm.rollback_transaction(self.conn).map_err(|e| {
|
||||||
|
log::error!("{:?}", e);
|
||||||
|
ServiceErrors::DatabaseConnectionLost
|
||||||
|
})?;
|
||||||
|
Err(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -1,19 +1,30 @@
|
|||||||
use actix::{Handler, Message};
|
use actix::{Handler, Message};
|
||||||
use diesel::pg::Pg;
|
|
||||||
use diesel::prelude::*;
|
use diesel::prelude::*;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use jirs_data::{NameString, Project, ProjectCategory, ProjectId, TimeTracking, UserId};
|
use jirs_data::{NameString, Project, ProjectCategory, ProjectId, TimeTracking, UserId};
|
||||||
|
|
||||||
use crate::db::DbExecutor;
|
use crate::db::DbPooledConn;
|
||||||
use crate::errors::ServiceErrors;
|
use crate::{db::DbExecutor, db_pool, errors::ServiceErrors, q, schema::projects::all_columns};
|
||||||
use crate::schema::projects::all_columns;
|
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize)]
|
#[derive(Serialize, Deserialize)]
|
||||||
pub struct LoadCurrentProject {
|
pub struct LoadCurrentProject {
|
||||||
pub project_id: ProjectId,
|
pub project_id: ProjectId,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl LoadCurrentProject {
|
||||||
|
pub fn execute(self, conn: &DbPooledConn) -> Result<Project, ServiceErrors> {
|
||||||
|
use crate::schema::projects::dsl::projects;
|
||||||
|
|
||||||
|
q!(projects.find(self.project_id))
|
||||||
|
.first::<Project>(conn)
|
||||||
|
.map_err(|e| {
|
||||||
|
error!("{:?}", e);
|
||||||
|
ServiceErrors::RecordNotFound("Project".to_string())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Message for LoadCurrentProject {
|
impl Message for LoadCurrentProject {
|
||||||
type Result = Result<Project, ServiceErrors>;
|
type Result = Result<Project, ServiceErrors>;
|
||||||
}
|
}
|
||||||
@ -22,26 +33,12 @@ impl Handler<LoadCurrentProject> for DbExecutor {
|
|||||||
type Result = Result<Project, ServiceErrors>;
|
type Result = Result<Project, ServiceErrors>;
|
||||||
|
|
||||||
fn handle(&mut self, msg: LoadCurrentProject, _ctx: &mut Self::Context) -> Self::Result {
|
fn handle(&mut self, msg: LoadCurrentProject, _ctx: &mut Self::Context) -> Self::Result {
|
||||||
use crate::schema::projects::dsl::projects;
|
let conn = db_pool!(self);
|
||||||
let conn = &self
|
|
||||||
.pool
|
|
||||||
.get()
|
|
||||||
.map_err(|_| ServiceErrors::DatabaseConnectionLost)?;
|
|
||||||
|
|
||||||
let query = projects.find(msg.project_id);
|
msg.execute(conn)
|
||||||
|
|
||||||
debug!(
|
|
||||||
"{}",
|
|
||||||
diesel::debug_query::<diesel::pg::Pg, _>(&query).to_string()
|
|
||||||
);
|
|
||||||
|
|
||||||
query
|
|
||||||
.first::<Project>(conn)
|
|
||||||
.map_err(|_| ServiceErrors::RecordNotFound("Project".to_string()))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize)]
|
|
||||||
pub struct CreateProject {
|
pub struct CreateProject {
|
||||||
pub name: NameString,
|
pub name: NameString,
|
||||||
pub url: Option<String>,
|
pub url: Option<String>,
|
||||||
@ -50,6 +47,38 @@ pub struct CreateProject {
|
|||||||
pub time_tracking: Option<TimeTracking>,
|
pub time_tracking: Option<TimeTracking>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl CreateProject {
|
||||||
|
pub fn execute(self, conn: &DbPooledConn) -> Result<Project, ServiceErrors> {
|
||||||
|
use crate::schema::projects::dsl::*;
|
||||||
|
|
||||||
|
crate::db::Guard::new(conn)?.run(|_guard| {
|
||||||
|
let p = q!(diesel::insert_into(projects)
|
||||||
|
.values((
|
||||||
|
name.eq(self.name),
|
||||||
|
self.url.map(|v| url.eq(v)),
|
||||||
|
self.description.map(|v| description.eq(v)),
|
||||||
|
self.category.map(|v| category.eq(v)),
|
||||||
|
self.time_tracking.map(|v| time_tracking.eq(v)),
|
||||||
|
))
|
||||||
|
.returning(all_columns))
|
||||||
|
.get_result::<Project>(conn)
|
||||||
|
.map_err(|e| {
|
||||||
|
error!("{:?}", e);
|
||||||
|
ServiceErrors::DatabaseQueryFailed(format!("{}", e))
|
||||||
|
})?;
|
||||||
|
|
||||||
|
crate::db::issue_statuses::CreateIssueStatus {
|
||||||
|
project_id: p.id,
|
||||||
|
position: 0,
|
||||||
|
name: "TODO".to_string(),
|
||||||
|
}
|
||||||
|
.execute(conn)?;
|
||||||
|
|
||||||
|
Ok(p)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Message for CreateProject {
|
impl Message for CreateProject {
|
||||||
type Result = Result<Project, ServiceErrors>;
|
type Result = Result<Project, ServiceErrors>;
|
||||||
}
|
}
|
||||||
@ -58,29 +87,12 @@ impl Handler<CreateProject> for DbExecutor {
|
|||||||
type Result = Result<Project, ServiceErrors>;
|
type Result = Result<Project, ServiceErrors>;
|
||||||
|
|
||||||
fn handle(&mut self, msg: CreateProject, _ctx: &mut Self::Context) -> Self::Result {
|
fn handle(&mut self, msg: CreateProject, _ctx: &mut Self::Context) -> Self::Result {
|
||||||
use crate::schema::projects::dsl::*;
|
let conn = db_pool!(self);
|
||||||
let conn = &self
|
|
||||||
.pool
|
|
||||||
.get()
|
|
||||||
.map_err(|_| ServiceErrors::DatabaseConnectionLost)?;
|
|
||||||
|
|
||||||
let query = diesel::insert_into(projects)
|
msg.execute(conn)
|
||||||
.values((
|
|
||||||
name.eq(msg.name),
|
|
||||||
msg.url.map(|v| url.eq(v)),
|
|
||||||
msg.description.map(|v| description.eq(v)),
|
|
||||||
msg.category.map(|v| category.eq(v)),
|
|
||||||
msg.time_tracking.map(|v| time_tracking.eq(v)),
|
|
||||||
))
|
|
||||||
.returning(all_columns);
|
|
||||||
debug!("{}", diesel::debug_query::<Pg, _>(&query));
|
|
||||||
query
|
|
||||||
.get_result::<Project>(conn)
|
|
||||||
.map_err(|e| ServiceErrors::DatabaseQueryFailed(format!("{}", e)))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize)]
|
|
||||||
pub struct UpdateProject {
|
pub struct UpdateProject {
|
||||||
pub project_id: ProjectId,
|
pub project_id: ProjectId,
|
||||||
pub name: Option<NameString>,
|
pub name: Option<NameString>,
|
||||||
@ -90,6 +102,27 @@ pub struct UpdateProject {
|
|||||||
pub time_tracking: Option<TimeTracking>,
|
pub time_tracking: Option<TimeTracking>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl UpdateProject {
|
||||||
|
pub fn execute(self, conn: &DbPooledConn) -> Result<Project, ServiceErrors> {
|
||||||
|
use crate::schema::projects::dsl::*;
|
||||||
|
|
||||||
|
q!(diesel::update(projects.find(self.project_id)).set((
|
||||||
|
self.name.map(|v| name.eq(v)),
|
||||||
|
self.url.map(|v| url.eq(v)),
|
||||||
|
self.description.map(|v| description.eq(v)),
|
||||||
|
self.category.map(|v| category.eq(v)),
|
||||||
|
self.time_tracking.map(|v| time_tracking.eq(v)),
|
||||||
|
)))
|
||||||
|
.execute(conn)
|
||||||
|
.map_err(|e| ServiceErrors::DatabaseQueryFailed(format!("{}", e)))?;
|
||||||
|
|
||||||
|
LoadCurrentProject {
|
||||||
|
project_id: self.project_id,
|
||||||
|
}
|
||||||
|
.execute(conn)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Message for UpdateProject {
|
impl Message for UpdateProject {
|
||||||
type Result = Result<Project, ServiceErrors>;
|
type Result = Result<Project, ServiceErrors>;
|
||||||
}
|
}
|
||||||
@ -98,29 +131,9 @@ impl Handler<UpdateProject> for DbExecutor {
|
|||||||
type Result = Result<Project, ServiceErrors>;
|
type Result = Result<Project, ServiceErrors>;
|
||||||
|
|
||||||
fn handle(&mut self, msg: UpdateProject, _ctx: &mut Self::Context) -> Self::Result {
|
fn handle(&mut self, msg: UpdateProject, _ctx: &mut Self::Context) -> Self::Result {
|
||||||
use crate::schema::projects::dsl::*;
|
let conn = db_pool!(self);
|
||||||
let conn = &self
|
|
||||||
.pool
|
|
||||||
.get()
|
|
||||||
.map_err(|_| ServiceErrors::DatabaseConnectionLost)?;
|
|
||||||
|
|
||||||
let update_query = diesel::update(projects.find(msg.project_id)).set((
|
msg.execute(conn)
|
||||||
msg.name.map(|v| name.eq(v)),
|
|
||||||
msg.url.map(|v| url.eq(v)),
|
|
||||||
msg.description.map(|v| description.eq(v)),
|
|
||||||
msg.category.map(|v| category.eq(v)),
|
|
||||||
msg.time_tracking.map(|v| time_tracking.eq(v)),
|
|
||||||
));
|
|
||||||
debug!("{}", diesel::debug_query::<Pg, _>(&update_query));
|
|
||||||
update_query
|
|
||||||
.execute(conn)
|
|
||||||
.map_err(|e| ServiceErrors::DatabaseQueryFailed(format!("{}", e)))?;
|
|
||||||
|
|
||||||
let project_query = projects.find(msg.project_id);
|
|
||||||
debug!("{}", diesel::debug_query::<Pg, _>(&project_query));
|
|
||||||
project_query
|
|
||||||
.first::<Project>(conn)
|
|
||||||
.map_err(|_| ServiceErrors::RecordNotFound("Project".to_string()))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -128,6 +141,24 @@ pub struct LoadProjects {
|
|||||||
pub user_id: UserId,
|
pub user_id: UserId,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl LoadProjects {
|
||||||
|
pub fn execute(self, conn: &DbPooledConn) -> Result<Vec<Project>, ServiceErrors> {
|
||||||
|
use crate::schema::projects::dsl::*;
|
||||||
|
use crate::schema::user_projects::dsl::{project_id, user_id, user_projects};
|
||||||
|
|
||||||
|
q!(projects
|
||||||
|
.inner_join(user_projects.on(project_id.eq(id)))
|
||||||
|
.filter(user_id.eq(self.user_id))
|
||||||
|
.distinct_on(id)
|
||||||
|
.select(all_columns))
|
||||||
|
.load::<Project>(conn)
|
||||||
|
.map_err(|e| {
|
||||||
|
error!("{:?}", e);
|
||||||
|
ServiceErrors::RecordNotFound("Project".to_string())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Message for LoadProjects {
|
impl Message for LoadProjects {
|
||||||
type Result = Result<Vec<Project>, ServiceErrors>;
|
type Result = Result<Vec<Project>, ServiceErrors>;
|
||||||
}
|
}
|
||||||
@ -136,22 +167,8 @@ impl Handler<LoadProjects> for DbExecutor {
|
|||||||
type Result = Result<Vec<Project>, ServiceErrors>;
|
type Result = Result<Vec<Project>, ServiceErrors>;
|
||||||
|
|
||||||
fn handle(&mut self, msg: LoadProjects, _ctx: &mut Self::Context) -> Self::Result {
|
fn handle(&mut self, msg: LoadProjects, _ctx: &mut Self::Context) -> Self::Result {
|
||||||
use crate::schema::projects::dsl::*;
|
let conn = db_pool!(self);
|
||||||
use crate::schema::user_projects::dsl::{project_id, user_id, user_projects};
|
|
||||||
|
|
||||||
let conn = &self
|
msg.execute(conn)
|
||||||
.pool
|
|
||||||
.get()
|
|
||||||
.map_err(|_| ServiceErrors::DatabaseConnectionLost)?;
|
|
||||||
|
|
||||||
let query = projects
|
|
||||||
.inner_join(user_projects.on(project_id.eq(id)))
|
|
||||||
.filter(user_id.eq(msg.user_id))
|
|
||||||
.distinct_on(id)
|
|
||||||
.select(all_columns);
|
|
||||||
debug!("{}", diesel::debug_query::<diesel::pg::Pg, _>(&query));
|
|
||||||
query
|
|
||||||
.load::<Project>(conn)
|
|
||||||
.map_err(|_| ServiceErrors::RecordNotFound("Project".to_string()))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,19 +1,73 @@
|
|||||||
use actix::{Handler, Message};
|
use actix::{Handler, Message};
|
||||||
use diesel::prelude::*;
|
use diesel::prelude::*;
|
||||||
use serde::{Deserialize, Serialize};
|
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
|
use jirs_data::msg::WsError;
|
||||||
use jirs_data::{Token, UserId};
|
use jirs_data::{Token, UserId};
|
||||||
|
|
||||||
use crate::db::DbExecutor;
|
use crate::{
|
||||||
use crate::errors::ServiceErrors;
|
db::{DbExecutor, DbPooledConn},
|
||||||
use crate::models::TokenForm;
|
db_pool,
|
||||||
|
errors::ServiceErrors,
|
||||||
|
q,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub struct FindUserId {
|
||||||
|
pub user_id: UserId,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FindUserId {
|
||||||
|
pub fn execute(self, conn: &DbPooledConn) -> Result<Token, ServiceErrors> {
|
||||||
|
use crate::schema::tokens::dsl::*;
|
||||||
|
|
||||||
|
q!(tokens.filter(user_id.eq(self.user_id)).order_by(id.desc()))
|
||||||
|
.first(conn)
|
||||||
|
.map_err(|e| {
|
||||||
|
error!("{:?}", e);
|
||||||
|
ServiceErrors::Error(WsError::NoBindToken)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Message for FindUserId {
|
||||||
|
type Result = Result<Token, ServiceErrors>;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Handler<FindUserId> for DbExecutor {
|
||||||
|
type Result = Result<Token, ServiceErrors>;
|
||||||
|
|
||||||
|
fn handle(&mut self, msg: FindUserId, _ctx: &mut Self::Context) -> Self::Result {
|
||||||
|
let conn = db_pool!(self);
|
||||||
|
msg.execute(conn)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug)]
|
|
||||||
pub struct FindBindToken {
|
pub struct FindBindToken {
|
||||||
pub token: Uuid,
|
pub token: Uuid,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl FindBindToken {
|
||||||
|
pub fn execute(self, conn: &DbPooledConn) -> Result<Token, ServiceErrors> {
|
||||||
|
use crate::schema::tokens::dsl::{bind_token, tokens};
|
||||||
|
|
||||||
|
let token: Token = q!(tokens.filter(bind_token.eq(Some(self.token))))
|
||||||
|
.first(conn)
|
||||||
|
.map_err(|e| {
|
||||||
|
error!("{:?}", e);
|
||||||
|
ServiceErrors::Error(WsError::BindTokenNotExists)
|
||||||
|
})?;
|
||||||
|
|
||||||
|
q!(diesel::update(tokens.find(token.id)).set(bind_token.eq(None as Option<Uuid>)))
|
||||||
|
.execute(conn)
|
||||||
|
.map_err(|e| {
|
||||||
|
error!("{:?}", e);
|
||||||
|
ServiceErrors::Error(WsError::FailedToDisableBindToken)
|
||||||
|
})?;
|
||||||
|
|
||||||
|
Ok(token)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Message for FindBindToken {
|
impl Message for FindBindToken {
|
||||||
type Result = Result<Token, ServiceErrors>;
|
type Result = Result<Token, ServiceErrors>;
|
||||||
}
|
}
|
||||||
@ -22,32 +76,63 @@ impl Handler<FindBindToken> for DbExecutor {
|
|||||||
type Result = Result<Token, ServiceErrors>;
|
type Result = Result<Token, ServiceErrors>;
|
||||||
|
|
||||||
fn handle(&mut self, msg: FindBindToken, _: &mut Self::Context) -> Self::Result {
|
fn handle(&mut self, msg: FindBindToken, _: &mut Self::Context) -> Self::Result {
|
||||||
use crate::schema::tokens::dsl::{bind_token, tokens};
|
let conn = db_pool!(self);
|
||||||
let conn = &self
|
msg.execute(conn)
|
||||||
.pool
|
}
|
||||||
.get()
|
}
|
||||||
.map_err(|_| ServiceErrors::DatabaseConnectionLost)?;
|
|
||||||
|
pub struct FindAccessToken {
|
||||||
let token: Token = tokens
|
pub token: Uuid,
|
||||||
.filter(bind_token.eq(Some(msg.token)))
|
}
|
||||||
.first(conn)
|
|
||||||
.map_err(|_e| ServiceErrors::RecordNotFound(format!("token for {}", msg.token)))?;
|
impl FindAccessToken {
|
||||||
|
pub fn execute(self, conn: &DbPooledConn) -> Result<Token, ServiceErrors> {
|
||||||
let erase_value: Option<Uuid> = None;
|
use crate::schema::tokens::dsl::{access_token, tokens};
|
||||||
diesel::update(tokens.find(token.id))
|
|
||||||
.set(bind_token.eq(erase_value))
|
q!(tokens.filter(access_token.eq(self.token)))
|
||||||
.execute(conn)
|
.first(conn)
|
||||||
.map_err(|_| ServiceErrors::DatabaseConnectionLost)?;
|
.map_err(|e| {
|
||||||
|
error!("{:?}", e);
|
||||||
Ok(token)
|
ServiceErrors::Error(WsError::AccessTokenNotExists)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Message for FindAccessToken {
|
||||||
|
type Result = Result<Token, ServiceErrors>;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Handler<FindAccessToken> for DbExecutor {
|
||||||
|
type Result = Result<Token, ServiceErrors>;
|
||||||
|
|
||||||
|
fn handle(&mut self, msg: FindAccessToken, _: &mut Self::Context) -> Self::Result {
|
||||||
|
let conn = db_pool!(self);
|
||||||
|
msg.execute(conn)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug)]
|
|
||||||
pub struct CreateBindToken {
|
pub struct CreateBindToken {
|
||||||
pub user_id: UserId,
|
pub user_id: UserId,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl CreateBindToken {
|
||||||
|
pub fn execute(self, conn: &DbPooledConn) -> Result<Token, ServiceErrors> {
|
||||||
|
use crate::schema::tokens::dsl::*;
|
||||||
|
|
||||||
|
q!(diesel::insert_into(tokens).values((
|
||||||
|
user_id.eq(self.user_id),
|
||||||
|
access_token.eq(Uuid::new_v4()),
|
||||||
|
refresh_token.eq(Uuid::new_v4()),
|
||||||
|
bind_token.eq(Some(Uuid::new_v4())),
|
||||||
|
)))
|
||||||
|
.get_result(conn)
|
||||||
|
.map_err(|e| {
|
||||||
|
error!("{:?}", e);
|
||||||
|
ServiceErrors::Error(WsError::FailedToCreateBindToken)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Message for CreateBindToken {
|
impl Message for CreateBindToken {
|
||||||
type Result = Result<Token, ServiceErrors>;
|
type Result = Result<Token, ServiceErrors>;
|
||||||
}
|
}
|
||||||
@ -56,26 +141,8 @@ impl Handler<CreateBindToken> for DbExecutor {
|
|||||||
type Result = Result<Token, ServiceErrors>;
|
type Result = Result<Token, ServiceErrors>;
|
||||||
|
|
||||||
fn handle(&mut self, msg: CreateBindToken, _: &mut Self::Context) -> Self::Result {
|
fn handle(&mut self, msg: CreateBindToken, _: &mut Self::Context) -> Self::Result {
|
||||||
use crate::schema::tokens::dsl::tokens;
|
let conn = db_pool!(self);
|
||||||
let conn = &self
|
|
||||||
.pool
|
|
||||||
.get()
|
|
||||||
.map_err(|_| ServiceErrors::DatabaseConnectionLost)?;
|
|
||||||
|
|
||||||
let access_token = Uuid::new_v4();
|
msg.execute(conn)
|
||||||
let refresh_token = Uuid::new_v4();
|
|
||||||
let bind_token = Some(Uuid::new_v4());
|
|
||||||
|
|
||||||
let form = TokenForm {
|
|
||||||
user_id: msg.user_id,
|
|
||||||
access_token,
|
|
||||||
refresh_token,
|
|
||||||
bind_token,
|
|
||||||
};
|
|
||||||
|
|
||||||
diesel::insert_into(tokens)
|
|
||||||
.values(form)
|
|
||||||
.get_result(conn)
|
|
||||||
.map_err(|_| ServiceErrors::RecordNotFound("issue comments".to_string()))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,12 +1,15 @@
|
|||||||
use actix::{Handler, Message};
|
use actix::{Handler, Message};
|
||||||
use diesel::connection::TransactionManager;
|
|
||||||
use diesel::pg::Pg;
|
|
||||||
use diesel::prelude::*;
|
use diesel::prelude::*;
|
||||||
|
|
||||||
|
use jirs_data::msg::WsError;
|
||||||
use jirs_data::{ProjectId, UserId, UserProject, UserProjectId, UserRole};
|
use jirs_data::{ProjectId, UserId, UserProject, UserProjectId, UserRole};
|
||||||
|
|
||||||
use crate::db::DbExecutor;
|
use crate::{
|
||||||
use crate::errors::ServiceErrors;
|
db::{DbExecutor, DbPooledConn},
|
||||||
|
db_pool,
|
||||||
|
errors::ServiceErrors,
|
||||||
|
q,
|
||||||
|
};
|
||||||
|
|
||||||
pub struct CurrentUserProject {
|
pub struct CurrentUserProject {
|
||||||
pub user_id: UserId,
|
pub user_id: UserId,
|
||||||
@ -22,16 +25,14 @@ impl Handler<CurrentUserProject> for DbExecutor {
|
|||||||
fn handle(&mut self, msg: CurrentUserProject, _: &mut Self::Context) -> Self::Result {
|
fn handle(&mut self, msg: CurrentUserProject, _: &mut Self::Context) -> Self::Result {
|
||||||
use crate::schema::user_projects::dsl::*;
|
use crate::schema::user_projects::dsl::*;
|
||||||
|
|
||||||
let conn = &self
|
let conn = db_pool!(self);
|
||||||
.pool
|
|
||||||
.get()
|
|
||||||
.map_err(|_| ServiceErrors::DatabaseConnectionLost)?;
|
|
||||||
|
|
||||||
let user_query = user_projects.filter(user_id.eq(msg.user_id).and(is_current.eq(true)));
|
q!(user_projects.filter(user_id.eq(msg.user_id).and(is_current.eq(true))))
|
||||||
debug!("{}", diesel::debug_query::<Pg, _>(&user_query));
|
|
||||||
user_query
|
|
||||||
.first(conn)
|
.first(conn)
|
||||||
.map_err(|_e| ServiceErrors::RecordNotFound(format!("user project {}", msg.user_id)))
|
.map_err(|e| {
|
||||||
|
error!("{:?}", e);
|
||||||
|
ServiceErrors::RecordNotFound(format!("user project {}", msg.user_id))
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -49,16 +50,14 @@ impl Handler<LoadUserProjects> for DbExecutor {
|
|||||||
fn handle(&mut self, msg: LoadUserProjects, _ctx: &mut Self::Context) -> Self::Result {
|
fn handle(&mut self, msg: LoadUserProjects, _ctx: &mut Self::Context) -> Self::Result {
|
||||||
use crate::schema::user_projects::dsl::*;
|
use crate::schema::user_projects::dsl::*;
|
||||||
|
|
||||||
let conn = &self
|
let conn = db_pool!(self);
|
||||||
.pool
|
|
||||||
.get()
|
|
||||||
.map_err(|_| ServiceErrors::DatabaseConnectionLost)?;
|
|
||||||
|
|
||||||
let user_query = user_projects.filter(user_id.eq(msg.user_id));
|
q!(user_projects.filter(user_id.eq(msg.user_id)))
|
||||||
debug!("{}", diesel::debug_query::<Pg, _>(&user_query));
|
|
||||||
user_query
|
|
||||||
.load(conn)
|
.load(conn)
|
||||||
.map_err(|_e| ServiceErrors::RecordNotFound(format!("user project {}", msg.user_id)))
|
.map_err(|e| {
|
||||||
|
error!("{:?}", e);
|
||||||
|
ServiceErrors::RecordNotFound(format!("user project {}", msg.user_id))
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -67,6 +66,51 @@ pub struct ChangeCurrentUserProject {
|
|||||||
pub id: UserProjectId,
|
pub id: UserProjectId,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl ChangeCurrentUserProject {
|
||||||
|
pub fn execute(self, conn: &DbPooledConn) -> Result<UserProject, ServiceErrors> {
|
||||||
|
use crate::schema::user_projects::dsl::*;
|
||||||
|
|
||||||
|
crate::db::Guard::new(conn)?.run(|_guard| {
|
||||||
|
let mut user_project: UserProject =
|
||||||
|
q!(user_projects.filter(id.eq(self.id).and(user_id.eq(self.user_id))))
|
||||||
|
.first(conn)
|
||||||
|
.map_err(|e| {
|
||||||
|
error!("{:?}", e);
|
||||||
|
ServiceErrors::RecordNotFound(format!("user project {}", self.user_id))
|
||||||
|
})?;
|
||||||
|
|
||||||
|
q!(diesel::update(user_projects)
|
||||||
|
.set(is_current.eq(false))
|
||||||
|
.filter(user_id.eq(self.user_id)))
|
||||||
|
.execute(conn)
|
||||||
|
.map(|_| ())
|
||||||
|
.map_err(|e| {
|
||||||
|
error!("{:?}", e);
|
||||||
|
ServiceErrors::DatabaseQueryFailed(format!(
|
||||||
|
"setting current flag to false while updating current project {}",
|
||||||
|
self.user_id
|
||||||
|
))
|
||||||
|
})?;
|
||||||
|
|
||||||
|
q!(diesel::update(user_projects)
|
||||||
|
.set(is_current.eq(true))
|
||||||
|
.filter(id.eq(self.id).and(user_id.eq(self.user_id))))
|
||||||
|
.execute(conn)
|
||||||
|
.map(|_| ())
|
||||||
|
.map_err(|e| {
|
||||||
|
error!("{:?}", e);
|
||||||
|
ServiceErrors::DatabaseQueryFailed(format!(
|
||||||
|
"set current flag on project while updating current project {}",
|
||||||
|
self.user_id
|
||||||
|
))
|
||||||
|
})?;
|
||||||
|
|
||||||
|
user_project.is_current = true;
|
||||||
|
Ok(user_project)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Message for ChangeCurrentUserProject {
|
impl Message for ChangeCurrentUserProject {
|
||||||
type Result = Result<UserProject, ServiceErrors>;
|
type Result = Result<UserProject, ServiceErrors>;
|
||||||
}
|
}
|
||||||
@ -75,63 +119,8 @@ impl Handler<ChangeCurrentUserProject> for DbExecutor {
|
|||||||
type Result = Result<UserProject, ServiceErrors>;
|
type Result = Result<UserProject, ServiceErrors>;
|
||||||
|
|
||||||
fn handle(&mut self, msg: ChangeCurrentUserProject, _ctx: &mut Self::Context) -> Self::Result {
|
fn handle(&mut self, msg: ChangeCurrentUserProject, _ctx: &mut Self::Context) -> Self::Result {
|
||||||
use crate::schema::user_projects::dsl::*;
|
let conn = db_pool!(self);
|
||||||
|
msg.execute(conn)
|
||||||
let conn = &self
|
|
||||||
.pool
|
|
||||||
.get()
|
|
||||||
.map_err(|_| ServiceErrors::DatabaseConnectionLost)?;
|
|
||||||
|
|
||||||
let tm = conn.transaction_manager();
|
|
||||||
|
|
||||||
tm.begin_transaction(conn)
|
|
||||||
.map_err(|_| ServiceErrors::DatabaseConnectionLost)?;
|
|
||||||
|
|
||||||
let query = user_projects.filter(id.eq(msg.id).and(user_id.eq(msg.user_id)));
|
|
||||||
debug!("{}", diesel::debug_query::<Pg, _>(&query));
|
|
||||||
let mut user_project: UserProject =
|
|
||||||
query
|
|
||||||
.first(conn)
|
|
||||||
.map_err(|_e| match tm.rollback_transaction(conn) {
|
|
||||||
Err(_) => ServiceErrors::DatabaseConnectionLost,
|
|
||||||
_ => ServiceErrors::RecordNotFound(format!("user project {}", msg.user_id)),
|
|
||||||
})?;
|
|
||||||
|
|
||||||
let query = diesel::update(user_projects)
|
|
||||||
.set(is_current.eq(false))
|
|
||||||
.filter(user_id.eq(msg.user_id));
|
|
||||||
debug!("{}", diesel::debug_query::<Pg, _>(&query));
|
|
||||||
query
|
|
||||||
.execute(conn)
|
|
||||||
.map(|_| ())
|
|
||||||
.map_err(|_e| match tm.rollback_transaction(conn) {
|
|
||||||
Err(_) => ServiceErrors::DatabaseConnectionLost,
|
|
||||||
_ => ServiceErrors::DatabaseQueryFailed(format!(
|
|
||||||
"setting current flag to false while updating current project {}",
|
|
||||||
msg.user_id
|
|
||||||
)),
|
|
||||||
})?;
|
|
||||||
|
|
||||||
let query = diesel::update(user_projects)
|
|
||||||
.set(is_current.eq(true))
|
|
||||||
.filter(id.eq(msg.id).and(user_id.eq(msg.user_id)));
|
|
||||||
debug!("{}", diesel::debug_query::<Pg, _>(&query));
|
|
||||||
query
|
|
||||||
.execute(conn)
|
|
||||||
.map(|_| ())
|
|
||||||
.map_err(|_e| match tm.rollback_transaction(conn) {
|
|
||||||
Err(_) => ServiceErrors::DatabaseConnectionLost,
|
|
||||||
_ => ServiceErrors::DatabaseQueryFailed(format!(
|
|
||||||
"set current flag on project while updating current project {}",
|
|
||||||
msg.user_id
|
|
||||||
)),
|
|
||||||
})?;
|
|
||||||
|
|
||||||
tm.commit_transaction(conn)
|
|
||||||
.map_err(|_| ServiceErrors::DatabaseConnectionLost)?;
|
|
||||||
|
|
||||||
user_project.is_current = true;
|
|
||||||
Ok(user_project)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -141,6 +130,42 @@ pub struct RemoveInvitedUser {
|
|||||||
pub project_id: ProjectId,
|
pub project_id: ProjectId,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl RemoveInvitedUser {
|
||||||
|
pub fn execute(self, conn: &DbPooledConn) -> Result<usize, ServiceErrors> {
|
||||||
|
use crate::schema::user_projects::dsl::*;
|
||||||
|
|
||||||
|
if self.invited_id == self.inviter_id {
|
||||||
|
return Err(ServiceErrors::Unauthorized);
|
||||||
|
}
|
||||||
|
|
||||||
|
q!(user_projects.filter(
|
||||||
|
user_id
|
||||||
|
.eq(self.inviter_id)
|
||||||
|
.and(project_id.eq(self.project_id))
|
||||||
|
.and(role.eq(UserRole::Owner)),
|
||||||
|
))
|
||||||
|
.first::<UserProject>(conn)
|
||||||
|
.map_err(|e| {
|
||||||
|
error!("{:?}", e);
|
||||||
|
ServiceErrors::Unauthorized
|
||||||
|
})?;
|
||||||
|
|
||||||
|
q!(diesel::delete(user_projects).filter(
|
||||||
|
user_id
|
||||||
|
.eq(self.invited_id)
|
||||||
|
.and(project_id.eq(self.project_id)),
|
||||||
|
))
|
||||||
|
.execute(conn)
|
||||||
|
.map_err(|e| {
|
||||||
|
error!("{:?}", e);
|
||||||
|
ServiceErrors::RecordNotFound(format!(
|
||||||
|
"user project user with id {} for project {}",
|
||||||
|
self.invited_id, self.project_id
|
||||||
|
))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Message for RemoveInvitedUser {
|
impl Message for RemoveInvitedUser {
|
||||||
type Result = Result<(), ServiceErrors>;
|
type Result = Result<(), ServiceErrors>;
|
||||||
}
|
}
|
||||||
@ -149,46 +174,48 @@ impl Handler<RemoveInvitedUser> for DbExecutor {
|
|||||||
type Result = Result<(), ServiceErrors>;
|
type Result = Result<(), ServiceErrors>;
|
||||||
|
|
||||||
fn handle(&mut self, msg: RemoveInvitedUser, _ctx: &mut Self::Context) -> Self::Result {
|
fn handle(&mut self, msg: RemoveInvitedUser, _ctx: &mut Self::Context) -> Self::Result {
|
||||||
use crate::schema::user_projects::dsl::*;
|
let conn = db_pool!(self);
|
||||||
|
msg.execute(conn)?;
|
||||||
let conn = &self
|
Ok(())
|
||||||
.pool
|
}
|
||||||
.get()
|
}
|
||||||
.map_err(|_| ServiceErrors::DatabaseConnectionLost)?;
|
|
||||||
|
pub struct CreateUserProject {
|
||||||
if msg.invited_id == msg.inviter_id {
|
pub user_id: UserId,
|
||||||
return Err(ServiceErrors::Unauthorized);
|
pub project_id: ProjectId,
|
||||||
}
|
pub is_current: bool,
|
||||||
|
pub is_default: bool,
|
||||||
{
|
pub role: UserRole,
|
||||||
let owner = UserRole::Owner;
|
}
|
||||||
let query = user_projects.filter(
|
|
||||||
user_id
|
impl CreateUserProject {
|
||||||
.eq(msg.inviter_id)
|
pub fn execute(self, conn: &DbPooledConn) -> Result<usize, ServiceErrors> {
|
||||||
.and(project_id.eq(msg.project_id))
|
use crate::schema::user_projects::dsl::*;
|
||||||
.and(role.eq(owner)),
|
q!(diesel::insert_into(user_projects).values((
|
||||||
);
|
user_id.eq(self.user_id),
|
||||||
debug!("{}", diesel::debug_query::<Pg, _>(&query));
|
project_id.eq(self.project_id),
|
||||||
query
|
is_current.eq(self.is_current),
|
||||||
.first::<UserProject>(conn)
|
is_default.eq(self.is_default),
|
||||||
.map_err(|_e| ServiceErrors::Unauthorized)?;
|
role.eq(self.role),
|
||||||
}
|
)))
|
||||||
|
.execute(conn)
|
||||||
{
|
.map_err(|e| {
|
||||||
let query = diesel::delete(user_projects).filter(
|
error!("{:?}", e);
|
||||||
user_id
|
ServiceErrors::Error(WsError::InvalidUserProject)
|
||||||
.eq(msg.invited_id)
|
})
|
||||||
.and(project_id.eq(msg.project_id)),
|
}
|
||||||
);
|
}
|
||||||
debug!("{}", diesel::debug_query::<Pg, _>(&query));
|
|
||||||
query.execute(conn).map_err(|_e| {
|
impl Message for CreateUserProject {
|
||||||
ServiceErrors::RecordNotFound(format!(
|
type Result = Result<(), ServiceErrors>;
|
||||||
"user project user with id {} for project {}",
|
}
|
||||||
msg.invited_id, msg.project_id
|
|
||||||
))
|
impl Handler<CreateUserProject> for DbExecutor {
|
||||||
})?;
|
type Result = Result<(), ServiceErrors>;
|
||||||
}
|
|
||||||
|
fn handle(&mut self, msg: CreateUserProject, _ctx: &mut Self::Context) -> Self::Result {
|
||||||
|
let conn = db_pool!(self);
|
||||||
|
msg.execute(conn)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,21 +1,34 @@
|
|||||||
use actix::{Handler, Message};
|
use actix::{Handler, Message};
|
||||||
use diesel::connection::TransactionManager;
|
|
||||||
use diesel::pg::Pg;
|
|
||||||
use diesel::prelude::*;
|
use diesel::prelude::*;
|
||||||
use serde::{Deserialize, Serialize};
|
use diesel::result::Error;
|
||||||
|
|
||||||
use jirs_data::{ProjectId, User, UserId};
|
use jirs_data::{msg::WsError, EmailString, ProjectId, User, UserId, UserRole, UsernameString};
|
||||||
|
|
||||||
use crate::db::projects::CreateProject;
|
use crate::db::user_projects::CreateUserProject;
|
||||||
use crate::db::{DbExecutor, DbPooledConn};
|
use crate::{
|
||||||
use crate::errors::ServiceErrors;
|
db::{projects::CreateProject, DbExecutor, DbPooledConn},
|
||||||
use crate::schema::users::all_columns;
|
db_pool,
|
||||||
|
errors::ServiceErrors,
|
||||||
|
q,
|
||||||
|
schema::users::all_columns,
|
||||||
|
};
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct FindUser {
|
pub struct FindUser {
|
||||||
pub user_id: UserId,
|
pub user_id: UserId,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl FindUser {
|
||||||
|
pub fn execute(self, conn: &DbPooledConn) -> Result<User, ServiceErrors> {
|
||||||
|
use crate::schema::users::dsl::*;
|
||||||
|
|
||||||
|
q!(users.find(self.user_id)).first(conn).map_err(|e| {
|
||||||
|
error!("{:?}", e);
|
||||||
|
ServiceErrors::Error(WsError::UserNotExists(self.user_id))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Message for FindUser {
|
impl Message for FindUser {
|
||||||
type Result = Result<User, ServiceErrors>;
|
type Result = Result<User, ServiceErrors>;
|
||||||
}
|
}
|
||||||
@ -24,27 +37,32 @@ impl Handler<FindUser> for DbExecutor {
|
|||||||
type Result = Result<User, ServiceErrors>;
|
type Result = Result<User, ServiceErrors>;
|
||||||
|
|
||||||
fn handle(&mut self, msg: FindUser, _ctx: &mut Self::Context) -> Self::Result {
|
fn handle(&mut self, msg: FindUser, _ctx: &mut Self::Context) -> Self::Result {
|
||||||
use crate::schema::users::dsl::*;
|
let conn = db_pool!(self);
|
||||||
|
msg.execute(conn)
|
||||||
let conn = &self
|
|
||||||
.pool
|
|
||||||
.get()
|
|
||||||
.map_err(|_| ServiceErrors::DatabaseConnectionLost)?;
|
|
||||||
|
|
||||||
let query = users.find(msg.user_id);
|
|
||||||
debug!("{}", diesel::debug_query::<Pg, _>(&query));
|
|
||||||
query
|
|
||||||
.first(conn)
|
|
||||||
.map_err(|_| ServiceErrors::RecordNotFound(format!("user with id = {}", msg.user_id)))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct LookupUser {
|
pub struct LookupUser {
|
||||||
pub name: String,
|
pub name: String,
|
||||||
pub email: String,
|
pub email: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl LookupUser {
|
||||||
|
pub fn execute(self, conn: &DbPooledConn) -> Result<User, ServiceErrors> {
|
||||||
|
use crate::schema::users::dsl::*;
|
||||||
|
|
||||||
|
q!(users
|
||||||
|
.distinct_on(id)
|
||||||
|
.filter(email.eq(self.email.as_str()))
|
||||||
|
.filter(name.eq(self.name.as_str())))
|
||||||
|
.first(conn)
|
||||||
|
.map_err(|e| {
|
||||||
|
error!("{:?}", e);
|
||||||
|
ServiceErrors::Error(WsError::NoMatchingPair(self.name, self.email))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Message for LookupUser {
|
impl Message for LookupUser {
|
||||||
type Result = Result<User, ServiceErrors>;
|
type Result = Result<User, ServiceErrors>;
|
||||||
}
|
}
|
||||||
@ -53,29 +71,33 @@ impl Handler<LookupUser> for DbExecutor {
|
|||||||
type Result = Result<User, ServiceErrors>;
|
type Result = Result<User, ServiceErrors>;
|
||||||
|
|
||||||
fn handle(&mut self, msg: LookupUser, _ctx: &mut Self::Context) -> Self::Result {
|
fn handle(&mut self, msg: LookupUser, _ctx: &mut Self::Context) -> Self::Result {
|
||||||
use crate::schema::users::dsl::*;
|
let conn = db_pool!(self);
|
||||||
|
msg.execute(conn)
|
||||||
let conn = &self
|
|
||||||
.pool
|
|
||||||
.get()
|
|
||||||
.map_err(|_| ServiceErrors::DatabaseConnectionLost)?;
|
|
||||||
|
|
||||||
let users_query = users
|
|
||||||
.distinct_on(id)
|
|
||||||
.filter(email.eq(msg.email.as_str()))
|
|
||||||
.filter(name.eq(msg.name.as_str()));
|
|
||||||
debug!("{}", diesel::debug_query::<Pg, _>(&users_query));
|
|
||||||
users_query
|
|
||||||
.first(conn)
|
|
||||||
.map_err(|_| ServiceErrors::RecordNotFound(format!("user {} {}", msg.name, msg.email)))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct LoadProjectUsers {
|
pub struct LoadProjectUsers {
|
||||||
pub project_id: i32,
|
pub project_id: i32,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl LoadProjectUsers {
|
||||||
|
pub fn execute(self, conn: &DbPooledConn) -> Result<Vec<User>, ServiceErrors> {
|
||||||
|
use crate::schema::user_projects::dsl::{project_id, user_id, user_projects};
|
||||||
|
use crate::schema::users::dsl::*;
|
||||||
|
|
||||||
|
q!(users
|
||||||
|
.distinct_on(id)
|
||||||
|
.inner_join(user_projects.on(user_id.eq(id)))
|
||||||
|
.filter(project_id.eq(self.project_id))
|
||||||
|
.select(all_columns))
|
||||||
|
.load(conn)
|
||||||
|
.map_err(|e| {
|
||||||
|
error!("{:?}", e);
|
||||||
|
ServiceErrors::Error(WsError::FailedToLoadProjectUsers)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Message for LoadProjectUsers {
|
impl Message for LoadProjectUsers {
|
||||||
type Result = Result<Vec<User>, ServiceErrors>;
|
type Result = Result<Vec<User>, ServiceErrors>;
|
||||||
}
|
}
|
||||||
@ -84,31 +106,33 @@ impl Handler<LoadProjectUsers> for DbExecutor {
|
|||||||
type Result = Result<Vec<User>, ServiceErrors>;
|
type Result = Result<Vec<User>, ServiceErrors>;
|
||||||
|
|
||||||
fn handle(&mut self, msg: LoadProjectUsers, _ctx: &mut Self::Context) -> Self::Result {
|
fn handle(&mut self, msg: LoadProjectUsers, _ctx: &mut Self::Context) -> Self::Result {
|
||||||
use crate::schema::user_projects::dsl::{project_id, user_id, user_projects};
|
let conn = db_pool!(self);
|
||||||
use crate::schema::users::dsl::*;
|
msg.execute(conn)
|
||||||
|
|
||||||
let conn = &self
|
|
||||||
.pool
|
|
||||||
.get()
|
|
||||||
.map_err(|_| ServiceErrors::DatabaseConnectionLost)?;
|
|
||||||
|
|
||||||
let users_query = users
|
|
||||||
.distinct_on(id)
|
|
||||||
.inner_join(user_projects.on(user_id.eq(id)))
|
|
||||||
.filter(project_id.eq(msg.project_id))
|
|
||||||
.select(all_columns);
|
|
||||||
debug!("{}", diesel::debug_query::<Pg, _>(&users_query));
|
|
||||||
users_query
|
|
||||||
.load(conn)
|
|
||||||
.map_err(|_| ServiceErrors::RecordNotFound("project users".to_string()))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct LoadIssueAssignees {
|
pub struct LoadIssueAssignees {
|
||||||
pub issue_id: i32,
|
pub issue_id: i32,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl LoadIssueAssignees {
|
||||||
|
pub fn execute(self, conn: &DbPooledConn) -> Result<Vec<User>, ServiceErrors> {
|
||||||
|
use crate::schema::issue_assignees::dsl::{issue_assignees, issue_id, user_id};
|
||||||
|
use crate::schema::users::dsl::*;
|
||||||
|
|
||||||
|
q!(users
|
||||||
|
.distinct_on(id)
|
||||||
|
.inner_join(issue_assignees.on(user_id.eq(id)))
|
||||||
|
.filter(issue_id.eq(self.issue_id))
|
||||||
|
.select(users::all_columns()))
|
||||||
|
.load(conn)
|
||||||
|
.map_err(|e| {
|
||||||
|
error!("{:?}", e);
|
||||||
|
ServiceErrors::Error(WsError::FailedToLoadAssignees)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Message for LoadIssueAssignees {
|
impl Message for LoadIssueAssignees {
|
||||||
type Result = Result<Vec<User>, ServiceErrors>;
|
type Result = Result<Vec<User>, ServiceErrors>;
|
||||||
}
|
}
|
||||||
@ -117,31 +141,110 @@ impl Handler<LoadIssueAssignees> for DbExecutor {
|
|||||||
type Result = Result<Vec<User>, ServiceErrors>;
|
type Result = Result<Vec<User>, ServiceErrors>;
|
||||||
|
|
||||||
fn handle(&mut self, msg: LoadIssueAssignees, _ctx: &mut Self::Context) -> Self::Result {
|
fn handle(&mut self, msg: LoadIssueAssignees, _ctx: &mut Self::Context) -> Self::Result {
|
||||||
use crate::schema::issue_assignees::dsl::{issue_assignees, issue_id, user_id};
|
let conn = db_pool!(self);
|
||||||
use crate::schema::users::dsl::*;
|
msg.execute(conn)
|
||||||
|
}
|
||||||
let conn = &self
|
}
|
||||||
.pool
|
|
||||||
.get()
|
pub struct CreateUser {
|
||||||
.map_err(|_| ServiceErrors::DatabaseConnectionLost)?;
|
pub name: UsernameString,
|
||||||
|
pub email: EmailString,
|
||||||
let users_query = users
|
}
|
||||||
.distinct_on(id)
|
|
||||||
.inner_join(issue_assignees.on(user_id.eq(id)))
|
impl CreateUser {
|
||||||
.filter(issue_id.eq(msg.issue_id))
|
pub fn execute(self, conn: &DbPooledConn) -> Result<User, ServiceErrors> {
|
||||||
.select(users::all_columns());
|
use crate::schema::users::dsl::*;
|
||||||
debug!("{}", diesel::debug_query::<Pg, _>(&users_query));
|
|
||||||
users_query
|
q!(diesel::insert_into(users)
|
||||||
.load(conn)
|
.values((name.eq(self.name.as_str()), email.eq(self.email.as_str()))))
|
||||||
.map_err(|_| ServiceErrors::RecordNotFound("issue users".to_string()))
|
.get_result(conn)
|
||||||
|
.map_err(|e| {
|
||||||
|
error!("{:?}", e);
|
||||||
|
let ws = match e {
|
||||||
|
Error::InvalidCString(_) => WsError::InvalidPair(self.name, self.email),
|
||||||
|
Error::DatabaseError(diesel::result::DatabaseErrorKind::UniqueViolation, _) => {
|
||||||
|
WsError::TakenPair(self.name, self.email)
|
||||||
|
}
|
||||||
|
Error::DatabaseError(_, _) => WsError::InvalidPair(self.name, self.email),
|
||||||
|
Error::NotFound => WsError::InvalidPair(self.name, self.email),
|
||||||
|
Error::QueryBuilderError(_) => WsError::InvalidPair(self.name, self.email),
|
||||||
|
Error::DeserializationError(_) => WsError::InvalidPair(self.name, self.email),
|
||||||
|
Error::SerializationError(_) => WsError::InvalidPair(self.name, self.email),
|
||||||
|
Error::RollbackTransaction => WsError::InvalidPair(self.name, self.email),
|
||||||
|
Error::AlreadyInTransaction => WsError::InvalidPair(self.name, self.email),
|
||||||
|
Error::__Nonexhaustive => WsError::InvalidPair(self.name, self.email),
|
||||||
|
};
|
||||||
|
ServiceErrors::Error(ws)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Message for CreateUser {
|
||||||
|
type Result = Result<User, ServiceErrors>;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Handler<CreateUser> for DbExecutor {
|
||||||
|
type Result = Result<User, ServiceErrors>;
|
||||||
|
|
||||||
|
fn handle(&mut self, msg: CreateUser, _ctx: &mut Self::Context) -> Self::Result {
|
||||||
|
let conn = db_pool!(self);
|
||||||
|
msg.execute(conn)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct Register {
|
pub struct Register {
|
||||||
pub name: String,
|
pub name: UsernameString,
|
||||||
pub email: String,
|
pub email: EmailString,
|
||||||
pub project_id: Option<ProjectId>,
|
pub project_id: Option<ProjectId>,
|
||||||
|
pub role: UserRole,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Register {
|
||||||
|
pub fn execute(self, conn: &DbPooledConn) -> Result<(), ServiceErrors> {
|
||||||
|
let Register {
|
||||||
|
name: given_name,
|
||||||
|
email: given_email,
|
||||||
|
project_id: given_project_id,
|
||||||
|
role: given_role,
|
||||||
|
} = self;
|
||||||
|
|
||||||
|
crate::db::Guard::new(conn)?.run(|_guard| {
|
||||||
|
if count_matching_users(given_name.as_str(), given_email.as_str(), conn) > 0 {
|
||||||
|
return Err(ServiceErrors::Error(WsError::InvalidLoginPair));
|
||||||
|
}
|
||||||
|
|
||||||
|
let current_project_id: ProjectId = match given_project_id {
|
||||||
|
Some(current_project_id) => current_project_id,
|
||||||
|
_ => {
|
||||||
|
CreateProject {
|
||||||
|
name: "initial".to_string(),
|
||||||
|
url: None,
|
||||||
|
description: None,
|
||||||
|
category: None,
|
||||||
|
time_tracking: None,
|
||||||
|
}
|
||||||
|
.execute(conn)?
|
||||||
|
.id
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let user: User = CreateUser {
|
||||||
|
name: given_name,
|
||||||
|
email: given_email,
|
||||||
|
}
|
||||||
|
.execute(conn)?;
|
||||||
|
|
||||||
|
CreateUserProject {
|
||||||
|
user_id: user.id,
|
||||||
|
project_id: current_project_id,
|
||||||
|
is_current: true,
|
||||||
|
is_default: true,
|
||||||
|
role: given_role,
|
||||||
|
}
|
||||||
|
.execute(conn)?;
|
||||||
|
Ok(())
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Message for Register {
|
impl Message for Register {
|
||||||
@ -151,74 +254,12 @@ impl Message for Register {
|
|||||||
impl Handler<Register> for DbExecutor {
|
impl Handler<Register> for DbExecutor {
|
||||||
type Result = Result<(), ServiceErrors>;
|
type Result = Result<(), ServiceErrors>;
|
||||||
|
|
||||||
fn handle(&mut self, msg: Register, ctx: &mut Self::Context) -> Self::Result {
|
fn handle(&mut self, msg: Register, _ctx: &mut Self::Context) -> Self::Result {
|
||||||
use crate::schema::users::dsl::*;
|
let conn = db_pool!(self);
|
||||||
|
msg.execute(conn)
|
||||||
let conn = &self
|
|
||||||
.pool
|
|
||||||
.get()
|
|
||||||
.map_err(|_| ServiceErrors::DatabaseConnectionLost)?;
|
|
||||||
|
|
||||||
let tm = conn.transaction_manager();
|
|
||||||
|
|
||||||
tm.begin_transaction(conn)
|
|
||||||
.map_err(|_| ServiceErrors::DatabaseConnectionLost)?;
|
|
||||||
let matching = count_matching_users(msg.name.as_str(), msg.email.as_str(), conn);
|
|
||||||
|
|
||||||
if matching > 0 {
|
|
||||||
return Err(ServiceErrors::RegisterCollision);
|
|
||||||
}
|
|
||||||
|
|
||||||
let current_project_id: ProjectId = match msg.project_id.as_ref().cloned() {
|
|
||||||
Some(current_project_id) => current_project_id,
|
|
||||||
_ => {
|
|
||||||
self.handle(
|
|
||||||
CreateProject {
|
|
||||||
name: "initial".to_string(),
|
|
||||||
url: None,
|
|
||||||
description: None,
|
|
||||||
category: None,
|
|
||||||
time_tracking: None,
|
|
||||||
},
|
|
||||||
ctx,
|
|
||||||
)?
|
|
||||||
.id
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let user: User = {
|
|
||||||
let insert_user_query =
|
|
||||||
diesel::insert_into(users).values((name.eq(msg.name), email.eq(msg.email)));
|
|
||||||
debug!("{}", diesel::debug_query::<Pg, _>(&insert_user_query));
|
|
||||||
insert_user_query
|
|
||||||
.get_result(conn)
|
|
||||||
.map_err(|_| ServiceErrors::RegisterCollision)?
|
|
||||||
};
|
|
||||||
|
|
||||||
{
|
|
||||||
use crate::schema::user_projects::dsl::*;
|
|
||||||
let insert_user_project_query = diesel::insert_into(user_projects).values((
|
|
||||||
user_id.eq(user.id),
|
|
||||||
project_id.eq(current_project_id),
|
|
||||||
is_current.eq(true),
|
|
||||||
is_default.eq(true),
|
|
||||||
));
|
|
||||||
debug!(
|
|
||||||
"{}",
|
|
||||||
diesel::debug_query::<Pg, _>(&insert_user_project_query)
|
|
||||||
);
|
|
||||||
insert_user_project_query
|
|
||||||
.execute(conn)
|
|
||||||
.map_err(|_| ServiceErrors::RegisterCollision)?;
|
|
||||||
}
|
|
||||||
tm.commit_transaction(conn)
|
|
||||||
.map_err(|_| ServiceErrors::DatabaseConnectionLost)?;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug)]
|
|
||||||
pub struct LoadInvitedUsers {
|
pub struct LoadInvitedUsers {
|
||||||
pub user_id: UserId,
|
pub user_id: UserId,
|
||||||
}
|
}
|
||||||
@ -234,35 +275,32 @@ impl Handler<LoadInvitedUsers> for DbExecutor {
|
|||||||
use crate::schema::invitations::dsl::{email as i_email, invitations, invited_by_id};
|
use crate::schema::invitations::dsl::{email as i_email, invitations, invited_by_id};
|
||||||
use crate::schema::users::dsl::{email as u_email, users};
|
use crate::schema::users::dsl::{email as u_email, users};
|
||||||
|
|
||||||
let conn = &self
|
let conn = db_pool!(self);
|
||||||
.pool
|
|
||||||
.get()
|
|
||||||
.map_err(|_| ServiceErrors::DatabaseConnectionLost)?;
|
|
||||||
|
|
||||||
let query = users
|
q!(users
|
||||||
.inner_join(invitations.on(i_email.eq(u_email)))
|
.inner_join(invitations.on(i_email.eq(u_email)))
|
||||||
.filter(invited_by_id.eq(msg.user_id))
|
.filter(invited_by_id.eq(msg.user_id))
|
||||||
.select(users::all_columns());
|
.select(users::all_columns()))
|
||||||
debug!("{}", diesel::debug_query::<Pg, _>(&query));
|
.load(conn)
|
||||||
query
|
.map_err(|e| {
|
||||||
.load(conn)
|
error!("{:?}", e);
|
||||||
.map_err(|e| ServiceErrors::DatabaseQueryFailed(format!("{}", e)))
|
ServiceErrors::Error(WsError::FailedToLoadInvitedUsers)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn count_matching_users(name: &str, email: &str, conn: &DbPooledConn) -> i64 {
|
fn count_matching_users(name: &str, email: &str, conn: &DbPooledConn) -> i64 {
|
||||||
use crate::schema::users::dsl;
|
use crate::schema::users::dsl;
|
||||||
|
|
||||||
let query = dsl::users
|
q!(dsl::users
|
||||||
.filter(dsl::email.eq(email).and(dsl::name.ne(name)))
|
.filter(dsl::email.eq(email).and(dsl::name.ne(name)))
|
||||||
.or_filter(dsl::email.ne(email).and(dsl::name.eq(name)))
|
.or_filter(dsl::email.ne(email).and(dsl::name.eq(name)))
|
||||||
.or_filter(dsl::email.eq(email).and(dsl::name.eq(name)))
|
.or_filter(dsl::email.eq(email).and(dsl::name.eq(name)))
|
||||||
.count();
|
.count())
|
||||||
info!("{}", diesel::debug_query::<diesel::pg::Pg, _>(&query));
|
.get_result::<i64>(conn)
|
||||||
query.get_result::<i64>(conn).unwrap_or(1)
|
.unwrap_or(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug)]
|
|
||||||
pub struct UpdateAvatarUrl {
|
pub struct UpdateAvatarUrl {
|
||||||
pub user_id: UserId,
|
pub user_id: UserId,
|
||||||
pub avatar_url: Option<String>,
|
pub avatar_url: Option<String>,
|
||||||
@ -278,27 +316,24 @@ impl Handler<UpdateAvatarUrl> for DbExecutor {
|
|||||||
fn handle(&mut self, msg: UpdateAvatarUrl, _ctx: &mut Self::Context) -> Self::Result {
|
fn handle(&mut self, msg: UpdateAvatarUrl, _ctx: &mut Self::Context) -> Self::Result {
|
||||||
use crate::schema::users::dsl::{avatar_url, id, users};
|
use crate::schema::users::dsl::{avatar_url, id, users};
|
||||||
|
|
||||||
let conn = &self
|
let conn = db_pool!(self);
|
||||||
.pool
|
|
||||||
.get()
|
|
||||||
.map_err(|_| ServiceErrors::DatabaseConnectionLost)?;
|
|
||||||
let update_query = diesel::update(users)
|
|
||||||
.set(avatar_url.eq(msg.avatar_url))
|
|
||||||
.filter(id.eq(msg.user_id));
|
|
||||||
debug!("{}", diesel::debug_query::<Pg, _>(&update_query));
|
|
||||||
update_query
|
|
||||||
.execute(conn)
|
|
||||||
.map_err(|e| ServiceErrors::DatabaseQueryFailed(format!("{}", e)))?;
|
|
||||||
|
|
||||||
let user_query = users.find(msg.user_id);
|
q!(diesel::update(users)
|
||||||
debug!("{}", diesel::debug_query::<Pg, _>(&user_query));
|
.set(avatar_url.eq(msg.avatar_url))
|
||||||
user_query
|
.filter(id.eq(msg.user_id)))
|
||||||
.first(conn)
|
.execute(conn)
|
||||||
.map_err(|e| ServiceErrors::DatabaseQueryFailed(format!("{}", e)))
|
.map_err(|e| {
|
||||||
|
error!("{:?}", e);
|
||||||
|
ServiceErrors::Error(WsError::FailedToChangeAvatar)
|
||||||
|
})?;
|
||||||
|
|
||||||
|
FindUser {
|
||||||
|
user_id: msg.user_id,
|
||||||
|
}
|
||||||
|
.execute(conn)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug)]
|
|
||||||
pub struct ProfileUpdate {
|
pub struct ProfileUpdate {
|
||||||
pub user_id: UserId,
|
pub user_id: UserId,
|
||||||
pub name: String,
|
pub name: String,
|
||||||
@ -315,21 +350,15 @@ impl Handler<ProfileUpdate> for DbExecutor {
|
|||||||
fn handle(&mut self, msg: ProfileUpdate, _ctx: &mut Self::Context) -> Self::Result {
|
fn handle(&mut self, msg: ProfileUpdate, _ctx: &mut Self::Context) -> Self::Result {
|
||||||
use crate::schema::users::dsl::{email, id, name, users};
|
use crate::schema::users::dsl::{email, id, name, users};
|
||||||
|
|
||||||
let conn = &self
|
let conn = db_pool!(self);
|
||||||
.pool
|
|
||||||
.get()
|
|
||||||
.map_err(|_| ServiceErrors::DatabaseConnectionLost)?;
|
|
||||||
let update_query = diesel::update(users)
|
|
||||||
.set((email.eq(msg.email), name.eq(msg.name)))
|
|
||||||
.filter(id.eq(msg.user_id));
|
|
||||||
debug!("{}", diesel::debug_query::<Pg, _>(&update_query));
|
|
||||||
update_query
|
|
||||||
.execute(conn)
|
|
||||||
.map_err(|e| ServiceErrors::DatabaseQueryFailed(format!("{}", e)))?;
|
|
||||||
|
|
||||||
let user_query = users.find(msg.user_id);
|
q!(diesel::update(users)
|
||||||
debug!("{}", diesel::debug_query::<Pg, _>(&user_query));
|
.set((email.eq(msg.email), name.eq(msg.name)))
|
||||||
user_query
|
.filter(id.eq(msg.user_id)))
|
||||||
|
.execute(conn)
|
||||||
|
.map_err(|e| ServiceErrors::DatabaseQueryFailed(format!("{}", e)))?;
|
||||||
|
|
||||||
|
q!(users.find(msg.user_id))
|
||||||
.first(conn)
|
.first(conn)
|
||||||
.map_err(|e| ServiceErrors::DatabaseQueryFailed(format!("{}", e)))
|
.map_err(|e| ServiceErrors::DatabaseQueryFailed(format!("{}", e)))
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
use actix_web::HttpResponse;
|
use actix_web::HttpResponse;
|
||||||
|
|
||||||
use jirs_data::ErrorResponse;
|
use jirs_data::{msg::WsError, ErrorResponse};
|
||||||
|
|
||||||
const TOKEN_NOT_FOUND: &str = "Token not found";
|
const TOKEN_NOT_FOUND: &str = "Token not found";
|
||||||
const DATABASE_CONNECTION_FAILED: &str = "Database connection failed";
|
const DATABASE_CONNECTION_FAILED: &str = "Database connection failed";
|
||||||
@ -12,6 +12,7 @@ pub enum ServiceErrors {
|
|||||||
DatabaseQueryFailed(String),
|
DatabaseQueryFailed(String),
|
||||||
RecordNotFound(String),
|
RecordNotFound(String),
|
||||||
RegisterCollision,
|
RegisterCollision,
|
||||||
|
Error(WsError),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ServiceErrors {
|
impl ServiceErrors {
|
||||||
@ -44,6 +45,9 @@ impl Into<HttpResponse> for ServiceErrors {
|
|||||||
ServiceErrors::RegisterCollision => HttpResponse::Unauthorized().json(ErrorResponse {
|
ServiceErrors::RegisterCollision => HttpResponse::Unauthorized().json(ErrorResponse {
|
||||||
errors: vec!["Register collision".to_string()],
|
errors: vec!["Register collision".to_string()],
|
||||||
}),
|
}),
|
||||||
|
ServiceErrors::Error(error) => HttpResponse::BadRequest().json(ErrorResponse {
|
||||||
|
errors: vec![error.to_str().to_string()],
|
||||||
|
}),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -8,13 +8,15 @@ extern crate diesel;
|
|||||||
extern crate log;
|
extern crate log;
|
||||||
|
|
||||||
use actix::Actor;
|
use actix::Actor;
|
||||||
use actix_cors::Cors;
|
// use actix_cors::Cors;
|
||||||
#[cfg(feature = "local-storage")]
|
#[cfg(feature = "local-storage")]
|
||||||
use actix_files as fs;
|
use actix_files as fs;
|
||||||
use actix_web::{App, HttpServer};
|
use actix_web::{App, HttpServer};
|
||||||
|
|
||||||
use crate::ws::WsServer;
|
use crate::ws::WsServer;
|
||||||
|
|
||||||
|
// use actix_web::http::Method;
|
||||||
|
|
||||||
pub mod db;
|
pub mod db;
|
||||||
pub mod errors;
|
pub mod errors;
|
||||||
pub mod mail;
|
pub mod mail;
|
||||||
@ -53,7 +55,6 @@ async fn main() -> Result<(), String> {
|
|||||||
HttpServer::new(move || {
|
HttpServer::new(move || {
|
||||||
let app = App::new()
|
let app = App::new()
|
||||||
.wrap(actix_web::middleware::Logger::default())
|
.wrap(actix_web::middleware::Logger::default())
|
||||||
.wrap(Cors::default())
|
|
||||||
.data(ws_server.clone())
|
.data(ws_server.clone())
|
||||||
.data(db_addr.clone())
|
.data(db_addr.clone())
|
||||||
.data(mail_addr.clone())
|
.data(mail_addr.clone())
|
||||||
|
@ -9,14 +9,6 @@ use jirs_data::{
|
|||||||
|
|
||||||
use crate::schema::*;
|
use crate::schema::*;
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize, Insertable)]
|
|
||||||
#[table_name = "comments"]
|
|
||||||
pub struct CommentForm {
|
|
||||||
pub body: String,
|
|
||||||
pub user_id: i32,
|
|
||||||
pub issue_id: i32,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize, Queryable)]
|
#[derive(Debug, Serialize, Deserialize, Queryable)]
|
||||||
pub struct Issue {
|
pub struct Issue {
|
||||||
pub id: i32,
|
pub id: i32,
|
||||||
|
@ -262,10 +262,7 @@ impl WebSocketActor {
|
|||||||
self.current_user_project
|
self.current_user_project
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.map(|u| u)
|
.map(|u| u)
|
||||||
.ok_or_else(|| {
|
.ok_or_else(|| WsMsg::AuthorizeExpired)
|
||||||
let _x = 1;
|
|
||||||
WsMsg::AuthorizeExpired
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn load_user_project(&self) -> Result<UserProject, WsMsg> {
|
fn load_user_project(&self) -> Result<UserProject, WsMsg> {
|
||||||
@ -273,11 +270,11 @@ impl WebSocketActor {
|
|||||||
match block_on(self.db.send(CurrentUserProject { user_id })) {
|
match block_on(self.db.send(CurrentUserProject { user_id })) {
|
||||||
Ok(Ok(user_project)) => Ok(user_project),
|
Ok(Ok(user_project)) => Ok(user_project),
|
||||||
Ok(Err(e)) => {
|
Ok(Err(e)) => {
|
||||||
error!("{:?}", e);
|
error!("load_user_project encounter service error {:?}", e);
|
||||||
Err(WsMsg::AuthorizeExpired)
|
Err(WsMsg::AuthorizeExpired)
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
error!("{}", e);
|
error!("load_user_project encounter mailbox error {}", e);
|
||||||
Err(WsMsg::AuthorizeExpired)
|
Err(WsMsg::AuthorizeExpired)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
use futures::executor::block_on;
|
use futures::executor::block_on;
|
||||||
|
|
||||||
use jirs_data::{UserId, UserProject, WsMsg};
|
use jirs_data::{UserId, UserProject, UserRole, WsMsg};
|
||||||
|
|
||||||
use crate::db;
|
use crate::{
|
||||||
use crate::db::users::Register as DbRegister;
|
db::{self, users::Register as DbRegister},
|
||||||
use crate::ws::auth::Authenticate;
|
ws::{auth::Authenticate, WebSocketActor, WsHandler, WsResult},
|
||||||
use crate::ws::{WebSocketActor, WsHandler, WsResult};
|
};
|
||||||
|
|
||||||
pub struct LoadProjectUsers;
|
pub struct LoadProjectUsers;
|
||||||
|
|
||||||
@ -41,6 +41,7 @@ impl WsHandler<Register> for WebSocketActor {
|
|||||||
name: name.clone(),
|
name: name.clone(),
|
||||||
email: email.clone(),
|
email: email.clone(),
|
||||||
project_id: None,
|
project_id: None,
|
||||||
|
role: UserRole::Owner,
|
||||||
})) {
|
})) {
|
||||||
Ok(Ok(_)) => Some(WsMsg::SignUpSuccess),
|
Ok(Ok(_)) => Some(WsMsg::SignUpSuccess),
|
||||||
Ok(Err(_)) => Some(WsMsg::SignUpPairTaken),
|
Ok(Err(_)) => Some(WsMsg::SignUpPairTaken),
|
||||||
|
Loading…
Reference in New Issue
Block a user