From ff5512fe263778479f4f07d3bc466c43137037a3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ahmet=20Kaan=20G=C3=9CM=C3=9C=C5=9E?= <96421894+Tahinli@users.noreply.github.com> Date: Sun, 26 May 2024 05:25:37 +0300 Subject: [PATCH] feat: :sparkles: send && receive messages --- .gitignore | 7 ++++ Cargo.toml | 16 ++++++++ configs/server_config.txt | 3 ++ src/chat.rs | 79 +++++++++++++++++++++++++++++++++++++++ src/lib.rs | 33 ++++++++++++++++ src/main.rs | 28 ++++++++++++++ src/routing.rs | 79 +++++++++++++++++++++++++++++++++++++++ src/test.rs | 1 + src/utils.rs | 35 +++++++++++++++++ 9 files changed, 281 insertions(+) create mode 100644 Cargo.toml create mode 100644 configs/server_config.txt create mode 100644 src/chat.rs create mode 100644 src/lib.rs create mode 100644 src/main.rs create mode 100644 src/routing.rs create mode 100644 src/test.rs create mode 100644 src/utils.rs diff --git a/.gitignore b/.gitignore index 6985cf1..800f447 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,8 @@ # will have compiled files and executables debug/ target/ +/certificates +/.vscode # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries # More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html @@ -12,3 +14,8 @@ Cargo.lock # MSVC Windows builds of rustc generate these, which store debugging information *.pdb + + +# Added by cargo + +/target diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..eef5e3d --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "acapair_chat_api" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +axum = "0.7.5" +axum-server = { version = "0.6.0", features = ["tls-rustls"] } +chrono = "0.4.38" +serde = { version = "1.0.203", features = ["derive"] } +serde_json = "1.0.117" +sha3 = "0.10.8" +tokio = { version = "1.37.0", features = ["full"] } +tower-http = { version = "0.5.2", features = ["full"] } diff --git a/configs/server_config.txt b/configs/server_config.txt new file mode 100644 index 0000000..600ef66 --- /dev/null +++ b/configs/server_config.txt @@ -0,0 +1,3 @@ +axum_address:192.168.1.2 +port:2334 +max_message:150 \ No newline at end of file diff --git a/src/chat.rs b/src/chat.rs new file mode 100644 index 0000000..3631389 --- /dev/null +++ b/src/chat.rs @@ -0,0 +1,79 @@ +use std::time::Duration; + +use chrono::Utc; +use serde::Serialize; +use sha3::{Digest, Sha3_512}; + +#[derive(Debug, Clone, Serialize)] +pub struct Message { + hash: String, + sender: String, + time_received: u64, + data: String, +} +#[derive(Debug, Serialize)] +pub struct Chat { + pub room_id: String, + pub messages: Vec, +} + +impl Chat { + pub fn new(room_id: String) -> Self { + Chat { + room_id, + messages: vec![], + } + } + + pub fn add_message(&mut self, mut message: Message) -> bool { + message.calculate_hash(); + if !self.is_message_exists(message.clone()) { + self.messages.push(message); + return true; + } + false + } + + fn is_message_exists(&self, new_message: Message) -> bool { + for message in &self.messages { + if new_message.hash == message.hash { + return true; + } + } + false + } + + pub async fn message_cleaner(&mut self, max_message_count: u64) { + loop { + tokio::time::sleep(Duration::from_secs(1)).await; + if self.messages.len() > max_message_count as usize { + self.messages.clear(); + } + } + } +} + +impl Message { + pub fn new(sender: String, data: String) -> Self { + Message { + hash: String::new(), + sender, + time_received: Utc::now().timestamp_millis() as u64, + data, + } + } + fn calculate_hash(&mut self) { + let message = Message { + hash: String::new(), + sender: self.sender.clone(), + time_received: self.time_received, + data: self.data.clone(), + }; + let message = serde_json::to_string(&message).unwrap(); + + let mut hasher = Sha3_512::new(); + hasher.update(message); + let hash = hasher.finalize(); + self.hash = format!("{:x}", hash); + } +} diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..3abb9b7 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,33 @@ +use std::{net::IpAddr, sync::Arc}; + +use chat::Chat; +use tokio::sync::Mutex; + +pub mod chat; +pub mod routing; +pub mod test; +pub mod utils; + +pub struct ServerConfig { + pub ip_address: IpAddr, + pub port: u16, + pub max_message_counter: u64, +} + +#[derive(Debug, Clone)] +pub struct AppState { + pub chats: Arc>>, + pub max_message_counter: u64, +} + +impl AppState { + pub async fn is_chat_exists(&mut self, room_id: &String) -> Option { + let chats = self.chats.lock().await; + for i in 0..chats.len() { + if chats[i].room_id == *room_id { + return Some(i); + } + } + None + } +} diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..ce96916 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,28 @@ +use std::{net::SocketAddr, sync::Arc}; + +use acapair_chat_api::{ + routing::routing, + utils::{read_server_config, tls_config}, + AppState, +}; +use tokio::sync::Mutex; + +#[tokio::main] +async fn main() { + println!("Hello, world!"); + + let server_config = read_server_config(); + + let tls_config = tls_config().await; + + let state = AppState { + chats: Arc::new(Mutex::new(vec![])), + max_message_counter: server_config.max_message_counter, + }; + let app = routing(axum::extract::State(state)).await; + let addr = SocketAddr::new(server_config.ip_address, server_config.port); + axum_server::bind_rustls(addr, tls_config) + .serve(app.into_make_service()) + .await + .unwrap(); +} diff --git a/src/routing.rs b/src/routing.rs new file mode 100644 index 0000000..8726e25 --- /dev/null +++ b/src/routing.rs @@ -0,0 +1,79 @@ +use axum::{ + extract::State, + http::StatusCode, + response::IntoResponse, + routing::{get, post}, + Json, Router, +}; +use serde::{Deserialize, Serialize}; +use tower_http::cors::CorsLayer; + +use crate::{ + chat::{Chat, Message}, + AppState, +}; + +#[derive(Debug, Deserialize)] +struct ReceivedMessage { + room_id: String, + username: String, + message: String, +} +#[derive(Debug, Serialize, Deserialize)] +struct SendRequest { + room_id: String, +} + +pub async fn routing(State(state): State) -> Router { + Router::new() + .route("/", get(alive)) + .route("/send", post(receive_message)) + .route("/receive", get(send_message)) + .layer(CorsLayer::permissive()) + .with_state(state) +} + +async fn alive() -> impl IntoResponse { + let alive_json = serde_json::json!({ + "server_status":"Alive", + }); + (StatusCode::OK, Json(alive_json)) +} + +async fn receive_message( + State(mut state): State, + Json(received_message): Json, +) { + let sender = received_message.username; + let data = received_message.message; + let room_id = received_message.room_id; + let message = Message::new(sender, data); + match state.is_chat_exists(&room_id).await { + Some(index) => { + let mut chats = state.chats.lock().await; + chats[index].add_message(message); + } + None => { + let mut new_chat = Chat::new(room_id); + new_chat.add_message(message); + let mut chats = state.chats.lock().await; + chats.push(new_chat); + } + } +} + +async fn send_message( + State(mut state): State, + Json(send_request): Json, +) -> impl IntoResponse { + match state.is_chat_exists(&send_request.room_id).await { + Some(index) => { + let chats = state.chats.lock().await; + ( + StatusCode::OK, + serde_json::to_string(&chats[index]).unwrap(), + ) + } + None => (StatusCode::BAD_REQUEST, serde_json::to_string("").unwrap()), + } +} diff --git a/src/test.rs b/src/test.rs new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/src/test.rs @@ -0,0 +1 @@ + diff --git a/src/utils.rs b/src/utils.rs new file mode 100644 index 0000000..e765588 --- /dev/null +++ b/src/utils.rs @@ -0,0 +1,35 @@ +use std::{fs::File, io::Read}; + +use axum_server::tls_rustls::RustlsConfig; + +use crate::ServerConfig; + +pub fn read_server_config() -> ServerConfig { + let mut config_file = File::open("configs/server_config.txt").unwrap(); + + let mut configs_buf = String::new(); + config_file.read_to_string(&mut configs_buf).unwrap(); + + let configs_uncleaned: Vec<&str> = configs_buf.split('\n').collect(); + + let ip_address: Vec<&str> = configs_uncleaned[0].split(':').collect(); + let ip_address = ip_address[1].parse().unwrap(); + + let port: Vec<&str> = configs_uncleaned[1].split(':').collect(); + let port = port[1].parse().unwrap(); + + let max_message_counter: Vec<&str> = configs_uncleaned[2].split(':').collect(); + let max_message_counter = max_message_counter[1].parse().unwrap(); + + ServerConfig { + ip_address, + port, + max_message_counter, + } +} + +pub async fn tls_config() -> RustlsConfig { + RustlsConfig::from_pem_file("certificates/fullchain.pem", "certificates/privkey.pem") + .await + .unwrap() +}