feat: ChatRequestResponse event

Signed-off-by: Awiteb <a@4rs.nl>
This commit is contained in:
Awiteb 2024-07-14 15:04:33 +03:00
parent 2bfff6c05d
commit 24e038f482
Signed by: awiteb
GPG key ID: 3F6B55640AA6682F
7 changed files with 133 additions and 7 deletions

View file

@ -16,11 +16,12 @@
//! Database extension to work with the balcklist table //! Database extension to work with the balcklist table
use chrono::Utc;
use oxidetalis_core::types::PublicKey; use oxidetalis_core::types::PublicKey;
use oxidetalis_entities::prelude::*; use oxidetalis_entities::prelude::*;
use sea_orm::DatabaseConnection; use sea_orm::DatabaseConnection;
use crate::errors::ServerResult; use crate::{errors::ServerResult, websocket::errors::WsError};
/// Extension trait for the `DatabaseConnection` to work with the blacklist /// Extension trait for the `DatabaseConnection` to work with the blacklist
/// table /// table
@ -32,6 +33,12 @@ pub trait BlackListExt {
blacklister: &UserModel, blacklister: &UserModel,
target_public_key: &PublicKey, target_public_key: &PublicKey,
) -> ServerResult<bool>; ) -> ServerResult<bool>;
/// Add the `target_public_key` to the blacklist of the `blacklister`
async fn add_to_blacklist(
&self,
blacklister: &UserModel,
target_public_key: &PublicKey,
) -> ServerResult<()>;
} }
impl BlackListExt for DatabaseConnection { impl BlackListExt for DatabaseConnection {
@ -49,4 +56,26 @@ impl BlackListExt for DatabaseConnection {
.map(|u| u.is_some()) .map(|u| u.is_some())
.map_err(Into::into) .map_err(Into::into)
} }
async fn add_to_blacklist(
&self,
blacklister: &UserModel,
target_public_key: &PublicKey,
) -> ServerResult<()> {
if self.is_blacklisted(blacklister, target_public_key).await? {
return Err(WsError::AlreadyOnTheblacklist.into());
}
if blacklister.public_key == target_public_key.to_string() {
return Err(WsError::CannotAddSelfToBlacklist.into());
}
BlacklistActiveModel {
user_id: Set(blacklister.id),
target: Set(target_public_key.to_string()),
blacklisted_at: Set(Utc::now()),
..Default::default()
}
.save(self)
.await?;
Ok(())
}
} }

View file

@ -30,7 +30,7 @@ pub trait OutChatRequestsExt {
&self, &self,
requester: &UserModel, requester: &UserModel,
recipient: &PublicKey, recipient: &PublicKey,
) -> ServerResult<bool>; ) -> ServerResult<Option<OutChatRequestsModel>>;
/// Save the chat request in the requester table /// Save the chat request in the requester table
async fn save_out_chat_request( async fn save_out_chat_request(
@ -38,6 +38,13 @@ pub trait OutChatRequestsExt {
requester: &UserModel, requester: &UserModel,
recipient: &PublicKey, recipient: &PublicKey,
) -> ServerResult<()>; ) -> ServerResult<()>;
/// Remove the chat request from requester table
async fn remove_out_chat_request(
&self,
requester: &UserModel,
recipient: &PublicKey,
) -> ServerResult<()>;
} }
impl OutChatRequestsExt for DatabaseConnection { impl OutChatRequestsExt for DatabaseConnection {
@ -46,13 +53,12 @@ impl OutChatRequestsExt for DatabaseConnection {
&self, &self,
requester: &UserModel, requester: &UserModel,
recipient: &PublicKey, recipient: &PublicKey,
) -> ServerResult<bool> { ) -> ServerResult<Option<OutChatRequestsModel>> {
requester requester
.find_related(OutChatRequestsEntity) .find_related(OutChatRequestsEntity)
.filter(OutChatRequestsColumn::Recipient.eq(recipient.to_string())) .filter(OutChatRequestsColumn::Recipient.eq(recipient.to_string()))
.one(self) .one(self)
.await .await
.map(|user| user.is_some())
.map_err(Into::into) .map_err(Into::into)
} }
@ -62,7 +68,11 @@ impl OutChatRequestsExt for DatabaseConnection {
requester: &UserModel, requester: &UserModel,
recipient: &PublicKey, recipient: &PublicKey,
) -> ServerResult<()> { ) -> ServerResult<()> {
if self.have_chat_request_to(requester, recipient).await? { if self
.have_chat_request_to(requester, recipient)
.await?
.is_some()
{
return Err(WsError::AlreadySendChatRequest.into()); return Err(WsError::AlreadySendChatRequest.into());
} }
OutChatRequestsActiveModel { OutChatRequestsActiveModel {
@ -75,4 +85,15 @@ impl OutChatRequestsExt for DatabaseConnection {
.await?; .await?;
Ok(()) Ok(())
} }
async fn remove_out_chat_request(
&self,
requester: &UserModel,
recipient: &PublicKey,
) -> ServerResult<()> {
if let Some(out_model) = self.have_chat_request_to(requester, recipient).await? {
out_model.delete(self).await?;
}
Ok(())
}
} }

View file

@ -30,7 +30,11 @@ ws_errors! {
RegistredUserEvent = "The event is only for registred users", RegistredUserEvent = "The event is only for registred users",
UserNotFound = "The user is not registered in the server", UserNotFound = "The user is not registered in the server",
AlreadyOnTheWhitelist = "The user is already on your whitelist", AlreadyOnTheWhitelist = "The user is already on your whitelist",
CannotAddSelfToWhitelist = "You cannot add yourself to the whitelist",
AlreadyOnTheblacklist = "The user is already on your blacklist",
CannotAddSelfToBlacklist = "You cannot add yourself to the blacklist",
AlreadySendChatRequest = "You have already sent a chat request to this user", AlreadySendChatRequest = "You have already sent a chat request to this user",
CannotSendChatRequestToSelf = "You cannot send a chat request to yourself", CannotSendChatRequestToSelf = "You cannot send a chat request to yourself",
CannotAddSelfToWhitelist = "You cannot add yourself to the whitelist", CannotRespondToOwnChatRequest = "You cannot respond to your own chat request",
NoChatRequestFromRecipient = "You do not have a chat request from the recipient",
} }

