Improve accept invitation
This commit is contained in:
parent
e618a4f23c
commit
3cb74084d9
94
.dockerignore
Normal file
94
.dockerignore
Normal file
@ -0,0 +1,94 @@
|
|||||||
|
# Created by .ignore support plugin (hsz.mobi)
|
||||||
|
### JetBrains template
|
||||||
|
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider
|
||||||
|
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
|
||||||
|
|
||||||
|
# User-specific stuff
|
||||||
|
.idea/**/workspace.xml
|
||||||
|
.idea/**/tasks.xml
|
||||||
|
.idea/**/usage.statistics.xml
|
||||||
|
.idea/**/dictionaries
|
||||||
|
.idea/**/shelf
|
||||||
|
|
||||||
|
# Generated files
|
||||||
|
.idea/**/contentModel.xml
|
||||||
|
|
||||||
|
# Sensitive or high-churn files
|
||||||
|
.idea/**/dataSources/
|
||||||
|
.idea/**/dataSources.ids
|
||||||
|
.idea/**/dataSources.local.xml
|
||||||
|
.idea/**/sqlDataSources.xml
|
||||||
|
.idea/**/dynamic.xml
|
||||||
|
.idea/**/uiDesigner.xml
|
||||||
|
.idea/**/dbnavigator.xml
|
||||||
|
|
||||||
|
# Gradle
|
||||||
|
.idea/**/gradle.xml
|
||||||
|
.idea/**/libraries
|
||||||
|
|
||||||
|
# Gradle and Maven with auto-import
|
||||||
|
# When using Gradle or Maven with auto-import, you should exclude module files,
|
||||||
|
# since they will be recreated, and may cause churn. Uncomment if using
|
||||||
|
# auto-import.
|
||||||
|
# .idea/artifacts
|
||||||
|
# .idea/compiler.xml
|
||||||
|
# .idea/jarRepositories.xml
|
||||||
|
# .idea/modules.xml
|
||||||
|
# .idea/*.iml
|
||||||
|
# .idea/modules
|
||||||
|
# *.iml
|
||||||
|
# *.ipr
|
||||||
|
|
||||||
|
# CMake
|
||||||
|
cmake-build-*/
|
||||||
|
|
||||||
|
# Mongo Explorer plugin
|
||||||
|
.idea/**/mongoSettings.xml
|
||||||
|
|
||||||
|
# File-based project format
|
||||||
|
*.iws
|
||||||
|
|
||||||
|
# IntelliJ
|
||||||
|
out/
|
||||||
|
|
||||||
|
# mpeltonen/sbt-idea plugin
|
||||||
|
.idea_modules/
|
||||||
|
|
||||||
|
# JIRA plugin
|
||||||
|
atlassian-ide-plugin.xml
|
||||||
|
|
||||||
|
# Cursive Clojure plugin
|
||||||
|
.idea/replstate.xml
|
||||||
|
|
||||||
|
# Crashlytics plugin (for Android Studio and IntelliJ)
|
||||||
|
com_crashlytics_export_strings.xml
|
||||||
|
crashlytics.properties
|
||||||
|
crashlytics-build.properties
|
||||||
|
fabric.properties
|
||||||
|
|
||||||
|
# Editor-based Rest Client
|
||||||
|
.idea/httpRequests
|
||||||
|
|
||||||
|
# Android studio 3.1+ serialized cache file
|
||||||
|
.idea/caches/build_file_checksums.ser
|
||||||
|
|
||||||
|
### Rust template
|
||||||
|
# Generated by Cargo
|
||||||
|
# will have compiled files and executables
|
||||||
|
/target/
|
||||||
|
|
||||||
|
# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries
|
||||||
|
# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html
|
||||||
|
Cargo.lock
|
||||||
|
|
||||||
|
# These are backup files generated by rustfmt
|
||||||
|
**/*.rs.bk
|
||||||
|
|
||||||
|
/tmp/
|
||||||
|
|
||||||
|
/jirs-client/target/
|
||||||
|
/jirs-client/tmp/
|
||||||
|
/jirs-client/build/
|
||||||
|
|
||||||
|
/jirs-server/target/
|
||||||
|
/jirs-server/tmp/
|
3
.gitignore
vendored
3
.gitignore
vendored
@ -7,4 +7,7 @@ db.toml
|
|||||||
db.test.toml
|
db.test.toml
|
||||||
pkg
|
pkg
|
||||||
jirs-client/pkg
|
jirs-client/pkg
|
||||||
|
jirs-client/tmp
|
||||||
|
jirs-client/build
|
||||||
tmp
|
tmp
|
||||||
|
jirs-server/target
|
||||||
|
@ -1,7 +1,11 @@
|
|||||||
version: '3.0'
|
version: '3.0'
|
||||||
|
|
||||||
services:
|
services:
|
||||||
db:
|
db:
|
||||||
image: postgres:latest
|
image: postgres:latest
|
||||||
ports:
|
ports:
|
||||||
- 5432:5432
|
- 5432:5432
|
||||||
|
server:
|
||||||
|
build:
|
||||||
|
dockerfile: ./jirs-server/Dockerfile
|
||||||
|
context: .
|
||||||
|
1
jirs-client/.gitignore
vendored
1
jirs-client/.gitignore
vendored
@ -5,3 +5,4 @@ dist
|
|||||||
tmp
|
tmp
|
||||||
dev/styles.css
|
dev/styles.css
|
||||||
build
|
build
|
||||||
|
target
|
||||||
|
20
jirs-client/js/css/invite.css
Normal file
20
jirs-client/js/css/invite.css
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
#invite > .styledForm {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
margin: 0 auto 24px;
|
||||||
|
width: 400px;
|
||||||
|
background: rgb(255, 255, 255) none repeat scroll 0 0;
|
||||||
|
border-radius: 3px;
|
||||||
|
box-shadow: rgba(0, 0, 0, 0.1) 0 0 10px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
color: var(--textMedium);
|
||||||
|
}
|
||||||
|
|
||||||
|
#invite > .styledForm:first-of-type {
|
||||||
|
margin-top: 124.5px;
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#invite > .styledForm:last-of-type {
|
||||||
|
box-shadow: rgba(0, 0, 0, 0.1) 0 10px 10px;
|
||||||
|
}
|
@ -30,3 +30,4 @@
|
|||||||
@import "./css/login.css";
|
@import "./css/login.css";
|
||||||
@import "./css/register.css";
|
@import "./css/register.css";
|
||||||
@import "./css/users.css";
|
@import "./css/users.css";
|
||||||
|
@import "./css/invite.css";
|
||||||
|
@ -3,9 +3,10 @@
|
|||||||
. .env
|
. .env
|
||||||
|
|
||||||
rm -Rf tmp
|
rm -Rf tmp
|
||||||
mkdir tmp
|
mkdir -p tmp
|
||||||
|
mkdir -p target
|
||||||
|
|
||||||
wasm-pack build --mode normal --dev --out-name jirs --out-dir ./tmp --target web
|
wasm-pack build --mode normal --dev --out-name jirs --out-dir ./tmp --target web -- --verbose
|
||||||
../target/debug/jirs-css -i ./js/styles.css -O ./tmp/styles.css
|
../target/debug/jirs-css -i ./js/styles.css -O ./tmp/styles.css
|
||||||
|
|
||||||
cp -r ./static/* ./tmp
|
cp -r ./static/* ./tmp
|
||||||
|
@ -50,12 +50,18 @@ pub enum ProfilePageChange {
|
|||||||
SubmitForm,
|
SubmitForm,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
|
pub enum InvitationPageChange {
|
||||||
|
SubmitForm,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq)]
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
pub enum PageChanged {
|
pub enum PageChanged {
|
||||||
Users(UsersPageChange),
|
Users(UsersPageChange),
|
||||||
ProjectSettings(ProjectPageChange),
|
ProjectSettings(ProjectPageChange),
|
||||||
Profile(ProfilePageChange),
|
Profile(ProfilePageChange),
|
||||||
Board(BoardPageChange),
|
Board(BoardPageChange),
|
||||||
|
Invitation(InvitationPageChange),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
|
@ -1,34 +1,61 @@
|
|||||||
|
use std::str::FromStr;
|
||||||
|
|
||||||
use seed::{prelude::*, *};
|
use seed::{prelude::*, *};
|
||||||
|
|
||||||
use jirs_data::InviteFieldId;
|
use jirs_data::{InviteFieldId, WsMsg};
|
||||||
|
|
||||||
use crate::model::{InvitePage, Model, Page, PageContent};
|
use crate::model::{InvitePage, Model, Page, PageContent};
|
||||||
|
use crate::shared::styled_button::StyledButton;
|
||||||
use crate::shared::styled_field::StyledField;
|
use crate::shared::styled_field::StyledField;
|
||||||
use crate::shared::styled_form::StyledForm;
|
use crate::shared::styled_form::StyledForm;
|
||||||
use crate::shared::styled_input::StyledInput;
|
use crate::shared::styled_input::StyledInput;
|
||||||
use crate::shared::{outer_layout, ToNode};
|
use crate::shared::{outer_layout, ToNode};
|
||||||
use crate::validations::is_token;
|
use crate::validations::is_token;
|
||||||
use crate::{FieldId, Msg};
|
use crate::ws::send_ws_msg;
|
||||||
|
use crate::{FieldId, InvitationPageChange, Msg, PageChanged, WebSocketChanged};
|
||||||
|
|
||||||
pub fn update(msg: Msg, model: &mut Model, _orders: &mut impl Orders<Msg>) {
|
pub fn update(msg: Msg, model: &mut Model, orders: &mut impl Orders<Msg>) {
|
||||||
if let Msg::ChangePage(Page::Project) = msg {
|
match model.page_content {
|
||||||
build_page_content(model);
|
PageContent::Invite(..) => (),
|
||||||
return;
|
_ if model.page == Page::Invite => build_page_content(model),
|
||||||
}
|
_ => (),
|
||||||
|
};
|
||||||
|
|
||||||
let page = match &mut model.page_content {
|
let page = match &mut model.page_content {
|
||||||
PageContent::Invite(page) => page,
|
PageContent::Invite(page) => page,
|
||||||
_ => return,
|
_ => return,
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Msg::StrInputChanged(FieldId::Invite(InviteFieldId::Token), text) = msg {
|
match msg {
|
||||||
page.token_touched = true;
|
Msg::WebSocketChange(WebSocketChanged::WsMsg(WsMsg::InvitationAcceptFailure(_))) => {
|
||||||
page.token = text;
|
page.error = Some("Invalid token".to_string());
|
||||||
|
}
|
||||||
|
Msg::StrInputChanged(FieldId::Invite(InviteFieldId::Token), text) => {
|
||||||
|
page.token_touched = true;
|
||||||
|
page.token = text;
|
||||||
|
}
|
||||||
|
Msg::PageChanged(PageChanged::Invitation(InvitationPageChange::SubmitForm)) => {
|
||||||
|
if let Ok(token) = uuid::Uuid::from_str(page.token.as_str()) {
|
||||||
|
send_ws_msg(
|
||||||
|
WsMsg::InvitationAcceptRequest(token),
|
||||||
|
model.ws.as_ref(),
|
||||||
|
orders,
|
||||||
|
);
|
||||||
|
page.error = None;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn build_page_content(model: &mut Model) {
|
fn build_page_content(model: &mut Model) {
|
||||||
model.page_content = PageContent::Invite(Box::new(InvitePage::default()));
|
let s: String = seed::document().location().unwrap().to_string().into();
|
||||||
|
let url = seed::Url::from_str(s.as_str()).unwrap();
|
||||||
|
let search = url.search();
|
||||||
|
let values = search.get("token").map(|v| v.clone()).unwrap_or_default();
|
||||||
|
let mut content = InvitePage::default();
|
||||||
|
content.token = values.get(0).map(|s| s.clone()).unwrap_or_default();
|
||||||
|
model.page_content = PageContent::Invite(Box::new(content));
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn view(model: &Model) -> Node<Msg> {
|
pub fn view(model: &Model) -> Node<Msg> {
|
||||||
@ -37,21 +64,46 @@ pub fn view(model: &Model) -> Node<Msg> {
|
|||||||
_ => return empty![],
|
_ => return empty![],
|
||||||
};
|
};
|
||||||
|
|
||||||
let token = StyledInput::build(FieldId::Invite(InviteFieldId::Token))
|
let token_field = token_field(page);
|
||||||
.valid(!page.token_touched || is_token(page.token.as_str()))
|
let submit_field = submit(page);
|
||||||
.build()
|
let error = match page.error.as_ref() {
|
||||||
.into_node();
|
Some(s) => div![class!["error"], s.as_str()],
|
||||||
let token_field = StyledField::build()
|
_ => empty![],
|
||||||
.input(token)
|
};
|
||||||
.label("Your invite token")
|
|
||||||
.build()
|
|
||||||
.into_node();
|
|
||||||
|
|
||||||
let form = StyledForm::build()
|
let form = StyledForm::build()
|
||||||
.heading("Welcome in JIRS")
|
.heading("Welcome in JIRS")
|
||||||
|
.on_submit(ev(Ev::Submit, move |ev| {
|
||||||
|
ev.prevent_default();
|
||||||
|
Msg::PageChanged(PageChanged::Invitation(InvitationPageChange::SubmitForm))
|
||||||
|
}))
|
||||||
.add_field(token_field)
|
.add_field(token_field)
|
||||||
|
.add_field(submit_field)
|
||||||
|
.add_field(error)
|
||||||
.build()
|
.build()
|
||||||
.into_node();
|
.into_node();
|
||||||
|
|
||||||
outer_layout(model, "invite", vec![form])
|
outer_layout(model, "invite", vec![form])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn submit(_page: &Box<InvitePage>) -> Node<Msg> {
|
||||||
|
let submit = StyledButton::build()
|
||||||
|
.text("Accept")
|
||||||
|
.primary()
|
||||||
|
.build()
|
||||||
|
.into_node();
|
||||||
|
StyledField::build().input(submit).build().into_node()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn token_field(page: &Box<InvitePage>) -> Node<Msg> {
|
||||||
|
let token = StyledInput::build(FieldId::Invite(InviteFieldId::Token))
|
||||||
|
.valid(!page.token_touched || is_token(page.token.as_str()))
|
||||||
|
.value(page.token.as_str())
|
||||||
|
.build()
|
||||||
|
.into_node();
|
||||||
|
StyledField::build()
|
||||||
|
.input(token)
|
||||||
|
.label("Your invite token")
|
||||||
|
.build()
|
||||||
|
.into_node()
|
||||||
|
}
|
||||||
|
@ -53,7 +53,7 @@ pub enum Msg {
|
|||||||
InviteRequest,
|
InviteRequest,
|
||||||
InviteRevokeRequest(InvitationId),
|
InviteRevokeRequest(InvitationId),
|
||||||
InviteApproveRequest(InvitationId),
|
InviteApproveRequest(InvitationId),
|
||||||
InvitedUserRemove(EmailString),
|
InvitedUserRemove(UserId),
|
||||||
|
|
||||||
// sign up
|
// sign up
|
||||||
SignUpRequest,
|
SignUpRequest,
|
||||||
|
@ -62,6 +62,11 @@ pub fn update(msg: &Msg, model: &mut model::Model, orders: &mut impl Orders<Msg>
|
|||||||
model.modals.push(ModalType::DebugModal);
|
model.modals.push(ModalType::DebugModal);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(debug_assertions)]
|
||||||
|
Msg::GlobalKeyDown { key, .. } if key.eq(">") => {
|
||||||
|
log!(model);
|
||||||
|
}
|
||||||
|
|
||||||
_ => (),
|
_ => (),
|
||||||
}
|
}
|
||||||
add_issue::update(msg, model, orders);
|
add_issue::update(msg, model, orders);
|
||||||
|
@ -262,6 +262,7 @@ pub struct ProjectPage {
|
|||||||
pub struct InvitePage {
|
pub struct InvitePage {
|
||||||
pub token: String,
|
pub token: String,
|
||||||
pub token_touched: bool,
|
pub token_touched: bool,
|
||||||
|
pub error: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
|
@ -77,10 +77,12 @@ pub fn inner_layout(
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn outer_layout(_model: &Model, page_name: &str, children: Vec<Node<Msg>>) -> Node<Msg> {
|
pub fn outer_layout(model: &Model, page_name: &str, children: Vec<Node<Msg>>) -> Node<Msg> {
|
||||||
|
let modal = crate::modal::view(model);
|
||||||
article![
|
article![
|
||||||
class!["outer-layout", "outerPage"],
|
class!["outer-layout", "outerPage"],
|
||||||
id![page_name],
|
id![page_name],
|
||||||
|
modal,
|
||||||
children
|
children
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
@ -45,11 +45,11 @@ pub fn update(msg: Msg, model: &mut Model, orders: &mut impl Orders<Msg>) {
|
|||||||
}
|
}
|
||||||
send_ws_msg(WsMsg::InvitationListRequest, model.ws.as_ref(), orders);
|
send_ws_msg(WsMsg::InvitationListRequest, model.ws.as_ref(), orders);
|
||||||
}
|
}
|
||||||
WebSocketChanged::WsMsg(WsMsg::InvitedUserRemoveSuccess(email)) => {
|
WebSocketChanged::WsMsg(WsMsg::InvitedUserRemoveSuccess(removed_id)) => {
|
||||||
let mut old = vec![];
|
let mut old = vec![];
|
||||||
std::mem::swap(&mut page.invited_users, &mut old);
|
std::mem::swap(&mut page.invited_users, &mut old);
|
||||||
for user in old {
|
for user in old {
|
||||||
if user.email != email {
|
if user.id != removed_id {
|
||||||
page.invited_users.push(user);
|
page.invited_users.push(user);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -110,9 +110,9 @@ pub fn update(msg: Msg, model: &mut Model, orders: &mut impl Orders<Msg>) {
|
|||||||
orders,
|
orders,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
Msg::InvitedUserRemove(email) => {
|
Msg::InvitedUserRemove(user_id) => {
|
||||||
send_ws_msg(
|
send_ws_msg(
|
||||||
WsMsg::InvitedUserRemoveRequest(email),
|
WsMsg::InvitedUserRemoveRequest(user_id),
|
||||||
model.ws.as_ref(),
|
model.ws.as_ref(),
|
||||||
orders,
|
orders,
|
||||||
);
|
);
|
||||||
|
@ -108,10 +108,12 @@ pub fn view(model: &Model) -> Node<Msg> {
|
|||||||
.invited_users
|
.invited_users
|
||||||
.iter()
|
.iter()
|
||||||
.map(|user| {
|
.map(|user| {
|
||||||
let email = user.email.clone();
|
let user_id = user.id;
|
||||||
let remove = StyledButton::build()
|
let remove = StyledButton::build()
|
||||||
.text("Remove")
|
.text("Remove")
|
||||||
.on_click(mouse_ev(Ev::Click, move |_| Msg::InvitedUserRemove(email)))
|
.on_click(mouse_ev(Ev::Click, move |_| {
|
||||||
|
Msg::InvitedUserRemove(user_id)
|
||||||
|
}))
|
||||||
.build()
|
.build()
|
||||||
.into_node();
|
.into_node();
|
||||||
let role = page
|
let role = page
|
||||||
|
@ -31,6 +31,8 @@ pub type MessageId = i32;
|
|||||||
pub type EmailString = String;
|
pub type EmailString = String;
|
||||||
pub type UsernameString = String;
|
pub type UsernameString = String;
|
||||||
pub type TitleString = String;
|
pub type TitleString = String;
|
||||||
|
pub type BindToken = Uuid;
|
||||||
|
pub type InvitationToken = Uuid;
|
||||||
|
|
||||||
#[cfg_attr(feature = "backend", derive(FromSqlRow, AsExpression))]
|
#[cfg_attr(feature = "backend", derive(FromSqlRow, AsExpression))]
|
||||||
#[cfg_attr(feature = "backend", sql_type = "IssueTypeType")]
|
#[cfg_attr(feature = "backend", sql_type = "IssueTypeType")]
|
||||||
@ -705,10 +707,11 @@ pub enum WsMsg {
|
|||||||
InvitationRevokeRequest(InvitationId),
|
InvitationRevokeRequest(InvitationId),
|
||||||
InvitationRevokeSuccess(InvitationId),
|
InvitationRevokeSuccess(InvitationId),
|
||||||
//
|
//
|
||||||
InvitationAcceptRequest(InvitationId),
|
InvitationAcceptRequest(InvitationToken),
|
||||||
InvitationAcceptSuccess(InvitationId),
|
InvitationAcceptSuccess(BindToken),
|
||||||
InvitedUserRemoveRequest(EmailString),
|
InvitationAcceptFailure(InvitationToken),
|
||||||
InvitedUserRemoveSuccess(EmailString),
|
InvitedUserRemoveRequest(UserId),
|
||||||
|
InvitedUserRemoveSuccess(UserId),
|
||||||
|
|
||||||
// project page
|
// project page
|
||||||
ProjectRequest,
|
ProjectRequest,
|
||||||
|
15
jirs-server/Dockerfile
Normal file
15
jirs-server/Dockerfile
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
FROM archlinux:latest
|
||||||
|
|
||||||
|
WORKDIR /app/
|
||||||
|
|
||||||
|
RUN pacman -Sy rustup gcc postgresql --noconfirm
|
||||||
|
|
||||||
|
ADD jirs-server .
|
||||||
|
ADD jirs-data .
|
||||||
|
|
||||||
|
RUN rustup toolchain install nightly && \
|
||||||
|
rustup default nightly && \
|
||||||
|
cargo install diesel_cli --no-default-features --features postgres && \
|
||||||
|
cd jirs-server && diesel setup
|
||||||
|
|
||||||
|
CMD cd jirs-server && cargo run --bin jirs_server
|
@ -3,8 +3,8 @@ use diesel::pg::Pg;
|
|||||||
use diesel::prelude::*;
|
use diesel::prelude::*;
|
||||||
|
|
||||||
use jirs_data::{
|
use jirs_data::{
|
||||||
EmailString, Invitation, InvitationId, InvitationState, ProjectId, User, UserId, UserRole,
|
EmailString, Invitation, InvitationId, InvitationState, InvitationToken, ProjectId, Token,
|
||||||
UsernameString,
|
User, UserId, UserRole, UsernameString,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::db::DbExecutor;
|
use crate::db::DbExecutor;
|
||||||
@ -139,15 +139,15 @@ impl Handler<RevokeInvitation> for DbExecutor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub struct AcceptInvitation {
|
pub struct AcceptInvitation {
|
||||||
pub id: InvitationId,
|
pub invitation_token: InvitationToken,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Message for AcceptInvitation {
|
impl Message for AcceptInvitation {
|
||||||
type Result = Result<User, ServiceErrors>;
|
type Result = Result<Token, ServiceErrors>;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Handler<AcceptInvitation> for DbExecutor {
|
impl Handler<AcceptInvitation> for DbExecutor {
|
||||||
type Result = Result<User, ServiceErrors>;
|
type Result = Result<Token, ServiceErrors>;
|
||||||
|
|
||||||
fn handle(&mut self, msg: AcceptInvitation, _ctx: &mut Self::Context) -> Self::Result {
|
fn handle(&mut self, msg: AcceptInvitation, _ctx: &mut Self::Context) -> Self::Result {
|
||||||
use crate::schema::invitations::dsl::*;
|
use crate::schema::invitations::dsl::*;
|
||||||
@ -157,7 +157,7 @@ impl Handler<AcceptInvitation> for DbExecutor {
|
|||||||
.get()
|
.get()
|
||||||
.map_err(|_| ServiceErrors::DatabaseConnectionLost)?;
|
.map_err(|_| ServiceErrors::DatabaseConnectionLost)?;
|
||||||
|
|
||||||
let query = invitations.find(msg.id);
|
let query = invitations.filter(bind_token.eq(msg.invitation_token));
|
||||||
debug!("{}", diesel::debug_query::<Pg, _>(&query).to_string());
|
debug!("{}", diesel::debug_query::<Pg, _>(&query).to_string());
|
||||||
let invitation: Invitation = query
|
let invitation: Invitation = query
|
||||||
.first(conn)
|
.first(conn)
|
||||||
@ -206,6 +206,15 @@ impl Handler<AcceptInvitation> for DbExecutor {
|
|||||||
.map_err(|e| ServiceErrors::DatabaseQueryFailed(format!("{}", e)))?;
|
.map_err(|e| ServiceErrors::DatabaseQueryFailed(format!("{}", e)))?;
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok(user)
|
let token = {
|
||||||
|
use crate::schema::tokens::dsl::*;
|
||||||
|
let query = tokens.filter(user_id.eq(user.id));
|
||||||
|
debug!("{}", diesel::debug_query::<Pg, _>(&query));
|
||||||
|
query
|
||||||
|
.first(conn)
|
||||||
|
.map_err(|e| ServiceErrors::DatabaseQueryFailed(format!("{}", e)))?
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(token)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,7 +2,7 @@ use actix::{Handler, Message};
|
|||||||
use diesel::pg::Pg;
|
use diesel::pg::Pg;
|
||||||
use diesel::prelude::*;
|
use diesel::prelude::*;
|
||||||
|
|
||||||
use jirs_data::{UserId, UserProject, UserProjectId};
|
use jirs_data::{ProjectId, UserId, UserProject, UserProjectId, UserRole};
|
||||||
|
|
||||||
use crate::db::DbExecutor;
|
use crate::db::DbExecutor;
|
||||||
use crate::errors::ServiceErrors;
|
use crate::errors::ServiceErrors;
|
||||||
@ -109,3 +109,61 @@ impl Handler<ChangeCurrentUserProject> for DbExecutor {
|
|||||||
Ok(user_project)
|
Ok(user_project)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub struct RemoveInvitedUser {
|
||||||
|
pub invited_id: UserId,
|
||||||
|
pub inviter_id: UserId,
|
||||||
|
pub project_id: ProjectId,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Message for RemoveInvitedUser {
|
||||||
|
type Result = Result<(), ServiceErrors>;
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
))
|
||||||
|
})?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
use futures::executor::block_on;
|
use futures::executor::block_on;
|
||||||
|
|
||||||
use jirs_data::{EmailString, InvitationId, UserRole, UsernameString, WsMsg};
|
use jirs_data::{EmailString, InvitationId, InvitationToken, UserRole, UsernameString, WsMsg};
|
||||||
|
|
||||||
use crate::db::invitations;
|
use crate::db::invitations;
|
||||||
use crate::ws::{WebSocketActor, WsHandler, WsResult};
|
use crate::ws::{WebSocketActor, WsHandler, WsResult};
|
||||||
@ -131,22 +131,23 @@ impl WsHandler<RevokeInvitation> for WebSocketActor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub struct AcceptInvitation {
|
pub struct AcceptInvitation {
|
||||||
pub id: InvitationId,
|
pub invitation_token: InvitationToken,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl WsHandler<AcceptInvitation> for WebSocketActor {
|
impl WsHandler<AcceptInvitation> for WebSocketActor {
|
||||||
fn handle_msg(&mut self, msg: AcceptInvitation, _ctx: &mut Self::Context) -> WsResult {
|
fn handle_msg(&mut self, msg: AcceptInvitation, _ctx: &mut Self::Context) -> WsResult {
|
||||||
self.require_user()?;
|
let AcceptInvitation { invitation_token } = msg;
|
||||||
let AcceptInvitation { id } = msg;
|
let res = match block_on(self.db.send(invitations::AcceptInvitation {
|
||||||
let res = match block_on(self.db.send(invitations::AcceptInvitation { id })) {
|
invitation_token: invitation_token.clone(),
|
||||||
Ok(Ok(_)) => Some(WsMsg::InvitationAcceptSuccess(id)),
|
})) {
|
||||||
|
Ok(Ok(token)) => Some(WsMsg::InvitationAcceptSuccess(token.access_token)),
|
||||||
Ok(Err(e)) => {
|
Ok(Err(e)) => {
|
||||||
error!("{:?}", e);
|
error!("{:?}", e);
|
||||||
return Ok(None);
|
Some(WsMsg::InvitationAcceptFailure(invitation_token))
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
error!("{}", e);
|
error!("{}", e);
|
||||||
return Ok(None);
|
Some(WsMsg::InvitationAcceptFailure(invitation_token))
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
Ok(res)
|
Ok(res)
|
||||||
|
@ -150,6 +150,9 @@ impl WebSocketActor {
|
|||||||
|
|
||||||
// users
|
// users
|
||||||
WsMsg::ProjectUsersRequest => self.handle_msg(LoadProjectUsers, ctx)?,
|
WsMsg::ProjectUsersRequest => self.handle_msg(LoadProjectUsers, ctx)?,
|
||||||
|
WsMsg::InvitedUserRemoveRequest(user_id) => {
|
||||||
|
self.handle_msg(RemoveInvitedUser { user_id }, ctx)?
|
||||||
|
}
|
||||||
|
|
||||||
// comments
|
// comments
|
||||||
WsMsg::IssueCommentsRequest(issue_id) => {
|
WsMsg::IssueCommentsRequest(issue_id) => {
|
||||||
@ -166,7 +169,9 @@ impl WebSocketActor {
|
|||||||
self.handle_msg(CreateInvitation { name, email, role }, ctx)?
|
self.handle_msg(CreateInvitation { name, email, role }, ctx)?
|
||||||
}
|
}
|
||||||
WsMsg::InvitationListRequest => self.handle_msg(ListInvitation, ctx)?,
|
WsMsg::InvitationListRequest => self.handle_msg(ListInvitation, ctx)?,
|
||||||
WsMsg::InvitationAcceptRequest(id) => self.handle_msg(AcceptInvitation { id }, ctx)?,
|
WsMsg::InvitationAcceptRequest(invitation_token) => {
|
||||||
|
self.handle_msg(AcceptInvitation { invitation_token }, ctx)?
|
||||||
|
}
|
||||||
WsMsg::InvitationRevokeRequest(id) => self.handle_msg(RevokeInvitation { id }, ctx)?,
|
WsMsg::InvitationRevokeRequest(id) => self.handle_msg(RevokeInvitation { id }, ctx)?,
|
||||||
WsMsg::InvitedUsersRequest => self.handle_msg(LoadInvitedUsers, ctx)?,
|
WsMsg::InvitedUsersRequest => self.handle_msg(LoadInvitedUsers, ctx)?,
|
||||||
|
|
||||||
|
@ -1,7 +1,8 @@
|
|||||||
use futures::executor::block_on;
|
use futures::executor::block_on;
|
||||||
|
|
||||||
use jirs_data::WsMsg;
|
use jirs_data::{UserId, UserProject, WsMsg};
|
||||||
|
|
||||||
|
use crate::db;
|
||||||
use crate::db::users::Register as DbRegister;
|
use crate::db::users::Register as DbRegister;
|
||||||
use crate::ws::auth::Authenticate;
|
use crate::ws::auth::Authenticate;
|
||||||
use crate::ws::{WebSocketActor, WsHandler, WsResult};
|
use crate::ws::{WebSocketActor, WsHandler, WsResult};
|
||||||
@ -101,3 +102,35 @@ impl WsHandler<ProfileUpdate> for WebSocketActor {
|
|||||||
Ok(Some(WsMsg::ProfileUpdated))
|
Ok(Some(WsMsg::ProfileUpdated))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub struct RemoveInvitedUser {
|
||||||
|
pub user_id: UserId,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl WsHandler<RemoveInvitedUser> for WebSocketActor {
|
||||||
|
fn handle_msg(&mut self, msg: RemoveInvitedUser, _ctx: &mut Self::Context) -> WsResult {
|
||||||
|
let RemoveInvitedUser {
|
||||||
|
user_id: invited_id,
|
||||||
|
} = msg;
|
||||||
|
let UserProject {
|
||||||
|
user_id: inviter_id,
|
||||||
|
project_id,
|
||||||
|
..
|
||||||
|
} = self.require_user_project()?.clone();
|
||||||
|
match block_on(self.db.send(db::user_projects::RemoveInvitedUser {
|
||||||
|
invited_id,
|
||||||
|
inviter_id,
|
||||||
|
project_id,
|
||||||
|
})) {
|
||||||
|
Ok(Ok(_users)) => Ok(Some(WsMsg::InvitedUserRemoveSuccess(invited_id))),
|
||||||
|
Ok(Err(e)) => {
|
||||||
|
error!("{:?}", e);
|
||||||
|
return Ok(None);
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
error!("{}", e);
|
||||||
|
return Ok(None);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user