From bf5bcf99924a5c74e0893f0684a1270402ff9fb0 Mon Sep 17 00:00:00 2001 From: Ben Grant Date: Mon, 15 May 2023 16:57:13 +1000 Subject: [PATCH] Co-locate related routes --- backend/src/docs.rs | 12 +-- backend/src/main.rs | 18 ++-- .../src/routes/{create_event.rs => event.rs} | 39 ++++++- backend/src/routes/get_event.rs | 42 -------- backend/src/routes/get_people.rs | 42 -------- backend/src/routes/mod.rs | 20 +--- .../src/routes/{get_person.rs => person.rs} | 100 +++++++++++++++++- backend/src/routes/{get_stats.rs => stats.rs} | 0 backend/src/routes/update_person.rs | 81 -------------- 9 files changed, 157 insertions(+), 197 deletions(-) rename backend/src/routes/{create_event.rs => event.rs} (75%) delete mode 100644 backend/src/routes/get_event.rs delete mode 100644 backend/src/routes/get_people.rs rename backend/src/routes/{get_person.rs => person.rs} (53%) rename backend/src/routes/{get_stats.rs => stats.rs} (100%) delete mode 100644 backend/src/routes/update_person.rs diff --git a/backend/src/docs.rs b/backend/src/docs.rs index 372ce2a..2ab310d 100644 --- a/backend/src/docs.rs +++ b/backend/src/docs.rs @@ -11,12 +11,12 @@ use utoipa::{ #[openapi( info(title = "Crab Fit API"), paths( - routes::get_stats::get_stats, - routes::create_event::create_event, - routes::get_event::get_event, - routes::get_people::get_people, - routes::get_person::get_person, - routes::update_person::update_person, + routes::stats::get_stats, + routes::event::create_event, + routes::event::get_event, + routes::person::get_people, + routes::person::get_person, + routes::person::update_person, ), components(schemas( payloads::StatsResponse, diff --git a/backend/src/main.rs b/backend/src/main.rs index 30d9194..10f4938 100644 --- a/backend/src/main.rs +++ b/backend/src/main.rs @@ -69,12 +69,18 @@ async fn main() { let app = Router::new() .merge(SwaggerUi::new("/docs").url("/docs/openapi.json", ApiDoc::openapi())) .route("/", get(get_root)) - .route("/stats", get(get_stats)) - .route("/event", post(create_event)) - .route("/event/:event_id", get(get_event)) - .route("/event/:event_id/people", get(get_people)) - .route("/event/:event_id/people/:person_name", get(get_person)) - .route("/event/:event_id/people/:person_name", patch(update_person)) + .route("/stats", get(stats::get_stats)) + .route("/event", post(event::create_event)) + .route("/event/:event_id", get(event::get_event)) + .route("/event/:event_id/people", get(person::get_people)) + .route( + "/event/:event_id/people/:person_name", + get(person::get_person), + ) + .route( + "/event/:event_id/people/:person_name", + patch(person::update_person), + ) .with_state(shared_state) .layer(cors) .layer(rate_limit) diff --git a/backend/src/routes/create_event.rs b/backend/src/routes/event.rs similarity index 75% rename from backend/src/routes/create_event.rs rename to backend/src/routes/event.rs index 0de6a4b..8f7945c 100644 --- a/backend/src/routes/create_event.rs +++ b/backend/src/routes/event.rs @@ -1,14 +1,49 @@ -use axum::{extract, http::StatusCode, Json}; +use axum::{ + extract::{self, Path}, + http::StatusCode, + Json, +}; use common::{adaptor::Adaptor, event::Event}; use rand::{seq::SliceRandom, thread_rng, Rng}; use regex::Regex; use crate::{ errors::ApiError, - payloads::{EventInput, EventResponse}, + payloads::{ApiResult, EventInput, EventResponse}, State, }; +#[utoipa::path( + get, + path = "/event/{event_id}", + params( + ("event_id", description = "The ID of the event"), + ), + responses( + (status = 200, description = "Ok", body = EventResponse), + (status = 404, description = "Not found"), + (status = 429, description = "Too many requests"), + ), + tag = "event", +)] +/// Get details about an event +pub async fn get_event( + extract::State(state): State, + Path(event_id): Path, +) -> ApiResult { + let adaptor = &state.lock().await.adaptor; + + let event = adaptor + .get_event(event_id) + .await + .map_err(ApiError::AdaptorError)?; + + match event { + Some(event) => Ok(Json(event.into())), + None => Err(ApiError::NotFound), + } +} + #[utoipa::path( post, path = "/event", diff --git a/backend/src/routes/get_event.rs b/backend/src/routes/get_event.rs deleted file mode 100644 index 36759fa..0000000 --- a/backend/src/routes/get_event.rs +++ /dev/null @@ -1,42 +0,0 @@ -use axum::{ - extract::{self, Path}, - Json, -}; -use common::adaptor::Adaptor; - -use crate::{ - errors::ApiError, - payloads::{ApiResult, EventResponse}, - State, -}; - -#[utoipa::path( - get, - path = "/event/{event_id}", - params( - ("event_id", description = "The ID of the event"), - ), - responses( - (status = 200, description = "Ok", body = EventResponse), - (status = 404, description = "Not found"), - (status = 429, description = "Too many requests"), - ), - tag = "event", -)] -/// Get details about an event -pub async fn get_event( - extract::State(state): State, - Path(event_id): Path, -) -> ApiResult { - let adaptor = &state.lock().await.adaptor; - - let event = adaptor - .get_event(event_id) - .await - .map_err(ApiError::AdaptorError)?; - - match event { - Some(event) => Ok(Json(event.into())), - None => Err(ApiError::NotFound), - } -} diff --git a/backend/src/routes/get_people.rs b/backend/src/routes/get_people.rs deleted file mode 100644 index cee8f6b..0000000 --- a/backend/src/routes/get_people.rs +++ /dev/null @@ -1,42 +0,0 @@ -use axum::{ - extract::{self, Path}, - Json, -}; -use common::adaptor::Adaptor; - -use crate::{ - errors::ApiError, - payloads::{ApiResult, PersonResponse}, - State, -}; - -#[utoipa::path( - get, - path = "/event/{event_id}/people", - params( - ("event_id", description = "The ID of the event"), - ), - responses( - (status = 200, description = "Ok", body = [PersonResponse]), - (status = 404, description = "Event not found"), - (status = 429, description = "Too many requests"), - ), - tag = "person", -)] -/// Get availabilities for an event -pub async fn get_people( - extract::State(state): State, - Path(event_id): Path, -) -> ApiResult, A> { - let adaptor = &state.lock().await.adaptor; - - let people = adaptor - .get_people(event_id) - .await - .map_err(ApiError::AdaptorError)?; - - match people { - Some(people) => Ok(Json(people.into_iter().map(|p| p.into()).collect())), - None => Err(ApiError::NotFound), - } -} diff --git a/backend/src/routes/mod.rs b/backend/src/routes/mod.rs index e9e6bed..5a4889d 100644 --- a/backend/src/routes/mod.rs +++ b/backend/src/routes/mod.rs @@ -1,17 +1,3 @@ -pub mod get_event; -pub use get_event::get_event; - -pub mod get_stats; -pub use get_stats::get_stats; - -pub mod create_event; -pub use create_event::create_event; - -pub mod get_people; -pub use get_people::get_people; - -pub mod get_person; -pub use get_person::get_person; - -pub mod update_person; -pub use update_person::update_person; +pub mod event; +pub mod person; +pub mod stats; diff --git a/backend/src/routes/get_person.rs b/backend/src/routes/person.rs similarity index 53% rename from backend/src/routes/get_person.rs rename to backend/src/routes/person.rs index 2f127e9..ec249d7 100644 --- a/backend/src/routes/get_person.rs +++ b/backend/src/routes/person.rs @@ -8,10 +8,41 @@ use common::{adaptor::Adaptor, person::Person}; use crate::{ errors::ApiError, - payloads::{ApiResult, PersonResponse}, + payloads::{ApiResult, PersonInput, PersonResponse}, State, }; +#[utoipa::path( + get, + path = "/event/{event_id}/people", + params( + ("event_id", description = "The ID of the event"), + ), + responses( + (status = 200, description = "Ok", body = [PersonResponse]), + (status = 404, description = "Event not found"), + (status = 429, description = "Too many requests"), + ), + tag = "person", +)] +/// Get availabilities for an event +pub async fn get_people( + extract::State(state): State, + Path(event_id): Path, +) -> ApiResult, A> { + let adaptor = &state.lock().await.adaptor; + + let people = adaptor + .get_people(event_id) + .await + .map_err(ApiError::AdaptorError)?; + + match people { + Some(people) => Ok(Json(people.into_iter().map(|p| p.into()).collect())), + None => Err(ApiError::NotFound), + } +} + #[utoipa::path( get, path = "/event/{event_id}/people/{person_name}", @@ -95,6 +126,73 @@ pub async fn get_person( } } +#[utoipa::path( + patch, + path = "/event/{event_id}/people/{person_name}", + params( + ("event_id", description = "The ID of the event"), + ("person_name", description = "The name of the person"), + ), + security((), ("password" = [])), + request_body(content = PersonInput, description = "Person details"), + responses( + (status = 200, description = "Ok", body = PersonResponse), + (status = 401, description = "Incorrect password"), + (status = 404, description = "Event or person not found"), + (status = 415, description = "Unsupported input format"), + (status = 422, description = "Invalid input provided"), + (status = 429, description = "Too many requests"), + ), + tag = "person", +)] +/// Update a person's availabilities +pub async fn update_person( + extract::State(state): State, + Path((event_id, person_name)): Path<(String, String)>, + bearer: Option>>, + Json(input): Json, +) -> ApiResult { + let adaptor = &state.lock().await.adaptor; + + let existing_people = adaptor + .get_people(event_id.clone()) + .await + .map_err(ApiError::AdaptorError)?; + + // Event not found + if existing_people.is_none() { + return Err(ApiError::NotFound); + } + + // Check if the user exists + let existing_person = existing_people + .unwrap() + .into_iter() + .find(|p| p.name == person_name) + .ok_or(ApiError::NotFound)?; + + // Verify password (if set) + if !verify_password(&existing_person, parse_password(bearer)) { + return Err(ApiError::NotAuthorized); + } + + Ok(Json( + adaptor + .upsert_person( + event_id, + Person { + name: existing_person.name, + password_hash: existing_person.password_hash, + created_at: existing_person.created_at, + availability: input.availability, + }, + ) + .await + .map_err(ApiError::AdaptorError)? + .into(), + )) +} + pub fn parse_password(bearer: Option>>) -> Option { bearer.map(|TypedHeader(Authorization(b))| { String::from_utf8( diff --git a/backend/src/routes/get_stats.rs b/backend/src/routes/stats.rs similarity index 100% rename from backend/src/routes/get_stats.rs rename to backend/src/routes/stats.rs diff --git a/backend/src/routes/update_person.rs b/backend/src/routes/update_person.rs deleted file mode 100644 index 8bb45e3..0000000 --- a/backend/src/routes/update_person.rs +++ /dev/null @@ -1,81 +0,0 @@ -use axum::{ - extract::{self, Path}, - headers::{authorization::Bearer, Authorization}, - Json, TypedHeader, -}; -use common::{adaptor::Adaptor, person::Person}; - -use crate::{ - errors::ApiError, - payloads::{ApiResult, PersonInput, PersonResponse}, - State, -}; - -use super::get_person::{parse_password, verify_password}; - -#[utoipa::path( - patch, - path = "/event/{event_id}/people/{person_name}", - params( - ("event_id", description = "The ID of the event"), - ("person_name", description = "The name of the person"), - ), - security((), ("password" = [])), - request_body(content = PersonInput, description = "Person details"), - responses( - (status = 200, description = "Ok", body = PersonResponse), - (status = 401, description = "Incorrect password"), - (status = 404, description = "Event or person not found"), - (status = 415, description = "Unsupported input format"), - (status = 422, description = "Invalid input provided"), - (status = 429, description = "Too many requests"), - ), - tag = "person", -)] -/// Update a person's availabilities -pub async fn update_person( - extract::State(state): State, - Path((event_id, person_name)): Path<(String, String)>, - bearer: Option>>, - Json(input): Json, -) -> ApiResult { - let adaptor = &state.lock().await.adaptor; - - let existing_people = adaptor - .get_people(event_id.clone()) - .await - .map_err(ApiError::AdaptorError)?; - - // Event not found - if existing_people.is_none() { - return Err(ApiError::NotFound); - } - - // Check if the user exists - let existing_person = existing_people - .unwrap() - .into_iter() - .find(|p| p.name == person_name) - .ok_or(ApiError::NotFound)?; - - // Verify password (if set) - if !verify_password(&existing_person, parse_password(bearer)) { - return Err(ApiError::NotAuthorized); - } - - Ok(Json( - adaptor - .upsert_person( - event_id, - Person { - name: existing_person.name, - password_hash: existing_person.password_hash, - created_at: existing_person.created_at, - availability: input.availability, - }, - ) - .await - .map_err(ApiError::AdaptorError)? - .into(), - )) -}