From f3b9e910402b795f20a2a043d64328e3f901def8 Mon Sep 17 00:00:00 2001 From: EliasSchriefer Date: Sun, 12 Dec 2021 22:52:11 +0100 Subject: [PATCH] Add sending messages to chats and groups --- src/graphql.rs | 136 ++++++++++++++++++++++++++++++++++++++++++++++--- test-api.sh | 17 +++++-- 2 files changed, 141 insertions(+), 12 deletions(-) diff --git a/src/graphql.rs b/src/graphql.rs index 7c3bebb..bf3aa90 100644 --- a/src/graphql.rs +++ b/src/graphql.rs @@ -6,6 +6,11 @@ use std::{ collections::BTreeSet, fmt::Display, str::FromStr, + time::{ + SystemTime, + SystemTimeError, + UNIX_EPOCH, + }, }; use juniper::{ EmptySubscription, @@ -149,13 +154,23 @@ async fn user_authentication(db: &SqlitePool, user: &uuid::adapter::Simple, pass } async fn user_exists(db: &SqlitePool, user: &uuid::adapter::Simple) -> FieldResult<()> { - if sqlx::query( - format!(r#"SELECT id FROM users WHERE id = "{}""#, user).as_str() - ).fetch_optional(db).await?.is_some() { - Ok(()) - } else { - Err(format!(r#"user "{}" does not exist on this server"#, user).into()) - } + if sqlx::query( + format!(r#"SELECT id FROM users WHERE id = "{}""#, user).as_str() + ).fetch_optional(db).await?.is_some() { + Ok(()) + } else { + Err(format!(r#"user "{}" does not exist on this server"#, user).into()) + } +} + +async fn chat_exists(db: &SqlitePool, chat: &uuid::adapter::Simple) -> FieldResult<()> { + if sqlx::query( + format!(r#"SELECT id FROM chat_index WHERE id = "{}""#, chat).as_str() + ).fetch_optional(db).await?.is_some() { + Ok(()) + } else { + Err(format!(r#"chat "{}" does not exist on this server"#, chat).into()) + } } #[derive(Clone, Debug)] @@ -390,6 +405,79 @@ pub struct GroupChat { users: Vec, } +#[derive(Clone, Debug, PartialEq, Eq, GraphQLInputObject, Type)] +pub struct MessageInput { + msg_type: MsgType, + content: Option, +} + +impl MessageInput { + pub fn try_into_message(self, user: &uuid::adapter::Simple) -> Result { + let sender: ID = user.to_string().into(); + + Ok(Message { + id: Uuid::new_v4().to_simple().to_string().into(), + timestamp: SystemTime::now() + .duration_since(UNIX_EPOCH)? + .as_millis(), + sender: sender.clone(), + msg_type: self.msg_type, + content: self.content, + hide_for: None, + seen_by: vec![sender], + }) + } +} + +#[derive(Clone, Debug, PartialEq, Eq, Type)] +pub struct Message { + id: ID, + timestamp: u128, + sender: ID, + msg_type: MsgType, + content: Option, + hide_for: Option>, + seen_by: Vec, +} + +#[graphql_object] +impl Message { + fn id(&self) -> &ID { + &self.id + } + + fn timestamp(&self) -> String { + self.timestamp.to_string() + } + + fn sender(&self) -> &ID { + &self.sender + } + + fn msg_type(&self) -> MsgType { + self.msg_type + } + + fn content(&self) -> Option<&str> { + self.content.as_ref().map(|s| &**s) + } + + fn hide_for(&self) -> Option<&[ID]> { + self.hide_for.as_ref().map(|v| &v[..]) + } + + fn seen_by(&self) -> &[ID] { + &self.seen_by + } +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, GraphQLEnum, Type)] +#[repr(u8)] +#[non_exhaustive] +pub enum MsgType { + Text = 0, +} + #[derive(Clone, Copy, Debug)] pub struct Query; @@ -668,6 +756,40 @@ impl Mutation { Ok(group_chat_uuid.to_simple().to_string().into()) } + + async fn sendMessage(context: &Context, user: ID, password_hash: String, chat: ID, msg: MessageInput) -> FieldResult { + let user = id_to_uuid(&user)?.to_simple(); + user_authentication(&context.db, &user, &password_hash).await?; + + let msg: Message = msg.try_into_message(&user)?; + let msg_id = id_to_uuid(&msg.id)?.to_simple(); + + let chat = id_to_uuid(&chat)?.to_simple(); + chat_exists(&context.db, &chat).await?; + + if msg.timestamp > i64::MAX as u128 { + eprintln!("WARNING: Timestamp value is greater than sqlite can handle"); + } + + sqlx::query(format!( + r#"INSERT INTO msgdata_{} VALUES ("{}", {}, "{}", {}, {}, {}, "{}")"#, + chat, + &msg_id, + msg.timestamp, + user, + msg.msg_type as u8, + msg.content + .map(|c| format!(r#""{}""#, c)) + .unwrap_or("NULL".to_string()), + msg.hide_for + .map(format_array_for_sql) + .map(|a| format!(r#""{}""#, a)) + .unwrap_or("NULL".to_string()), + format_array_for_sql(msg.seen_by), + ).as_str()).execute(&context.db).await?; + + Ok(msg_id.to_string().into()) + } } pub type Schema<'root_node> = RootNode<'root_node, Query, Mutation, EmptySubscription>; diff --git a/test-api.sh b/test-api.sh index 4ef3c38..6cbf512 100755 --- a/test-api.sh +++ b/test-api.sh @@ -1,13 +1,20 @@ #!/usr/bin/env bash echo Create users @a, @b, and @c: -userIDs=$(curl "localhost:8080/graphql" -X POST -H "content-type: application/json" --data '{"query":"mutation addUsers($a: NewUser!, $b: NewUser!, $c: NewUser!) {\n a: newUser(newUser: $a)\n b: newUser(newUser: $b)\n c: newUser(newUser: $c)\n}","variables":{"a":{"userName":"@a","passwordHash":"1234567890"},"b":{"userName":"@b","passwordHash":"1234567890"},"c":{"userName":"@c","passwordHash":"1234567890"}},"operationName":"addUsers"}' | jq .data) +userIDs=$(curl "localhost:8081/graphql" -X POST -H "content-type: application/json" --data '{"query":"mutation addUsers($a: NewUser!, $b: NewUser!, $c: NewUser!) {\n a: newUser(newUser: $a)\n b: newUser(newUser: $b)\n c: newUser(newUser: $c)\n}","variables":{"a":{"userName":"@a","passwordHash":"1234567890"},"b":{"userName":"@b","passwordHash":"1234567890"},"c":{"userName":"@c","passwordHash":"1234567890"}},"operationName":"addUsers"}' | jq .data) echo "$userIDs" | jq . a=$(echo "$userIDs" | jq -r .a) b=$(echo "$userIDs" | jq -r .b) c=$(echo "$userIDs" | jq -r .c) -echo Create chats between @a and @b, @a and @c, and group chats between @a, @b, and @c: -curl "localhost:8080/graphql" -X POST -H "content-type: application/json" --data "{\"query\":\"mutation createChat(\$a: ID!, \$b: ID!, \$c: ID!, \$pwHash: String!) {\n ab: newChat(user: \$a, passwordHash: \$pwHash, with: \$b)\n ac: newChat(user: \$a, passwordHash: \$pwHash, with: \$c)\n abc: newGroupChat(user: \$a, passwordHash: \$pwHash, with: [\$b, \$c], title: \\\"ABC\\\")\n}\",\"variables\":{\"pwHash\":\"1234567890\",\"a\":\"$a\",\"b\":\"$b\",\"c\":\"$c\"},\"operationName\":\"createChat\"}" | jq .data +echo -e \nCreate chats between @a and @b, @a and @c, and group chats between @a, @b, and @c: +curl "localhost:8081/graphql" -X POST -H "content-type: application/json" --data "{\"query\":\"mutation createChat(\$a: ID!, \$b: ID!, \$c: ID!, \$pwHash: String!) {\n ab: newChat(user: \$a, passwordHash: \$pwHash, with: \$b)\n ac: newChat(user: \$a, passwordHash: \$pwHash, with: \$c)\n abc: newGroupChat(user: \$a, passwordHash: \$pwHash, with: [\$b, \$c], title: \\\"ABC\\\")\n}\",\"variables\":{\"pwHash\":\"1234567890\",\"a\":\"$a\",\"b\":\"$b\",\"c\":\"$c\"},\"operationName\":\"createChat\"}" | jq .data -echo List all users\' chats: -curl "localhost:8080/graphql" -X POST -H "content-type: application/json" --data "{\"query\":\"query listChats(\$a: ID!, \$b: ID!, \$c: ID!, \$pwHash: String!) {\n aChats: chats(user: \$a, passwordHash: \$pwHash) {\n id\n users\n }\n aGroupChats: groupChats(user: \$a, passwordHash: \$pwHash) {\n id\n title\n description\n users\n }\n bChats: chats(user: \$b, passwordHash: \$pwHash) {\n id\n users\n }\n bGroupChats: groupChats(user: \$b, passwordHash: \$pwHash) {\n id\n title\n description\n users\n }\n cChats: chats(user: \$c, passwordHash: \$pwHash) {\n id\n users\n }\n cGroupChats: groupChats(user: \$c, passwordHash: \$pwHash) {\n id\n title\n description\n users\n }\n}\",\"variables\":{\"pwHash\":\"1234567890\",\"a\":\"$a\",\"b\":\"$b\",\"c\":\"$c\"},\"operationName\":\"listChats\"}" | jq .data \ No newline at end of file +echo -e \nList all users\' chats: +chatIDs=$(curl "localhost:8081/graphql" -X POST -H "content-type: application/json" --data "{\"query\":\"query listChats(\$a: ID!, \$b: ID!, \$c: ID!, \$pwHash: String!) {\n aChats: chats(user: \$a, passwordHash: \$pwHash) {\n id\n users\n }\n aGroupChats: groupChats(user: \$a, passwordHash: \$pwHash) {\n id\n title\n description\n users\n }\n bChats: chats(user: \$b, passwordHash: \$pwHash) {\n id\n users\n }\n bGroupChats: groupChats(user: \$b, passwordHash: \$pwHash) {\n id\n title\n description\n users\n }\n cChats: chats(user: \$c, passwordHash: \$pwHash) {\n id\n users\n }\n cGroupChats: groupChats(user: \$c, passwordHash: \$pwHash) {\n id\n title\n description\n users\n }\n}\",\"variables\":{\"pwHash\":\"1234567890\",\"a\":\"$a\",\"b\":\"$b\",\"c\":\"$c\"},\"operationName\":\"listChats\"}" | jq .data) +echo "$chatIDs" | jq . +ab=$(echo "$chatIDs" | jq -r .aChats[0].id) +abc=$(echo "$chatIDs" | jq -r .aGroupChats[0].id) + +echo -e \nSend a private/group message: +#echo "{\"pwHash\":\"1234567890\",\"a\":\"$a\",\"bChat\":\"$ab\",\"abc\":\"$abc\",\"msg\":{\"msgType\":\"TEXT\",\"content\":\"Test\"}}" | jq . +curl "localhost:8081/graphql" -X POST -H "content-type: application/json" --data "{\"query\":\"mutation sendMessages(\$a: ID!, \$pwHash: String!, \$bChat: ID!, \$abc: ID!, \$msg: MessageInput!) {\n chat: sendMessage(user: \$a, passwordHash: \$pwHash, chat: \$bChat, msg: \$msg)\n group: sendMessage(user: \$a, passwordHash: \$pwHash, chat: \$abc, msg: \$msg)\n}\",\"variables\":{\"pwHash\":\"1234567890\",\"a\":\"$a\",\"bChat\":\"$ab\",\"abc\":\"$abc\",\"msg\":{\"msgType\":\"TEXT\",\"content\":\"Test\"}},\"operationName\":\"sendMessages\"}" | jq . \ No newline at end of file