Bladeren bron

Create get a workout route

Lee Morgan 1 maand geleden
bovenliggende
commit
a6e4f0ab68
8 gewijzigde bestanden met toevoegingen van 185 en 10 verwijderingen
  1. 10 5
      CLAUDE.md
  2. 2 0
      docs/openapi.yaml
  3. 98 0
      docs/paths/workout-id.yaml
  4. 1 0
      src/main.rs
  5. 11 4
      src/models/exercise.rs
  6. 2 1
      src/models/workout.rs
  7. 60 0
      src/routes/workouts/get.rs
  8. 1 0
      src/routes/workouts/mod.rs

+ 10 - 5
CLAUDE.md

@@ -28,11 +28,16 @@ Routes are registered as Actix handler functions decorated with `#[get(...)]` /
 
 `.env` is read at runtime (not compile time). Required variables:
 
-| Variable  | Values                        |
-|-----------|-------------------------------|
-| `APP_ENV` | `development` or `production` |
-| `PORT`    | numeric port, e.g. `8000`     |
+| Variable       | Description                                        |
+|----------------|----------------------------------------------------|
+| `APP_ENV`      | `development` or `production`                      |
+| `PORT`         | numeric port, e.g. `8000`                          |
+| `DATABASE_URL` | Postgres connection string, e.g. `postgres://user:password@localhost/torus` |
+| `JWT_SECRET`   | Secret key used to sign and verify JWTs            |
 
 ## Rules
 
-1. Never run or offer to run the program
+1. Do not build the program after finishing a task
+2. Do not run redocly to build the docs
+3. Any time that a route is created or changed, update the docs as well
+4. Make sure that any errors that are ever created contain and send to the front-end a good, brief description that can just be printed for the user by the front-end

+ 2 - 0
docs/openapi.yaml

@@ -26,6 +26,8 @@ paths:
     $ref: './paths/user-logout.yaml'
   /workout:
     $ref: './paths/workout.yaml'
+  /workout/{id}:
+    $ref: './paths/workout-id.yaml'
 
 components:
   securitySchemes:

+ 98 - 0
docs/paths/workout-id.yaml

@@ -0,0 +1,98 @@
+get:
+  summary: Get a workout
+  operationId: getWorkout
+  tags:
+    - Workouts
+  security:
+    - cookieAuth: []
+  parameters:
+    - name: id
+      in: path
+      required: true
+      description: UUID of the workout to retrieve.
+      schema:
+        type: string
+        format: uuid
+        example: 0191a23b-dead-7000-beef-000000000002
+  responses:
+    '200':
+      description: Workout with all exercises.
+      content:
+        application/json:
+          schema:
+            type: object
+            required:
+              - id
+              - name
+              - exercises
+            properties:
+              id:
+                type: string
+                format: uuid
+                example: 0191a23b-dead-7000-beef-000000000002
+              name:
+                type: string
+                example: Push Day
+              exercises:
+                type: array
+                items:
+                  type: object
+                  required:
+                    - id
+                    - name
+                    - exercise_type
+                  properties:
+                    id:
+                      type: string
+                      format: uuid
+                      example: 0191a23b-dead-7000-beef-000000000003
+                    name:
+                      type: string
+                      example: Bench Press
+                    notes:
+                      type: string
+                      nullable: true
+                      example: Keep elbows tucked
+                    exercise_type:
+                      $ref: '../components/schemas/ExerciseType.yaml'
+          example:
+            id: 0191a23b-dead-7000-beef-000000000002
+            name: Push Day
+            exercises:
+              - id: 0191a23b-dead-7000-beef-000000000003
+                name: Bench Press
+                notes: null
+                exercise_type: WeightedReps
+              - id: 0191a23b-dead-7000-beef-000000000004
+                name: Push-ups
+                notes: null
+                exercise_type: BodyweightReps
+    '401':
+      description: Missing or invalid auth token.
+      content:
+        application/json:
+          schema:
+            $ref: '../components/schemas/ErrorResponse.yaml'
+          examples:
+            missing:
+              summary: No cookie present
+              value:
+                error: Missing auth token
+            invalid:
+              summary: Token invalid or expired
+              value:
+                error: Invalid auth token
+    '404':
+      description: Workout not found.
+      content:
+        application/json:
+          schema:
+            $ref: '../components/schemas/ErrorResponse.yaml'
+          example:
+            error: Workout not found
+    '500':
+      description: Internal server error.
+      content:
+        application/json:
+          schema:
+            $ref: '../components/schemas/ErrorResponse.yaml'

