Destroy invitation message after accepting invitation

This commit is contained in:
Adrian Wozniak 2020-05-30 20:35:00 +02:00
parent d3b2fbce32
commit 9b6bfc76cb
8 changed files with 248 additions and 45 deletions

View File

@ -113,13 +113,13 @@ nav#sidebar .linkItem > a > .linkText {
.styledTooltip.messages > .messagesList > .message {
padding: 15px;
max-height: 90px;
overflow: hidden;
/*max-height: 90px;*/
/*overflow: hidden;*/
}
.styledTooltip.messages > .messagesList > .message:hover {
max-height: 100%;
}
/*.styledTooltip.messages > .messagesList > .message:hover {*/
/* max-height: 100%;*/
/*}*/
.styledTooltip.messages > .messagesList > .message > .top {
display: flex;

View File

@ -213,8 +213,21 @@ pub fn update(msg: &WsMsg, model: &mut Model, orders: &mut impl Orders<Msg>) {
}
}
// messages
WsMsg::Message(received) => {
let mut old = vec![];
std::mem::swap(&mut old, &mut model.messages);
for m in old {
if m.id != received.id {
model.messages.push(m);
} else {
model.messages.push(received.clone());
}
}
model.messages.sort_by(|a, b| a.id.cmp(&b.id));
}
WsMsg::MessagesResponse(v) => {
model.messages = v.clone();
model.messages.sort_by(|a, b| a.id.cmp(&b.id));
}
WsMsg::MessageMarkedSeen(id) => {
let mut old = vec![];
@ -224,6 +237,7 @@ pub fn update(msg: &WsMsg, model: &mut Model, orders: &mut impl Orders<Msg>) {
model.messages.push(m);
}
}
model.messages.sort_by(|a, b| a.id.cmp(&b.id));
}
_ => (),
};

View File

@ -9,7 +9,7 @@ use jirs_data::{
};
use crate::db::tokens::CreateBindToken;
use crate::db::users::{FindUser, Register};
use crate::db::users::{LookupUser, Register};
use crate::db::DbExecutor;
use crate::errors::ServiceErrors;
@ -236,7 +236,7 @@ impl Handler<AcceptInvitation> for DbExecutor {
};
let user: User = self.handle(
FindUser {
LookupUser {
name: invitation.name.clone(),
email: invitation.email.clone(),
},

View File

@ -1,11 +1,13 @@
use actix::Handler;
use diesel::prelude::*;
use jirs_data::{Message, MessageId, UserId};
use jirs_data::{BindToken, Message, MessageId, MessageType, User, UserId};
use crate::db::users::{FindUser, LookupUser};
use crate::db::DbExecutor;
use crate::errors::ServiceErrors;
#[derive(Debug)]
pub struct LoadMessages {
pub user_id: UserId,
}
@ -36,6 +38,7 @@ impl Handler<LoadMessages> for DbExecutor {
}
}
#[derive(Debug)]
pub struct MarkMessageSeen {
pub user_id: UserId,
pub message_id: MessageId,
@ -61,13 +64,119 @@ impl Handler<MarkMessageSeen> for DbExecutor {
.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()))?;
if size > 0 {
Ok(msg.message_id)
} else {
Err(ServiceErrors::DatabaseQueryFailed(format!(
"failed to delete message for {:?}",
msg
)))
}
}
}
#[derive(Debug)]
pub enum CreateMessageReceiver {
Reference(UserId),
Lookup { name: String, email: String },
}
#[derive(Debug)]
pub struct CreateMessage {
pub receiver: CreateMessageReceiver,
pub sender_id: UserId,
pub summary: String,
pub description: String,
pub message_type: MessageType,
pub hyper_link: String,
}
impl actix::Message for CreateMessage {
type Result = Result<Message, ServiceErrors>;
}
impl Handler<CreateMessage> for DbExecutor {
type Result = Result<Message, ServiceErrors>;
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 user: User = match {
match msg.receiver {
CreateMessageReceiver::Lookup { name, email } => {
self.handle(LookupUser { name, email }, ctx)
}
CreateMessageReceiver::Reference(user_id) => self.handle(FindUser { user_id }, ctx),
}
} {
Ok(user) => user,
_ => {
return Err(ServiceErrors::RecordNotFound(
"No matching user found".to_string(),
))
}
};
let query = diesel::insert_into(messages).values((
receiver_id.eq(user.id),
sender_id.eq(msg.sender_id),
summary.eq(msg.summary),
description.eq(msg.description),
message_type.eq(msg.message_type),
hyper_link.eq(msg.hyper_link),
));
debug!(
"{}",
diesel::debug_query::<diesel::pg::Pg, _>(&query).to_string()
);
query
.execute(conn)
.map_err(|_| ServiceErrors::DatabaseQueryFailed("load user messages".to_string()))?;
Ok(msg.message_id)
.get_result(conn)
.map_err(|_| ServiceErrors::DatabaseQueryFailed("create message failed".to_string()))
}
}
#[derive(Debug)]
pub struct LookupMessagesByToken {
pub token: BindToken,
pub user_id: UserId,
}
impl actix::Message for LookupMessagesByToken {
type Result = Result<Vec<Message>, ServiceErrors>;
}
impl Handler<LookupMessagesByToken> for DbExecutor {
type Result = Result<Vec<Message>, ServiceErrors>;
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 query = 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()))
}
}

