wip: I can't figure out the error with ENUM and the postgres type

This commit is contained in:
2024-12-10 15:24:50 -08:00
parent 0ecd7c8a4c
commit 982ab52e69
10 changed files with 146 additions and 6 deletions

1
Cargo.lock generated
View File

@@ -305,6 +305,7 @@ dependencies = [
"iana-time-zone",
"js-sys",
"num-traits",
"serde",
"wasm-bindgen",
"windows-targets 0.52.6",
]

View File

@@ -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"

View File

@@ -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(),
},
}
}

View File

@@ -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))
}
}

View File

@@ -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));

View 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")),
}
}
}

View File

@@ -1 +1,2 @@
pub mod users;
pub mod exercise_types;

View 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
View 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)
}

View File

@@ -2,3 +2,4 @@ pub(super) use utoipa_axum::router::OpenApiRouter;
pub(super) use utoipa_axum::routes;
pub mod auth;
pub mod exercises;