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",
|
||||
"js-sys",
|
||||
"num-traits",
|
||||
"serde",
|
||||
"wasm-bindgen",
|
||||
"windows-targets 0.52.6",
|
||||
]
|
||||
|
||||
@@ -21,7 +21,7 @@ sqlx = { version = "0.8.2", features = [
|
||||
"uuid",
|
||||
] }
|
||||
uuid = { version = "1.4.1", features = ["serde", "v4"] }
|
||||
chrono = "0.4.39"
|
||||
chrono = { version = "0.4.26", features = ["serde"] }
|
||||
dotenv = "0.15.0"
|
||||
argon2 = "0.5.3"
|
||||
jwt = "0.16.0"
|
||||
|
||||
@@ -11,6 +11,7 @@ pub enum Errors {
|
||||
Ise(anyhow::Error),
|
||||
Unimplemented,
|
||||
Unauthorized,
|
||||
JWTExpired,
|
||||
}
|
||||
|
||||
pub enum AppError {
|
||||
@@ -73,6 +74,12 @@ impl IntoResponse for AppError {
|
||||
Errors::Unimplemented => {
|
||||
(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 anyhow::bail;
|
||||
use axum::{
|
||||
async_trait,
|
||||
extract::{FromRef, FromRequestParts},
|
||||
http::{header, request::Parts},
|
||||
};
|
||||
|
||||
use chrono::{DateTime, Utc};
|
||||
use jwt::VerifyWithKey;
|
||||
use sqlx::types::Uuid;
|
||||
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]
|
||||
impl<S> FromRequestParts<S> for UserId
|
||||
@@ -39,6 +37,10 @@ where
|
||||
.verify_with_key(&state.jwt_key)
|
||||
.map_err(|_| AppError::Error(Errors::Unauthorized))?;
|
||||
|
||||
if claims.exp < chrono::Utc::now().timestamp() {
|
||||
return Err(AppError::Error(Errors::JWTExpired));
|
||||
}
|
||||
|
||||
Ok(UserId(claims.sub))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -72,6 +72,7 @@ async fn main() -> anyhow::Result<()> {
|
||||
.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()))
|
||||
.split_for_parts();
|
||||
|
||||
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 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 mod auth;
|
||||
pub mod exercises;
|
||||
|
||||
Reference in New Issue
Block a user