+ 1 - 0
src/main.rs

@@ -54,6 +54,7 @@ async fn main() -> std::io::Result<()> {
             .service(routes::users::logout::logout)
             .service(routes::users::get::get_user)
             .service(routes::workouts::create::create_workout)
+            .service(routes::workouts::get::get_workout)
     })
     .bind((host.as_str(), port))?
     .run()

+ 11 - 4
src/models/exercise.rs

@@ -1,8 +1,8 @@
-use serde::Deserialize;
-use sqlx::Postgres;
+use serde::{Deserialize, Serialize};
+use sqlx::{PgPool, Postgres};
 use uuid::Uuid;
 
-#[derive(sqlx::Type, Deserialize, Clone)]
+#[derive(sqlx::Type, Serialize, Deserialize, Clone)]
 #[sqlx(type_name = "exercise_type", rename_all = "PascalCase")]
 pub enum ExerciseType {
     WeightedReps,
@@ -11,7 +11,7 @@ pub enum ExerciseType {
     TimedCardio,
 }
 
-#[derive(sqlx::FromRow)]
+#[derive(sqlx::FromRow, Serialize)]
 pub struct Exercise {
     pub id: Uuid,
     pub workout: Uuid,
@@ -21,6 +21,13 @@ pub struct Exercise {
 }
 
 impl Exercise {
+    pub async fn find_by_workout(pool: &PgPool, workout_id: Uuid) -> Result<Vec<Exercise>, sqlx::Error> {
+        sqlx::query_as::<_, Exercise>("SELECT * FROM exercises WHERE workout = $1 ORDER BY id")
+            .bind(workout_id)
+            .fetch_all(pool)
+            .await
+    }
+
     pub async fn create<'c, E>(
         executor: E,
         workout_id: Uuid,

+ 2 - 1
src/models/workout.rs

@@ -1,7 +1,8 @@
+use serde::Serialize;
 use sqlx::{PgPool, Postgres};
 use uuid::Uuid;
 
-#[derive(sqlx::FromRow)]
+#[derive(sqlx::FromRow, Serialize)]
 pub struct Workout {
     pub id: Uuid,
     pub name: String,

+ 60 - 0
src/routes/workouts/get.rs

@@ -0,0 +1,60 @@
+use actix_web::{get, web, HttpResponse};
+use serde::Serialize;
+use serde_json::json;
+use sqlx::PgPool;
+use uuid::Uuid;
+
+use crate::auth::AuthUser;
+use crate::models::exercise::{Exercise, ExerciseType};
+use crate::models::workout::Workout;
+
+#[derive(Serialize)]
+struct ExerciseResponse {
+    id: Uuid,
+    name: String,
+    notes: Option<String>,
+    exercise_type: ExerciseType,
+}
+
+impl From<Exercise> for ExerciseResponse {
+    fn from(e: Exercise) -> Self {
+        Self {
+            id: e.id,
+            name: e.name,
+            notes: e.notes,
+            exercise_type: e.exercise_type,
+        }
+    }
+}
+
+#[get("/workout/{id}")]
+pub async fn get_workout(
+    pool: web::Data<PgPool>,
+    _auth: AuthUser,
+    path: web::Path<Uuid>,
+) -> HttpResponse {
+    let id = path.into_inner();
+
+    let workout = match Workout::find_by_id(pool.get_ref(), id).await {
+        Ok(Some(w)) => w,
+        Ok(None) => return HttpResponse::NotFound().json(json!({"error": "Workout not found"})),
+        Err(_) => {
+            return HttpResponse::InternalServerError().json(json!({"error": "An error occurred"}))
+        }
+    };
+
+    let exercises = match Exercise::find_by_workout(pool.get_ref(), id).await {
+        Ok(e) => e,
+        Err(_) => {
+            return HttpResponse::InternalServerError().json(json!({"error": "An error occurred"}))
+        }
+    };
+
+    let exercises: Vec<ExerciseResponse> = exercises.into_iter().map(Into::into).collect();
+
+    HttpResponse::Ok().json(json!({
+        "id": workout.id,
+        "name": workout.name,
+        "exercises": exercises,
+    }))
+}

+ 1 - 0
src/routes/workouts/mod.rs

@@ -1 +1,2 @@
 pub mod create;
+pub mod get;