Skip to content

Commit

Permalink
chore:experimental modifications, part of re-writting backend in rust…
Browse files Browse the repository at this point in the history
… fully, wip
  • Loading branch information
DeveloperMindset123 committed Dec 28, 2024
1 parent 75a0892 commit 77bbe46
Show file tree
Hide file tree
Showing 3 changed files with 274 additions and 127 deletions.
172 changes: 45 additions & 127 deletions server/src/main.rs
Original file line number Diff line number Diff line change
@@ -1,129 +1,47 @@
// #[allow(unused_imports)]
// #[allow(dead_code)]
// #[allow(unused_variables)]
// #[macro_use] extern crate rocket;
// #[cfg(test)] mod test;
// use rocket::serde::json::{json, Value};
// use rocket::{State, Shutdown};
// use rocket::fs::{relative, FileServer};
// use rocket::form::Form;
// use rocket::response::stream::{EventStream, Event};
// use rocket::serde::{Serialize, Deserialize};
// use rocket::tokio::sync::broadcast::{channel, Sender, error::RecvError};
// use rocket::tokio::select;
// mod utils;

// // type Id = usize;
// // type MessageList = Mutex<Vec<String>>;
// // type Messages<'r> = &'r State<MessageList>; // nested type alias

// /// only primary modification has been made to Message
// // #[derive(Debug, Clone, FromForm, Serialize, Deserialize)]
// // #[cfg_attr(test, derive(PartialEq, UriDisplayQuery))]
// // #[serde(crate = "rocket::serde")]
// // struct Message {
// // #[field(validate = len(1..20))]
// // pub room: String,
// // #[field(validate = len(1..50))]
// // pub user_email: String,
// // pub sender : String,
// // pub reciever : String,
// // // TODO : uncomment later
// // // pub sequenceNumber : u32,

// // #[field(validate=len(1..300))]
// // pub message: String,
// // }

// #[derive(Debug, Clone, FromForm, Serialize, Deserialize)]
// #[cfg_attr(test, derive(PartialEq, UriDisplayQuery))]
// #[serde(crate = "rocket::serde")]
// struct Message {
// #[field(validate = len(2..30))]
// pub room: String,

// #[field(validate = len(2..20))]
// pub user_email: String,
// pub sender : String,
// #[field(validate = len(..300))]
// pub message: String,
// }
// /// recieve a message from a form submission and broadcast it to any recievers
// /// /events/<reciever> originally
// #[get("/chat/events")]
// async fn events(
// queue : &State<Sender<Message>>, mut end : Shutdown) -> EventStream![] {
// let mut rx = queue.subscribe();
// EventStream! {
// loop {
// let msg = select! {
// msg = rx.recv() => match msg {
// Ok(msg) => msg,
// Err(RecvError::Closed) => break,
// Err(RecvError::Lagged(_)) => continue,
// },
// _ = &mut end => break,
// };

// // println!("Message sent successfully!");
// yield Event::json(&msg);
// }
// }
// }

// #[post("/chat/message", data = "<form>")]
// fn post(form: Form<Message>, queue: &State<Sender<Message>>) {
// let _res = queue.send(form.into_inner());

// // format!("The response is {:#?}", _res);
// println!("The response is {:#?}", _res);
// }

// // handles HTTP 404 code, returns this messages
// #[catch(404)]
// fn not_found() -> Value {
// json!({
// "status" : "error",
// "reason" : "Resource was not found."
// })
// }
// // #[post("/test_submission")]
// // the return type is automatically inferrred when set to _
// // #[launch] indicates function to execute when server starts
// #[launch]
// fn rocket() -> _ {
// // the launch function returns an instance of
// // Rocket<Build> from a function named rocket
// // routes parameter takes in an array of values
// // specifies the type for the channel
// // to be of the same as the struct we have defined
// // the .build() method creates a new Rocket application using the default configuration provider

// // the routes! macro takes in array containing information
// // regarding each of the functions corresponding to the routes
// // "/chat" specifies the base url
// // in this case, that would be http://localhost:8000/chat/message_send for example --> this post method will contain the JSON data as the payload.
// rocket::build().manage(channel::<Message>(1024).0).mount("/", routes![post,
// events,
// shutdown_custom
// ])
// }

// // // define the function we can use to make our api calls
// // // 2 parameters accepted
// // async fn make_api_calls(client : &Client, apiEndpoint : &str) -> Result<Value, reqwest::Error> {
// // // we can format the url using the format!() macro
// // // the return type will either be successful (Value) or result in an error that will be "containerized"
// // let endpoint_url = format!("http://localhost:8000/{apiEndpoint}");
// // let response = client.get(&endpoint_url).send().await?;

// // // convert the response into json data format
// // let result_value = response.json::<Value>().await?;
// // // explicit return
// // Ok(result_value)

// // }
#[macro_use]
extern crate diesel;
use actix::*;
use actix_cors::Cors;
use actix_files::Files;
use actix_web::{http, web, App, HttpServer};
use diesel::{
prelude::*,
r2d2::{self, ConnectionManager},
};
use diesel::pg::PgConnection;
use std::env;
mod db;
mod models;
mod routes;
mod schema;
mod session;

pub type PostgresPool = Pool<ConnectionManager<PgConnection>>;

pub async fn get_pool() -> PostgresPool {
dotenv().ok();
let url = env::var("DATABASE_URL").expect("no DB URL");

let migr = ConnectionManager::<PgConnection>::new(url);
r2d2::Pool::builder().build(migr).expect("could not build connection pool")
}

