feat: send && receive messages

This commit is contained in:
Ahmet Kaan GÜMÜŞ 2024-05-26 05:25:37 +03:00
parent 1491669001
commit ff5512fe26
9 changed files with 281 additions and 0 deletions

7
.gitignore vendored
View file

@ -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

16
Cargo.toml Normal file
View file

@ -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"] }

View file

@ -0,0 +1,3 @@
axum_address:192.168.1.2
port:2334
max_message:150

79
src/chat.rs Normal file
View file

@ -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<Message>,
}
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);
}
}

33
src/lib.rs Normal file
View file

@ -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<Mutex<Vec<Chat>>>,
pub max_message_counter: u64,
}
impl AppState {
pub async fn is_chat_exists(&mut self, room_id: &String) -> Option<usize> {
let chats = self.chats.lock().await;
for i in 0..chats.len() {
if chats[i].room_id == *room_id {
return Some(i);
}
}
None
}
}

28
src/main.rs Normal file
View file

@ -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();
}

79
src/routing.rs Normal file
View file

@ -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<AppState>) -> 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<AppState>,
Json(received_message): Json<ReceivedMessage>,
) {
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<AppState>,
Json(send_request): Json<SendRequest>,
) -> 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()),
}
}

1
src/test.rs Normal file
View file

@ -0,0 +1 @@

35
src/utils.rs Normal file
View file

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