Handle mark seen
This commit is contained in:
parent
e7ade80fe3
commit
d3b2fbce32
@ -121,8 +121,19 @@ nav#sidebar .linkItem > a > .linkText {
|
|||||||
max-height: 100%;
|
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-family: var(--font-bold);
|
||||||
|
font-size: 20px;
|
||||||
|
line-height: 32px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.styledTooltip.messages > .messagesList > .message > .top > .action {
|
||||||
|
width: 32px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.styledTooltip.messages > .messagesList > .message > .description {
|
.styledTooltip.messages > .messagesList > .message > .description {
|
||||||
|
@ -42,6 +42,9 @@ pub fn update(msg: &Msg, model: &mut Model, orders: &mut impl Orders<Msg>) {
|
|||||||
orders,
|
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>> {
|
fn message_ui(model: &Model, message: &Message) -> Option<Node<Msg>> {
|
||||||
let Message {
|
let Message {
|
||||||
id: _,
|
id,
|
||||||
receiver_id: _,
|
receiver_id: _,
|
||||||
sender_id: _,
|
sender_id: _,
|
||||||
summary,
|
summary,
|
||||||
@ -164,6 +167,7 @@ fn message_ui(model: &Model, message: &Message) -> Option<Node<Msg>> {
|
|||||||
created_at: _,
|
created_at: _,
|
||||||
updated_at: _,
|
updated_at: _,
|
||||||
} = message;
|
} = message;
|
||||||
|
let message_id = *id;
|
||||||
|
|
||||||
let hyperlink = if hyper_link.is_empty() && !hyper_link.starts_with("#") {
|
let hyperlink = if hyper_link.is_empty() && !hyper_link.starts_with("#") {
|
||||||
empty![]
|
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 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 {
|
let node = match message_type {
|
||||||
MessageType::ReceivedInvitation => {
|
MessageType::ReceivedInvitation => {
|
||||||
@ -215,20 +234,20 @@ fn message_ui(model: &Model, message: &Message) -> Option<Node<Msg>> {
|
|||||||
div![
|
div![
|
||||||
class!["message"],
|
class!["message"],
|
||||||
attrs![At::Class => format!("{}", message_type)],
|
attrs![At::Class => format!("{}", message_type)],
|
||||||
div![class!["summary"], summary],
|
top,
|
||||||
div![class!["description"], message_description],
|
div![class!["description"], message_description],
|
||||||
div![class!["actions"], accept, reject],
|
div![class!["actions"], accept, reject],
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
MessageType::AssignedToIssue => div![
|
MessageType::AssignedToIssue => div![
|
||||||
class!["message assignedToIssue"],
|
class!["message assignedToIssue"],
|
||||||
div![class!["summary"], summary],
|
top,
|
||||||
div![class!["description"], message_description],
|
div![class!["description"], message_description],
|
||||||
hyperlink,
|
hyperlink,
|
||||||
],
|
],
|
||||||
MessageType::Mention => div![
|
MessageType::Mention => div![
|
||||||
class!["message mention"],
|
class!["message mention"],
|
||||||
div![class!["summary"], summary],
|
top,
|
||||||
div![class!["description"], message_description],
|
div![class!["description"], message_description],
|
||||||
hyperlink,
|
hyperlink,
|
||||||
],
|
],
|
||||||
|
@ -216,6 +216,15 @@ pub fn update(msg: &WsMsg, model: &mut Model, orders: &mut impl Orders<Msg>) {
|
|||||||
WsMsg::MessagesResponse(v) => {
|
WsMsg::MessagesResponse(v) => {
|
||||||
model.messages = v.clone();
|
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();
|
orders.render();
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
use actix::{Handler, Message};
|
use actix::{Handler, Message};
|
||||||
|
use diesel::connection::TransactionManager;
|
||||||
use diesel::pg::Pg;
|
use diesel::pg::Pg;
|
||||||
use diesel::prelude::*;
|
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 struct RevokeInvitation {
|
||||||
pub id: InvitationId,
|
pub id: InvitationId,
|
||||||
}
|
}
|
||||||
@ -119,24 +154,14 @@ impl Message for RevokeInvitation {
|
|||||||
impl Handler<RevokeInvitation> for DbExecutor {
|
impl Handler<RevokeInvitation> for DbExecutor {
|
||||||
type Result = Result<(), ServiceErrors>;
|
type Result = Result<(), ServiceErrors>;
|
||||||
|
|
||||||
fn handle(&mut self, msg: RevokeInvitation, _ctx: &mut Self::Context) -> Self::Result {
|
fn handle(&mut self, msg: RevokeInvitation, ctx: &mut Self::Context) -> Self::Result {
|
||||||
use crate::schema::invitations::dsl::*;
|
self.handle(
|
||||||
|
UpdateInvitationState {
|
||||||
let conn = &self
|
id: msg.id,
|
||||||
.pool
|
state: InvitationState::Revoked,
|
||||||
.get()
|
},
|
||||||
.map_err(|_| ServiceErrors::DatabaseConnectionLost)?;
|
ctx,
|
||||||
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(())
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -159,13 +184,24 @@ impl Handler<AcceptInvitation> for DbExecutor {
|
|||||||
.get()
|
.get()
|
||||||
.map_err(|_| ServiceErrors::DatabaseConnectionLost)?;
|
.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));
|
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).map_err(|e| {
|
||||||
.first(conn)
|
if tm.rollback_transaction(conn).is_err() {
|
||||||
.map_err(|e| ServiceErrors::DatabaseQueryFailed(format!("{}", e)))?;
|
return ServiceErrors::DatabaseConnectionLost;
|
||||||
|
}
|
||||||
|
ServiceErrors::DatabaseQueryFailed(format!("{}", e))
|
||||||
|
})?;
|
||||||
|
|
||||||
if invitation.state == InvitationState::Revoked {
|
if invitation.state == InvitationState::Revoked {
|
||||||
|
if tm.rollback_transaction(conn).is_err() {
|
||||||
|
return Err(ServiceErrors::DatabaseConnectionLost);
|
||||||
|
}
|
||||||
return Err(ServiceErrors::DatabaseQueryFailed(
|
return Err(ServiceErrors::DatabaseQueryFailed(
|
||||||
"This invitation is no longer valid".to_string(),
|
"This invitation is no longer valid".to_string(),
|
||||||
));
|
));
|
||||||
@ -180,6 +216,9 @@ impl Handler<AcceptInvitation> for DbExecutor {
|
|||||||
.filter(state.eq(InvitationState::Sent));
|
.filter(state.eq(InvitationState::Sent));
|
||||||
debug!("{}", diesel::debug_query::<Pg, _>(&query).to_string());
|
debug!("{}", diesel::debug_query::<Pg, _>(&query).to_string());
|
||||||
query.execute(conn).map_err(|e| {
|
query.execute(conn).map_err(|e| {
|
||||||
|
if tm.rollback_transaction(conn).is_err() {
|
||||||
|
return ServiceErrors::DatabaseConnectionLost;
|
||||||
|
}
|
||||||
ServiceErrors::DatabaseQueryFailed(format!("update invitation {} {}", invitation.id, e))
|
ServiceErrors::DatabaseQueryFailed(format!("update invitation {} {}", invitation.id, e))
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
@ -214,9 +253,12 @@ impl Handler<AcceptInvitation> for DbExecutor {
|
|||||||
role.eq(invitation.role),
|
role.eq(invitation.role),
|
||||||
));
|
));
|
||||||
debug!("{}", diesel::debug_query::<Pg, _>(&query));
|
debug!("{}", diesel::debug_query::<Pg, _>(&query));
|
||||||
query
|
query.execute(conn).map_err(|e| {
|
||||||
.execute(conn)
|
if tm.rollback_transaction(conn).is_err() {
|
||||||
.map_err(|e| ServiceErrors::DatabaseQueryFailed(format!("{}", e)))?;
|
return ServiceErrors::DatabaseConnectionLost;
|
||||||
|
}
|
||||||
|
ServiceErrors::DatabaseQueryFailed(format!("{}", e))
|
||||||
|
})?;
|
||||||
};
|
};
|
||||||
|
|
||||||
let token = {
|
let token = {
|
||||||
@ -225,10 +267,16 @@ impl Handler<AcceptInvitation> for DbExecutor {
|
|||||||
let query = tokens.filter(user_id.eq(user.id)).order_by(id.desc());
|
let query = tokens.filter(user_id.eq(user.id)).order_by(id.desc());
|
||||||
debug!("{}", diesel::debug_query::<Pg, _>(&query));
|
debug!("{}", diesel::debug_query::<Pg, _>(&query));
|
||||||
query.first(conn).map_err(|e| {
|
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))
|
ServiceErrors::DatabaseQueryFailed(format!("token for user {} {}", user.id, e))
|
||||||
})?
|
})?
|
||||||
};
|
};
|
||||||
|
|
||||||
|
tm.commit_transaction(conn)
|
||||||
|
.map_err(|_| ServiceErrors::DatabaseConnectionLost)?;
|
||||||
|
|
||||||
Ok(token)
|
Ok(token)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,8 +1,10 @@
|
|||||||
use crate::db::DbExecutor;
|
|
||||||
use crate::errors::ServiceErrors;
|
|
||||||
use actix::Handler;
|
use actix::Handler;
|
||||||
use diesel::prelude::*;
|
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 struct LoadMessages {
|
||||||
pub user_id: UserId,
|
pub user_id: UserId,
|
||||||
@ -33,3 +35,39 @@ impl Handler<LoadMessages> for DbExecutor {
|
|||||||
.map_err(|_| ServiceErrors::DatabaseQueryFailed("load user messages".to_string()))
|
.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 futures::executor::block_on;
|
||||||
|
|
||||||
|
use jirs_data::{MessageId, WsMsg};
|
||||||
|
|
||||||
use crate::db::messages;
|
use crate::db::messages;
|
||||||
use jirs_data::WsMsg;
|
use crate::ws::{WebSocketActor, WsHandler, WsResult};
|
||||||
|
|
||||||
pub struct LoadMessages;
|
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
|
// messages
|
||||||
WsMsg::MessagesRequest => self.handle_msg(LoadMessages, ctx)?,
|
WsMsg::MessagesRequest => self.handle_msg(LoadMessages, ctx)?,
|
||||||
|
WsMsg::MessageMarkSeen(id) => self.handle_msg(MarkMessageSeen { id }, ctx)?,
|
||||||
|
|
||||||
// else fail
|
// 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> {
|
fn load_user_project(&self) -> Result<UserProject, WsMsg> {
|
||||||
let user_id = self.require_user()?.id;
|
let user_id = self.require_user()?.id;
|
||||||
match block_on(self.db.send(CurrentUserProject { user_id })) {
|
match block_on(self.db.send(CurrentUserProject { user_id })) {
|
||||||
|
Loading…
Reference in New Issue
Block a user