Skip to content

Commit

Permalink
Added list_chats query
Browse files Browse the repository at this point in the history
  • Loading branch information
megrogan committed Dec 22, 2020
1 parent 2054af2 commit b3e749d
Show file tree
Hide file tree
Showing 10 changed files with 180 additions and 85 deletions.
12 changes: 12 additions & 0 deletions commands.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
dfx identity use matt
dfx canister call user_mgmt set_username '("Matt")'
dfx canister call user_mgmt get_principal '("Matt")'
dfx identity use hamish
dfx canister call user_mgmt set_username '("Hamish")'
dfx canister call user_mgmt get_principal '("Hamish")'
dfx canister call chats create_chat '(principal "lpfg6-5goq7-vn6yz-wegiu-r6goc-sxoor-wj3b2-dpyup-k6i5m-qhv5m-xae", "Hi Matt, how's it going")'
dfx canister call chats send_message '(45_292_552_032_833_753, "hello!!??")'
dfx identity use matt
dfx canister call chats list_chats
dfx canister call chats get_messages '(45_292_552_032_833_753, 0)'
dfx canister call chats send_message '(45_292_552_032_833_753, "whats up?")'
16 changes: 12 additions & 4 deletions src/chats/chats.did
Original file line number Diff line number Diff line change
@@ -1,13 +1,21 @@
type Message =
type Message =
record {
id: nat64;
timestamp: nat64;
sent_by_me: bool;
text: text;
}
};

type ChatSummary =
record {
id: nat64;
them: principal;
most_recent: Message;
};

service : {
create_chat: (principal) -> (opt nat64);
send_message: (nat64, text) -> (bool);
create_chat: (principal, text) -> (opt nat64);
send_message: (nat64, text) -> (opt nat64);
get_messages: (nat64, nat64) -> (opt vec Message) query;
list_chats: () -> (vec ChatSummary) query;
}
16 changes: 8 additions & 8 deletions src/chats/src/api.rs
Original file line number Diff line number Diff line change
@@ -1,26 +1,26 @@
use ic_cdk_macros::*;
use ic_types::Principal;
use crate::domain::chat::Message;
use crate::domain::chat::{ChatId, ChatSummary, Message};
use crate::queries::*;
use crate::updates::*;
use crate::domain::chat_list::ChatId;

// #[query]
// fn list_chats() -> Vec<ChatSummary> {
// }
#[query]
fn list_chats() -> Vec<ChatSummary> {
list_chats::query()
}

#[query]
fn get_messages(chat_id: ChatId, from_index: usize) -> Option<Vec<Message>> {
get_messages::query(chat_id, from_index)
}