View File

@ -1,4 +1,5 @@
use actix::{Handler, Message};
use diesel::connection::TransactionManager;
use diesel::pg::Pg;
use diesel::prelude::*;
use serde::{Deserialize, Serialize};
@ -9,12 +10,10 @@ use crate::db::projects::CreateProject;
use crate::db::{DbExecutor, DbPooledConn};
use crate::errors::ServiceErrors;
use crate::schema::users::all_columns;
use diesel::connection::TransactionManager;
#[derive(Serialize, Deserialize, Debug)]
#[derive(Debug)]
pub struct FindUser {
pub name: String,
pub email: String,
pub user_id: UserId,
}
impl Message for FindUser {
@ -27,6 +26,35 @@ impl Handler<FindUser> for DbExecutor {
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)))
}
}
#[derive(Debug)]
pub struct LookupUser {
pub name: String,
pub email: String,
}
impl Message for LookupUser {
type Result = Result<User, ServiceErrors>;
}
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()
@ -43,7 +71,7 @@ impl Handler<FindUser> for DbExecutor {
}
}
#[derive(Serialize, Deserialize)]
#[derive(Debug)]
pub struct LoadProjectUsers {
pub project_id: i32,
}
@ -76,7 +104,7 @@ impl Handler<LoadProjectUsers> for DbExecutor {
}
}
#[derive(Serialize, Deserialize)]
#[derive(Debug)]
pub struct LoadIssueAssignees {
pub issue_id: i32,
}
@ -109,7 +137,7 @@ impl Handler<LoadIssueAssignees> for DbExecutor {
}
}
#[derive(Serialize, Deserialize, Debug)]
#[derive(Debug)]
pub struct Register {
pub name: String,
pub email: String,
@ -309,11 +337,13 @@ impl Handler<ProfileUpdate> for DbExecutor {
#[cfg(test)]
mod tests {
use diesel::connection::TransactionManager;
use jirs_data::{Project, ProjectCategory};
use crate::db::build_pool;
use super::*;
use diesel::connection::TransactionManager;
use jirs_data::{Project, ProjectCategory};
#[test]
fn check_collision() {

View File

@ -5,7 +5,7 @@ use jirs_data::{Token, WsMsg};
use crate::db::authorize_user::AuthorizeUser;
use crate::db::tokens::{CreateBindToken, FindBindToken};
use crate::db::users::FindUser;
use crate::db::users::LookupUser;
use crate::mail::welcome::Welcome;
use crate::ws::{WebSocketActor, WsHandler, WsResult};
@ -18,7 +18,7 @@ impl WsHandler<Authenticate> for WebSocketActor {
fn handle_msg(&mut self, msg: Authenticate, _ctx: &mut Self::Context) -> WsResult {
let Authenticate { name, email } = msg;
// TODO check attempt number, allow only 5 times per day
let user = match block_on(self.db.send(FindUser { name, email })) {
let user = match block_on(self.db.send(LookupUser { name, email })) {
Ok(Ok(user)) => user,
Ok(Err(e)) => {
error!("{:?}", e);

View File

@ -1,9 +1,12 @@
use futures::executor::block_on;
use jirs_data::{EmailString, InvitationId, InvitationToken, UserRole, UsernameString, WsMsg};
use jirs_data::{
EmailString, InvitationId, InvitationToken, MessageType, UserRole, UsernameString, WsMsg,
};
use crate::db::invitations;
use crate::ws::{WebSocketActor, WsHandler, WsResult};
use crate::db::messages::CreateMessageReceiver;
use crate::ws::{InnerMsg, WebSocketActor, WsHandler, WsMessageSender, WsResult};
pub struct ListInvitation;
@ -40,18 +43,14 @@ impl WsHandler<CreateInvitation> for WebSocketActor {
Some(up) => up.project_id,
_ => return Ok(None),
};
let (user_id, inviter_name) =
match self.current_user.as_ref().map(|u| (u.id, u.name.clone())) {
Some(id) => id,
_ => return Ok(None),
};
let (user_id, inviter_name) = self.require_user().map(|u| (u.id, u.name.clone()))?;
let CreateInvitation { email, name, role } = msg;
let invitation = match block_on(self.db.send(invitations::CreateInvitation {
let invitation = match block_on(self.db.send(crate::db::invitations::CreateInvitation {
user_id,
project_id,
email,
name,
email: email.clone(),
name: name.clone(),
role,
})) {
Ok(Ok(invitation)) => invitation,
@ -80,6 +79,24 @@ impl WsHandler<CreateInvitation> for WebSocketActor {
}
}
// If user exists then send message to him
match block_on(self.db.send(crate::db::messages::CreateMessage {
receiver: CreateMessageReceiver::Lookup { name, email },
sender_id: user_id,
summary: "You have been invited to project".to_string(),
description: "You have been invited to project".to_string(),
message_type: MessageType::ReceivedInvitation,
hyper_link: format!("#{}", invitation.bind_token),
})) {
Ok(Ok(message)) => {
self.addr.do_send(InnerMsg::SendToUser(
message.receiver_id,
WsMsg::Message(message),
));
}
_ => {}
}
Ok(Some(WsMsg::InvitationSendSuccess))
}
}
@ -135,21 +152,45 @@ pub struct AcceptInvitation {
}
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 {
let AcceptInvitation { invitation_token } = msg;
let res = match block_on(self.db.send(invitations::AcceptInvitation {
let token = match block_on(self.db.send(invitations::AcceptInvitation {
invitation_token: invitation_token.clone(),
})) {
Ok(Ok(token)) => Some(WsMsg::InvitationAcceptSuccess(token.access_token)),
Ok(Ok(token)) => token,
Ok(Err(e)) => {
error!("{:?}", e);
Some(WsMsg::InvitationAcceptFailure(invitation_token))
return Ok(Some(WsMsg::InvitationAcceptFailure(invitation_token)));
}
Err(e) => {
error!("{}", e);
Some(WsMsg::InvitationAcceptFailure(invitation_token))
return Ok(Some(WsMsg::InvitationAcceptFailure(invitation_token)));
}
};
Ok(res)
for message in block_on(self.db.send(crate::db::messages::LookupMessagesByToken {
token: invitation_token,
user_id: token.user_id,
}))
.unwrap_or(Ok(vec![]))
.unwrap_or_default()
{
match block_on(self.db.send(crate::db::messages::MarkMessageSeen {
user_id: token.user_id,
message_id: message.id,
})) {
Ok(Ok(id)) => {
ctx.send_msg(&WsMsg::MessageMarkedSeen(id));
}
Ok(Err(e)) => {
error!("{:?}", e);
}
Err(e) => {
error!("{}", e);
}
}
}
Ok(Some(WsMsg::InvitationAcceptSuccess(token.access_token)))
}
}

View File

@ -325,6 +325,7 @@ pub enum InnerMsg {
Join(ProjectId, UserId, Recipient<InnerMsg>),
Leave(ProjectId, UserId, Recipient<InnerMsg>),
BroadcastToChannel(ProjectId, WsMsg),
SendToUser(UserId, WsMsg),
Transfer(WsMsg),
}
@ -381,13 +382,17 @@ impl Handler<InnerMsg> for WsServer {
v.remove_item(&recipient);
}
}
InnerMsg::SendToUser(user_id, msg) => {
if let Some(v) = self.sessions.get(&user_id) {
self.send_to_recipients(v, &msg);
}
}
InnerMsg::BroadcastToChannel(project_id, msg) => {
debug!("Begin broadcast to channel {} msg {:?}", project_id, msg);
let set = match self.rooms.get(&project_id) {
Some(s) => s,
_ => return debug!(" channel not found, aborting..."),
};
let _s = set.len();
for r in set.keys() {
let v = match self.sessions.get(r) {
Some(v) => v,
@ -396,12 +401,7 @@ impl Handler<InnerMsg> for WsServer {
continue;
}
};
for recipient in v.iter() {
match recipient.do_send(InnerMsg::Transfer(msg.clone())) {
Ok(_) => debug!("msg sent"),
Err(e) => error!("{}", e),
};
}
self.send_to_recipients(v, &msg);
}
}
_ => (),
@ -413,6 +413,15 @@ impl WsServer {
pub fn ensure_room(&mut self, room: i32) {
self.rooms.entry(room).or_insert_with(HashMap::new);
}
fn send_to_recipients(&self, recipients: &Vec<Recipient<InnerMsg>>, msg: &WsMsg) {
for recipient in recipients.iter() {
match recipient.do_send(InnerMsg::Transfer(msg.clone())) {
Ok(_) => debug!("msg sent"),
Err(e) => error!("{}", e),
};
}
}
}
#[get("/ws/")]