pub fn main() {
println!("Complete reset");
// TODO : Test this
#[actix_web::main]
async fn main() -> std::io::Result<()> {
let server = server::ChatServer::new().start;
let server_addr = "127.0.0.1";
let server_port = "8080";
let pool = get_pool().await;
let app = HttpServer::new(move || {
let cors = Cors::default().allowed_origin("http://localhost:3000").allowed_origin("http://localhost:8080").allowed_methods(vec!["GET","POST"]).allowed_headers(vec![http::header::Authorization, http::header::ACCEPT]).allowed_header(http::header::CONTENT_TYPE).max_age(3600);

// TODO : change from /ws to /websocket for clarity
// after functionality has been established
App::new().app_data(web::Data::new(server.clone())).app_data(web::Data::new(pool.clone())).wrap(cors).service("/ws", web::get().to(routes::chat_server)).service(routes::create_user).service(routes::get_user_by_id).service(routes::get_user_by_phone).service(routes::get_conversation_by_id).service(routes::get_rooms).service(Files::new("/", "/.static"))
}).workers(2).bind({server_addr, server_port})?.run();
println!("Server running at http://{server_addr}:{server_port}/");

// closure
app.await();
}
149 changes: 149 additions & 0 deletions server/src/routes.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
use std::time::Instant;
use actix::*;
use actix_files::NamedFile;
use actix_web::{get, post, web, Error, HttpRequest, HttpResponse, Responder};
use actix_web_actors::ws; // websocket
use diesel::{
prelude::*,
r2d2::{self, ConnectionManager},
};
use serde_json::json;
use uuid::Uuid;
use crate::db;
use crate::server;
use crate::session;

// NOTE: this is being re-defined here
pub type PostgresPool = Pool<ConnectionManager<PgConnection>>;

pub async fn index() -> impl Responder {
NamedFile::open_async("./static/index.html").await.unwrap();
}

pub async fn chat_server(
req : HttpRequest,
stream : web::Payload,
pool : web::Data<DbPool>,
srv : web::Data<Addr<server::ChatServer>>
) -> Result<HttpResponse, Error> {
ws::start(
session::WsChatSession {
id : 0,
hb : Instant::now(),
room : "main".to_string(),
name : None,
addr : srv.get_ref().clone(),
db_pool : pool
},
&req,
stream
)
}


/// actix_web::error::ErrorUnprocessableEntity is a helper function that wraps any kind of errors to generate UNPROCESSABLE_ENTITY message instead.
#[post("/users/create")]
pub async fn create_user(
pool : web::Data<DbPool>,
form : web::Json<models::NewUser>
) -> Result<HttpResponse, Error> {
let user = web::block(move || {
let mut conn = pool.get()?;

// may not need a phone number
// we want only distinct emails
// replace phone with emails
db::insert_new_user(&mut conn, &form.username, &form.email, &form.password)
}).await?
.map_err(actix_web::error::ErrorUnprocessableEntity)?;
Ok(HttpResponse::Ok().json(user))
}


/// function to retrieve information about an user given their id
#[get("/users/{user_id}")]
pub async fn get_user_by_id(
pool : web::Data<DbPool>,
id : web::Path<Uuid>
) -> Result<HttpResponse, Error> {
let user_id = id.to_owned();
let user = web::block(move || {
let mut conn = pool.get()?;
db::find_user_by_uid(&mut conn, user_id)
})
.await?
.map_err(actix_web::error::ErrorInternalServerError)?;

if let Some(user) = user {
Ok(HttpResponse::Ok().json(user))
} else {
let res = HttpResponse::NotFound().body(
json!({
"error" : 404,
"message" : format!("No user found with user id : {id}")
}).to_string(),
);
Ok(res)
}
}

/// function to define an user given their email
/// originally /users/phone/{phone_number}
#[get("/users/email/{email_address}")]
pub async fn get_user_by_email
/// function that attempts to retrieve conversation history
/// given a particular room id
/// actix_web::error::ErrorInternalServerError : helper function to wrap the errors and instead generating "INTERNAL_SERVER_ERROR" response instead
#[get("/conversations/{uid}")]
pub async fn get_conversation_by_id(
pool : web::Data<DbPool>,
uid : web::Path<Uuid>,
) -> Result<HttpResponse, Error> {
let room_id = uid.to_owned();

// define conversations as a closure
let conversations = web::block(move || {
let mut conn = pool.get()?;
db::get_conversation_by_id(&mut conn, room_id)
})
.await?
.map_err(actix_web::error::ErrorInternalServerError)?;
if let Some(data) = conversations {
Ok(HttpResponse::Ok().json(data))
} else {
let res = HttpResponse::NotFound().body(
json!({
"error" : 404,
"message" : format!("No conversation with room id : {room_id}")
}).to_string(),
);
Ok(res);
}
}

#[get("/rooms")]
pub async fn get_rooms(
pool : web::Data<DbPool>
) -> Result<HttpResponse, Error> {

// map_err : is used to transform one type of error to another
let rooms = web::block(move || {
let mut conn = pool.get()?;
db::get_all_rooms(&mut conn)
})
.await?
.map_err(actix_web::error::ErrorInternalServerError)?;

if !room.is_empty() {
Ok(HttpResponse::Ok().json(rooms))
} else {
let res = HttpResponse::NotFound().body(
json!({
"error" : 404,
"message" : "No Rooms available at the moment"
}).to_string(),
);

Ok(res)
}
}
Loading

0 comments on commit 77bbe46

Please sign in to comment.