#[update]
fn create_chat(recipient: Principal) -> Option<ChatId> {
create_chat::update(recipient)
fn create_chat(recipient: Principal, text: String) -> Option<ChatId> {
create_chat::update(recipient, text)
}

#[update]
fn send_message(chat_id: ChatId, text: String) -> bool {
fn send_message(chat_id: ChatId, text: String) -> Option<u64> {
send_message::update(chat_id, text)
}

104 changes: 91 additions & 13 deletions src/chats/src/domain/chat.rs
Original file line number Diff line number Diff line change
@@ -1,32 +1,44 @@
use ic_cdk::export::candid::CandidType;
use ic_types::Principal;
use highway::{HighwayHasher, HighwayHash};
use serde::Deserialize;

#[derive(CandidType, Deserialize)]
pub struct Chat {
id: ChatId,
user1: Principal,
user2: Principal,
messages: Vec<MessageInternal>
}

impl Chat {
pub fn new(user1: Principal, user2: Principal) -> Chat {
pub fn get_id(&self) -> ChatId {
self.id
}

pub fn new(id: ChatId, sender: Principal, recipient: Principal, text: String, timestamp: u64) -> Chat {

let message = MessageInternal {
id: 0,
timestamp,
sent_by_user1: true,
text
};

Chat {
user1,
user2,
messages: Vec::new()
id,
user1: sender,
user2: recipient,
messages: vec![message]
}
}

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,
None => 0
};
pub fn push_message(&mut self, sender: &Principal, text: String, timestamp: u64) -> u64 {
let id = self.messages.last().unwrap().id + 1;

let message = MessageInternal {
id,
Expand All @@ -36,6 +48,8 @@ impl Chat {
};

self.messages.push(message);

id
}

pub fn get_messages(&self, me: &Principal, from_index: usize) -> Vec<Message> {
Expand All @@ -45,9 +59,60 @@ impl Chat {

self.messages[from_index..]
.iter()
.map(|m| Message::new(m.id, m.timestamp, m.sent_by_user1 == (*me == self.user1), m.text.clone()))
.map(|m| Message::new(
m.id,
m.timestamp,
m.sent_by_user1 == (*me == self.user1),
m.text.clone()))
.collect()
}

pub fn to_summary(&self, me: &Principal) -> ChatSummary {
let message = self.messages.last().unwrap();

ChatSummary {
id: self.id,
them: if self.user1 == *me { self.user2.clone() } else { self.user1.clone() },
most_recent: Message::new(
message.id,
message.timestamp,
message.sent_by_user1 == (*me == self.user1),
message.text.clone())
}
}
}

/// 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 {
pub 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())
}
}

#[derive(CandidType)]
pub struct ChatSummary {
id: ChatId,
them: Principal,
most_recent: Message,
}

impl ChatSummary {
pub fn get_most_recent(&self) -> &Message {
&self.most_recent
}
}

#[derive(CandidType)]
Expand All @@ -67,6 +132,10 @@ impl Message {
text
}
}

pub fn get_timestamp(&self) -> u64 {
self.timestamp
}
}

