wip: I can't figure out the error with ENUM and the postgres type
This commit is contained in:
1
Cargo.lock
generated
1
Cargo.lock
generated
@@ -305,6 +305,7 @@ dependencies = [
|
|||||||
"iana-time-zone",
|
"iana-time-zone",
|
||||||
"js-sys",
|
"js-sys",
|
||||||
"num-traits",
|
"num-traits",
|
||||||
|
"serde",
|
||||||
"wasm-bindgen",
|
"wasm-bindgen",
|
||||||
"windows-targets 0.52.6",
|
"windows-targets 0.52.6",
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ sqlx = { version = "0.8.2", features = [
|
|||||||
"uuid",
|
"uuid",
|
||||||
] }
|
] }
|
||||||
uuid = { version = "1.4.1", features = ["serde", "v4"] }
|
uuid = { version = "1.4.1", features = ["serde", "v4"] }
|
||||||
chrono = "0.4.39"
|
chrono = { version = "0.4.26", features = ["serde"] }
|
||||||
dotenv = "0.15.0"
|
dotenv = "0.15.0"
|
||||||
argon2 = "0.5.3"
|
argon2 = "0.5.3"
|
||||||
jwt = "0.16.0"
|
jwt = "0.16.0"
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ pub enum Errors {
|
|||||||
Ise(anyhow::Error),
|
Ise(anyhow::Error),
|
||||||
Unimplemented,
|
Unimplemented,
|
||||||
Unauthorized,
|
Unauthorized,
|
||||||
|
JWTExpired,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub enum AppError {
|
pub enum AppError {
|
||||||
@@ -73,6 +74,12 @@ impl IntoResponse for AppError {
|
|||||||
Errors::Unimplemented => {
|
Errors::Unimplemented => {
|
||||||
(StatusCode::NOT_IMPLEMENTED, "Not implemented").into_response()
|
(StatusCode::NOT_IMPLEMENTED, "Not implemented").into_response()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Errors::JWTExpired => (
|
||||||
|
StatusCode::UNAUTHORIZED,
|
||||||
|
"JWT has expired. Please log in again.",
|
||||||
|
)
|
||||||
|
.into_response(),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,19 +1,17 @@
|
|||||||
use std::sync::Arc;
|
|
||||||
|
|
||||||
use crate::*;
|
use crate::*;
|
||||||
use anyhow::bail;
|
|
||||||
use axum::{
|
use axum::{
|
||||||
async_trait,
|
async_trait,
|
||||||
extract::{FromRef, FromRequestParts},
|
extract::{FromRef, FromRequestParts},
|
||||||
http::{header, request::Parts},
|
http::{header, request::Parts},
|
||||||
};
|
};
|
||||||
|
|
||||||
use chrono::{DateTime, Utc};
|
|
||||||
use jwt::VerifyWithKey;
|
use jwt::VerifyWithKey;
|
||||||
use sqlx::types::Uuid;
|
use sqlx::types::Uuid;
|
||||||
use util::auth::JWTClaims;
|
use util::auth::JWTClaims;
|
||||||
|
|
||||||
pub struct UserId(Uuid);
|
/// Remember, this is FromRequestParts, so it has to be ABOVE the extractors
|
||||||
|
/// that eat the entire request
|
||||||
|
pub struct UserId(pub Uuid);
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
impl<S> FromRequestParts<S> for UserId
|
impl<S> FromRequestParts<S> for UserId
|
||||||
@@ -39,6 +37,10 @@ where
|
|||||||
.verify_with_key(&state.jwt_key)
|
.verify_with_key(&state.jwt_key)
|
||||||
.map_err(|_| AppError::Error(Errors::Unauthorized))?;
|
.map_err(|_| AppError::Error(Errors::Unauthorized))?;
|
||||||
|
|
||||||
|
if claims.exp < chrono::Utc::now().timestamp() {
|
||||||
|
return Err(AppError::Error(Errors::JWTExpired));
|
||||||
|
}
|
||||||
|
|
||||||
Ok(UserId(claims.sub))
|
Ok(UserId(claims.sub))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -72,6 +72,7 @@ async fn main() -> anyhow::Result<()> {
|
|||||||
.routes(routes!(index))
|
.routes(routes!(index))
|
||||||
.with_state(state.clone())
|
.with_state(state.clone())
|
||||||
.nest("/api/v1/auth", v1::auth::router(state.clone()))
|
.nest("/api/v1/auth", v1::auth::router(state.clone()))
|
||||||
|
.nest("/api/v1/exercises", v1::exercises::router(state.clone()))
|
||||||
.split_for_parts();
|
.split_for_parts();
|
||||||
|
|
||||||
let router = router.merge(SwaggerUi::new("/docs").url("/docs/openapi.json", api));
|
let router = router.merge(SwaggerUi::new("/docs").url("/docs/openapi.json", api));
|
||||||
|
|||||||
55
src/structs/exercise_types.rs
Normal file
55
src/structs/exercise_types.rs
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
use anyhow::anyhow;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use utoipa::ToSchema;
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, PartialEq, PartialOrd, sqlx::Type, Deserialize, Serialize, ToSchema)]
|
||||||
|
#[sqlx(type_name = "exercise_type", rename_all = "snake_case")]
|
||||||
|
pub enum ExerciseType {
|
||||||
|
Dumbbell,
|
||||||
|
Barbell,
|
||||||
|
Bodyweight,
|
||||||
|
Machine,
|
||||||
|
Kettlebell,
|
||||||
|
ResistanceBand,
|
||||||
|
Cable,
|
||||||
|
MedicineBall,
|
||||||
|
Plyometric,
|
||||||
|
PlateLoadedMachine,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::fmt::Display for ExerciseType {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
match self {
|
||||||
|
ExerciseType::Dumbbell => write!(f, "dumbbell"),
|
||||||
|
ExerciseType::Barbell => write!(f, "barbell"),
|
||||||
|
ExerciseType::Bodyweight => write!(f, "bodyweight"),
|
||||||
|
ExerciseType::Machine => write!(f, "machine"),
|
||||||
|
ExerciseType::Kettlebell => write!(f, "kettlebell"),
|
||||||
|
ExerciseType::ResistanceBand => write!(f, "resistance_band"),
|
||||||
|
ExerciseType::Cable => write!(f, "cable"),
|
||||||
|
ExerciseType::MedicineBall => write!(f, "medicine_ball"),
|
||||||
|
ExerciseType::Plyometric => write!(f, "plyometric"),
|
||||||
|
ExerciseType::PlateLoadedMachine => write!(f, "plate_loaded_machine"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::str::FromStr for ExerciseType {
|
||||||
|
type Err = anyhow::Error;
|
||||||
|
|
||||||
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
|
match s {
|
||||||
|
"dumbbell" => Ok(ExerciseType::Dumbbell),
|
||||||
|
"barbell" => Ok(ExerciseType::Barbell),
|
||||||
|
"bodyweight" => Ok(ExerciseType::Bodyweight),
|
||||||
|
"machine" => Ok(ExerciseType::Machine),
|
||||||
|
"kettlebell" => Ok(ExerciseType::Kettlebell),
|
||||||
|
"resistance_band" => Ok(ExerciseType::ResistanceBand),
|
||||||
|
"cable" => Ok(ExerciseType::Cable),
|
||||||
|
"medicine_ball" => Ok(ExerciseType::MedicineBall),
|
||||||
|
"plyometric" => Ok(ExerciseType::Plyometric),
|
||||||
|
"plate_loaded_machine" => Ok(ExerciseType::PlateLoadedMachine),
|
||||||
|
_ => Err(anyhow!("Invalid exercise type")),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1 +1,2 @@
|
|||||||
pub mod users;
|
pub mod users;
|
||||||
|
pub mod exercise_types;
|
||||||
|
|||||||
59
src/v1/exercises/create.rs
Normal file
59
src/v1/exercises/create.rs
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
use chrono::{DateTime, Utc};
|
||||||
|
use extractors::users::UserId;
|
||||||
|
use sqlx::{query, query_as};
|
||||||
|
use structs::exercise_types::ExerciseType;
|
||||||
|
use uuid::Uuid;
|
||||||
|
|
||||||
|
use crate::*;
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, ToSchema)]
|
||||||
|
pub struct CreateExerciseBody {
|
||||||
|
name: String,
|
||||||
|
// todo: make this an enum
|
||||||
|
exercise_type: ExerciseType,
|
||||||
|
description: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize, Serialize, Clone, sqlx::Type, PartialEq, sqlx::FromRow)]
|
||||||
|
pub struct Exercise {
|
||||||
|
pub id: Uuid,
|
||||||
|
pub name: String,
|
||||||
|
pub exercise_type: ExerciseType,
|
||||||
|
pub description: String,
|
||||||
|
pub author_id: Uuid,
|
||||||
|
pub official: bool,
|
||||||
|
pub created_at: DateTime<Utc>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[utoipa::path(post, path = "/create", responses((status = OK, body = String)), tag = super::EXERCISES_TAG)]
|
||||||
|
pub async fn create(
|
||||||
|
State(state): State<AppState>,
|
||||||
|
UserId(user): UserId,
|
||||||
|
Json(body): Json<CreateExerciseBody>,
|
||||||
|
) -> Result<String, AppError> {
|
||||||
|
let is_admin = query!("SELECT is_admin FROM users WHERE id = $1", user)
|
||||||
|
.fetch_one(&*state.db)
|
||||||
|
.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,
|
||||||
|
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
|
||||||
|
"#,
|
||||||
|
body.name,
|
||||||
|
body.exercise_type.to_string(),
|
||||||
|
body.description,
|
||||||
|
user,
|
||||||
|
false
|
||||||
|
);
|
||||||
|
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
13
src/v1/exercises/mod.rs
Normal file
13
src/v1/exercises/mod.rs
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
use crate::AppState;
|
||||||
|
|
||||||
|
pub(super) use super::*;
|
||||||
|
|
||||||
|
pub mod create;
|
||||||
|
|
||||||
|
pub const EXERCISES_TAG: &str = "exercises";
|
||||||
|
|
||||||
|
pub fn router(state: AppState) -> OpenApiRouter {
|
||||||
|
OpenApiRouter::new()
|
||||||
|
.routes(routes!(create::create))
|
||||||
|
.with_state(state)
|
||||||
|
}
|
||||||
@@ -2,3 +2,4 @@ pub(super) use utoipa_axum::router::OpenApiRouter;
|
|||||||
pub(super) use utoipa_axum::routes;
|
pub(super) use utoipa_axum::routes;
|
||||||
|
|
||||||
pub mod auth;
|
pub mod auth;
|
||||||
|
pub mod exercises;
|
||||||
|
|||||||
Reference in New Issue
Block a user