Skip to content

Commit

Permalink
feat: chat websocket
Browse files Browse the repository at this point in the history
  • Loading branch information
SongoMen committed Feb 28, 2023
1 parent b94956a commit 9169dc1
Show file tree
Hide file tree
Showing 15 changed files with 483 additions and 6 deletions.
121 changes: 121 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions backend/api/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@ hyper = { version = "0.14.24", features = ["full"] }
common = { path = "../../common" }
sqlx = { version = "0.6.2", features = ["postgres", "runtime-tokio-rustls", "json", "chrono"] }
routerify = "3.0.0"
chrono = { version = "0.4.23", default-features = false }
futures = "0.3.26"
hyper-tungstenite = "0.9.0"
serde_json = "1.0.93"

[dev-dependencies]
Expand Down
10 changes: 10 additions & 0 deletions backend/api/src/api/macros.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
#[macro_export]
macro_rules! make_response {
($status:expr, $body:expr) => {
Response::builder()
.status($status)
.header("Content-Type", "application/json")
.body(Body::from($body.to_string()))
.expect("failed to build response")
};
}
3 changes: 2 additions & 1 deletion backend/api/src/api/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ use tokio::{net::TcpSocket, select};

use crate::global::GlobalState;

mod macros;
mod v1;

pub fn routes(global: Arc<GlobalState>) -> Router<Body, Infallible> {
Expand Down Expand Up @@ -46,7 +47,7 @@ pub async fn run(global: Arc<GlobalState>) -> Result<()> {
tokio::spawn(Http::new().serve_connection(
socket,
request_service.build(addr),
));
).with_upgrades());
},
}
}
Expand Down
7 changes: 6 additions & 1 deletion backend/api/src/api/tests.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
use std::time::Duration;
use std::{collections::HashMap, time::Duration};

use common::{context::Context, logging};
use hyper::{Client, StatusCode};

use tokio::sync::Mutex;

use crate::config::AppConfig;

use super::*;
Expand All @@ -27,6 +29,7 @@ async fn test_api_v6() {
},
ctx,
db,
chats: Mutex::new(HashMap::new()),
});

let handle = tokio::spawn(run(global));
Expand Down Expand Up @@ -82,6 +85,7 @@ async fn test_api_v4() {
},
ctx,
db,
chats: Mutex::new(HashMap::new()),
});

let handle = tokio::spawn(run(global));
Expand Down Expand Up @@ -132,6 +136,7 @@ async fn test_api_bad_bind() {
},
ctx,
db,
chats: Mutex::new(HashMap::new()),
});

assert!(run(global).await.is_err());
Expand Down
51 changes: 51 additions & 0 deletions backend/api/src/api/v1/chat/messages.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
use chrono::Utc;
use serde::{Deserialize, Serialize};
use sqlx::PgPool;
use std::collections::HashMap;

#[derive(Deserialize)]
pub enum ChatMessage {
Outgoing(OutgoingChatMessage),
Incoming(IncomingChatMessage),
}

#[derive(Serialize, Deserialize)]
pub struct OutgoingChatMessage {
// message or info
pub action: String,
pub username: String,
pub message: String,
// nickname color, badges etc
pub metadata: HashMap<String, String>,
}

#[derive(Serialize, Deserialize)]
pub struct IncomingChatMessage {
// auth or message
pub action: String,
pub message: String,
}

impl IncomingChatMessage {
pub fn to_outgoing(&self, username: String) -> String {
serde_json::to_string(&OutgoingChatMessage {
action: "message".to_string(),
username,
message: self.message.to_string(),
metadata: HashMap::new(),
})
.unwrap()
}

pub async fn save_to_db(&self, chat_id: i64, user_id: i64, pool: &PgPool) {
let _ = sqlx::query!(
"INSERT INTO chat_messages (chat_room_id, author_id, message, created_at) VALUES ($1, $2, $3, $4)",
chat_id,
user_id,
self.message,
Utc::now()
)
.execute(pool)
.await;
}
}
17 changes: 17 additions & 0 deletions backend/api/src/api/v1/chat/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
use hyper::Body;
use routerify::Router;
use std::convert::Infallible;

mod messages;
mod validators;
mod ws;

pub fn routes() -> Router<Body, Infallible> {
Router::builder()
.any_method("/:chatId", ws::upgrade_request)
.build()
.unwrap()
}

#[cfg(test)]
mod tests;
1 change: 1 addition & 0 deletions backend/api/src/api/v1/chat/tests.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@

43 changes: 43 additions & 0 deletions backend/api/src/api/v1/chat/validators.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
use super::messages::IncomingChatMessage;
use common::types::{chat_room, user};
use sqlx::PgPool;

const MAX_MESSAGE_LENGTH: usize = 255;

pub fn valid_chat_message(message: String) -> Option<IncomingChatMessage> {
let final_message = serde_json::from_str::<IncomingChatMessage>(&message);

if let Ok(message_result) = final_message {
if message_result.message.len() <= MAX_MESSAGE_LENGTH {
return Some(message_result);
}
}

None
}

pub async fn valid_user(session_id: i64, pool: &PgPool) -> Option<user::Model> {
let user = sqlx::query_as!(
user::Model,
"SELECT * FROM users WHERE id = (SELECT user_id FROM sessions WHERE id = $1)",
session_id
)
.fetch_optional(pool)
.await
.unwrap_or(None);

user
}

pub async fn valid_chat(chat_id: i64, pool: &PgPool) -> Option<chat_room::Model> {
let chat = sqlx::query_as!(
chat_room::Model,
"SELECT * FROM chat_rooms WHERE id = $1",
chat_id
)
.fetch_optional(pool)
.await
.unwrap_or(None);

chat
}
Loading

0 comments on commit 9169dc1

Please sign in to comment.