Fix accept invitation, fix link send to user. Change state of form, display invitations and users

This commit is contained in:
Adrian Woźniak 2020-04-22 14:12:12 +02:00
parent 4316f6888d
commit ff0d4bb329
28 changed files with 274 additions and 118 deletions

View File

@ -0,0 +1,30 @@
#users > .usersSection,
#users > .invitationsSection {
padding: 25px 40px 35px;
}
#users > .usersSection > .usersList,
#users > .invitationsSection > .invitationsList {
list-style: none;
}
#users > .usersSection > .usersList > .user,
#users > .invitationsSection > .invitationsList > .invitation {
list-style: none;
display: flex;
justify-content: space-between;
margin-top: 20px;
}
#users > .invitationsSection > .invitationsList > .invitation > * {
width: 25%;
}
#users .invitationActions {
display: flex;
justify-content: space-between;
}
#users .invitationActions > .error {
color: var(--danger);
}

View File

@ -27,3 +27,4 @@
@import "./css/timeTracking.css";
@import "./css/login.css";
@import "./css/register.css";
@import "./css/users.css";

View File

@ -11,25 +11,19 @@ use crate::validations::is_token;
use crate::{FieldId, Msg};
pub fn update(msg: Msg, model: &mut Model, _orders: &mut impl Orders<Msg>) {
match msg {
Msg::ChangePage(Page::Project) => {
model.page_content = PageContent::Invite(InvitePage::default());
if let Msg::ChangePage(Page::Project) = msg {
model.page_content = PageContent::Invite(Box::new(InvitePage::default()));
return;
}
_ => (),
}
let page = match &mut model.page_content {
PageContent::Invite(page) => page,
_ => return,
};
match msg {
Msg::InputChanged(FieldId::Invite(InviteFieldId::Token), text) => {
if let Msg::InputChanged(FieldId::Invite(InviteFieldId::Token), text) = msg {
page.token_touched = true;
page.token = text.clone();
}
_ => (),
page.token = text;
}
}

View File

@ -153,6 +153,7 @@ pub enum Msg {
AuthTokenErased,
SignInRequest,
BindClientRequest,
InviteRequest,
// sign up
SignUpRequest,
@ -360,7 +361,7 @@ fn authorize_or_redirect() {
Err(..) => {
let pathname = seed::document().location().unwrap().pathname().unwrap();
match pathname.as_str() {
"/login" | "/register" => {}
"/login" | "/register" | "/invite" => {}
_ => {
seed::push_route(vec!["login"]);
}

View File

@ -39,9 +39,9 @@ pub fn update(msg: &Msg, model: &mut crate::model::Model, orders: &mut impl Orde
let payload = jirs_data::CreateIssuePayload {
title: modal.title.clone(),
issue_type: modal.issue_type.clone(),
status: modal.status.clone(),
priority: modal.priority.clone(),
issue_type: modal.issue_type,
status: modal.status,
priority: modal.priority,
description: modal.description.clone(),
description_text: modal.description_text.clone(),
estimate: modal.estimate,

View File

@ -40,7 +40,7 @@ pub fn update(msg: &Msg, model: &mut Model, orders: &mut impl Orders<Msg>) {
send_ws_msg(WsMsg::IssueUpdateRequest(
modal.id,
IssueFieldId::Type,
PayloadVariant::IssueType(modal.payload.issue_type.clone()),
PayloadVariant::IssueType(modal.payload.issue_type),
));
}
Msg::StyledSelectChanged(
@ -51,7 +51,7 @@ pub fn update(msg: &Msg, model: &mut Model, orders: &mut impl Orders<Msg>) {
send_ws_msg(WsMsg::IssueUpdateRequest(
modal.id,
IssueFieldId::Status,
PayloadVariant::IssueStatus(modal.payload.status.clone()),
PayloadVariant::IssueStatus(modal.payload.status),
));
}
Msg::StyledSelectChanged(
@ -143,7 +143,7 @@ pub fn update(msg: &Msg, model: &mut Model, orders: &mut impl Orders<Msg>) {
send_ws_msg(WsMsg::IssueUpdateRequest(
modal.id,
IssueFieldId::TimeSpend,
PayloadVariant::OptionI32(modal.payload.time_spent.clone()),
PayloadVariant::OptionI32(modal.payload.time_spent),
));
}
Msg::InputChanged(
@ -154,7 +154,7 @@ pub fn update(msg: &Msg, model: &mut Model, orders: &mut impl Orders<Msg>) {
send_ws_msg(WsMsg::IssueUpdateRequest(
modal.id,
IssueFieldId::TimeRemaining,
PayloadVariant::OptionI32(modal.payload.time_remaining.clone()),
PayloadVariant::OptionI32(modal.payload.time_remaining),
));
}
Msg::ModalChanged(FieldChange::TabChanged(
@ -189,7 +189,7 @@ pub fn update(msg: &Msg, model: &mut Model, orders: &mut impl Orders<Msg>) {
send_ws_msg(WsMsg::IssueUpdateRequest(
modal.id,
IssueFieldId::TimeRemaining,
PayloadVariant::OptionI32(modal.payload.estimate.clone()),
PayloadVariant::OptionI32(modal.payload.estimate),
));
}
_ if value.is_empty() => {
@ -197,7 +197,7 @@ pub fn update(msg: &Msg, model: &mut Model, orders: &mut impl Orders<Msg>) {
send_ws_msg(WsMsg::IssueUpdateRequest(
modal.id,
IssueFieldId::TimeRemaining,
PayloadVariant::OptionI32(modal.payload.estimate.clone()),
PayloadVariant::OptionI32(modal.payload.estimate),
));
}
_ => {}

View File

@ -50,7 +50,7 @@ pub fn update(msg: &Msg, model: &mut model::Model, orders: &mut impl Orders<Msg>
Msg::ChangePage(Page::AddIssue) => {
let mut modal = AddIssueModal::default();
modal.project_id = model.project.as_ref().map(|p| p.id);
model.modals.push(ModalType::AddIssue(modal));
model.modals.push(ModalType::AddIssue(Box::new(modal)));
}
_ => (),
@ -101,7 +101,7 @@ fn push_edit_modal(issue_id: i32, model: &mut Model) {
Some(issue) => issue,
_ => return,
};
ModalType::EditIssue(issue_id, EditIssueModal::new(issue))
ModalType::EditIssue(issue_id, Box::new(EditIssueModal::new(issue)))
};
send_ws_msg(WsMsg::IssueCommentsRequest(issue_id));
model.modals.push(modal);

View File

@ -11,8 +11,8 @@ use crate::{EditIssueModalSection, FieldId, ProjectFieldId, HOST_URL};
#[derive(Clone, Debug, PartialOrd, PartialEq, Hash)]
pub enum ModalType {
AddIssue(AddIssueModal),
EditIssue(IssueId, EditIssueModal),
AddIssue(Box<AddIssueModal>),
EditIssue(IssueId, Box<EditIssueModal>),
DeleteIssueConfirm(IssueId),
DeleteCommentConfirm(CommentId),
TimeTracking(IssueId),
@ -49,9 +49,9 @@ impl EditIssueModal {
link_copied: false,
payload: UpdateIssuePayload {
title: issue.title.clone(),
issue_type: issue.issue_type.clone(),
status: issue.status.clone(),
priority: issue.priority.clone(),
issue_type: issue.issue_type,
status: issue.status,
priority: issue.priority,
list_position: issue.list_position,
description: issue.description.clone(),
description_text: issue.description_text.clone(),
@ -252,6 +252,20 @@ pub struct SignUpPage {
pub email_touched: bool,
}
#[derive(Debug, Clone, Copy, PartialOrd, PartialEq)]
pub enum InvitationFormState {
Initial = 1,
Sent = 2,
Succeed = 3,
Failed = 4,
}
impl Default for InvitationFormState {
fn default() -> Self {
InvitationFormState::Initial
}
}
#[derive(Debug)]
pub struct UsersPage {
pub name: String,
@ -263,6 +277,10 @@ pub struct UsersPage {
pub user_role_state: StyledSelectState,
pub pending_invitations: Vec<String>,
pub error: String,
pub form_state: InvitationFormState,
pub invited_users: Vec<User>,
pub invitations: Vec<Invitation>,
}
impl Default for UsersPage {
@ -276,18 +294,21 @@ impl Default for UsersPage {
user_role_state: StyledSelectState::new(FieldId::Users(UsersFieldId::UserRole)),
pending_invitations: vec![],
error: "".to_string(),
form_state: Default::default(),
invited_users: vec![],
invitations: vec![],
}
}
}
#[derive(Debug)]
pub enum PageContent {
SignIn(SignInPage),
SignUp(SignUpPage),
Project(ProjectPage),
ProjectSettings(ProjectSettingsPage),
Invite(InvitePage),
Users(UsersPage),
SignIn(Box<SignInPage>),
SignUp(Box<SignUpPage>),
Project(Box<ProjectPage>),
ProjectSettings(Box<ProjectSettingsPage>),
Invite(Box<InvitePage>),
Users(Box<UsersPage>),
}
#[derive(Debug)]
@ -332,7 +353,7 @@ impl Default for Model {
comments_by_project_id: Default::default(),
page: Page::Project,
host_url,
page_content: PageContent::Project(ProjectPage::default()),
page_content: PageContent::Project(Box::new(ProjectPage::default())),
modals: vec![],
project: None,
comments: vec![],

View File

@ -22,7 +22,7 @@ pub fn update(msg: Msg, model: &mut crate::model::Model, orders: &mut impl Order
Msg::ChangePage(Page::Project)
| Msg::ChangePage(Page::AddIssue)
| Msg::ChangePage(Page::EditIssue(..)) => {
model.page_content = PageContent::Project(ProjectPage::default());
model.page_content = PageContent::Project(Box::new(ProjectPage::default()));
}
_ => (),
}
@ -283,7 +283,7 @@ fn project_issue_list(model: &Model, status: jirs_data::IssueStatus) -> Node<Msg
.issues
.iter()
.filter(|issue| {
issue_filter_status(issue, &status)
issue_filter_status(issue, status)
&& issue_filter_with_text(issue, project_page.text_filter.as_str())
&& issue_filter_with_only_my(issue, project_page.only_my_filter, &model.user)
&& issue_filter_with_only_recent(issue, ids.as_slice())
@ -292,13 +292,13 @@ fn project_issue_list(model: &Model, status: jirs_data::IssueStatus) -> Node<Msg
.collect();
let label = status.to_label();
let send_status = status.clone();
let send_status = status;
let drop_handler = drag_ev(Ev::Drop, move |ev| {
ev.prevent_default();
Msg::IssueDropZone(send_status)
});
let send_status = status.clone();
let send_status = status;
let drag_over_handler = drag_ev(Ev::DragOver, move |ev| {
ev.prevent_default();
Msg::IssueDragOverStatus(send_status)
@ -321,8 +321,8 @@ fn project_issue_list(model: &Model, status: jirs_data::IssueStatus) -> Node<Msg
}
#[inline]
fn issue_filter_status(issue: &Issue, status: &IssueStatus) -> bool {
&issue.status == status
fn issue_filter_status(issue: &Issue, status: IssueStatus) -> bool {
issue.status == status
}
#[inline]
@ -384,7 +384,7 @@ fn project_issue(model: &Model, issue: &Issue) -> Node<Msg> {
ev.stop_propagation();
Msg::ExchangePosition(issue_id)
});
let issue_id = issue.id.clone();
let issue_id = issue.id;
let drag_out = drag_ev(Ev::DragLeave, move |_| Msg::DragLeave(issue_id));
let class_list = vec!["issue"];

View File

@ -76,7 +76,7 @@ fn build_page_content(model: &mut Model) {
Some(project) => project,
_ => return,
};
model.page_content = PageContent::ProjectSettings(ProjectSettingsPage::new(project));
model.page_content = PageContent::ProjectSettings(Box::new(ProjectSettingsPage::new(project)));
}
pub fn view(model: &model::Model) -> Node<Msg> {

View File

@ -35,6 +35,7 @@ pub struct StyledButtonBuilder {
on_click: Option<EventHandler<Msg>>,
children: Option<Vec<Node<Msg>>>,
class_list: Vec<String>,
button_type: Option<String>,
}
impl StyledButtonBuilder {
@ -107,6 +108,11 @@ impl StyledButtonBuilder {
self
}
pub fn set_type_reset(mut self) -> Self {
self.button_type = Some("reset".to_string());
self
}
pub fn build(self) -> StyledButton {
StyledButton {
variant: self.variant.unwrap_or_else(|| Variant::Primary),
@ -117,6 +123,7 @@ impl StyledButtonBuilder {
on_click: self.on_click,
children: self.children.unwrap_or_default(),
class_list: self.class_list,
button_type: self.button_type.unwrap_or_else(|| "submit".to_string()),
}
}
}
@ -130,6 +137,7 @@ pub struct StyledButton {
on_click: Option<EventHandler<Msg>>,
children: Vec<Node<Msg>>,
class_list: Vec<String>,
button_type: String,
}
impl StyledButton {
@ -154,6 +162,7 @@ pub fn render(values: StyledButton) -> Node<Msg> {
on_click,
children,
mut class_list,
button_type,
} = values;
class_list.push("styledButton".to_string());
class_list.push(variant.to_string());
@ -185,6 +194,7 @@ pub fn render(values: StyledButton) -> Node<Msg> {
seed::button![
attrs![
At::Class => class_list.join(" "),
At::Type => button_type,
],
handler,
if disabled {

View File

@ -183,7 +183,7 @@ pub fn render(values: StyledTextarea) -> Node<Msg> {
let textarea = seed::to_textarea(&target);
let value = textarea.value();
if handler_disable_auto_resize && value.contains("\n") {
if handler_disable_auto_resize && value.contains('\n') {
event.prevent_default();
}

View File

@ -23,7 +23,7 @@ pub fn update(msg: Msg, model: &mut model::Model, orders: &mut impl Orders<Msg>)
}
if msg == Msg::ChangePage(Page::SignIn) {
model.page_content = PageContent::SignIn(SignInPage::default());
model.page_content = PageContent::SignIn(Box::new(SignInPage::default()));
return;
}

View File

@ -20,7 +20,7 @@ pub fn update(msg: Msg, model: &mut model::Model, _orders: &mut impl Orders<Msg>
}
if msg == Msg::ChangePage(Page::SignUp) {
model.page_content = PageContent::SignUp(SignUpPage::default());
model.page_content = PageContent::SignUp(Box::new(SignUpPage::default()));
return;
}

View File

@ -1,9 +1,9 @@
use seed::{prelude::*, *};
use jirs_data::UserRole;
use jirs_data::{ToVec, UsersFieldId};
use jirs_data::{ToVec, UserRole, UsersFieldId, WsMsg};
use crate::model::{Model, Page, PageContent, UsersPage};
use crate::api::send_ws_msg;
use crate::model::{InvitationFormState, Model, Page, PageContent, UsersPage};
use crate::shared::styled_button::StyledButton;
use crate::shared::styled_field::StyledField;
use crate::shared::styled_form::StyledForm;
@ -15,13 +15,10 @@ use crate::validations::is_email;
use crate::{FieldId, Msg};
pub fn update(msg: Msg, model: &mut Model, orders: &mut impl Orders<Msg>) {
match msg {
Msg::ChangePage(Page::Users) => {
model.page_content = PageContent::Users(UsersPage::default());
if let Msg::ChangePage(Page::Users) = msg {
model.page_content = PageContent::Users(Box::new(UsersPage::default()));
return;
}
_ => (),
}
let page = match &mut model.page_content {
PageContent::Users(page) => page,
@ -31,6 +28,16 @@ pub fn update(msg: Msg, model: &mut Model, orders: &mut impl Orders<Msg>) {
page.user_role_state.update(&msg, orders);
match msg {
Msg::WsMsg(WsMsg::AuthorizeLoaded(Ok(_))) | Msg::ChangePage(Page::Users) => {
send_ws_msg(WsMsg::InvitationListRequest);
send_ws_msg(WsMsg::InvitedUsersRequest);
}
Msg::WsMsg(WsMsg::InvitedUsersLoaded(users)) => {
page.invited_users = users;
}
Msg::WsMsg(WsMsg::InvitationListLoaded(invitations)) => {
page.invitations = invitations;
}
Msg::StyledSelectChanged(
FieldId::Users(UsersFieldId::UserRole),
StyledSelectChange::Changed(role),
@ -45,6 +52,20 @@ pub fn update(msg: Msg, model: &mut Model, orders: &mut impl Orders<Msg>) {
page.email = email;
page.email_touched = true;
}
Msg::InviteRequest => {
page.form_state = InvitationFormState::Sent;
send_ws_msg(WsMsg::InvitationSendRequest {
name: page.name.clone(),
email: page.email.clone(),
})
}
Msg::WsMsg(WsMsg::InvitationSendSuccess) => {
send_ws_msg(WsMsg::InvitationListRequest);
page.form_state = InvitationFormState::Succeed;
}
Msg::WsMsg(WsMsg::InvitationSendFailure) => {
page.form_state = InvitationFormState::Failed;
}
_ => (),
}
}
@ -103,12 +124,27 @@ pub fn view(model: &Model) -> Node<Msg> {
let submit = StyledButton::build()
.add_class("submitUserInvite")
.active(true)
.active(page.form_state != InvitationFormState::Sent)
.primary()
.text("Invite user")
.build()
.into_node();
let submit_field = StyledField::build().input(submit).build().into_node();
let submit_supplement = match page.form_state {
InvitationFormState::Succeed => StyledButton::build()
.add_class("resetUserInvite")
.active(true)
.empty()
.set_type_reset()
.text("Reset")
.build()
.into_node(),
InvitationFormState::Failed => div![class!["error"], "There was an error"],
_ => empty![],
};
let submit_field = StyledField::build()
.input(div![class!["invitationActions"], submit, submit_supplement])
.build()
.into_node();
let form = StyledForm::build()
.heading("Invite new user")
@ -116,8 +152,59 @@ pub fn view(model: &Model) -> Node<Msg> {
.add_field(email_field)
.add_field(user_role_field)
.add_field(submit_field)
.on_submit(ev(Ev::Submit, |ev| {
ev.prevent_default();
Msg::InviteRequest
}))
.build()
.into_node();
inner_layout(model, "users", vec![form], empty![])
let users: Vec<Node<Msg>> = page
.invited_users
.iter()
.map(|user| {
let remove = StyledButton::build().text("Remove").build().into_node();
li![
class!["user"],
span![user.name],
span![user.email],
span![format!("{}", user.user_role)],
remove,
]
})
.collect();
let users_section = section![
class!["usersSection"],
h1![class!["heading"], "Users"],
ul![class!["usersList"], users],
];
let invitations: Vec<Node<Msg>> = page
.invitations
.iter()
.map(|invitation| {
let revoke = StyledButton::build().text("Revoke").build().into_node();
li![
class!["invitation"],
span![invitation.name],
span![invitation.email],
span![format!("{}", invitation.state)],
revoke,
]
})
.collect();
let invitations_section = section![
class!["invitationsSection"],
h1![class!["heading"], "Invitations"],
ul![class!["invitationsList"], invitations],
];
inner_layout(
model,
"users",
vec![form, users_section, invitations_section],
empty![],
)
}

View File

@ -18,7 +18,7 @@ pub fn is_email(s: &str) -> bool {
_ => (),
}
}
return false;
false
}
pub fn is_token(s: &str) -> bool {

View File

@ -64,7 +64,7 @@ pub fn exchange_position(issue_bellow_id: IssueId, model: &mut Model) {
model.issues.push(c);
}
dragged.list_position = below.list_position + 1;
dragged.status = below.status.clone();
dragged.status = below.status;
}
std::mem::swap(&mut dragged.list_position, &mut below.list_position);
@ -96,7 +96,7 @@ pub fn sync(model: &mut Model) {
send_ws_msg(WsMsg::IssueUpdateRequest(
issue.id,
IssueFieldId::Status,
PayloadVariant::IssueStatus(issue.status.clone()),
PayloadVariant::IssueStatus(issue.status),
));
send_ws_msg(WsMsg::IssueUpdateRequest(
issue.id,
@ -150,7 +150,6 @@ pub fn change_status(status: IssueStatus, model: &mut Model) {
if issue.status == status {
model.issues.push(issue);
return;
} else {
issue.status = status;
issue.list_position = pos + 1;

View File

@ -29,7 +29,7 @@ pub type UsernameString = String;
#[cfg_attr(feature = "backend", derive(FromSqlRow, AsExpression))]
#[cfg_attr(feature = "backend", sql_type = "IssueTypeType")]
#[derive(Clone, Deserialize, Serialize, Debug, PartialOrd, PartialEq, Hash)]
#[derive(Clone, Copy, Deserialize, Serialize, Debug, PartialOrd, PartialEq, Hash)]
pub enum IssueType {
Task,
Bug,
@ -93,7 +93,7 @@ impl std::fmt::Display for IssueType {
#[cfg_attr(feature = "backend", derive(FromSqlRow, AsExpression))]
#[cfg_attr(feature = "backend", sql_type = "IssueStatusType")]
#[derive(Clone, Deserialize, Serialize, Debug, PartialOrd, PartialEq, Hash)]
#[derive(Clone, Copy, Deserialize, Serialize, Debug, PartialOrd, PartialEq, Hash)]
pub enum IssueStatus {
Backlog,
Selected,

View File

@ -57,7 +57,7 @@ impl Handler<CreateInvitation> for DbExecutor {
let conn = &self
.pool
.get()
.map_err(|_| ServiceErrors::DatabaseConnectionLost)?;
.map_err(|e| ServiceErrors::DatabaseQueryFailed(format!("{}", e)))?;
let form = InvitationForm {
name: msg.name,
@ -70,7 +70,7 @@ impl Handler<CreateInvitation> for DbExecutor {
debug!("{}", diesel::debug_query::<Pg, _>(&query).to_string());
query
.get_result(conn)
.map_err(|_| ServiceErrors::DatabaseConnectionLost)
.map_err(|e| ServiceErrors::DatabaseQueryFailed(format!("{}", e)))
}
}
@ -96,7 +96,7 @@ impl Handler<DeleteInvitation> for DbExecutor {
debug!("{}", diesel::debug_query::<Pg, _>(&query).to_string());
query
.execute(conn)
.map_err(|_| ServiceErrors::DatabaseConnectionLost)?;
.map_err(|e| ServiceErrors::DatabaseQueryFailed(format!("{}", e)))?;
Ok(())
}
}
@ -125,7 +125,7 @@ impl Handler<RevokeInvitation> for DbExecutor {
debug!("{}", diesel::debug_query::<Pg, _>(&query).to_string());
query
.execute(conn)
.map_err(|_| ServiceErrors::DatabaseConnectionLost)?;
.map_err(|e| ServiceErrors::DatabaseQueryFailed(format!("{}", e)))?;
Ok(())
}
}
@ -154,15 +154,22 @@ impl Handler<AcceptInvitation> for DbExecutor {
debug!("{}", diesel::debug_query::<Pg, _>(&query).to_string());
let invitation: Invitation = query
.first(conn)
.map_err(|_| ServiceErrors::DatabaseConnectionLost)?;
.map_err(|e| ServiceErrors::DatabaseQueryFailed(format!("{}", e)))?;
if invitation.state == InvitationState::Revoked {
return Err(ServiceErrors::DatabaseQueryFailed(
"This invitation is no longer valid".to_string(),
));
}
let query = diesel::update(invitations)
.set(state.eq(InvitationState::Accepted))
.filter(id.eq(invitation.id));
.filter(id.eq(invitation.id))
.filter(state.eq(InvitationState::Sent));
debug!("{}", diesel::debug_query::<Pg, _>(&query).to_string());
query
.execute(conn)
.map_err(|_| ServiceErrors::DatabaseConnectionLost)?;
.map_err(|e| ServiceErrors::DatabaseQueryFailed(format!("{}", e)))?;
let form = UserForm {
name: invitation.name,
@ -174,7 +181,7 @@ impl Handler<AcceptInvitation> for DbExecutor {
debug!("{}", diesel::debug_query::<Pg, _>(&query).to_string());
let user: User = query
.get_result(conn)
.map_err(|_| ServiceErrors::DatabaseConnectionLost)?;
.map_err(|e| ServiceErrors::DatabaseQueryFailed(format!("{}", e)))?;
Ok(user)
}

View File

@ -1,6 +1,6 @@
use actix::{Handler, Message};
use lettre;
use lettre_email;
// use lettre;
// use lettre_email;
use uuid::Uuid;
use crate::mail::MailExecutor;
@ -35,7 +35,7 @@ impl Handler<Invite> for MailExecutor {
<p>
</p>
<p>
Please click this link: <a href="{addr}/invite?token={bind_token}"></a>
Please click this link: <a href="{addr}/invite?token={bind_token}">{addr}/invite?token={bind_token}</a>
</p>
</body>
</html>
@ -46,7 +46,7 @@ impl Handler<Invite> for MailExecutor {
);
let email = lettre_email::Email::builder()
.from(from.clone())
.from(from)
.to(msg.email.as_str())
.html(html.as_str())
.subject("Invitation to JIRS project")

View File

@ -1,7 +1,7 @@
use std::fs::*;
use actix::{Actor, SyncContext};
use lettre;
// use lettre;
use serde::{Deserialize, Serialize};
pub mod invite;

View File

@ -1,6 +1,6 @@
use actix::{Handler, Message};
use lettre;
use lettre_email;
// use lettre;
// use lettre_email;
use uuid::Uuid;
use crate::mail::MailExecutor;
@ -45,7 +45,7 @@ impl Handler<Welcome> for MailExecutor {
);
let email = lettre_email::Email::builder()
.from(from.clone())
.from(from)
.to(msg.email.as_str())
.html(html.as_str())
.subject("Welcome to JIRS")

View File

@ -43,7 +43,7 @@ impl WsHandler<Authenticate> for WebSocketActor {
if let Some(bind_token) = token.bind_token.as_ref().cloned() {
match block_on(self.mail.send(Welcome {
bind_token,
email: user.email.clone(),
email: user.email,
})) {
Ok(Ok(_)) => (),
Ok(Err(e)) => {
@ -69,7 +69,7 @@ impl WsHandler<CheckAuthToken> for WebSocketActor {
let user: jirs_data::User = match block_on(self.db.send(AuthorizeUser {
access_token: msg.token,
})) {
Ok(Ok(u)) => u.into(),
Ok(Ok(u)) => u,
Ok(Err(_)) => {
return Ok(Some(WsMsg::AuthorizeLoaded(Err(
"Invalid auth token".to_string()

View File

@ -15,7 +15,7 @@ impl WsHandler<LoadIssueComments> for WebSocketActor {
let comments = match block_on(self.db.send(crate::db::comments::LoadIssueComments {
issue_id: msg.issue_id,
})) {
Ok(Ok(comments)) => comments.into_iter().map(|c| c.into()).collect(),
Ok(Ok(comments)) => comments,
_ => return Ok(None),
};

View File

@ -45,15 +45,29 @@ impl WsHandler<CreateInvitation> for WebSocketActor {
name,
})) {
Ok(Ok(invitation)) => invitation,
_ => return Ok(Some(WsMsg::InvitationSendFailure)),
Ok(Err(e)) => {
error!("{:?}", e);
return Ok(Some(WsMsg::InvitationSendFailure));
}
Err(e) => {
error!("{}", e);
return Ok(Some(WsMsg::InvitationSendFailure));
}
};
match block_on(self.mail.send(crate::mail::invite::Invite {
bind_token: invitation.bind_token.clone(),
email: invitation.email.clone(),
bind_token: invitation.bind_token,
email: invitation.email,
inviter_name,
})) {
Ok(Ok(_)) => (),
_ => return Ok(Some(WsMsg::InvitationSendFailure)),
Ok(Err(e)) => {
error!("{:?}", e);
return Ok(Some(WsMsg::InvitationSendFailure));
}
Err(e) => {
error!("{}", e);
return Ok(Some(WsMsg::InvitationSendFailure));
}
}
Ok(Some(WsMsg::InvitationSendSuccess))

View File

@ -9,9 +9,12 @@ use jirs_data::{ProjectId, UserId, WsMsg};
use crate::db::DbExecutor;
use crate::mail::MailExecutor;
use crate::ws::auth::{Authenticate, CheckAuthToken, CheckBindToken};
use crate::ws::auth::*;
use crate::ws::comments::*;
use crate::ws::invitations::*;
use crate::ws::issues::UpdateIssueHandler;
use crate::ws::issues::*;
use crate::ws::projects::*;
use crate::ws::users::*;
pub mod auth;
pub mod comments;
@ -47,9 +50,8 @@ impl Handler<InnerMsg> for WebSocketActor {
type Result = ();
fn handle(&mut self, msg: InnerMsg, ctx: &mut Self::Context) -> Self::Result {
match msg {
InnerMsg::Transfer(msg) => ctx.send_msg(&msg),
_ => {}
if let InnerMsg::Transfer(msg) = msg {
ctx.send_msg(&msg)
};
}
}
@ -87,11 +89,11 @@ impl WebSocketActor {
ctx,
)?,
WsMsg::IssueCreateRequest(payload) => self.handle_msg(payload, ctx)?,
WsMsg::IssueDeleteRequest(id) => self.handle_msg(issues::DeleteIssue { id }, ctx)?,
WsMsg::ProjectIssuesRequest => self.handle_msg(issues::LoadIssues, ctx)?,
WsMsg::IssueDeleteRequest(id) => self.handle_msg(DeleteIssue { id }, ctx)?,
WsMsg::ProjectIssuesRequest => self.handle_msg(LoadIssues, ctx)?,
// projects
WsMsg::ProjectRequest => self.handle_msg(projects::CurrentProject, ctx)?,
WsMsg::ProjectRequest => self.handle_msg(CurrentProject, ctx)?,
WsMsg::ProjectUpdateRequest(payload) => self.handle_msg(payload, ctx)?,
// auth
@ -107,7 +109,7 @@ impl WebSocketActor {
// register
WsMsg::SignUpRequest(email, username) => self.handle_msg(
users::Register {
Register {
name: username,
email,
},
@ -115,32 +117,26 @@ impl WebSocketActor {
)?,
// users
WsMsg::ProjectUsersRequest => self.handle_msg(users::LoadProjectUsers, ctx)?,
WsMsg::ProjectUsersRequest => self.handle_msg(LoadProjectUsers, ctx)?,
// comments
WsMsg::IssueCommentsRequest(issue_id) => {
self.handle_msg(comments::LoadIssueComments { issue_id }, ctx)?
self.handle_msg(LoadIssueComments { issue_id }, ctx)?
}
WsMsg::CreateComment(payload) => self.handle_msg(payload, ctx)?,
WsMsg::UpdateComment(payload) => self.handle_msg(payload, ctx)?,
WsMsg::CommentDeleteRequest(comment_id) => {
self.handle_msg(comments::DeleteComment { comment_id }, ctx)?
self.handle_msg(DeleteComment { comment_id }, ctx)?
}
// invitations
WsMsg::InvitationSendRequest { name, email } => self.handle_msg(
CreateInvitation {
name: name.clone(),
email: email.clone(),
},
ctx,
)?,
WsMsg::InvitationSendRequest { name, email } => {
self.handle_msg(CreateInvitation { name, email }, ctx)?
}
WsMsg::InvitationListRequest => self.handle_msg(ListInvitation, ctx)?,
WsMsg::InvitationAcceptRequest(id) => self.handle_msg(AcceptInvitation { id }, ctx)?,
WsMsg::InvitationRevokeRequest(id) => self.handle_msg(RevokeInvitation { id }, ctx)?,
WsMsg::InvitedUsersRequest => None,
WsMsg::InvitedUsersRequest => self.handle_msg(LoadInvitedUsers, ctx)?,
// else fail
_ => {
@ -296,9 +292,7 @@ impl Handler<InnerMsg> for WsServer {
impl WsServer {
pub fn ensure_room(&mut self, room: i32) {
if !self.rooms.contains_key(&room) {
self.rooms.insert(room, HashSet::new());
}
self.rooms.entry(room).or_insert_with(HashSet::new);
}
}

View File

@ -12,7 +12,7 @@ impl WsHandler<CurrentProject> for WebSocketActor {
let project_id = self.require_user()?.project_id;
let m = match block_on(self.db.send(LoadCurrentProject { project_id })) {
Ok(Ok(project)) => Some(WsMsg::ProjectLoaded(project.into())),
Ok(Ok(project)) => Some(WsMsg::ProjectLoaded(project)),
Ok(Err(e)) => {
error!("{:?}", e);
None
@ -39,6 +39,6 @@ impl WsHandler<UpdateProjectPayload> for WebSocketActor {
Ok(Ok(project)) => project,
_ => return Ok(None),
};
Ok(Some(WsMsg::ProjectLoaded(project.into())))
Ok(Some(WsMsg::ProjectLoaded(project)))
}
}

View File

@ -14,9 +14,7 @@ impl WsHandler<LoadProjectUsers> for WebSocketActor {
let project_id = self.require_user()?.project_id;
let m = match block_on(self.db.send(Msg { project_id })) {
Ok(Ok(v)) => Some(WsMsg::ProjectUsersLoaded(
v.into_iter().map(|i| i.into()).collect(),
)),
Ok(Ok(v)) => Some(WsMsg::ProjectUsersLoaded(v)),
_ => None,
};
Ok(m)