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]]
|
||||
name = "serde"
|
||||
version = "1.0.116"
|
||||
version = "1.0.117"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "96fe57af81d28386a513cbc6858332abc6117cfdb5999647c6444b8f43a370a5"
|
||||
checksum = "b88fa983de7720629c9387e9f517353ed404164b1e482c970a90c1a4aaf7dc1a"
|
||||
dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
@ -3135,9 +3135,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.116"
|
||||
version = "1.0.117"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f630a6370fd8e457873b4bd2ffdae75408bc291ba72be773772a4c2a065d9ae8"
|
||||
checksum = "cbd1ae72adb44aab48f325a02444a5fc079349a8d804c1fc922aed3f7454c74e"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@ -3329,9 +3329,9 @@ checksum = "2d67a5a62ba6e01cb2192ff309324cb4875d0c451d55fe2319433abe7a05a8ee"
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "1.0.44"
|
||||
version = "1.0.45"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e03e57e4fcbfe7749842d53e24ccb9aa12b7252dbe5e91d2acad31834c8b8fdd"
|
||||
checksum = "ea9c5432ff16d6152371f808fb5a871cd67368171b09bb21b43df8e4a47a3556"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"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_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:
|
||||
build:
|
||||
dockerfile: ./jirs-server/Dockerfile
|
||||
@ -57,3 +75,5 @@ services:
|
||||
|
||||
volumes:
|
||||
assets:
|
||||
build:
|
||||
rs-target:
|
||||
|
@ -29,7 +29,7 @@ lto = true
|
||||
opt-level = 's'
|
||||
|
||||
[dependencies]
|
||||
jirs-data = { path = "../jirs-data" }
|
||||
jirs-data = { path = "../jirs-data", features = ["frontend"] }
|
||||
|
||||
wee_alloc = "*"
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
use syntect::easy::HighlightLines;
|
||||
use syntect::highlighting::{FontStyle, Style};
|
||||
use syntect::parsing::SyntaxReference;
|
||||
// use syntect::easy::HighlightLines;
|
||||
// use syntect::highlighting::{FontStyle, Style};
|
||||
// use syntect::parsing::SyntaxReference;
|
||||
use wasm_bindgen::prelude::*;
|
||||
|
||||
#[wasm_bindgen(final, js_name = JirsCodeBuilder)]
|
||||
@ -15,51 +15,52 @@ impl JirsCodeBuilder {
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub fn hi_code(&mut self, lang: &str, code: &str) -> String {
|
||||
let syntax = {
|
||||
match crate::hi::syntax_set::load().find_syntax_by_name(lang) {
|
||||
Some(s) => s.clone(),
|
||||
_ => {
|
||||
return code.to_string();
|
||||
}
|
||||
}
|
||||
};
|
||||
let mut buffer: Vec<String> = Vec::with_capacity(code.lines().count() * 2);
|
||||
for line in code.lines() {
|
||||
self.hi(&syntax, line, &mut buffer);
|
||||
buffer.push("<br />".to_string());
|
||||
}
|
||||
buffer.join("")
|
||||
pub fn hi_code(&mut self, _lang: &str, code: &str) -> String {
|
||||
// let syntax = {
|
||||
// match crate::hi::syntax_set::load().find_syntax_by_name(lang) {
|
||||
// Some(s) => s.clone(),
|
||||
// _ => {
|
||||
// return code.to_string();
|
||||
// }
|
||||
// }
|
||||
// };
|
||||
// let mut buffer: Vec<String> = Vec::with_capacity(code.lines().count() * 2);
|
||||
// for line in code.lines() {
|
||||
// self.hi(&syntax, line, &mut buffer);
|
||||
// buffer.push("<br />".to_string());
|
||||
// }
|
||||
// buffer.join("")
|
||||
code.to_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 tokens = { h.highlight(line, &crate::hi::syntax_set::load()) };
|
||||
|
||||
for (style, token) in tokens.into_iter() {
|
||||
let Style {
|
||||
foreground: f,
|
||||
background: b,
|
||||
font_style,
|
||||
} = style;
|
||||
let fs = if font_style == FontStyle::BOLD {
|
||||
"font-weight: bold"
|
||||
} else if font_style == FontStyle::ITALIC {
|
||||
"font-style: italic"
|
||||
} else if font_style == FontStyle::UNDERLINE {
|
||||
"text-decoration: underline"
|
||||
} else {
|
||||
""
|
||||
};
|
||||
|
||||
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>"#,
|
||||
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,
|
||||
fs = fs
|
||||
));
|
||||
}
|
||||
}
|
||||
// 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 tokens = { h.highlight(line, &crate::hi::syntax_set::load()) };
|
||||
//
|
||||
// for (style, token) in tokens.into_iter() {
|
||||
// let Style {
|
||||
// foreground: f,
|
||||
// background: b,
|
||||
// font_style,
|
||||
// } = style;
|
||||
// let fs = if font_style == FontStyle::BOLD {
|
||||
// "font-weight: bold"
|
||||
// } else if font_style == FontStyle::ITALIC {
|
||||
// "font-style: italic"
|
||||
// } else if font_style == FontStyle::UNDERLINE {
|
||||
// "text-decoration: underline"
|
||||
// } else {
|
||||
// ""
|
||||
// };
|
||||
//
|
||||
// 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>"#,
|
||||
// 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,
|
||||
// fs = fs
|
||||
// ));
|
||||
// }
|
||||
// }
|
||||
}
|
||||
|
||||
pub fn define() {
|
||||
|
@ -9,10 +9,10 @@ use jirs_data::*;
|
||||
|
||||
use crate::model::{ModalType, Model, Page};
|
||||
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_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};
|
||||
|
||||
mod changes;
|
||||
@ -85,7 +85,7 @@ pub enum Msg {
|
||||
StrInputChanged(FieldId, String),
|
||||
U32InputChanged(FieldId, u32),
|
||||
FileInputChanged(FieldId, Vec<File>),
|
||||
Rte(FieldId, RteMsg),
|
||||
// Rte(FieldId, RteMsg),
|
||||
|
||||
// issues
|
||||
AddIssue,
|
||||
@ -279,9 +279,9 @@ pub fn render(host_url: String, ws_url: String) {
|
||||
HOST_URL = host_url;
|
||||
WS_URL = ws_url;
|
||||
}
|
||||
if !cfg!(debug_assertions) {
|
||||
crate::hi::syntax_set::load();
|
||||
}
|
||||
// if !cfg!(debug_assertions) {
|
||||
// crate::hi::syntax_set::load();
|
||||
// }
|
||||
elements::define();
|
||||
|
||||
let _app = seed::App::builder(update, view)
|
||||
|
@ -14,7 +14,7 @@ use crate::{
|
||||
drag::DragState, styled_checkbox::StyledCheckboxState,
|
||||
styled_date_time_input::StyledDateTimeInputState, styled_editor::Mode,
|
||||
styled_image_input::StyledImageInputState, styled_input::StyledInputState,
|
||||
styled_rte::StyledRteState, styled_select::StyledSelectState,
|
||||
/*styled_rte::StyledRteState,*/ styled_select::StyledSelectState,
|
||||
},
|
||||
EditIssueModalSection, FieldId, Msg, ProjectFieldId,
|
||||
};
|
||||
@ -371,7 +371,7 @@ pub struct ProjectSettingsPage {
|
||||
pub edit_column_id: Option<IssueStatusId>,
|
||||
pub creating_issue_status: bool,
|
||||
pub name: StyledInputState,
|
||||
pub description_rte: StyledRteState,
|
||||
// pub description_rte: StyledRteState,
|
||||
}
|
||||
|
||||
impl ProjectSettingsPage {
|
||||
@ -411,9 +411,9 @@ impl ProjectSettingsPage {
|
||||
FieldId::ProjectSettings(ProjectFieldId::IssueStatusName),
|
||||
"",
|
||||
),
|
||||
description_rte: StyledRteState::new(FieldId::ProjectSettings(
|
||||
ProjectFieldId::Description,
|
||||
)),
|
||||
// description_rte: StyledRteState::new(FieldId::ProjectSettings(
|
||||
// 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.time_tracking.update(&msg);
|
||||
page.name.update(&msg);
|
||||
page.description_rte.update(&msg, orders);
|
||||
// page.description_rte.update(&msg, orders);
|
||||
|
||||
match msg {
|
||||
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_icon::{Icon, StyledIcon};
|
||||
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::{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_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 desc_rte = StyledField::build()
|
||||
.input(
|
||||
StyledRte::build(FieldId::ProjectSettings(ProjectFieldId::Description))
|
||||
.state(&page.description_rte)
|
||||
.build()
|
||||
.into_node(),
|
||||
)
|
||||
.build()
|
||||
.into_node();
|
||||
// let desc_rte = StyledField::build()
|
||||
// .input(
|
||||
// StyledRte::build(FieldId::ProjectSettings(ProjectFieldId::Description))
|
||||
// .state(&page.description_rte)
|
||||
// .build()
|
||||
// .into_node(),
|
||||
// )
|
||||
// .build()
|
||||
// .into_node();
|
||||
|
||||
let category_field = category_field(page);
|
||||
|
||||
@ -89,7 +89,7 @@ pub fn view(model: &model::Model) -> Node<Msg> {
|
||||
}))
|
||||
.add_field(name_field)
|
||||
.add_field(url_field)
|
||||
.add_field(desc_rte)
|
||||
// .add_field(desc_rte)
|
||||
.add_field(description_field)
|
||||
.add_field(category_field)
|
||||
.add_field(time_tracking_field)
|
||||
|
@ -24,7 +24,7 @@ pub mod styled_image_input;
|
||||
pub mod styled_input;
|
||||
pub mod styled_link;
|
||||
pub mod styled_modal;
|
||||
pub mod styled_rte;
|
||||
// pub mod styled_rte;
|
||||
pub mod styled_select;
|
||||
pub mod styled_select_child;
|
||||
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>) {
|
||||
match msg {
|
||||
Msg::MessageInvitationApproved(token) => {
|
||||
send_ws_msg(
|
||||
WsMsg::InvitationAcceptRequest(*token),
|
||||
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);
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
let m = match msg {
|
||||
Msg::MessageInvitationApproved(token) => WsMsg::InvitationAcceptRequest(*token),
|
||||
Msg::MessageInvitationDismiss(token) => WsMsg::InvitationRejectRequest(*token),
|
||||
Msg::MessageSeen(id) => WsMsg::MessageMarkSeen(*id),
|
||||
_ => return,
|
||||
};
|
||||
send_ws_msg(m, model.ws.as_ref(), orders);
|
||||
}
|
||||
|
||||
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![
|
||||
about_tooltip_popup(model),
|
||||
messages_tooltip_popup(model),
|
||||
@ -92,8 +88,7 @@ pub fn render(model: &Model) -> Vec<Node<Msg>> {
|
||||
attrs![At::Href => "/"],
|
||||
div![class!["styledLogo"], logo_svg]
|
||||
],
|
||||
navbar_left_item("Search issues", Icon::Search, None, None),
|
||||
navbar_left_item("Create Issue", Icon::Plus, Some("/add-issue"), None),
|
||||
issue_nav,
|
||||
div![
|
||||
class!["bottom"],
|
||||
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>> {
|
||||
let Message {
|
||||
id,
|
||||
receiver_id: _,
|
||||
sender_id: _,
|
||||
summary,
|
||||
description,
|
||||
message_type,
|
||||
hyper_link,
|
||||
created_at: _,
|
||||
updated_at: _,
|
||||
..
|
||||
} = message;
|
||||
let message_id = *id;
|
||||
|
||||
@ -203,10 +195,7 @@ fn message_ui(model: &Model, message: &Message) -> Option<Node<Msg>> {
|
||||
|
||||
let node = match message_type {
|
||||
MessageType::ReceivedInvitation => {
|
||||
let token: InvitationToken = match hyper_link.trim_start_matches('#').parse() {
|
||||
Err(_) => return None,
|
||||
Ok(n) => n,
|
||||
};
|
||||
let token: InvitationToken = hyper_link.trim_start_matches('#').parse().ok()?;
|
||||
let accept = StyledButton::build()
|
||||
.primary()
|
||||
.text("Accept")
|
||||
|
@ -70,10 +70,10 @@ impl<'l> StyledButtonBuilder<'l> {
|
||||
self.variant(Variant::Empty)
|
||||
}
|
||||
|
||||
pub fn button_id(mut self, button_id: ButtonId) -> Self {
|
||||
self.button_id = Some(button_id);
|
||||
self
|
||||
}
|
||||
// pub fn button_id(mut self, button_id: ButtonId) -> Self {
|
||||
// self.button_id = Some(button_id);
|
||||
// self
|
||||
// }
|
||||
|
||||
pub fn disabled(mut self, value: bool) -> Self {
|
||||
self.disabled = Some(value);
|
||||
|
@ -173,13 +173,13 @@ impl<'l> StyledSelectBuilder<'l> {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn try_state<'state: 'l>(self, state: Option<&'state StyledSelectState>) -> Self {
|
||||
if let Some(s) = state {
|
||||
self.state(s)
|
||||
} else {
|
||||
self
|
||||
}
|
||||
}
|
||||
// pub fn try_state<'state: 'l>(self, state: Option<&'state StyledSelectState>) -> Self {
|
||||
// if let Some(s) = state {
|
||||
// self.state(s)
|
||||
// } else {
|
||||
// self
|
||||
// }
|
||||
// }
|
||||
|
||||
pub fn state<'state: 'l>(self, state: &'state StyledSelectState) -> Self {
|
||||
self.opened(state.opened)
|
||||
|
@ -89,15 +89,15 @@ impl<'l> StyledTooltipBuilder<'l> {
|
||||
self
|
||||
}
|
||||
|
||||
pub fn table_tooltip(mut self) -> Self {
|
||||
self.variant = Variant::TableBuilder;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn code_tooltip(mut self) -> Self {
|
||||
self.variant = Variant::CodeBuilder;
|
||||
self
|
||||
}
|
||||
// pub fn table_tooltip(mut self) -> Self {
|
||||
// self.variant = Variant::TableBuilder;
|
||||
// self
|
||||
// }
|
||||
//
|
||||
// pub fn code_tooltip(mut self) -> Self {
|
||||
// self.variant = Variant::CodeBuilder;
|
||||
// self
|
||||
// }
|
||||
|
||||
pub fn date_time_picker(mut self) -> Self {
|
||||
self.variant = Variant::DateTimeBuilder;
|
||||
|
@ -13,7 +13,8 @@ name = "jirs_data"
|
||||
path = "./src/lib.rs"
|
||||
|
||||
[features]
|
||||
backend = [ "diesel" ]
|
||||
backend = ["diesel"]
|
||||
frontend = []
|
||||
|
||||
[dependencies]
|
||||
serde = "*"
|
||||
|
@ -14,7 +14,7 @@ pub use payloads::*;
|
||||
pub use sql::*;
|
||||
|
||||
mod fields;
|
||||
mod msg;
|
||||
pub mod msg;
|
||||
mod payloads;
|
||||
|
||||
#[cfg(feature = "backend")]
|
||||
@ -80,6 +80,7 @@ impl IssueType {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "frontend")]
|
||||
impl Into<u32> for IssueType {
|
||||
fn into(self) -> u32 {
|
||||
match self {
|
||||
@ -90,6 +91,7 @@ impl Into<u32> for IssueType {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "frontend")]
|
||||
impl Into<IssueType> for u32 {
|
||||
fn into(self) -> IssueType {
|
||||
match self {
|
||||
@ -102,7 +104,7 @@ impl Into<IssueType> for u32 {
|
||||
}
|
||||
|
||||
impl IssueType {
|
||||
pub fn to_str(&self) -> &'static str {
|
||||
pub fn to_str<'l>(&self) -> &'l str {
|
||||
match self {
|
||||
IssueType::Task => "task",
|
||||
IssueType::Bug => "bug",
|
||||
|
@ -9,6 +9,114 @@ use crate::{
|
||||
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)]
|
||||
pub enum WsMsg {
|
||||
Ping,
|
||||
@ -124,4 +232,7 @@ pub enum WsMsg {
|
||||
EpicUpdated(Epic),
|
||||
EpicDelete(EpicId),
|
||||
EpicDeleted(EpicId),
|
||||
|
||||
// errors
|
||||
Error(WsError),
|
||||
}
|
||||
|
@ -41,7 +41,6 @@ dotenv = { version = "*" }
|
||||
|
||||
byteorder = "1.0"
|
||||
chrono = { version = "0.4", features = ["serde"] }
|
||||
libc = { version = "0.2.0" }
|
||||
time = { version = "0.1" }
|
||||
url = { version = "2.1.0" }
|
||||
percent-encoding = { version = "2.1.0" }
|
||||
@ -66,6 +65,7 @@ async-trait = { version = "*" }
|
||||
|
||||
futures = { version = "*" }
|
||||
openssl-sys = { version = "*", features = ["vendored"] }
|
||||
libc = { version = "0.2.0", default-features = false }
|
||||
|
||||
lettre = { version = "*" }
|
||||
lettre_email = { version = "*" }
|
||||
|
@ -1,16 +1,13 @@
|
||||
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::{
|
||||
db::{DbExecutor, DbPool, SyncQuery},
|
||||
db::{tokens::FindAccessToken, DbExecutor, DbPool, DbPooledConn, SyncQuery},
|
||||
db_pool,
|
||||
errors::ServiceErrors,
|
||||
};
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct AuthorizeUser {
|
||||
pub access_token: uuid::Uuid,
|
||||
}
|
||||
@ -19,30 +16,26 @@ impl Message for AuthorizeUser {
|
||||
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 {
|
||||
type Result = Result<User, ServiceErrors>;
|
||||
|
||||
fn handle(&mut self, msg: AuthorizeUser, _: &mut Self::Context) -> Self::Result {
|
||||
use crate::schema::tokens::dsl::{access_token, tokens};
|
||||
use crate::schema::users::dsl::{id, users};
|
||||
|
||||
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)))
|
||||
let conn = db_pool!(self);
|
||||
msg.execute(conn)
|
||||
}
|
||||
}
|
||||
|
||||
@ -50,21 +43,10 @@ impl SyncQuery for AuthorizeUser {
|
||||
type Result = std::result::Result<User, crate::errors::ServiceErrors>;
|
||||
|
||||
fn handle(&self, pool: &DbPool) -> Self::Result {
|
||||
use crate::schema::tokens::dsl::{access_token, tokens};
|
||||
use crate::schema::users::dsl::{id, users};
|
||||
|
||||
let conn = pool
|
||||
.get()
|
||||
.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)
|
||||
let conn = pool.get().map_err(|e| {
|
||||
error!("{:?}", e);
|
||||
crate::errors::ServiceErrors::DatabaseConnectionLost
|
||||
})?;
|
||||
self.execute(&conn)
|
||||
}
|
||||
}
|
||||
|
@ -1,17 +1,32 @@
|
||||
use actix::{Handler, Message};
|
||||
use diesel::pg::Pg;
|
||||
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 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 {
|
||||
type Result = Result<Vec<Comment>, ServiceErrors>;
|
||||
}
|
||||
@ -20,28 +35,33 @@ impl Handler<LoadIssueComments> for DbExecutor {
|
||||
type Result = Result<Vec<Comment>, ServiceErrors>;
|
||||
|
||||
fn handle(&mut self, msg: LoadIssueComments, _ctx: &mut Self::Context) -> Self::Result {
|
||||
use crate::schema::comments::dsl::*;
|
||||
|
||||
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()))
|
||||
let conn = db_pool!(self);
|
||||
msg.execute(conn)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct CreateComment {
|
||||
pub user_id: i32,
|
||||
pub issue_id: i32,
|
||||
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 {
|
||||
type Result = Result<Comment, ServiceErrors>;
|
||||
}
|
||||
@ -50,35 +70,35 @@ impl Handler<CreateComment> for DbExecutor {
|
||||
type Result = Result<Comment, ServiceErrors>;
|
||||
|
||||
fn handle(&mut self, msg: CreateComment, _ctx: &mut Self::Context) -> Self::Result {
|
||||
use crate::models::CommentForm;
|
||||
use crate::schema::comments::dsl::*;
|
||||
|
||||
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()))
|
||||
let conn = db_pool!(self);
|
||||
msg.execute(conn)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct UpdateComment {
|
||||
pub comment_id: i32,
|
||||
pub user_id: i32,
|
||||
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 {
|
||||
type Result = Result<Comment, ServiceErrors>;
|
||||
}
|
||||
@ -87,32 +107,32 @@ impl Handler<UpdateComment> for DbExecutor {
|
||||
type Result = Result<Comment, ServiceErrors>;
|
||||
|
||||
fn handle(&mut self, msg: UpdateComment, _ctx: &mut Self::Context) -> Self::Result {
|
||||
use crate::schema::comments::dsl::*;
|
||||
|
||||
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)
|
||||
let conn = db_pool!(self);
|
||||
msg.execute(conn)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct DeleteComment {
|
||||
pub comment_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 {
|
||||
type Result = Result<(), ServiceErrors>;
|
||||
}
|
||||
@ -121,22 +141,8 @@ impl Handler<DeleteComment> for DbExecutor {
|
||||
type Result = Result<(), ServiceErrors>;
|
||||
|
||||
fn handle(&mut self, msg: DeleteComment, _ctx: &mut Self::Context) -> Self::Result {
|
||||
use crate::schema::comments::dsl::*;
|
||||
|
||||
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()))?;
|
||||
let conn = db_pool!(self);
|
||||
msg.execute(conn)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
@ -1,13 +1,10 @@
|
||||
use actix::{Handler, Message};
|
||||
use diesel::pg::Pg;
|
||||
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 project_id: i32,
|
||||
}
|
||||
@ -22,20 +19,17 @@ impl Handler<LoadEpics> for DbExecutor {
|
||||
fn handle(&mut self, msg: LoadEpics, _ctx: &mut Self::Context) -> Self::Result {
|
||||
use crate::schema::epics::dsl::*;
|
||||
|
||||
let conn = &self
|
||||
.pool
|
||||
.get()
|
||||
.map_err(|_| ServiceErrors::DatabaseConnectionLost)?;
|
||||
let conn = db_pool!(self);
|
||||
|
||||
let epics_query = epics.distinct_on(id).filter(project_id.eq(msg.project_id));
|
||||
debug!("{}", diesel::debug_query::<Pg, _>(&epics_query));
|
||||
epics_query
|
||||
q!(epics.distinct_on(id).filter(project_id.eq(msg.project_id)))
|
||||
.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 user_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 {
|
||||
use crate::schema::epics::dsl::*;
|
||||
|
||||
let conn = &self
|
||||
.pool
|
||||
.get()
|
||||
.map_err(|_| ServiceErrors::DatabaseConnectionLost)?;
|
||||
let conn = db_pool!(self);
|
||||
|
||||
let epic_query = diesel::insert_into(epics).values((
|
||||
q!(diesel::insert_into(epics).values((
|
||||
name.eq(msg.name.as_str()),
|
||||
user_id.eq(msg.user_id),
|
||||
project_id.eq(msg.project_id),
|
||||
));
|
||||
debug!("{}", diesel::debug_query::<Pg, _>(&epic_query));
|
||||
epic_query
|
||||
.get_result::<Epic>(conn)
|
||||
.map_err(|_| ServiceErrors::RecordNotFound("epics".to_string()))
|
||||
)))
|
||||
.get_result::<Epic>(conn)
|
||||
.map_err(|e| {
|
||||
error!("{:?}", e);
|
||||
ServiceErrors::Error(WsError::InvalidEpic)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct UpdateEpic {
|
||||
pub epic_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 {
|
||||
use crate::schema::epics::dsl::*;
|
||||
|
||||
let conn = &self
|
||||
.pool
|
||||
.get()
|
||||
.map_err(|_| ServiceErrors::DatabaseConnectionLost)?;
|
||||
let query = diesel::update(
|
||||
let conn = db_pool!(self);
|
||||
|
||||
q!(diesel::update(
|
||||
epics
|
||||
.filter(project_id.eq(msg.project_id))
|
||||
.find(msg.epic_id),
|
||||
)
|
||||
.set(name.eq(msg.name));
|
||||
info!("{}", diesel::debug_query::<Pg, _>(&query));
|
||||
let row: Epic = query
|
||||
.get_result::<Epic>(conn)
|
||||
.map_err(|_| ServiceErrors::RecordNotFound("epics".to_string()))?;
|
||||
Ok(row)
|
||||
.set(name.eq(msg.name)))
|
||||
.get_result::<Epic>(conn)
|
||||
.map_err(|e| {
|
||||
error!("{:?}", e);
|
||||
ServiceErrors::Error(WsError::FailedToUpdateEpic)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct DeleteEpic {
|
||||
pub epic_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 {
|
||||
use crate::schema::epics::dsl::*;
|
||||
|
||||
let conn = &self
|
||||
.pool
|
||||
.get()
|
||||
.map_err(|_| ServiceErrors::DatabaseConnectionLost)?;
|
||||
let conn = db_pool!(self);
|
||||
|
||||
q!(diesel::delete(
|
||||
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(())
|
||||
}
|
||||
}
|
||||
|
@ -1,22 +1,49 @@
|
||||
use actix::{Handler, Message};
|
||||
use diesel::connection::TransactionManager;
|
||||
use diesel::pg::Pg;
|
||||
use diesel::prelude::*;
|
||||
|
||||
use jirs_data::{
|
||||
EmailString, Invitation, InvitationId, InvitationState, InvitationToken, ProjectId, Token,
|
||||
User, UserId, UserRole, UsernameString,
|
||||
msg::WsError, EmailString, Invitation, InvitationId, InvitationState, InvitationToken,
|
||||
ProjectId, Token, User, UserId, UserRole, UsernameString,
|
||||
};
|
||||
|
||||
use crate::db::DbPooledConn;
|
||||
use crate::{
|
||||
db::{
|
||||
tokens::CreateBindToken,
|
||||
users::{LookupUser, Register},
|
||||
DbExecutor,
|
||||
},
|
||||
db_pool,
|
||||
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 user_id: UserId,
|
||||
}
|
||||
@ -31,20 +58,18 @@ impl Handler<ListInvitation> for DbExecutor {
|
||||
fn handle(&mut self, msg: ListInvitation, _ctx: &mut Self::Context) -> Self::Result {
|
||||
use crate::schema::invitations::dsl::*;
|
||||
|
||||
let conn = &self
|
||||
.pool
|
||||
.get()
|
||||
.map_err(|_| ServiceErrors::DatabaseConnectionLost)?;
|
||||
let conn = db_pool!(self);
|
||||
|
||||
let query = invitations
|
||||
q!(invitations
|
||||
.filter(invited_by_id.eq(msg.user_id))
|
||||
.filter(state.ne(InvitationState::Accepted))
|
||||
.order_by(state.asc())
|
||||
.then_order_by(updated_at.desc());
|
||||
debug!("{}", diesel::debug_query::<Pg, _>(&query).to_string());
|
||||
query
|
||||
.load(conn)
|
||||
.map_err(|_| ServiceErrors::DatabaseConnectionLost)
|
||||
.then_order_by(updated_at.desc()))
|
||||
.load(conn)
|
||||
.map_err(|e| {
|
||||
error!("{:?}", e);
|
||||
ServiceErrors::Error(WsError::FailedToLoadInvitations)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@ -56,6 +81,25 @@ pub struct CreateInvitation {
|
||||
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 {
|
||||
type Result = Result<Invitation, ServiceErrors>;
|
||||
}
|
||||
@ -64,25 +108,8 @@ impl Handler<CreateInvitation> for DbExecutor {
|
||||
type Result = Result<Invitation, ServiceErrors>;
|
||||
|
||||
fn handle(&mut self, msg: CreateInvitation, _ctx: &mut Self::Context) -> Self::Result {
|
||||
use crate::schema::invitations::dsl::*;
|
||||
|
||||
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)))
|
||||
let conn = db_pool!(self);
|
||||
msg.execute(conn)
|
||||
}
|
||||
}
|
||||
|
||||
@ -90,6 +117,18 @@ pub struct DeleteInvitation {
|
||||
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 {
|
||||
type Result = Result<(), ServiceErrors>;
|
||||
}
|
||||
@ -98,17 +137,8 @@ impl Handler<DeleteInvitation> for DbExecutor {
|
||||
type Result = Result<(), ServiceErrors>;
|
||||
|
||||
fn handle(&mut self, msg: DeleteInvitation, _ctx: &mut Self::Context) -> Self::Result {
|
||||
use crate::schema::invitations::dsl::*;
|
||||
|
||||
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)))?;
|
||||
let conn = db_pool!(self);
|
||||
msg.execute(conn)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@ -118,6 +148,24 @@ struct UpdateInvitationState {
|
||||
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 {
|
||||
type Result = Result<(), ServiceErrors>;
|
||||
}
|
||||
@ -126,23 +174,8 @@ impl Handler<UpdateInvitationState> for DbExecutor {
|
||||
type Result = Result<(), ServiceErrors>;
|
||||
|
||||
fn handle(&mut self, msg: UpdateInvitationState, _ctx: &mut Self::Context) -> Self::Result {
|
||||
use crate::schema::invitations::dsl::*;
|
||||
|
||||
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)))?;
|
||||
let conn = db_pool!(self);
|
||||
msg.execute(conn)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@ -158,14 +191,14 @@ impl Message for RevokeInvitation {
|
||||
impl Handler<RevokeInvitation> for DbExecutor {
|
||||
type Result = Result<(), ServiceErrors>;
|
||||
|
||||
fn handle(&mut self, msg: RevokeInvitation, ctx: &mut Self::Context) -> Self::Result {
|
||||
self.handle(
|
||||
UpdateInvitationState {
|
||||
id: msg.id,
|
||||
state: InvitationState::Revoked,
|
||||
},
|
||||
ctx,
|
||||
)
|
||||
fn handle(&mut self, msg: RevokeInvitation, _ctx: &mut Self::Context) -> Self::Result {
|
||||
let conn = db_pool!(self);
|
||||
UpdateInvitationState {
|
||||
id: msg.id,
|
||||
state: InvitationState::Revoked,
|
||||
}
|
||||
.execute(conn)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@ -173,6 +206,85 @@ pub struct AcceptInvitation {
|
||||
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 {
|
||||
type Result = Result<Token, ServiceErrors>;
|
||||
}
|
||||
@ -180,107 +292,9 @@ impl Message for AcceptInvitation {
|
||||
impl Handler<AcceptInvitation> for DbExecutor {
|
||||
type Result = Result<Token, ServiceErrors>;
|
||||
|
||||
fn handle(&mut self, msg: AcceptInvitation, ctx: &mut Self::Context) -> Self::Result {
|
||||
use crate::schema::invitations::dsl::*;
|
||||
fn handle(&mut self, msg: AcceptInvitation, _ctx: &mut Self::Context) -> Self::Result {
|
||||
let conn = db_pool!(self);
|
||||
|
||||
let conn = &self
|
||||
.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)
|
||||
msg.execute(conn)
|
||||
}
|
||||
}
|
||||
|
@ -1,17 +1,34 @@
|
||||
use actix::{Handler, Message};
|
||||
use diesel::pg::Pg;
|
||||
use diesel::prelude::*;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
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 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 {
|
||||
type Result = Result<Vec<IssueAssignee>, ServiceErrors>;
|
||||
}
|
||||
@ -20,18 +37,8 @@ impl Handler<LoadAssignees> for DbExecutor {
|
||||
type Result = Result<Vec<IssueAssignee>, ServiceErrors>;
|
||||
|
||||
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
|
||||
.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()))
|
||||
msg.execute(conn)
|
||||
}
|
||||
}
|
||||
|
@ -1,15 +1,30 @@
|
||||
use actix::{Handler, Message};
|
||||
use diesel::pg::Pg;
|
||||
use diesel::prelude::*;
|
||||
|
||||
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 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 {
|
||||
type Result = Result<Vec<IssueStatus>, ServiceErrors>;
|
||||
}
|
||||
@ -18,20 +33,9 @@ impl Handler<LoadIssueStatuses> for DbExecutor {
|
||||
type Result = Result<Vec<IssueStatus>, ServiceErrors>;
|
||||
|
||||
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
|
||||
.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()))
|
||||
msg.execute(conn)
|
||||
}
|
||||
}
|
||||
|
||||
@ -41,6 +45,22 @@ pub struct CreateIssueStatus {
|
||||
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 {
|
||||
type Result = Result<IssueStatus, ServiceErrors>;
|
||||
}
|
||||
@ -49,22 +69,9 @@ impl Handler<CreateIssueStatus> for DbExecutor {
|
||||
type Result = Result<IssueStatus, ServiceErrors>;
|
||||
|
||||
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
|
||||
.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()))
|
||||
msg.execute(conn)
|
||||
}
|
||||
}
|
||||
|
||||
@ -83,18 +90,16 @@ impl Handler<DeleteIssueStatus> for DbExecutor {
|
||||
fn handle(&mut self, msg: DeleteIssueStatus, _ctx: &mut Self::Context) -> Self::Result {
|
||||
use crate::schema::issue_statuses::dsl::{id, issue_statuses, project_id};
|
||||
|
||||
let conn = &self
|
||||
.pool
|
||||
.get()
|
||||
.map_err(|_| ServiceErrors::DatabaseConnectionLost)?;
|
||||
let conn = db_pool!(self);
|
||||
|
||||
let issue_assignees_query = diesel::delete(issue_statuses)
|
||||
q!(diesel::delete(issue_statuses)
|
||||
.filter(id.eq(msg.issue_status_id))
|
||||
.filter(project_id.eq(msg.project_id));
|
||||
debug!("{}", diesel::debug_query::<Pg, _>(&issue_assignees_query));
|
||||
issue_assignees_query
|
||||
.execute(conn)
|
||||
.map_err(|_| ServiceErrors::RecordNotFound("issue users".to_string()))?;
|
||||
.filter(project_id.eq(msg.project_id)))
|
||||
.execute(conn)
|
||||
.map_err(|e| {
|
||||
error!("{:?}", e);
|
||||
ServiceErrors::RecordNotFound("issue users".to_string())
|
||||
})?;
|
||||
Ok(msg.issue_status_id)
|
||||
}
|
||||
}
|
||||
@ -118,22 +123,20 @@ impl Handler<UpdateIssueStatus> for DbExecutor {
|
||||
id, issue_statuses, name, position, project_id, updated_at,
|
||||
};
|
||||
|
||||
let conn = &self
|
||||
.pool
|
||||
.get()
|
||||
.map_err(|_| ServiceErrors::DatabaseConnectionLost)?;
|
||||
let conn = db_pool!(self);
|
||||
|
||||
let issue_assignees_query = diesel::update(issue_statuses)
|
||||
q!(diesel::update(issue_statuses)
|
||||
.set((
|
||||
name.eq(msg.name),
|
||||
position.eq(msg.position),
|
||||
updated_at.eq(chrono::Utc::now().naive_utc()),
|
||||
))
|
||||
.filter(id.eq(msg.issue_status_id))
|
||||
.filter(project_id.eq(msg.project_id));
|
||||
debug!("{}", diesel::debug_query::<Pg, _>(&issue_assignees_query));
|
||||
issue_assignees_query
|
||||
.get_result::<IssueStatus>(conn)
|
||||
.map_err(|_| ServiceErrors::RecordNotFound("issue users".to_string()))
|
||||
.filter(project_id.eq(msg.project_id)))
|
||||
.get_result::<IssueStatus>(conn)
|
||||
.map_err(|e| {
|
||||
error!("{:?}", e);
|
||||
ServiceErrors::RecordNotFound("issue users".to_string())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -4,9 +4,10 @@ use diesel::expression::sql_literal::sql;
|
||||
use diesel::prelude::*;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use jirs_data::msg::WsError;
|
||||
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";
|
||||
|
||||
@ -24,19 +25,18 @@ impl Handler<LoadIssue> for DbExecutor {
|
||||
|
||||
fn handle(&mut self, msg: LoadIssue, _ctx: &mut Self::Context) -> Self::Result {
|
||||
use crate::schema::issues::dsl::{id, issues};
|
||||
let conn = &self
|
||||
.pool
|
||||
.get()
|
||||
.map_err(|_| ServiceErrors::DatabaseConnectionLost)?;
|
||||
|
||||
let conn = db_pool!(self);
|
||||
|
||||
let query = issues.filter(id.eq(msg.issue_id)).distinct();
|
||||
debug!(
|
||||
"{}",
|
||||
diesel::debug_query::<diesel::pg::Pg, _>(&query).to_string()
|
||||
);
|
||||
query
|
||||
.first::<Issue>(conn)
|
||||
.map_err(|_| ServiceErrors::RecordNotFound("project issues".to_string()))
|
||||
query.first::<Issue>(conn).map_err(|e| {
|
||||
error!("{:?}", e);
|
||||
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 {
|
||||
use crate::schema::issues::dsl::{issues, project_id};
|
||||
let conn = &self
|
||||
.pool
|
||||
.get()
|
||||
.map_err(|_| ServiceErrors::DatabaseConnectionLost)?;
|
||||
|
||||
let conn = db_pool!(self);
|
||||
|
||||
let chain = issues.filter(project_id.eq(msg.project_id)).distinct();
|
||||
debug!(
|
||||
"{}",
|
||||
diesel::debug_query::<diesel::pg::Pg, _>(&chain).to_string()
|
||||
);
|
||||
let vec = chain
|
||||
.load::<Issue>(conn)
|
||||
.map_err(|_| ServiceErrors::RecordNotFound("project issues".to_string()))?;
|
||||
let vec = chain.load::<Issue>(conn).map_err(|e| {
|
||||
error!("{:?}", e);
|
||||
ServiceErrors::RecordNotFound("project issues".to_string())
|
||||
})?;
|
||||
Ok(vec)
|
||||
}
|
||||
}
|
||||
@ -98,10 +98,8 @@ impl Handler<UpdateIssue> for DbExecutor {
|
||||
|
||||
fn handle(&mut self, msg: UpdateIssue, _ctx: &mut Self::Context) -> Self::Result {
|
||||
use crate::schema::issues::dsl::{self, issues};
|
||||
let conn = &self
|
||||
.pool
|
||||
.get()
|
||||
.map_err(|_| ServiceErrors::DatabaseConnectionLost)?;
|
||||
|
||||
let conn = db_pool!(self);
|
||||
|
||||
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()
|
||||
);
|
||||
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())
|
||||
})?;
|
||||
|
||||
@ -142,12 +141,18 @@ impl Handler<UpdateIssue> for DbExecutor {
|
||||
diesel::delete(dsl::issue_assignees)
|
||||
.filter(not(dsl::user_id.eq_any(user_ids)).and(dsl::issue_id.eq(current_issue_id)))
|
||||
.execute(conn)
|
||||
.map_err(|_| ServiceErrors::DatabaseConnectionLost)?;
|
||||
.map_err(|e| {
|
||||
error!("{:?}", e);
|
||||
ServiceErrors::DatabaseConnectionLost
|
||||
})?;
|
||||
let existing: Vec<i32> = dsl::issue_assignees
|
||||
.select(dsl::user_id)
|
||||
.filter(dsl::issue_id.eq(current_issue_id))
|
||||
.get_results::<i32>(conn)
|
||||
.map_err(|_| ServiceErrors::DatabaseConnectionLost)?;
|
||||
.map_err(|e| {
|
||||
error!("{:?}", e);
|
||||
ServiceErrors::DatabaseConnectionLost
|
||||
})?;
|
||||
let mut values = vec![];
|
||||
for user_id in user_ids.iter() {
|
||||
if !existing.contains(user_id) {
|
||||
@ -160,15 +165,16 @@ impl Handler<UpdateIssue> for DbExecutor {
|
||||
diesel::insert_into(dsl::issue_assignees)
|
||||
.values(values)
|
||||
.execute(conn)
|
||||
.map_err(|_| {
|
||||
.map_err(|e| {
|
||||
error!("{:?}", e);
|
||||
ServiceErrors::DatabaseQueryFailed(FAILED_CONNECT_USER_AND_ISSUE.to_string())
|
||||
})?;
|
||||
}
|
||||
|
||||
issues
|
||||
.find(msg.issue_id)
|
||||
.first::<Issue>(conn)
|
||||
.map_err(|_| ServiceErrors::DatabaseConnectionLost)
|
||||
issues.find(msg.issue_id).first::<Issue>(conn).map_err(|e| {
|
||||
error!("{:?}", e);
|
||||
ServiceErrors::DatabaseConnectionLost
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@ -188,10 +194,7 @@ impl Handler<DeleteIssue> for DbExecutor {
|
||||
use crate::schema::issue_assignees::dsl::{issue_assignees, issue_id};
|
||||
use crate::schema::issues::dsl::issues;
|
||||
|
||||
let conn = &self
|
||||
.pool
|
||||
.get()
|
||||
.map_err(|_| ServiceErrors::DatabaseConnectionLost)?;
|
||||
let conn = db_pool!(self);
|
||||
|
||||
diesel::delete(issue_assignees.filter(issue_id.eq(msg.issue_id)))
|
||||
.execute(conn)
|
||||
@ -231,16 +234,16 @@ impl Handler<CreateIssue> for DbExecutor {
|
||||
use crate::schema::issue_assignees::dsl;
|
||||
use crate::schema::issues::dsl::issues;
|
||||
|
||||
let conn = &self
|
||||
.pool
|
||||
.get()
|
||||
.map_err(|_| ServiceErrors::DatabaseConnectionLost)?;
|
||||
let conn = db_pool!(self);
|
||||
|
||||
let list_position = issues
|
||||
// .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)
|
||||
.map_err(|_| ServiceErrors::DatabaseConnectionLost)?;
|
||||
.map_err(|e| {
|
||||
error!("resolve new issue position failed {}", e);
|
||||
ServiceErrors::DatabaseConnectionLost
|
||||
})?;
|
||||
|
||||
info!("{:?}", msg.issue_type);
|
||||
info!("msg.issue_status_id {:?}", msg.issue_status_id);
|
||||
@ -252,9 +255,12 @@ impl Handler<CreateIssue> for DbExecutor {
|
||||
},
|
||||
ctx,
|
||||
)
|
||||
.map_err(|_| ServiceErrors::DatabaseConnectionLost)?
|
||||
.map_err(|e| {
|
||||
error!("{:?}", e);
|
||||
ServiceErrors::Error(WsError::FailedToFetchIssueStatuses)
|
||||
})?
|
||||
.get(0)
|
||||
.ok_or_else(|| ServiceErrors::DatabaseConnectionLost)?
|
||||
.ok_or_else(|| ServiceErrors::Error(WsError::NoIssueStatuses))?
|
||||
.id
|
||||
} else {
|
||||
msg.issue_status_id
|
||||
@ -302,7 +308,10 @@ impl Handler<CreateIssue> for DbExecutor {
|
||||
diesel::insert_into(dsl::issue_assignees)
|
||||
.values(values)
|
||||
.execute(conn)
|
||||
.map_err(|_| ServiceErrors::DatabaseConnectionLost)?;
|
||||
.map_err(|e| {
|
||||
error!("{:?}", e);
|
||||
ServiceErrors::DatabaseConnectionLost
|
||||
})?;
|
||||
|
||||
Ok(issue)
|
||||
}
|
||||
|
@ -8,7 +8,9 @@ use crate::{
|
||||
users::{FindUser, LookupUser},
|
||||
DbExecutor,
|
||||
},
|
||||
db_pool,
|
||||
errors::ServiceErrors,
|
||||
q,
|
||||
};
|
||||
|
||||
#[derive(Debug)]
|
||||
@ -26,19 +28,14 @@ impl Handler<LoadMessages> for DbExecutor {
|
||||
fn handle(&mut self, msg: LoadMessages, _ctx: &mut Self::Context) -> Self::Result {
|
||||
use crate::schema::messages::dsl::*;
|
||||
|
||||
let conn = &self
|
||||
.pool
|
||||
.get()
|
||||
.map_err(|_| ServiceErrors::DatabaseConnectionLost)?;
|
||||
let conn = db_pool!(self);
|
||||
|
||||
let query = messages.filter(receiver_id.eq(msg.user_id));
|
||||
debug!(
|
||||
"{}",
|
||||
diesel::debug_query::<diesel::pg::Pg, _>(&query).to_string()
|
||||
);
|
||||
query
|
||||
q!(messages.filter(receiver_id.eq(msg.user_id)))
|
||||
.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 {
|
||||
use crate::schema::messages::dsl::*;
|
||||
|
||||
let conn = &self
|
||||
.pool
|
||||
.get()
|
||||
.map_err(|_| ServiceErrors::DatabaseConnectionLost)?;
|
||||
let conn = db_pool!(self);
|
||||
|
||||
let query = diesel::delete(
|
||||
let size = q!(diesel::delete(
|
||||
messages
|
||||
.find(msg.message_id)
|
||||
.filter(receiver_id.eq(msg.user_id)),
|
||||
);
|
||||
debug!("{}", diesel::debug_query::<diesel::pg::Pg, _>(&query));
|
||||
let size = query
|
||||
.execute(conn)
|
||||
.map_err(|_| ServiceErrors::DatabaseQueryFailed("load user messages".to_string()))?;
|
||||
))
|
||||
.execute(conn)
|
||||
.map_err(|e| {
|
||||
error!("{:?}", e);
|
||||
ServiceErrors::DatabaseQueryFailed("load user messages".to_string())
|
||||
})?;
|
||||
|
||||
if size > 0 {
|
||||
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 {
|
||||
use crate::schema::messages::dsl::*;
|
||||
|
||||
let conn = &self
|
||||
.pool
|
||||
.get()
|
||||
.map_err(|_| ServiceErrors::DatabaseConnectionLost)?;
|
||||
let conn = db_pool!(self);
|
||||
|
||||
let user: User = match {
|
||||
match msg.receiver {
|
||||
@ -143,9 +135,10 @@ impl Handler<CreateMessage> for DbExecutor {
|
||||
"{}",
|
||||
diesel::debug_query::<diesel::pg::Pg, _>(&query).to_string()
|
||||
);
|
||||
query
|
||||
.get_result(conn)
|
||||
.map_err(|_| ServiceErrors::DatabaseQueryFailed("create message failed".to_string()))
|
||||
query.get_result(conn).map_err(|e| {
|
||||
error!("{:?}", e);
|
||||
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 {
|
||||
use crate::schema::messages::dsl::*;
|
||||
|
||||
let conn = &self
|
||||
.pool
|
||||
.get()
|
||||
.map_err(|_| ServiceErrors::DatabaseConnectionLost)?;
|
||||
let conn = db_pool!(self);
|
||||
|
||||
let query = messages.filter(
|
||||
q!(messages.filter(
|
||||
hyper_link
|
||||
.eq(format!("#{}", msg.token))
|
||||
.and(receiver_id.eq(msg.user_id)),
|
||||
);
|
||||
debug!(
|
||||
"{}",
|
||||
diesel::debug_query::<diesel::pg::Pg, _>(&query).to_string()
|
||||
);
|
||||
query
|
||||
.load(conn)
|
||||
.map_err(|_| ServiceErrors::DatabaseQueryFailed("create message failed".to_string()))
|
||||
))
|
||||
.load(conn)
|
||||
.map_err(|e| {
|
||||
error!("{:?}", e);
|
||||
ServiceErrors::DatabaseQueryFailed("create message failed".to_string())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -5,6 +5,8 @@ use diesel::pg::PgConnection;
|
||||
use diesel::r2d2::{self, ConnectionManager};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::errors::ServiceErrors;
|
||||
|
||||
pub mod authorize_user;
|
||||
pub mod comments;
|
||||
pub mod epics;
|
||||
@ -105,3 +107,68 @@ impl Configuration {
|
||||
"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 diesel::pg::Pg;
|
||||
use diesel::prelude::*;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use jirs_data::{NameString, Project, ProjectCategory, ProjectId, TimeTracking, UserId};
|
||||
|
||||
use crate::db::DbExecutor;
|
||||
use crate::errors::ServiceErrors;
|
||||
use crate::schema::projects::all_columns;
|
||||
use crate::db::DbPooledConn;
|
||||
use crate::{db::DbExecutor, db_pool, errors::ServiceErrors, q, schema::projects::all_columns};
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct LoadCurrentProject {
|
||||
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 {
|
||||
type Result = Result<Project, ServiceErrors>;
|
||||
}
|
||||
@ -22,26 +33,12 @@ impl Handler<LoadCurrentProject> for DbExecutor {
|
||||
type Result = Result<Project, ServiceErrors>;
|
||||
|
||||
fn handle(&mut self, msg: LoadCurrentProject, _ctx: &mut Self::Context) -> Self::Result {
|
||||
use crate::schema::projects::dsl::projects;
|
||||
let conn = &self
|
||||
.pool
|
||||
.get()
|
||||
.map_err(|_| ServiceErrors::DatabaseConnectionLost)?;
|
||||
let conn = db_pool!(self);
|
||||
|
||||
let query = projects.find(msg.project_id);
|
||||
|
||||
debug!(
|
||||
"{}",
|
||||
diesel::debug_query::<diesel::pg::Pg, _>(&query).to_string()
|
||||
);
|
||||
|
||||
query
|
||||
.first::<Project>(conn)
|
||||
.map_err(|_| ServiceErrors::RecordNotFound("Project".to_string()))
|
||||
msg.execute(conn)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct CreateProject {
|
||||
pub name: NameString,
|
||||
pub url: Option<String>,
|
||||
@ -50,6 +47,38 @@ pub struct CreateProject {
|
||||
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 {
|
||||
type Result = Result<Project, ServiceErrors>;
|
||||
}
|
||||
@ -58,29 +87,12 @@ impl Handler<CreateProject> for DbExecutor {
|
||||
type Result = Result<Project, ServiceErrors>;
|
||||
|
||||
fn handle(&mut self, msg: CreateProject, _ctx: &mut Self::Context) -> Self::Result {
|
||||
use crate::schema::projects::dsl::*;
|
||||
let conn = &self
|
||||
.pool
|
||||
.get()
|
||||
.map_err(|_| ServiceErrors::DatabaseConnectionLost)?;
|
||||
let conn = db_pool!(self);
|
||||
|
||||
let query = diesel::insert_into(projects)
|
||||
.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)))
|
||||
msg.execute(conn)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct UpdateProject {
|
||||
pub project_id: ProjectId,
|
||||
pub name: Option<NameString>,
|
||||
@ -90,6 +102,27 @@ pub struct UpdateProject {
|
||||
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 {
|
||||
type Result = Result<Project, ServiceErrors>;
|
||||
}
|
||||
@ -98,29 +131,9 @@ impl Handler<UpdateProject> for DbExecutor {
|
||||
type Result = Result<Project, ServiceErrors>;
|
||||
|
||||
fn handle(&mut self, msg: UpdateProject, _ctx: &mut Self::Context) -> Self::Result {
|
||||
use crate::schema::projects::dsl::*;
|
||||
let conn = &self
|
||||
.pool
|
||||
.get()
|
||||
.map_err(|_| ServiceErrors::DatabaseConnectionLost)?;
|
||||
let conn = db_pool!(self);
|
||||
|
||||
let update_query = diesel::update(projects.find(msg.project_id)).set((
|
||||
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()))
|
||||
msg.execute(conn)
|
||||
}
|
||||
}
|
||||
|
||||
@ -128,6 +141,24 @@ pub struct LoadProjects {
|
||||
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 {
|
||||
type Result = Result<Vec<Project>, ServiceErrors>;
|
||||
}
|
||||
@ -136,22 +167,8 @@ impl Handler<LoadProjects> for DbExecutor {
|
||||
type Result = Result<Vec<Project>, ServiceErrors>;
|
||||
|
||||
fn handle(&mut self, msg: LoadProjects, _ctx: &mut Self::Context) -> Self::Result {
|
||||
use crate::schema::projects::dsl::*;
|
||||
use crate::schema::user_projects::dsl::{project_id, user_id, user_projects};
|
||||
let conn = db_pool!(self);
|
||||
|
||||
let conn = &self
|
||||
.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()))
|
||||
msg.execute(conn)
|
||||
}
|
||||
}
|
||||
|
@ -1,19 +1,73 @@
|
||||
use actix::{Handler, Message};
|
||||
use diesel::prelude::*;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use uuid::Uuid;
|
||||
|
||||
use jirs_data::msg::WsError;
|
||||
use jirs_data::{Token, UserId};
|
||||
|
||||
use crate::db::DbExecutor;
|
||||
use crate::errors::ServiceErrors;
|
||||
use crate::models::TokenForm;
|
||||
use crate::{
|
||||
db::{DbExecutor, DbPooledConn},
|
||||
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 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 {
|
||||
type Result = Result<Token, ServiceErrors>;
|
||||
}
|
||||
@ -22,32 +76,63 @@ impl Handler<FindBindToken> for DbExecutor {
|
||||
type Result = Result<Token, ServiceErrors>;
|
||||
|
||||
fn handle(&mut self, msg: FindBindToken, _: &mut Self::Context) -> Self::Result {
|
||||
use crate::schema::tokens::dsl::{bind_token, tokens};
|
||||
let conn = &self
|
||||
.pool
|
||||
.get()
|
||||
.map_err(|_| ServiceErrors::DatabaseConnectionLost)?;
|
||||
|
||||
let token: Token = tokens
|
||||
.filter(bind_token.eq(Some(msg.token)))
|
||||
.first(conn)
|
||||
.map_err(|_e| ServiceErrors::RecordNotFound(format!("token for {}", msg.token)))?;
|
||||
|
||||
let erase_value: Option<Uuid> = None;
|
||||
diesel::update(tokens.find(token.id))
|
||||
.set(bind_token.eq(erase_value))
|
||||
.execute(conn)
|
||||
.map_err(|_| ServiceErrors::DatabaseConnectionLost)?;
|
||||
|
||||
Ok(token)
|
||||
let conn = db_pool!(self);
|
||||
msg.execute(conn)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct FindAccessToken {
|
||||
pub token: Uuid,
|
||||
}
|
||||
|
||||
impl FindAccessToken {
|
||||
pub fn execute(self, conn: &DbPooledConn) -> Result<Token, ServiceErrors> {
|
||||
use crate::schema::tokens::dsl::{access_token, tokens};
|
||||
|
||||
q!(tokens.filter(access_token.eq(self.token)))
|
||||
.first(conn)
|
||||
.map_err(|e| {
|
||||
error!("{:?}", e);
|
||||
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 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 {
|
||||
type Result = Result<Token, ServiceErrors>;
|
||||
}
|
||||
@ -56,26 +141,8 @@ impl Handler<CreateBindToken> for DbExecutor {
|
||||
type Result = Result<Token, ServiceErrors>;
|
||||
|
||||
fn handle(&mut self, msg: CreateBindToken, _: &mut Self::Context) -> Self::Result {
|
||||
use crate::schema::tokens::dsl::tokens;
|
||||
let conn = &self
|
||||
.pool
|
||||
.get()
|
||||
.map_err(|_| ServiceErrors::DatabaseConnectionLost)?;
|
||||
let conn = db_pool!(self);
|
||||
|
||||
let access_token = Uuid::new_v4();
|
||||
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()))
|
||||
msg.execute(conn)
|
||||
}
|
||||
}
|
||||
|
@ -1,12 +1,15 @@
|
||||
use actix::{Handler, Message};
|
||||
use diesel::connection::TransactionManager;
|
||||
use diesel::pg::Pg;
|
||||
use diesel::prelude::*;
|
||||
|
||||
use jirs_data::msg::WsError;
|
||||
use jirs_data::{ProjectId, UserId, UserProject, UserProjectId, UserRole};
|
||||
|
||||
use crate::db::DbExecutor;
|
||||
use crate::errors::ServiceErrors;
|
||||
use crate::{
|
||||
db::{DbExecutor, DbPooledConn},
|
||||
db_pool,
|
||||
errors::ServiceErrors,
|
||||
q,
|
||||
};
|
||||
|
||||
pub struct CurrentUserProject {
|
||||
pub user_id: UserId,
|
||||
@ -22,16 +25,14 @@ impl Handler<CurrentUserProject> for DbExecutor {
|
||||
fn handle(&mut self, msg: CurrentUserProject, _: &mut Self::Context) -> Self::Result {
|
||||
use crate::schema::user_projects::dsl::*;
|
||||
|
||||
let conn = &self
|
||||
.pool
|
||||
.get()
|
||||
.map_err(|_| ServiceErrors::DatabaseConnectionLost)?;
|
||||
let conn = db_pool!(self);
|
||||
|
||||
let user_query = user_projects.filter(user_id.eq(msg.user_id).and(is_current.eq(true)));
|
||||
debug!("{}", diesel::debug_query::<Pg, _>(&user_query));
|
||||
user_query
|
||||
q!(user_projects.filter(user_id.eq(msg.user_id).and(is_current.eq(true))))
|
||||
.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 {
|
||||
use crate::schema::user_projects::dsl::*;
|
||||
|
||||
let conn = &self
|
||||
.pool
|
||||
.get()
|
||||
.map_err(|_| ServiceErrors::DatabaseConnectionLost)?;
|
||||
let conn = db_pool!(self);
|
||||
|
||||
let user_query = user_projects.filter(user_id.eq(msg.user_id));
|
||||
debug!("{}", diesel::debug_query::<Pg, _>(&user_query));
|
||||
user_query
|
||||
q!(user_projects.filter(user_id.eq(msg.user_id)))
|
||||
.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,
|
||||
}
|
||||
|
||||
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 {
|
||||
type Result = Result<UserProject, ServiceErrors>;
|
||||
}
|
||||
@ -75,63 +119,8 @@ impl Handler<ChangeCurrentUserProject> for DbExecutor {
|
||||
type Result = Result<UserProject, ServiceErrors>;
|
||||
|
||||
fn handle(&mut self, msg: ChangeCurrentUserProject, _ctx: &mut Self::Context) -> Self::Result {
|
||||
use crate::schema::user_projects::dsl::*;
|
||||
|
||||
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)
|
||||
let conn = db_pool!(self);
|
||||
msg.execute(conn)
|
||||
}
|
||||
}
|
||||
|
||||
@ -141,6 +130,42 @@ pub struct RemoveInvitedUser {
|
||||
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 {
|
||||
type Result = Result<(), ServiceErrors>;
|
||||
}
|
||||
@ -149,46 +174,48 @@ impl Handler<RemoveInvitedUser> for DbExecutor {
|
||||
type Result = Result<(), ServiceErrors>;
|
||||
|
||||
fn handle(&mut self, msg: RemoveInvitedUser, _ctx: &mut Self::Context) -> Self::Result {
|
||||
use crate::schema::user_projects::dsl::*;
|
||||
|
||||
let conn = &self
|
||||
.pool
|
||||
.get()
|
||||
.map_err(|_| ServiceErrors::DatabaseConnectionLost)?;
|
||||
|
||||
if msg.invited_id == msg.inviter_id {
|
||||
return Err(ServiceErrors::Unauthorized);
|
||||
}
|
||||
|
||||
{
|
||||
let owner = UserRole::Owner;
|
||||
let query = user_projects.filter(
|
||||
user_id
|
||||
.eq(msg.inviter_id)
|
||||
.and(project_id.eq(msg.project_id))
|
||||
.and(role.eq(owner)),
|
||||
);
|
||||
debug!("{}", diesel::debug_query::<Pg, _>(&query));
|
||||
query
|
||||
.first::<UserProject>(conn)
|
||||
.map_err(|_e| ServiceErrors::Unauthorized)?;
|
||||
}
|
||||
|
||||
{
|
||||
let query = diesel::delete(user_projects).filter(
|
||||
user_id
|
||||
.eq(msg.invited_id)
|
||||
.and(project_id.eq(msg.project_id)),
|
||||
);
|
||||
debug!("{}", diesel::debug_query::<Pg, _>(&query));
|
||||
query.execute(conn).map_err(|_e| {
|
||||
ServiceErrors::RecordNotFound(format!(
|
||||
"user project user with id {} for project {}",
|
||||
msg.invited_id, msg.project_id
|
||||
))
|
||||
})?;
|
||||
}
|
||||
|
||||
let conn = db_pool!(self);
|
||||
msg.execute(conn)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub struct CreateUserProject {
|
||||
pub user_id: UserId,
|
||||
pub project_id: ProjectId,
|
||||
pub is_current: bool,
|
||||
pub is_default: bool,
|
||||
pub role: UserRole,
|
||||
}
|
||||
|
||||
impl CreateUserProject {
|
||||
pub fn execute(self, conn: &DbPooledConn) -> Result<usize, ServiceErrors> {
|
||||
use crate::schema::user_projects::dsl::*;
|
||||
q!(diesel::insert_into(user_projects).values((
|
||||
user_id.eq(self.user_id),
|
||||
project_id.eq(self.project_id),
|
||||
is_current.eq(self.is_current),
|
||||
is_default.eq(self.is_default),
|
||||
role.eq(self.role),
|
||||
)))
|
||||
.execute(conn)
|
||||
.map_err(|e| {
|
||||
error!("{:?}", e);
|
||||
ServiceErrors::Error(WsError::InvalidUserProject)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Message for CreateUserProject {
|
||||
type Result = Result<(), ServiceErrors>;
|
||||
}
|
||||
|
||||
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(())
|
||||
}
|
||||
}
|
||||
|
@ -1,21 +1,34 @@
|
||||
use actix::{Handler, Message};
|
||||
use diesel::connection::TransactionManager;
|
||||
use diesel::pg::Pg;
|
||||
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::{DbExecutor, DbPooledConn};
|
||||
use crate::errors::ServiceErrors;
|
||||
use crate::schema::users::all_columns;
|
||||
use crate::db::user_projects::CreateUserProject;
|
||||
use crate::{
|
||||
db::{projects::CreateProject, DbExecutor, DbPooledConn},
|
||||
db_pool,
|
||||
errors::ServiceErrors,
|
||||
q,
|
||||
schema::users::all_columns,
|
||||
};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct FindUser {
|
||||
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 {
|
||||
type Result = Result<User, ServiceErrors>;
|
||||
}
|
||||
@ -24,27 +37,32 @@ impl Handler<FindUser> for DbExecutor {
|
||||
type Result = Result<User, ServiceErrors>;
|
||||
|
||||
fn handle(&mut self, msg: FindUser, _ctx: &mut Self::Context) -> Self::Result {
|
||||
use crate::schema::users::dsl::*;
|
||||
|
||||
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)))
|
||||
let conn = db_pool!(self);
|
||||
msg.execute(conn)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct LookupUser {
|
||||
pub name: 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 {
|
||||
type Result = Result<User, ServiceErrors>;
|
||||
}
|
||||
@ -53,29 +71,33 @@ impl Handler<LookupUser> for DbExecutor {
|
||||
type Result = Result<User, ServiceErrors>;
|
||||
|
||||
fn handle(&mut self, msg: LookupUser, _ctx: &mut Self::Context) -> Self::Result {
|
||||
use crate::schema::users::dsl::*;
|
||||
|
||||
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)))
|
||||
let conn = db_pool!(self);
|
||||
msg.execute(conn)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct LoadProjectUsers {
|
||||
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 {
|
||||
type Result = Result<Vec<User>, ServiceErrors>;
|
||||
}
|
||||
@ -84,31 +106,33 @@ impl Handler<LoadProjectUsers> for DbExecutor {
|
||||
type Result = Result<Vec<User>, ServiceErrors>;
|
||||
|
||||
fn handle(&mut self, msg: LoadProjectUsers, _ctx: &mut Self::Context) -> Self::Result {
|
||||
use crate::schema::user_projects::dsl::{project_id, user_id, user_projects};
|
||||
use crate::schema::users::dsl::*;
|
||||
|
||||
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()))
|
||||
let conn = db_pool!(self);
|
||||
msg.execute(conn)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct LoadIssueAssignees {
|
||||
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 {
|
||||
type Result = Result<Vec<User>, ServiceErrors>;
|
||||
}
|
||||
@ -117,31 +141,110 @@ impl Handler<LoadIssueAssignees> for DbExecutor {
|
||||
type Result = Result<Vec<User>, ServiceErrors>;
|
||||
|
||||
fn handle(&mut self, msg: LoadIssueAssignees, _ctx: &mut Self::Context) -> Self::Result {
|
||||
use crate::schema::issue_assignees::dsl::{issue_assignees, issue_id, user_id};
|
||||
use crate::schema::users::dsl::*;
|
||||
|
||||
let conn = &self
|
||||
.pool
|
||||
.get()
|
||||
.map_err(|_| ServiceErrors::DatabaseConnectionLost)?;
|
||||
|
||||
let users_query = users
|
||||
.distinct_on(id)
|
||||
.inner_join(issue_assignees.on(user_id.eq(id)))
|
||||
.filter(issue_id.eq(msg.issue_id))
|
||||
.select(users::all_columns());
|
||||
debug!("{}", diesel::debug_query::<Pg, _>(&users_query));
|
||||
users_query
|
||||
.load(conn)
|
||||
.map_err(|_| ServiceErrors::RecordNotFound("issue users".to_string()))
|
||||
let conn = db_pool!(self);
|
||||
msg.execute(conn)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct CreateUser {
|
||||
pub name: UsernameString,
|
||||
pub email: EmailString,
|
||||
}
|
||||
|
||||
impl CreateUser {
|
||||
pub fn execute(self, conn: &DbPooledConn) -> Result<User, ServiceErrors> {
|
||||
use crate::schema::users::dsl::*;
|
||||
|
||||
q!(diesel::insert_into(users)
|
||||
.values((name.eq(self.name.as_str()), email.eq(self.email.as_str()))))
|
||||
.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 name: String,
|
||||
pub email: String,
|
||||
pub name: UsernameString,
|
||||
pub email: EmailString,
|
||||
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 {
|
||||
@ -151,74 +254,12 @@ impl Message for Register {
|
||||
impl Handler<Register> for DbExecutor {
|
||||
type Result = Result<(), ServiceErrors>;
|
||||
|
||||
fn handle(&mut self, msg: Register, ctx: &mut Self::Context) -> Self::Result {
|
||||
use crate::schema::users::dsl::*;
|
||||
|
||||
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(())
|
||||
fn handle(&mut self, msg: Register, _ctx: &mut Self::Context) -> Self::Result {
|
||||
let conn = db_pool!(self);
|
||||
msg.execute(conn)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct LoadInvitedUsers {
|
||||
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::users::dsl::{email as u_email, users};
|
||||
|
||||
let conn = &self
|
||||
.pool
|
||||
.get()
|
||||
.map_err(|_| ServiceErrors::DatabaseConnectionLost)?;
|
||||
let conn = db_pool!(self);
|
||||
|
||||
let query = users
|
||||
q!(users
|
||||
.inner_join(invitations.on(i_email.eq(u_email)))
|
||||
.filter(invited_by_id.eq(msg.user_id))
|
||||
.select(users::all_columns());
|
||||
debug!("{}", diesel::debug_query::<Pg, _>(&query));
|
||||
query
|
||||
.load(conn)
|
||||
.map_err(|e| ServiceErrors::DatabaseQueryFailed(format!("{}", e)))
|
||||
.select(users::all_columns()))
|
||||
.load(conn)
|
||||
.map_err(|e| {
|
||||
error!("{:?}", e);
|
||||
ServiceErrors::Error(WsError::FailedToLoadInvitedUsers)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn count_matching_users(name: &str, email: &str, conn: &DbPooledConn) -> i64 {
|
||||
use crate::schema::users::dsl;
|
||||
|
||||
let query = dsl::users
|
||||
q!(dsl::users
|
||||
.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.eq(email).and(dsl::name.eq(name)))
|
||||
.count();
|
||||
info!("{}", diesel::debug_query::<diesel::pg::Pg, _>(&query));
|
||||
query.get_result::<i64>(conn).unwrap_or(1)
|
||||
.count())
|
||||
.get_result::<i64>(conn)
|
||||
.unwrap_or(1)
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct UpdateAvatarUrl {
|
||||
pub user_id: UserId,
|
||||
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 {
|
||||
use crate::schema::users::dsl::{avatar_url, id, users};
|
||||
|
||||
let conn = &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 conn = db_pool!(self);
|
||||
|
||||
let user_query = users.find(msg.user_id);
|
||||
debug!("{}", diesel::debug_query::<Pg, _>(&user_query));
|
||||
user_query
|
||||
.first(conn)
|
||||
.map_err(|e| ServiceErrors::DatabaseQueryFailed(format!("{}", e)))
|
||||
q!(diesel::update(users)
|
||||
.set(avatar_url.eq(msg.avatar_url))
|
||||
.filter(id.eq(msg.user_id)))
|
||||
.execute(conn)
|
||||
.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 user_id: UserId,
|
||||
pub name: String,
|
||||
@ -315,21 +350,15 @@ impl Handler<ProfileUpdate> for DbExecutor {
|
||||
fn handle(&mut self, msg: ProfileUpdate, _ctx: &mut Self::Context) -> Self::Result {
|
||||
use crate::schema::users::dsl::{email, id, name, users};
|
||||
|
||||
let conn = &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 conn = db_pool!(self);
|
||||
|
||||
let user_query = users.find(msg.user_id);
|
||||
debug!("{}", diesel::debug_query::<Pg, _>(&user_query));
|
||||
user_query
|
||||
q!(diesel::update(users)
|
||||
.set((email.eq(msg.email), name.eq(msg.name)))
|
||||
.filter(id.eq(msg.user_id)))
|
||||
.execute(conn)
|
||||
.map_err(|e| ServiceErrors::DatabaseQueryFailed(format!("{}", e)))?;
|
||||
|
||||
q!(users.find(msg.user_id))
|
||||
.first(conn)
|
||||
.map_err(|e| ServiceErrors::DatabaseQueryFailed(format!("{}", e)))
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
use actix_web::HttpResponse;
|
||||
|
||||
use jirs_data::ErrorResponse;
|
||||
use jirs_data::{msg::WsError, ErrorResponse};
|
||||
|
||||
const TOKEN_NOT_FOUND: &str = "Token not found";
|
||||
const DATABASE_CONNECTION_FAILED: &str = "Database connection failed";
|
||||
@ -12,6 +12,7 @@ pub enum ServiceErrors {
|
||||
DatabaseQueryFailed(String),
|
||||
RecordNotFound(String),
|
||||
RegisterCollision,
|
||||
Error(WsError),
|
||||
}
|
||||
|
||||
impl ServiceErrors {
|
||||
@ -44,6 +45,9 @@ impl Into<HttpResponse> for ServiceErrors {
|
||||
ServiceErrors::RegisterCollision => HttpResponse::Unauthorized().json(ErrorResponse {
|
||||
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;
|
||||
|
||||
use actix::Actor;
|
||||
use actix_cors::Cors;
|
||||
// use actix_cors::Cors;
|
||||
#[cfg(feature = "local-storage")]
|
||||
use actix_files as fs;
|
||||
use actix_web::{App, HttpServer};
|
||||
|
||||
use crate::ws::WsServer;
|
||||
|
||||
// use actix_web::http::Method;
|
||||
|
||||
pub mod db;
|
||||
pub mod errors;
|
||||
pub mod mail;
|
||||
@ -53,7 +55,6 @@ async fn main() -> Result<(), String> {
|
||||
HttpServer::new(move || {
|
||||
let app = App::new()
|
||||
.wrap(actix_web::middleware::Logger::default())
|
||||
.wrap(Cors::default())
|
||||
.data(ws_server.clone())
|
||||
.data(db_addr.clone())
|
||||
.data(mail_addr.clone())
|
||||
|
@ -9,14 +9,6 @@ use jirs_data::{
|
||||
|
||||
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)]
|
||||
pub struct Issue {
|
||||
pub id: i32,
|
||||
|
@ -262,10 +262,7 @@ impl WebSocketActor {
|
||||
self.current_user_project
|
||||
.as_ref()
|
||||
.map(|u| u)
|
||||
.ok_or_else(|| {
|
||||
let _x = 1;
|
||||
WsMsg::AuthorizeExpired
|
||||
})
|
||||
.ok_or_else(|| WsMsg::AuthorizeExpired)
|
||||
}
|
||||
|
||||
fn load_user_project(&self) -> Result<UserProject, WsMsg> {
|
||||
@ -273,11 +270,11 @@ impl WebSocketActor {
|
||||
match block_on(self.db.send(CurrentUserProject { user_id })) {
|
||||
Ok(Ok(user_project)) => Ok(user_project),
|
||||
Ok(Err(e)) => {
|
||||
error!("{:?}", e);
|
||||
error!("load_user_project encounter service error {:?}", e);
|
||||
Err(WsMsg::AuthorizeExpired)
|
||||
}
|
||||
Err(e) => {
|
||||
error!("{}", e);
|
||||
error!("load_user_project encounter mailbox error {}", e);
|
||||
Err(WsMsg::AuthorizeExpired)
|
||||
}
|
||||
}
|
||||
|
@ -1,11 +1,11 @@
|
||||
use futures::executor::block_on;
|
||||
|
||||
use jirs_data::{UserId, UserProject, WsMsg};
|
||||
use jirs_data::{UserId, UserProject, UserRole, WsMsg};
|
||||
|
||||
use crate::db;
|
||||
use crate::db::users::Register as DbRegister;
|
||||
use crate::ws::auth::Authenticate;
|
||||
use crate::ws::{WebSocketActor, WsHandler, WsResult};
|
||||
use crate::{
|
||||
db::{self, users::Register as DbRegister},
|
||||
ws::{auth::Authenticate, WebSocketActor, WsHandler, WsResult},
|
||||
};
|
||||
|
||||
pub struct LoadProjectUsers;
|
||||
|
||||
@ -41,6 +41,7 @@ impl WsHandler<Register> for WebSocketActor {
|
||||
name: name.clone(),
|
||||
email: email.clone(),
|
||||
project_id: None,
|
||||
role: UserRole::Owner,
|
||||
})) {
|
||||
Ok(Ok(_)) => Some(WsMsg::SignUpSuccess),
|
||||
Ok(Err(_)) => Some(WsMsg::SignUpPairTaken),
|
||||
|
Loading…
Reference in New Issue
Block a user