View file

@ -29,6 +29,9 @@ pub struct ClientEvent {
signature: Signature, signature: Signature,
} }
// ## Important for contuributors
// Please make sure to order the event data alphabetically.
/// Client websocket event type /// Client websocket event type
#[derive(Serialize, Deserialize, Clone, Eq, PartialEq, Debug)] #[derive(Serialize, Deserialize, Clone, Eq, PartialEq, Debug)]
#[serde(rename_all = "PascalCase", tag = "event", content = "data")] #[serde(rename_all = "PascalCase", tag = "event", content = "data")]
@ -39,6 +42,8 @@ pub enum ClientEventType {
Pong { timestamp: u64 }, Pong { timestamp: u64 },
/// Request to chat with a user /// Request to chat with a user
ChatRequest { to: PublicKey }, ChatRequest { to: PublicKey },
/// Response to a chat request
ChatRequestResponse { accepted: bool, to: PublicKey },
} }
impl ClientEventType { impl ClientEventType {

View file

@ -55,6 +55,8 @@ pub enum ServerEventType {
Message { msg: String }, Message { msg: String },
/// New chat request from someone /// New chat request from someone
ChatRequest { from: PublicKey }, ChatRequest { from: PublicKey },
/// New chat request response from someone
ChatRequestResponse { from: PublicKey, accepted: bool },
/// Error event /// Error event
Error { Error {
name: &'static str, name: &'static str,
@ -105,6 +107,11 @@ impl ServerEvent<Unsigned> {
Self::new(ServerEventType::ChatRequest { from: *from }) Self::new(ServerEventType::ChatRequest { from: *from })
} }
/// Create chat request response event
pub fn chat_request_response(from: PublicKey, accepted: bool) -> Self {
Self::new(ServerEventType::ChatRequestResponse { from, accepted })
}
/// Sign the event /// Sign the event
pub fn sign(self, shared_secret: &[u8; 32]) -> ServerEvent<Signed> { pub fn sign(self, shared_secret: &[u8; 32]) -> ServerEvent<Signed> {
ServerEvent::<Signed> { ServerEvent::<Signed> {

View file

@ -53,7 +53,7 @@ pub async fn handle_chat_request(
"You are unable to send a chat request because you are on the recipient's blacklist.", "You are unable to send a chat request because you are on the recipient's blacklist.",
); );
} }
if try_ws!(db.have_chat_request_to(from_user, to_public_key).await) { if try_ws!(db.have_chat_request_to(from_user, to_public_key).await).is_some() {
return ServerEvent::message("You have already sent a chat request to this user."); return ServerEvent::message("You have already sent a chat request to this user.");
} }
@ -75,3 +75,60 @@ pub async fn handle_chat_request(
} }
ServerEvent::message("Chat request sent successfully.") ServerEvent::message("Chat request sent successfully.")
} }
pub async fn handle_chat_response(
db: &DatabaseConnection,
recipient: Option<&UserModel>,
sender_public_key: &PublicKey,
accepted: bool,
) -> ServerEvent<Unsigned> {
let Some(recipient_user) = recipient else {
return WsError::RegistredUserEvent.into();
};
let Some(sender_user) = try_ws!(db.get_user_by_pubk(sender_public_key).await) else {
return WsError::UserNotFound.into();
};
if recipient_user.id == sender_user.id {
return WsError::CannotRespondToOwnChatRequest.into();
}
// FIXME: When change the entity public key to a PublicKey type, change this
let recipient_public_key =
PublicKey::from_str(&recipient_user.public_key).expect("Is valid public key");
if try_ws!(
db.have_chat_request_to(&sender_user, &recipient_public_key)
.await
)
.is_none()
{
return WsError::NoChatRequestFromRecipient.into();
}
if let Some(conn_id) = ONLINE_USERS.is_online(sender_public_key).await {
ONLINE_USERS
.send(
&conn_id,
ServerEvent::chat_request_response(recipient_public_key, accepted),
)
.await;
} else {
// TODO: Create a table for chat request responses, and send them when
// the user logs in
}
// We don't need to handle the case where the sender is blacklisted or
// whitelisted already, just add it if it is not already there
let _ = if accepted {
db.add_to_whitelist(recipient_user, sender_public_key).await
} else {
db.add_to_blacklist(recipient_user, sender_public_key).await
};
try_ws!(
db.remove_out_chat_request(&sender_user, &recipient_public_key)
.await
);
ServerEvent::message("Chat request response sent successfully.")
}

View file

@ -219,6 +219,9 @@ async fn handle_events(
ClientEventType::ChatRequest { to } => { ClientEventType::ChatRequest { to } => {
Some(handlers::handle_chat_request(db, user, to).await) Some(handlers::handle_chat_request(db, user, to).await)
} }
ClientEventType::ChatRequestResponse { to, accepted } => {
Some(handlers::handle_chat_response(db, user, to, *accepted).await)
}
} }
} }