feat: ✨ send && receive messages
This commit is contained in:
parent
1491669001
commit
ff5512fe26
9 changed files with 281 additions and 0 deletions
7
.gitignore
vendored
7
.gitignore
vendored
|
@ -2,6 +2,8 @@
|
||||||
# will have compiled files and executables
|
# will have compiled files and executables
|
||||||
debug/
|
debug/
|
||||||
target/
|
target/
|
||||||
|
/certificates
|
||||||
|
/.vscode
|
||||||
|
|
||||||
# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries
|
# 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
|
# 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
|
# MSVC Windows builds of rustc generate these, which store debugging information
|
||||||
*.pdb
|
*.pdb
|
||||||
|
|
||||||
|
|
||||||
|
# Added by cargo
|
||||||
|
|
||||||
|
/target
|
||||||
|
|
16
Cargo.toml
Normal file
16
Cargo.toml
Normal 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"] }
|
3
configs/server_config.txt
Normal file
3
configs/server_config.txt
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
axum_address:192.168.1.2
|
||||||
|
port:2334
|
||||||
|
max_message:150
|
79
src/chat.rs
Normal file
79
src/chat.rs
Normal 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
33
src/lib.rs
Normal 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
28
src/main.rs
Normal 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
79
src/routing.rs
Normal 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
1
src/test.rs
Normal file
|
@ -0,0 +1 @@
|
||||||
|
|
35
src/utils.rs
Normal file
35
src/utils.rs
Normal 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()
|
||||||
|
}
|
Loading…
Add table
Add a link
Reference in a new issue