feat: Chat request implementation #14

Manually merged
awiteb merged 55 commits from awiteb/chat-request-and-response into master 2024-07-18 14:21:39 +02:00 AGit
7 changed files with 133 additions and 7 deletions
Showing only changes of commit 24e038f482 - Show all commits

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? {
awiteb marked this conversation as resolved
Review

I noticed that several methods have logcall but others don't, is it remaining from debugging session? or planning to keep it

I noticed that several methods have `logcall` but others don't, is it remaining from debugging session? or planning to keep it
Review

It was for debugging session, but I will use it on more functions

It was for debugging session, but I will use it on more functions
Review
image

There are a lot of functions that need to be checked, 300+ functions to check if the log is necessary or not, I hate this

<img width="360" alt="image" src="/attachments/985a139f-d8ae-4f7f-a782-f25891ac074e"> There are a lot of functions that need to be checked, 300+ functions to check if the log is necessary or not, I hate this
Review

Ok, it is just 100 functions

Ok, it is just 100 functions
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>>;
awiteb marked this conversation as resolved
Review

should return true based on the name and description of the func

should return `true` based on the name and description of the func
Review

small nitpick (also applies to have_chat_request_to below)
I would name it get_chat_request_to, as have seems that it would be boolean

small nitpick (also applies to `have_chat_request_to` below) I would name it `get_chat_request_to`, as `have` seems that it would be boolean
/// 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 },
awiteb marked this conversation as resolved
Review

in the client, you mentioned to make it alphabetical, should it be the same here?

in the client, you mentioned to make it alphabetical, should it be the same here?
Review

Yes, I forgot it

Yes, I forgot it
/// 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.",
awiteb marked this conversation as resolved
Review

Maybe would be better to move this to WsError easier to manage errors
there are also others below this

Maybe would be better to move this to `WsError` easier to manage errors there are also others below this
Review

Right, this should be an error, not a message. I don't know how is this happened

Right, this should be an error, not a message. I don't know how is this happened
Review

I'll remove Message event it is actually useless. I don't know why I thought it was good.

I'll remove `Message` event it is actually useless. I don't know why I thought it was good.
Review

I don't know why I thought it was good.

Well, I remembered when I added it, it was a pain to add a new error (before ws_error macro) so I thought it was good for simple messages.

> I don't know why I thought it was good. Well, I remembered when I added it, it was a pain to add a new error (before `ws_error` macro) so I thought it was good for simple messages.
); );
} }
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)
}
} }
} }