Skip to content

Commit

Permalink
Merge pull request #3 from EleisonC/sign-up-success-route-impl
Browse files Browse the repository at this point in the history
[sprint3][task2]ft-impl sign up route success route
  • Loading branch information
EleisonC authored May 23, 2024
2 parents 459f25d + 56d4280 commit 833f253
Show file tree
Hide file tree
Showing 10 changed files with 218 additions and 11 deletions.
19 changes: 19 additions & 0 deletions auth-service/src/app_state.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
use std::sync::Arc;
use tokio::sync::RwLock;

use crate::services::hashmap_user_store::HashmapUserStore;

pub type UserStoreType = Arc<RwLock<HashmapUserStore>>;

#[derive(Clone)]
pub struct AppState {
pub user_store: UserStoreType,
}

impl AppState {
pub fn new(user_store: UserStoreType) -> Self {
Self {
user_store
}
}
}
3 changes: 3 additions & 0 deletions auth-service/src/domain/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
mod user;

pub use user::*;
16 changes: 16 additions & 0 deletions auth-service/src/domain/user.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
#[derive(Clone, Debug, PartialEq)]
pub struct User {
pub email: String,
pub password: String,
pub requires2fa: bool
}

impl User {
pub fn new(email: String, password: String, requires2fa: bool) -> Self {
User {
email,
password,
requires2fa
}
}
}
13 changes: 10 additions & 3 deletions auth-service/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,19 @@
use std::error::Error;
use axum::{
response::{Html, IntoResponse},
response::IntoResponse,
routing::{get, post},
serve::Serve, Router,
http::StatusCode
};
use tower_http::services::ServeDir;
use app_state::AppState;

pub mod routes;
pub mod services;
pub mod domain;
pub mod app_state;


