diff --git a/src/chats/Cargo.toml b/src/chats/Cargo.toml index acbe460ac3..ba10310afe 100644 --- a/src/chats/Cargo.toml +++ b/src/chats/Cargo.toml @@ -14,5 +14,6 @@ crate-type = ["cdylib"] ic-cdk = "0.2.0" ic-cdk-macros = "0.2.0" ic-types = "0.1.1" +highway = "0.6.3" serde = "1.0.111" shared = { path = "../shared", version = "0.1.0" } diff --git a/src/chats/chats.did b/src/chats/chats.did index 4be4819643..655d367eca 100644 --- a/src/chats/chats.did +++ b/src/chats/chats.did @@ -7,6 +7,7 @@ type Message = } service : { - send_message: (principal, text) -> (); - get_messages: (principal, nat64) -> (vec Message) query; + create_chat: (principal) -> (opt nat64); + send_message: (nat64, text) -> (bool); + get_messages: (nat64, nat64) -> (opt vec Message) query; } \ No newline at end of file diff --git a/src/chats/src/api.rs b/src/chats/src/api.rs index 49e9d7660a..b83f8b29ac 100644 --- a/src/chats/src/api.rs +++ b/src/chats/src/api.rs @@ -3,13 +3,24 @@ use ic_types::Principal; use crate::domain::chat::Message; use crate::queries::*; use crate::updates::*; +use crate::domain::chat_list::ChatId; + +// #[query] +// fn list_chats() -> Vec { +// } + +#[query] +fn get_messages(chat_id: ChatId, from_index: usize) -> Option> { + get_messages::query(chat_id, from_index) +} #[update] -fn send_message(recipient: Principal, text: String) { - send_message::update(recipient, text) +fn create_chat(recipient: Principal) -> Option { + create_chat::update(recipient) +} + +#[update] +fn send_message(chat_id: ChatId, text: String) -> bool { + send_message::update(chat_id, text) } -#[query] -fn get_messages(from_user: Principal, from_index: usize) -> Vec { - get_messages::query(from_user, from_index) -} \ No newline at end of file diff --git a/src/chats/src/domain/chat.rs b/src/chats/src/domain/chat.rs index 9961aaa2ce..6af0991845 100644 --- a/src/chats/src/domain/chat.rs +++ b/src/chats/src/domain/chat.rs @@ -18,6 +18,10 @@ impl Chat { } } + pub fn involves_user(&self, user: &Principal) -> bool { + self.user1 == *user || self.user2 == *user + } + pub fn push_message(&mut self, sender: &Principal, text: String, timestamp: u64) { let id = match self.messages.last() { Some(m) => m.id + 1, @@ -44,10 +48,6 @@ impl Chat { .map(|m| Message::new(m.id, m.timestamp, m.sent_by_user1 == (*me == self.user1), m.text.clone())) .collect() } - - pub fn get_key(&self) -> (Principal, Principal) { - (self.user1.clone(), self.user2.clone()) - } } #[derive(CandidType)] diff --git a/src/chats/src/domain/chat_list.rs b/src/chats/src/domain/chat_list.rs index d6d6250d74..164c47b6b7 100644 --- a/src/chats/src/domain/chat_list.rs +++ b/src/chats/src/domain/chat_list.rs @@ -1,56 +1,91 @@ use std::collections::{HashMap, hash_map::Entry::{Occupied, Vacant}}; +use ic_cdk::export::candid::CandidType; use ic_types::Principal; +use highway::{HighwayHasher, HighwayHash}; +use serde::Deserialize; use shared::StableState; use super::chat::Chat; #[derive(Default)] pub struct ChatList { - chats: HashMap<(Principal, Principal), Chat> + chats: HashMap } impl ChatList { - pub fn get(&self, sender: Principal, recipient: Principal) -> Option<&Chat> { - let (user1, user2) = ChatList::order_users(sender, recipient); - self.chats.get(&(user1, user2)) + pub fn create(&mut self, sender: Principal, recipient: Principal) -> Option { + + let chat_id = ChatId::new(&sender, &recipient); + + match self.chats.entry(chat_id) { + Occupied(_) => None, + Vacant(e) => { + e.insert(Chat::new(sender, recipient)); + Some(chat_id) + } + } } - pub fn get_or_add_chat(&mut self, sender: Principal, recipient: Principal) -> &mut Chat { - let (user1, user2) = ChatList::order_users(sender, recipient); + pub fn get(&self, chat_id: ChatId, on_behalf_of: &Principal) -> Option<&Chat> { - match self.chats.entry((user1.clone(), user2.clone())) { - Occupied(e) => e.into_mut(), - Vacant(e) => e.insert(Chat::new(user1, user2)) + let chat = self.chats.get(&chat_id)?; + + if !chat.involves_user(on_behalf_of) { + return None; } + + Some(chat) } - fn order_users(user1: Principal, user2: Principal) -> (Principal, Principal) { - if user1 < user2 { - (user1, user2) - } else { - (user2, user1) + pub fn get_mut(&mut self, chat_id: ChatId, on_behalf_of: &Principal) -> Option<&mut Chat> { + + let chat = self.chats.get_mut(&chat_id)?; + + if !chat.involves_user(on_behalf_of) { + return None; } + + Some(chat) } } impl StableState for ChatList { - type State = Vec; + type State = Vec<(ChatId, Chat)>; - fn drain(self) -> Vec { + fn drain(self) -> Vec<(ChatId, Chat)> { self.chats .into_iter() - .map(|(_, v)| v) .collect() } - fn fill(chats: Vec) -> ChatList { - let map: HashMap<(Principal, Principal), Chat> = chats + fn fill(chats: Vec<(ChatId, Chat)>) -> ChatList { + let map: HashMap = chats .into_iter() - .map(|c| ((c.get_key(), c))) .collect(); ChatList { chats: map } } -} \ No newline at end of file +} + +/// TODO: We would preferably use a Uuid or u128 but these haven't yet got a CandidType implementation +#[derive(CandidType, Deserialize, PartialEq, Eq, Hash, Copy, Clone)] +pub struct ChatId(u64); + +impl ChatId { + fn new(user1: &Principal, user2: &Principal) -> ChatId { + let mut hasher = HighwayHasher::default(); + + if user1 < user2 { + hasher.append(user1.as_slice()); + hasher.append(user2.as_slice()); + } else { + hasher.append(user2.as_slice()); + hasher.append(user1.as_slice()); + } + + ChatId(hasher.finalize64()) + } +} + diff --git a/src/chats/src/queries/get_messages.rs b/src/chats/src/queries/get_messages.rs index f3184091c1..bb64996d15 100644 --- a/src/chats/src/queries/get_messages.rs +++ b/src/chats/src/queries/get_messages.rs @@ -1,15 +1,13 @@ use ic_cdk::storage; -use ic_types::Principal; -use crate::domain::chat_list::ChatList; +use crate::domain::chat_list::{ChatId, ChatList}; use crate::domain::chat::Message; -pub fn query(from_user: Principal, from_index: usize) -> Vec { - let me = ic_cdk::caller(); +pub fn query(chat_id: ChatId, from_index: usize) -> Option> { let chat_list: &mut ChatList = storage::get_mut(); + let me = ic_cdk::caller(); + + let chat = chat_list.get(chat_id, &me)?; - match chat_list.get(from_user, me.clone()) { - Some(c) => c.get_messages(&me, from_index), - None => Vec::new() - } + Some(chat.get_messages(&me, from_index)) } \ No newline at end of file diff --git a/src/chats/src/updates/create_chat.rs b/src/chats/src/updates/create_chat.rs new file mode 100644 index 0000000000..0cf72ef2d9 --- /dev/null +++ b/src/chats/src/updates/create_chat.rs @@ -0,0 +1,11 @@ +use ic_types::Principal; +use crate::domain::chat_list::{ChatId, ChatList}; +use ic_cdk::{storage}; + +pub fn update(recipient: Principal) -> Option { + + let chat_list: &mut ChatList = storage::get_mut(); + let me = ic_cdk::caller(); + + chat_list.create(me, recipient) +} \ No newline at end of file diff --git a/src/chats/src/updates/mod.rs b/src/chats/src/updates/mod.rs index 96cb9a4016..6f23a341c5 100644 --- a/src/chats/src/updates/mod.rs +++ b/src/chats/src/updates/mod.rs @@ -1 +1,2 @@ -pub mod send_message; \ No newline at end of file +pub mod send_message; +pub mod create_chat; \ No newline at end of file diff --git a/src/chats/src/updates/send_message.rs b/src/chats/src/updates/send_message.rs index 03de64f1af..fcbbf08393 100644 --- a/src/chats/src/updates/send_message.rs +++ b/src/chats/src/updates/send_message.rs @@ -1,15 +1,16 @@ +use crate::domain::chat_list::{ChatId, ChatList}; use ic_cdk::{api, storage}; -use ic_types::Principal; -use crate::domain::chat_list::ChatList; -pub fn update(recipient: Principal, text: String) { - let me = ic_cdk::caller(); +pub fn update(chat_id: ChatId, text: String) -> bool { let chat_list: &mut ChatList = storage::get_mut(); + let me = ic_cdk::caller(); - let chat = chat_list.get_or_add_chat(me.clone(), recipient); - - let timestamp = api::time() as u64; + if let Some(chat) = chat_list.get_mut(chat_id, &me) { + let timestamp = api::time() as u64; + chat.push_message(&me, text, timestamp); + return true; + } - chat.push_message(&me, text, timestamp); + false } \ No newline at end of file