Co-locate related routes
This commit is contained in:
parent
aa3b323cb6
commit
bf5bcf9992
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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<A: Adaptor>(
|
||||
extract::State(state): State<A>,
|
||||
Path(event_id): Path<String>,
|
||||
) -> ApiResult<EventResponse, A> {
|
||||
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",
|
||||
|
|
@ -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<A: Adaptor>(
|
||||
extract::State(state): State<A>,
|
||||
Path(event_id): Path<String>,
|
||||
) -> ApiResult<EventResponse, A> {
|
||||
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),
|
||||
}
|
||||
}
|
||||
|
|
@ -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<A: Adaptor>(
|
||||
extract::State(state): State<A>,
|
||||
Path(event_id): Path<String>,
|
||||
) -> ApiResult<Vec<PersonResponse>, 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),
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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<A: Adaptor>(
|
||||
extract::State(state): State<A>,
|
||||
Path(event_id): Path<String>,
|
||||
) -> ApiResult<Vec<PersonResponse>, 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<A: Adaptor>(
|
|||
}
|
||||
}
|
||||
|
||||
#[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<A: Adaptor>(
|
||||
extract::State(state): State<A>,
|
||||
Path((event_id, person_name)): Path<(String, String)>,
|
||||
bearer: Option<TypedHeader<Authorization<Bearer>>>,
|
||||
Json(input): Json<PersonInput>,
|
||||
) -> ApiResult<PersonResponse, A> {
|
||||
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<TypedHeader<Authorization<Bearer>>>) -> Option<String> {
|
||||
bearer.map(|TypedHeader(Authorization(b))| {
|
||||
String::from_utf8(
|
||||
|
|
@ -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<A: Adaptor>(
|
||||
extract::State(state): State<A>,
|
||||
Path((event_id, person_name)): Path<(String, String)>,
|
||||
bearer: Option<TypedHeader<Authorization<Bearer>>>,
|
||||
Json(input): Json<PersonInput>,
|
||||
) -> ApiResult<PersonResponse, A> {
|
||||
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(),
|
||||
))
|
||||
}
|
||||
Loading…
Reference in a new issue