diff --git a/src/callers/common.rs b/src/callers/common.rs index e69de29..b33a3fe 100644 --- a/src/callers/common.rs +++ b/src/callers/common.rs @@ -0,0 +1,54 @@ +pub mod response { + use serde::{Deserialize, Serialize}; + + #[derive(Deserialize, Serialize, utoipa::ToSchema)] + pub struct TestResult { + pub message: String, + } +} + +pub mod endpoint { + use super::*; + use axum::{Extension, Json, http::StatusCode}; + + /// Endpoint to hit the root + /// basic handler that responds with a static string + #[utoipa::path( + get, + path = super::super::endpoints::ROOT, + responses( + (status = 200, description = "Test", body = &str), + ) + )] + pub async fn root() -> &'static str { + "Hello, World!" + } + + /// Endpoint to do a database ping + #[utoipa::path( + get, + path = super::super::endpoints::DBTEST, + responses( + (status = 200, description = "Successful ping of the db", body = super::response::TestResult), + (status = 400, description = "Failure in pinging the db", body = super::response::TestResult) + ) + )] + pub async fn db_ping( + Extension(pool): Extension, + ) -> (StatusCode, Json) { + match sqlx::query("SELECT 1").execute(&pool).await { + Ok(_) => { + let tr = response::TestResult { + message: String::from("This works"), + }; + (StatusCode::OK, Json(tr)) + } + Err(e) => ( + StatusCode::BAD_REQUEST, + Json(response::TestResult { + message: e.to_string(), + }), + ), + } + } +} diff --git a/src/callers/mod.rs b/src/callers/mod.rs index e69de29..33ddec1 100644 --- a/src/callers/mod.rs +++ b/src/callers/mod.rs @@ -0,0 +1,8 @@ +pub mod common; +pub mod register; + +pub mod endpoints { + pub const ROOT: &str = "/"; + pub const REGISTER: &str = "/api/v2/register"; + pub const DBTEST: &str = "/api/v2/test/db"; +} diff --git a/src/callers/register.rs b/src/callers/register.rs index e69de29..dc644ff 100644 --- a/src/callers/register.rs +++ b/src/callers/register.rs @@ -0,0 +1,159 @@ +use axum::{Json, http::StatusCode}; + +use crate::hashing; +use crate::repo; + +pub mod request { + use serde::{Deserialize, Serialize}; + + #[derive(Default, Deserialize, Serialize, utoipa::ToSchema)] + pub struct Request { + #[serde(skip_serializing_if = "String::is_empty")] + pub username: String, + #[serde(skip_serializing_if = "String::is_empty")] + pub password: String, + #[serde(skip_serializing_if = "String::is_empty")] + pub email: String, + #[serde(skip_serializing_if = "String::is_empty")] + pub phone: String, + #[serde(skip_serializing_if = "String::is_empty")] + pub firstname: String, + #[serde(skip_serializing_if = "String::is_empty")] + pub lastname: String, + } +} + +pub mod response { + use serde::{Deserialize, Serialize}; + + #[derive(Deserialize, Serialize, utoipa::ToSchema)] + pub struct Response { + pub message: String, + pub data: Vec, + } +} + +/// Endpoint to register a user +#[utoipa::path( + post, + path = super::endpoints::REGISTER, + request_body( + content = request::Request, + description = "Data required to register", + content_type = "application/json" + ), + responses( + (status = 201, description = "User created", body = response::Response), + (status = 404, description = "User already exists", body = response::Response), + (status = 400, description = "Issue creating user", body = response::Response) + ) +)] +pub async fn register_user( + axum::Extension(pool): axum::Extension, + Json(payload): Json, +) -> (StatusCode, Json) { + let registration_enabled = match is_registration_enabled().await { + Ok(value) => value, + Err(err) => { + eprintln!("Error: {err:?}"); + return ( + axum::http::StatusCode::INTERNAL_SERVER_ERROR, + Json(response::Response { + message: String::from("Registration check failed"), + data: Vec::new(), + }), + ); + } + }; + + if registration_enabled { + let mut user = icarus_models::user::User { + username: payload.username.clone(), + password: payload.password.clone(), + email: payload.email.clone(), + phone: payload.phone.clone(), + firstname: payload.firstname.clone(), + lastname: payload.lastname.clone(), + status: String::from("Active"), + email_verified: true, + ..Default::default() + }; + + match repo::user::exists(&pool, &user.username).await { + Ok(res) => { + if res { + ( + StatusCode::BAD_REQUEST, + Json(response::Response { + message: String::from("Error"), + data: Vec::new(), + }), + ) + } else { + let salt_string = hashing::generate_salt().unwrap(); + let mut salt = textsender_models::user::salt::Salt::default(); + let generated_salt = salt_string; + salt.salt = generated_salt.to_string(); + salt.id = repo::salt::insert(&pool, &salt).await.unwrap(); + user.salt_id = salt.id; + let hashed_password = + hashing::hash_password(&user.password, &generated_salt).unwrap(); + user.password = hashed_password; + + match repo::user::insert(&pool, &user).await { + Ok((id, date_created)) => { + user.id = id; + user.date_created = date_created; + ( + StatusCode::CREATED, + Json(response::Response { + message: String::from("User created"), + data: vec![user], + }), + ) + } + Err(err) => ( + StatusCode::BAD_REQUEST, + Json(response::Response { + message: err.to_string(), + data: vec![user], + }), + ), + } + } + } + Err(err) => ( + StatusCode::BAD_REQUEST, + Json(response::Response { + message: err.to_string(), + data: vec![user], + }), + ), + } + } else { + ( + axum::http::StatusCode::NOT_ACCEPTABLE, + Json(response::Response { + message: String::from("Registration is not enabled"), + data: Vec::new(), + }), + ) + } +} + +/// Checks to see if registration is enabled +async fn is_registration_enabled() -> Result { + let key = String::from("ENABLE_REGISTRATION"); + let var = textsender_models::envy::environment::get_env(&key).await; + let parsed_value = var.value.to_uppercase(); + + if parsed_value == "TRUE" { + Ok(true) + } else if parsed_value == "FALSE" { + Ok(false) + } else { + Err(std::io::Error::other( + "Could not determine value of ENABLE_REGISTRATION", + )) + } +}