pub struct Application {
server: Serve<Router, Router>,
// address is exposed as public field
Expand All @@ -16,7 +22,7 @@ pub struct Application {
}

impl Application {
pub async fn build(address: &str) -> Result<Self, Box<dyn Error>> {
pub async fn build(app_state: AppState, address: &str) -> Result<Self, Box<dyn Error>> {
// Move the Router difinition from main.rs to here
// Also, remove the `hello` route
// we dont need it at this point!
Expand All @@ -26,7 +32,8 @@ impl Application {
.route("/login", post(login))
.route("/logout", get(logout))
.route("/verify-2fa", post(verify_2fa))
.route("/verify-token", post(verify_token));
.route("/verify-token", post(verify_token))
.with_state(app_state.clone());

let router = app;

Expand Down
9 changes: 7 additions & 2 deletions auth-service/src/main.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,13 @@
use auth_service::Application;
use std::sync::Arc;
use tokio::sync::RwLock;

use auth_service::{app_state::AppState, services, Application};

#[tokio::main]
async fn main() {
let app = Application::build("0.0.0.0:3000")
let user_store = services::HashmapUserStore::default();
let app_state = AppState::new(Arc::new(RwLock::new(user_store)));
let app = Application::build(app_state, "0.0.0.0:3000")
.await
.expect("Failed to build app");

Expand Down
25 changes: 21 additions & 4 deletions auth-service/src/routes/signup.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
use axum::{http::StatusCode, response::IntoResponse, Json};
use serde::Deserialize;
use axum::{http::StatusCode, extract::State,
response::IntoResponse, Json};
use serde::{Deserialize, Serialize};

use crate::{app_state::AppState, domain::User};
#[derive(Deserialize)]
pub struct SignupRequest {
pub email:String,
Expand All @@ -9,6 +11,21 @@ pub struct SignupRequest {
pub requires_2fa: bool,
}

pub async fn signup(Json(request): Json<SignupRequest>) -> impl IntoResponse {
StatusCode::OK.into_response()
#[derive(Serialize, Deserialize, Debug, PartialEq)]
pub struct SignupResponse {
pub message: String
}

pub async fn signup(State(state): State<AppState>, Json(request): Json<SignupRequest>) -> impl IntoResponse {
let user = User::new(request.email, request.password, request.requires_2fa);

let mut user_store = state.user_store.write().await;

user_store.add_user(user).unwrap();

let response = Json(SignupResponse {
message: "User created successfully!".to_string()
});

(StatusCode::CREATED, response)
}
99 changes: 99 additions & 0 deletions auth-service/src/services/hashmap_user_store.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
use std::collections::HashMap;
use crate::domain::User;

#[derive(Debug, PartialEq)]
pub enum UserStoreError {
UserAlreadyExists,
UserNotFound,
InvalidCredentials,
UnexpectedError,
}

#[derive(Default, Debug)]
pub struct HashmapUserStore {
users: HashMap<String, User>
}

impl HashmapUserStore {
pub fn add_user(&mut self, user: User) -> Result<(), UserStoreError> {
let current_user_email = user.email.clone();

if self.users.contains_key(&current_user_email) {
return Err(UserStoreError::UserAlreadyExists)
}

self.users.insert(current_user_email, user);
Ok(())
}

pub fn get_user(&self, email: &str) -> Result<User, UserStoreError> {
if let Some(user) = self.users.get(email) {
return Ok(user.clone())
} else {
Err(UserStoreError::UserNotFound)
}
}

pub fn validate_user(&self, email: &str, password: &str) -> Result<(), UserStoreError> {
if let Some(user) = self.users.get(email) {
if user.password == password {
return Ok(())
}
return Err(UserStoreError::InvalidCredentials)
} else {
return Err(UserStoreError::UserNotFound)
}
}
}

#[cfg(test)]
mod tests {
use super::*;

#[tokio::test]
async fn test_add_user() {
let mut store = HashmapUserStore::default();
let user = User::new(
"[email protected]".to_string(),
"password".to_string(),
false
);

let result = store.add_user(user.clone());
assert_eq!(result, Ok(()));
}

#[tokio::test]
async fn test_get_user() {
let mut store = HashmapUserStore::default();
let user = User::new(
"[email protected]".to_string(),
"password".to_string(),
false
);
store.add_user(user.clone()).unwrap();

let result = store.get_user(&user.email);
match result {
Ok(u) => assert_eq!(u, user),
Err(e) => panic!("Expected Ok, got Err: {:?}", e),
}
}

#[tokio::test]
async fn test_validate_user() {
let mut store = HashmapUserStore::default();
let user = User::new(
"[email protected]".to_string(),
"password".to_string(),
true
);

store.add_user(user.clone()).unwrap();

let result = store.validate_user(&user.email, &user.password);
assert_eq!(result, Ok(()));
}
}


4 changes: 4 additions & 0 deletions auth-service/src/services/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
pub mod hashmap_user_store;


pub use hashmap_user_store::*;
8 changes: 6 additions & 2 deletions auth-service/tests/api/helpers.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
use auth_service::Application;
use auth_service::{app_state::AppState, services, Application};
use uuid::Uuid;
use std::sync::Arc;
use tokio::sync::RwLock;


pub struct TestApp {
Expand All @@ -9,7 +11,9 @@ pub struct TestApp {

impl TestApp {
pub async fn new() -> Self {
let app = Application::build("127.0.0.1:0")
let test_user_store = services::HashmapUserStore::default();
let test_app_state = AppState::new(Arc::new(RwLock::new(test_user_store)));
let app = Application::build(test_app_state, "127.0.0.1:0")
.await
.expect("Failed to build app");

Expand Down
33 changes: 33 additions & 0 deletions auth-service/tests/api/signup.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use crate::helpers::{self, TestApp};
use auth_service::routes::SignupResponse;

#[tokio::test]
async fn should_return_422_if_malformed_input() {
Expand Down Expand Up @@ -38,3 +39,35 @@ async fn should_return_422_if_malformed_input() {
}
}

#[tokio::test]
async fn should_return_201_if_valid_input() {
let app = TestApp::new().await;

let random_email = helpers::get_random_email();

let valid_data = serde_json::json!({
"email": random_email,
"password": "string",
"requires2FA": true
});

let response = app.signup(&valid_data).await;

assert_eq!(response.status().as_u16(),
201,
"User created successfully!"
);

let expected_response = SignupResponse {
message: "User created successfully!".to_owned(),
};

assert_eq!(
response
.json::<SignupResponse>()
.await
.expect("Could not deserialize response body to UserBody"),
expected_response
);
}

0 comments on commit 833f253

Please sign in to comment.