#[derive(CandidType, Deserialize)]
Expand All @@ -88,7 +157,10 @@ mod tests {
let user1 = Principal::from_text("yy53y-szfmc-h2gyu-zlind-wzikf-6zuqh-i4x6u-fvado-rvydl-qxwlz-oqe").unwrap();
let user2 = Principal::from_text("ups66-6ukpx-mitsu-vhso3-ixjld-5p3m5-fbq6p-bbbma-oyzvm-mjr2w-qae").unwrap();

let mut chat = Chat::new(user1.clone(), user2.clone());
let mut chat = Chat::new(
ChatId::new(&user1, &user2),
user1.clone(),
user2.clone());

for i in 0..10 {
let text = i.to_string();
Expand All @@ -113,7 +185,10 @@ mod tests {
let user1 = Principal::from_text("yy53y-szfmc-h2gyu-zlind-wzikf-6zuqh-i4x6u-fvado-rvydl-qxwlz-oqe").unwrap();
let user2 = Principal::from_text("ups66-6ukpx-mitsu-vhso3-ixjld-5p3m5-fbq6p-bbbma-oyzvm-mjr2w-qae").unwrap();

let mut chat = Chat::new(user1.clone(), user2.clone());
let mut chat = Chat::new(
ChatId::new(&user1, &user2),
user1.clone(),
user2.clone());

let timestamp = SystemTime::now().duration_since(SystemTime::UNIX_EPOCH).unwrap().as_nanos() as u64;

Expand All @@ -136,7 +211,10 @@ mod tests {
let user1 = Principal::from_text("yy53y-szfmc-h2gyu-zlind-wzikf-6zuqh-i4x6u-fvado-rvydl-qxwlz-oqe").unwrap();
let user2 = Principal::from_text("ups66-6ukpx-mitsu-vhso3-ixjld-5p3m5-fbq6p-bbbma-oyzvm-mjr2w-qae").unwrap();

let mut chat = Chat::new(user1.clone(), user2.clone());
let mut chat = Chat::new(
ChatId::new(&user1, &user2),
user1.clone(),
user2.clone());

let timestamp = SystemTime::now().duration_since(SystemTime::UNIX_EPOCH).unwrap().as_nanos() as u64;

Expand Down
73 changes: 30 additions & 43 deletions src/chats/src/domain/chat_list.rs
Original file line number Diff line number Diff line change
@@ -1,91 +1,78 @@
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;
use super::chat::{Chat, ChatId, ChatSummary};

#[derive(Default)]
pub struct ChatList {
chats: HashMap<ChatId, Chat>
}

impl ChatList {

pub fn create(&mut self, sender: Principal, recipient: Principal) -> Option<ChatId> {

pub fn create(&mut self, sender: Principal, recipient: Principal, text: String, timestamp: u64) -> Option<ChatId> {
let chat_id = ChatId::new(&sender, &recipient);

match self.chats.entry(chat_id) {
Occupied(_) => None,
Vacant(e) => {
e.insert(Chat::new(sender, recipient));
e.insert(Chat::new(chat_id, sender, recipient, text, timestamp));
Some(chat_id)
}
}
}

pub fn get(&self, chat_id: ChatId, on_behalf_of: &Principal) -> Option<&Chat> {

pub fn get(&self, chat_id: ChatId, me: &Principal) -> Option<&Chat> {
let chat = self.chats.get(&chat_id)?;

if !chat.involves_user(on_behalf_of) {
if !chat.involves_user(me) {
return None;
}

Some(chat)
}

pub fn get_mut(&mut self, chat_id: ChatId, on_behalf_of: &Principal) -> Option<&mut Chat> {

pub fn get_mut(&mut self, chat_id: ChatId, me: &Principal) -> Option<&mut Chat> {
let chat = self.chats.get_mut(&chat_id)?;

if !chat.involves_user(on_behalf_of) {
if !chat.involves_user(me) {
return None;
}

Some(chat)
}

pub fn list_chats(&self, user: &Principal) -> Vec<ChatSummary> {
// For now this will iterate through every chat...
let mut list: Vec<_> = self
.chats
.values()
.filter(|chat| chat.involves_user(user))
.map(|chat| chat.to_summary(user))
.collect();

list.sort_unstable_by(|c1, c2| {
let t1 = c1.get_most_recent().get_timestamp();
let t2 = c2.get_most_recent().get_timestamp();
t2.cmp(&t1)
});

list
}
}

impl StableState for ChatList {
type State = Vec<(ChatId, Chat)>;
type State = Vec<Chat>;

fn drain(self) -> Vec<(ChatId, Chat)> {
fn drain(self) -> Vec<Chat> {
self.chats
.into_iter()
.map(|(_, c)| c)
.collect()
}

fn fill(chats: Vec<(ChatId, Chat)>) -> ChatList {
fn fill(chats: Vec<Chat>) -> ChatList {
let map: HashMap<ChatId, Chat> = chats
.into_iter()
.map(|c| (c.get_id(), c))
.collect();

ChatList {
chats: map
}
}
}

/// 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())
}
}

8 changes: 3 additions & 5 deletions src/chats/src/queries/get_messages.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,10 @@
use ic_cdk::storage;
use crate::domain::chat_list::{ChatId, ChatList};
use crate::domain::chat::Message;
use crate::domain::chat::{ChatId, Message};
use crate::domain::chat_list::ChatList;

pub fn query(chat_id: ChatId, from_index: usize) -> Option<Vec<Message>> {

let chat_list: &mut ChatList = storage::get_mut();
let chat_list: &ChatList = storage::get();
let me = ic_cdk::caller();

let chat = chat_list.get(chat_id, &me)?;

Some(chat.get_messages(&me, from_index))
Expand Down
10 changes: 10 additions & 0 deletions src/chats/src/queries/list_chats.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
use ic_cdk::storage;
use crate::domain::chat_list::ChatList;
use crate::domain::chat::ChatSummary;

pub fn query() -> Vec<ChatSummary> {
let chat_list: &ChatList = storage::get();
let me = ic_cdk::caller();

chat_list.list_chats(&me)
}
Loading

0 comments on commit b3e749d

Please sign in to comment.