Handle mark seen
This commit is contained in:
parent
e7ade80fe3
commit
d3b2fbce32
@ -121,8 +121,19 @@ nav#sidebar .linkItem > a > .linkText {
|
||||
max-height: 100%;
|
||||
}
|
||||
|
||||
.styledTooltip.messages > .messagesList > .message > .summary {
|
||||
.styledTooltip.messages > .messagesList > .message > .top {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.styledTooltip.messages > .messagesList > .message > .top > .summary {
|
||||
font-family: var(--font-bold);
|
||||
font-size: 20px;
|
||||
line-height: 32px;
|
||||
}
|
||||
|
||||
.styledTooltip.messages > .messagesList > .message > .top > .action {
|
||||
width: 32px;
|
||||
}
|
||||
|
||||
.styledTooltip.messages > .messagesList > .message > .description {
|
||||
|
@ -42,6 +42,9 @@ pub fn update(msg: &Msg, model: &mut Model, orders: &mut impl Orders<Msg>) {
|
||||
orders,
|
||||
);
|
||||
}
|
||||
Msg::MessageSeen(id) => {
|
||||
send_ws_msg(WsMsg::MessageMarkSeen(*id), model.ws.as_ref(), orders);
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
@ -154,7 +157,7 @@ fn messages_tooltip_popup(model: &Model) -> Node<Msg> {
|
||||
|
||||
fn message_ui(model: &Model, message: &Message) -> Option<Node<Msg>> {
|
||||
let Message {
|
||||
id: _,
|
||||
id,
|
||||
receiver_id: _,
|
||||
sender_id: _,
|
||||
summary,
|
||||
@ -164,6 +167,7 @@ fn message_ui(model: &Model, message: &Message) -> Option<Node<Msg>> {
|
||||
created_at: _,
|
||||
updated_at: _,
|
||||
} = message;
|
||||
let message_id = *id;
|
||||
|
||||
let hyperlink = if hyper_link.is_empty() && !hyper_link.starts_with("#") {
|
||||
empty![]
|
||||
@ -181,6 +185,21 @@ fn message_ui(model: &Model, message: &Message) -> Option<Node<Msg>> {
|
||||
};
|
||||
|
||||
let message_description = parse_description(model, description.as_str());
|
||||
let close_button = StyledButton::build()
|
||||
.icon(Icon::Close)
|
||||
.empty()
|
||||
.on_click(mouse_ev(Ev::Click, move |ev| {
|
||||
ev.stop_propagation();
|
||||
ev.prevent_default();
|
||||
Some(Msg::MessageSeen(message_id))
|
||||
}))
|
||||
.build()
|
||||
.into_node();
|
||||
let top = div![
|
||||
class!["top"],
|
||||
div![class!["summary"], summary],
|
||||
div![class!["action"], close_button],
|
||||
];
|
||||
|
||||
let node = match message_type {
|
||||
MessageType::ReceivedInvitation => {
|
||||
@ -215,20 +234,20 @@ fn message_ui(model: &Model, message: &Message) -> Option<Node<Msg>> {
|
||||
div![
|
||||
class!["message"],
|
||||
attrs![At::Class => format!("{}", message_type)],
|
||||
div![class!["summary"], summary],
|
||||
top,
|
||||
div![class!["description"], message_description],
|
||||
div![class!["actions"], accept, reject],
|
||||
]
|
||||
}
|
||||
MessageType::AssignedToIssue => div![
|
||||
class!["message assignedToIssue"],
|
||||
div![class!["summary"], summary],
|
||||
top,
|
||||
div![class!["description"], message_description],
|
||||
hyperlink,
|
||||
],
|
||||
MessageType::Mention => div![
|
||||
class!["message mention"],
|
||||
div![class!["summary"], summary],
|
||||
top,
|
||||
div![class!["description"], message_description],
|
||||
hyperlink,
|
||||
],
|
||||
|
@ -216,6 +216,15 @@ pub fn update(msg: &WsMsg, model: &mut Model, orders: &mut impl Orders<Msg>) {
|
||||
WsMsg::MessagesResponse(v) => {
|
||||
model.messages = v.clone();
|
||||
}
|
||||
WsMsg::MessageMarkedSeen(id) => {
|
||||
let mut old = vec![];
|
||||
std::mem::swap(&mut old, &mut model.messages);
|
||||
for m in old {
|
||||
if m.id != *id {
|
||||
model.messages.push(m);
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => (),
|
||||
};
|
||||
orders.render();
|
||||
|
@ -1,4 +1,5 @@
|
||||
use actix::{Handler, Message};
|
||||
use diesel::connection::TransactionManager;
|
||||
use diesel::pg::Pg;
|
||||
use diesel::prelude::*;
|
||||
|
||||
@ -108,6 +109,40 @@ impl Handler<DeleteInvitation> for DbExecutor {
|
||||
}
|
||||
}
|
||||
|
||||
struct UpdateInvitationState {
|
||||
pub id: InvitationId,
|
||||
pub state: InvitationState,
|
||||
}
|
||||
|
||||
impl Message for UpdateInvitationState {
|
||||
type Result = Result<(), ServiceErrors>;
|
||||
}
|
||||
|
||||
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)))?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub struct RevokeInvitation {
|
||||
pub id: InvitationId,
|
||||
}
|
||||
@ -119,24 +154,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 {
|
||||
use crate::schema::invitations::dsl::*;
|
||||
|
||||
let conn = &self
|
||||
.pool
|
||||
.get()
|
||||
.map_err(|_| ServiceErrors::DatabaseConnectionLost)?;
|
||||
let query = diesel::update(invitations)
|
||||
.set((
|
||||
state.eq(InvitationState::Revoked),
|
||||
updated_at.eq(chrono::Utc::now().naive_utc()),
|
||||
))
|
||||
.filter(id.eq(msg.id));
|
||||
debug!("{}", diesel::debug_query::<Pg, _>(&query).to_string());
|
||||
query
|
||||
.execute(conn)
|
||||
.map_err(|e| ServiceErrors::DatabaseQueryFailed(format!("{}", e)))?;
|
||||
Ok(())
|
||||
fn handle(&mut self, msg: RevokeInvitation, ctx: &mut Self::Context) -> Self::Result {
|
||||
self.handle(
|
||||
UpdateInvitationState {
|
||||
id: msg.id,
|
||||
state: InvitationState::Revoked,
|
||||
},
|
||||
ctx,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@ -159,13 +184,24 @@ impl Handler<AcceptInvitation> for DbExecutor {
|
||||
.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| ServiceErrors::DatabaseQueryFailed(format!("{}", e)))?;
|
||||
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(),
|
||||
));
|
||||
@ -180,6 +216,9 @@ impl Handler<AcceptInvitation> for DbExecutor {
|
||||
.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))
|
||||
})?;
|
||||
|
||||
@ -214,9 +253,12 @@ impl Handler<AcceptInvitation> for DbExecutor {
|
||||
role.eq(invitation.role),
|
||||
));
|
||||
debug!("{}", diesel::debug_query::<Pg, _>(&query));
|
||||
query
|
||||
.execute(conn)
|
||||
.map_err(|e| ServiceErrors::DatabaseQueryFailed(format!("{}", e)))?;
|
||||
query.execute(conn).map_err(|e| {
|
||||
if tm.rollback_transaction(conn).is_err() {
|
||||
return ServiceErrors::DatabaseConnectionLost;
|
||||
}
|
||||
ServiceErrors::DatabaseQueryFailed(format!("{}", e))
|
||||
})?;
|
||||
};
|
||||
|
||||
let token = {
|
||||
@ -225,10 +267,16 @@ impl Handler<AcceptInvitation> for DbExecutor {
|
||||
let query = tokens.filter(user_id.eq(user.id)).order_by(id.desc());
|
||||
debug!("{}", diesel::debug_query::<Pg, _>(&query));
|
||||
query.first(conn).map_err(|e| {
|
||||
if tm.rollback_transaction(conn).is_err() {
|
||||
return ServiceErrors::DatabaseConnectionLost;
|
||||
}
|
||||
ServiceErrors::DatabaseQueryFailed(format!("token for user {} {}", user.id, e))
|
||||
})?
|
||||
};
|
||||
|
||||
tm.commit_transaction(conn)
|
||||
.map_err(|_| ServiceErrors::DatabaseConnectionLost)?;
|
||||
|
||||
Ok(token)
|
||||
}
|
||||
}
|
||||
|
@ -1,8 +1,10 @@
|
||||
use crate::db::DbExecutor;
|
||||
use crate::errors::ServiceErrors;
|
||||
use actix::Handler;
|
||||
use diesel::prelude::*;
|
||||
use jirs_data::{Message, UserId};
|
||||
|
||||
use jirs_data::{Message, MessageId, UserId};
|
||||
|
||||
use crate::db::DbExecutor;
|
||||
use crate::errors::ServiceErrors;
|
||||
|
||||
pub struct LoadMessages {
|
||||
pub user_id: UserId,
|
||||
@ -33,3 +35,39 @@ impl Handler<LoadMessages> for DbExecutor {
|
||||
.map_err(|_| ServiceErrors::DatabaseQueryFailed("load user messages".to_string()))
|
||||
}
|
||||
}
|
||||
|
||||
pub struct MarkMessageSeen {
|
||||
pub user_id: UserId,
|
||||
pub message_id: MessageId,
|
||||
}
|
||||
|
||||
impl actix::Message for MarkMessageSeen {
|
||||
type Result = Result<MessageId, ServiceErrors>;
|
||||
}
|
||||
|
||||
impl Handler<MarkMessageSeen> for DbExecutor {
|
||||
type Result = Result<MessageId, ServiceErrors>;
|
||||
|
||||
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 query = diesel::delete(
|
||||
messages
|
||||
.find(msg.message_id)
|
||||
.filter(receiver_id.eq(msg.user_id)),
|
||||
);
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
@ -1,8 +1,9 @@
|
||||
use crate::ws::{WebSocketActor, WsHandler, WsResult};
|
||||
use futures::executor::block_on;
|
||||
|
||||
use jirs_data::{MessageId, WsMsg};
|
||||
|
||||
use crate::db::messages;
|
||||
use jirs_data::WsMsg;
|
||||
use crate::ws::{WebSocketActor, WsHandler, WsResult};
|
||||
|
||||
pub struct LoadMessages;
|
||||
|
||||
@ -22,3 +23,27 @@ impl WsHandler<LoadMessages> for WebSocketActor {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct MarkMessageSeen {
|
||||
pub id: MessageId,
|
||||
}
|
||||
|
||||
impl WsHandler<MarkMessageSeen> for WebSocketActor {
|
||||
fn handle_msg(&mut self, msg: MarkMessageSeen, _ctx: &mut Self::Context) -> WsResult {
|
||||
let user_id = self.require_user()?.id;
|
||||
match block_on(self.db.send(messages::MarkMessageSeen {
|
||||
message_id: msg.id,
|
||||
user_id,
|
||||
})) {
|
||||
Ok(Ok(id)) => Ok(Some(WsMsg::MessageMarkedSeen(id))),
|
||||
Ok(Err(e)) => {
|
||||
error!("{:?}", e);
|
||||
return Ok(None);
|
||||
}
|
||||
Err(e) => {
|
||||
error!("{}", e);
|
||||
return Ok(None);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -189,6 +189,7 @@ impl WebSocketActor {
|
||||
|
||||
// messages
|
||||
WsMsg::MessagesRequest => self.handle_msg(LoadMessages, ctx)?,
|
||||
WsMsg::MessageMarkSeen(id) => self.handle_msg(MarkMessageSeen { id }, ctx)?,
|
||||
|
||||
// else fail
|
||||
_ => {
|
||||
@ -241,13 +242,6 @@ impl WebSocketActor {
|
||||
})
|
||||
}
|
||||
|
||||
// fn require_project(&self) -> Result<&Project, WsMsg> {
|
||||
// self.current_project
|
||||
// .as_ref()
|
||||
// .map(|u| u)
|
||||
// .ok_or_else(|| WsMsg::AuthorizeExpired)
|
||||
// }
|
||||
|
||||
fn load_user_project(&self) -> Result<UserProject, WsMsg> {
|
||||
let user_id = self.require_user()?.id;
|
||||
match block_on(self.db.send(CurrentUserProject { user_id })) {
|
||||
|
Loading…
Reference in New Issue
Block a user