diff --git a/src/main.rs b/src/main.rs index 609a5e8..626d45d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -6,7 +6,7 @@ use utoipa_axum::{router::OpenApiRouter, routes}; use utoipa_swagger_ui::SwaggerUi; // tags -use v1::auth::AUTH_TAG; +use v1::{auth::AUTH_TAG, body_parts::BODY_PARTS_TAG, exercises::EXERCISES_TAG}; mod v1; mod structs; @@ -27,6 +27,8 @@ pub(crate) use utoipa::ToSchema; #[openapi( tags( (name = AUTH_TAG, description = "Authentication API endpoints"), + (name = EXERCISES_TAG, description = "Exercise API endpoints"), + (name = BODY_PARTS_TAG, description = "Body part API endpoints"), // (name = CUSTOMER_TAG, description = "Customer API endpoints"), // (name = ORDER_TAG, description = "Order API endpoints") ), @@ -71,8 +73,7 @@ async fn main() -> anyhow::Result<()> { .routes(routes!(health_check)) .routes(routes!(index)) .with_state(state.clone()) - .nest("/api/v1/auth", v1::auth::router(state.clone())) - .nest("/api/v1/exercises", v1::exercises::router(state.clone())) + .nest("/api/v1", v1::router(state.clone())) .split_for_parts(); let router = router.merge(SwaggerUi::new("/docs").url("/docs/openapi.json", api)); diff --git a/src/v1/auth/mod.rs b/src/v1/auth/mod.rs index e71ed2c..13a88af 100644 --- a/src/v1/auth/mod.rs +++ b/src/v1/auth/mod.rs @@ -7,7 +7,7 @@ pub mod login; pub const AUTH_TAG: &str = "auth"; -pub fn router(state: AppState) -> OpenApiRouter { +pub(super) fn router(state: AppState) -> OpenApiRouter { OpenApiRouter::new() .routes(routes!(signup::signup)) .routes(routes!(login::login)) diff --git a/src/v1/body_parts/create.rs b/src/v1/body_parts/create.rs new file mode 100644 index 0000000..071a5a6 --- /dev/null +++ b/src/v1/body_parts/create.rs @@ -0,0 +1,34 @@ +use extractors::users::UserId; +use sqlx::query; + +use crate::*; + +#[derive(Serialize, Deserialize, ToSchema)] +pub struct CreateBodyPartBody { + name: String, +} + +#[utoipa::path(post, path = "/create", responses((status = OK, body = String)), tag = super::BODY_PARTS_TAG)] +pub async fn create( + State(state): State, + UserId(user_id): UserId, + Json(body): Json, +) -> Result { + let is_admin = query!("SELECT is_admin FROM users WHERE id = $1", user_id) + .fetch_one(&*state.db) + .await? + .is_admin; + + if !is_admin { + return Err(AppError::Error(Errors::Unauthorized)); + } + + let out = query!( + "INSERT INTO body_parts (name) VALUES ($1) RETURNING id", + body.name + ) + .fetch_one(&*state.db) + .await?; + + Ok(out.id.to_string()) +} diff --git a/src/v1/body_parts/mod.rs b/src/v1/body_parts/mod.rs new file mode 100644 index 0000000..6b8d117 --- /dev/null +++ b/src/v1/body_parts/mod.rs @@ -0,0 +1,13 @@ +use crate::AppState; + +pub(super) use super::*; + +pub mod create; + +pub const BODY_PARTS_TAG: &str = "body_parts"; + +pub(super) fn router(state: AppState) -> OpenApiRouter { + OpenApiRouter::new() + .routes(routes!(create::create)) + .with_state(state) +} diff --git a/src/v1/exercises/create.rs b/src/v1/exercises/create.rs index a8eb977..8cbb576 100644 --- a/src/v1/exercises/create.rs +++ b/src/v1/exercises/create.rs @@ -1,6 +1,6 @@ -use chrono::{DateTime, Utc}; +use chrono::NaiveDateTime; use extractors::users::UserId; -use sqlx::{query, query_as}; +use sqlx::query; use structs::exercise_types::ExerciseType; use uuid::Uuid; @@ -12,6 +12,9 @@ pub struct CreateExerciseBody { // todo: make this an enum exercise_type: ExerciseType, description: String, + body_parts: Vec, + primary_muscles: Vec, + secondary_muscles: Vec, } #[derive(Debug, Deserialize, Serialize, Clone, sqlx::Type, PartialEq, sqlx::FromRow)] @@ -22,7 +25,7 @@ pub struct Exercise { pub description: String, pub author_id: Uuid, pub official: bool, - pub created_at: DateTime, + pub created_at: NaiveDateTime, } #[utoipa::path(post, path = "/create", responses((status = OK, body = String)), tag = super::EXERCISES_TAG)] @@ -36,24 +39,19 @@ pub async fn create( .await? .is_admin; - if !is_admin { - return Err(AppError::Error(Errors::Unauthorized)); - } - // INSERT INTO users (id, created_at, updated_at, role) VALUES ($1, NOW(), NOW(), ($2::text)::user_role) RETURNING id, created_at, updated_at, role as "role:UserRole" - // - let insert = query_as!( - Exercise, + query!( r#" - INSERT INTO exercises (name, exercise_type, description, author_id, official) - VALUES ($1, ($2::text)::exercise_type, $3, $4, $5) - RETURNING id, name, exercise_type, description, author_id, official, created_at + INSERT INTO exercises (name, exercise_type, official, author_id, description) + VALUES ($1, $2, $3, $4, $5) "#, body.name, - body.exercise_type.to_string(), - body.description, + body.exercise_type as ExerciseType, + is_admin, user, - false - ); + body.description + ) + .fetch_one(&*state.db) + .await?; todo!() } diff --git a/src/v1/exercises/mod.rs b/src/v1/exercises/mod.rs index 175e517..65e129d 100644 --- a/src/v1/exercises/mod.rs +++ b/src/v1/exercises/mod.rs @@ -6,7 +6,7 @@ pub mod create; pub const EXERCISES_TAG: &str = "exercises"; -pub fn router(state: AppState) -> OpenApiRouter { +pub(super) fn router(state: AppState) -> OpenApiRouter { OpenApiRouter::new() .routes(routes!(create::create)) .with_state(state) diff --git a/src/v1/mod.rs b/src/v1/mod.rs index 7d84948..8e53b96 100644 --- a/src/v1/mod.rs +++ b/src/v1/mod.rs @@ -1,5 +1,18 @@ pub(super) use utoipa_axum::router::OpenApiRouter; pub(super) use utoipa_axum::routes; +use crate::AppState; + pub mod auth; +pub mod body_parts; pub mod exercises; +pub mod muscles; + +pub fn router(state: AppState) -> OpenApiRouter { + OpenApiRouter::new() + .with_state(state.clone()) + .nest("/auth", auth::router(state.clone())) + .nest("/exercises", exercises::router(state.clone())) + .nest("/body_parts", body_parts::router(state.clone())) + .nest("/muscles", muscles::router(state.clone())) +} diff --git a/src/v1/muscles/create.rs b/src/v1/muscles/create.rs new file mode 100644 index 0000000..ec96fbb --- /dev/null +++ b/src/v1/muscles/create.rs @@ -0,0 +1,35 @@ +use extractors::users::UserId; +use sqlx::query; + +use crate::*; + +#[derive(Serialize, Deserialize, ToSchema)] +pub struct CreateMuscleBody { + name: String, + body_parts: Vec, +} + +#[utoipa::path(post, path = "/create", responses((status = OK, body = String)), tag = super::MUSCLES_TAG)] +pub async fn create( + State(state): State, + UserId(user_id): UserId, + Json(body): Json, +) -> Result { + let is_admin = query!("SELECT is_admin FROM users WHERE id = $1", user_id) + .fetch_one(&*state.db) + .await? + .is_admin; + + if !is_admin { + return Err(AppError::Error(Errors::Unauthorized)); + } + + let out = query!( + "INSERT INTO muscles (name) VALUES ($1) RETURNING id", + body.name + ) + .fetch_one(&*state.db) + .await?; + + Ok(out.id.to_string()) +} diff --git a/src/v1/muscles/mod.rs b/src/v1/muscles/mod.rs new file mode 100644 index 0000000..4f7394b --- /dev/null +++ b/src/v1/muscles/mod.rs @@ -0,0 +1,13 @@ +use crate::AppState; + +pub(super) use super::*; + +pub mod create; + +pub const MUSCLES_TAG: &str = "muscles"; + +pub(super) fn router(state: AppState) -> OpenApiRouter { + OpenApiRouter::new() + .routes(routes!(create::create)) + .with_state(state) +}