From 03a4b19b2aeec15984bc81e9d451c663e8e328b1 Mon Sep 17 00:00:00 2001 From: wajeht <58354193+wajeht@users.noreply.github.com> Date: Fri, 19 Aug 2022 02:31:38 -0400 Subject: [PATCH] feat: scaffold exercise history with chart --- package.json | 2 +- .../api/v1/exercises/exercises.controller.js | 20 +- .../api/v1/exercises/exercises.queries.js | 37 ++++ src/apps/api/v1/exercises/exercises.router.js | 12 ++ .../api/v1/exercises/exercises.validation.js | 27 ++- src/apps/ui/App.vue | 18 +- .../components/dashboard/SessionDetails.vue | 81 ++++--- .../ui/components/dashboard/VideoDetails.vue | 49 +++-- src/apps/ui/components/shared/Signup.vue | 2 +- src/apps/ui/pages/dashboard/Block.vue | 20 ++ src/apps/ui/pages/dashboard/Community.vue | 56 ++--- src/apps/ui/pages/dashboard/Exercise.vue | 199 ++++++++++++++++++ src/apps/ui/router.vue.js | 33 ++- 13 files changed, 467 insertions(+), 89 deletions(-) create mode 100644 src/apps/ui/pages/dashboard/Block.vue create mode 100644 src/apps/ui/pages/dashboard/Exercise.vue diff --git a/package.json b/package.json index 17a7f4f9..9a8f3fd5 100644 --- a/package.json +++ b/package.json @@ -29,7 +29,7 @@ "release:alpha": "npm run release -- --prerelease alpha", "rmds": "find . -name '.DS_Store' -delete", "prepare": "husky install", - "commit": "cz" + "commit": "git add -A && cz" }, "bin": { "gains": "./src/bin/gains.js" diff --git a/src/apps/api/v1/exercises/exercises.controller.js b/src/apps/api/v1/exercises/exercises.controller.js index c43a3115..dccb6dea 100644 --- a/src/apps/api/v1/exercises/exercises.controller.js +++ b/src/apps/api/v1/exercises/exercises.controller.js @@ -7,6 +7,24 @@ import * as ExercisesQueries from './exercises.queries.js'; import * as ExerciseCategoriesQueries from '../exercise-categories/exercise-categories.queries.js'; import { omit } from 'lodash-es'; +/** + * It gets the exercise history for a given exercise id + * @param req - The request object. + * @param res - The response object. + * @returns The exercise history for a specific exercise. + */ +export async function getExerciseHistory(req, res) { + const exercise_id = req.params.exercise_id; + const exercise = await ExercisesQueries.getExerciseHistoryByExerciseId(exercise_id); + + return res.status(StatusCodes.OK).json({ + status: 'success', + request_url: req.originalUrl, + message: 'The resource was returned successfully!', + data: exercise, + }); +} + /** * It gets an exercise by its id * @param req - The request object. @@ -18,7 +36,7 @@ export async function getExercise(req, res) { const exercise = await ExercisesQueries.getExerciseById(eid); - if (!exercise.length) throw new CustomError.BadRequestError(`There are no exercise available for exercise id ${eid}!`); // prettier-ignore + // if (!exercise.length) throw new CustomError.BadRequestError(`There are no exercise available for exercise id ${eid}!`); // prettier-ignore return res.status(StatusCodes.OK).json({ status: 'success', diff --git a/src/apps/api/v1/exercises/exercises.queries.js b/src/apps/api/v1/exercises/exercises.queries.js index d15d02d8..b9e2600d 100644 --- a/src/apps/api/v1/exercises/exercises.queries.js +++ b/src/apps/api/v1/exercises/exercises.queries.js @@ -19,6 +19,43 @@ export function getExerciseById(id) { return db.select('*').from('exercises').where({ id }).andWhere({ deleted: false }); } +/** + * Get all the sets for a given exercise, ordered by the date they were created. + * @param id - the id of the exercise you want to get the history for + * @returns An array of objects + */ +export async function getExerciseHistoryByExerciseId(id) { + const { rows } = await db.raw( + ` + select + s.reps, + s.weight, + s.rpe as "rpe", + s.notes as "notes", + s.created_at as "created_at", + e."name" as "exercise_name", + ec."name" as "category_name", + e.id as "exercise_id", + ec.id as "category_id", + s.id as "set_id", + s.session_id as "session_id", + s.log_id as "log_id", + e.user_id as "user_id" + from + exercises e + inner join sets s on s.exercise_id = e.id + inner join exercise_categories ec on ec.id = e.exercise_category_id + where + e.deleted = false + and e.id = ? + order by + s.created_at desc + `, + [id], + ); + return rows; +} + /** * Get all exercises from the database where the user_id matches the uid passed in and where the * deleted column is false. diff --git a/src/apps/api/v1/exercises/exercises.router.js b/src/apps/api/v1/exercises/exercises.router.js index e6eff91f..38e1f5e3 100644 --- a/src/apps/api/v1/exercises/exercises.router.js +++ b/src/apps/api/v1/exercises/exercises.router.js @@ -42,6 +42,18 @@ exercises.get( catchAsyncErrors(ExercisesController.getExercise), ); +/** + * GET /api/v1/exercises/{exercise_id}/history + * @tags exercises + * @summary get history of a exercises + * @param {number} exercise_id.path.required - the exercise id - application/x-www-form-urlencoded + */ +exercises.get( + '/:exercise_id/history', + validator(ExercisesValidation.getExerciseHistory), + catchAsyncErrors(ExercisesController.getExerciseHistory), +); + /** * POST /api/v1/exercises * @tags exercises diff --git a/src/apps/api/v1/exercises/exercises.validation.js b/src/apps/api/v1/exercises/exercises.validation.js index 6728479f..c2ac4b01 100644 --- a/src/apps/api/v1/exercises/exercises.validation.js +++ b/src/apps/api/v1/exercises/exercises.validation.js @@ -15,6 +15,12 @@ export const getExercise = [ .isNumeric() .withMessage('exercise id must be an number!') .bail() + .toInt() + .custom(async (eid) => { + const exercise = await ExercisesQueries.getExerciseById(eid); + if (exercise.length === 0) throw new Error('exercise does not exist!'); + return true; + }) .toInt(), ]; @@ -47,7 +53,8 @@ export const getExercises = [ const user = await ExerciseCategoriesQueries.getExerciseCategoriesById(ecid); if (user.length === 0) throw new Error('category_id does not exist!'); return true; - }), + }) + .toInt(), ]; export const postExercise = [ @@ -147,3 +154,21 @@ export const patchExerciseNote = [ return true; }), ]; + +export const getExerciseHistory = [ + param('exercise_id') + .trim() + .notEmpty() + .withMessage('exercise id must not be empty!') + .bail() + .isNumeric() + .withMessage('exercise id must be an number!') + .bail() + .toInt() + .custom(async (exercise_id) => { + const exercise = await ExercisesQueries.getExerciseById(exercise_id); + if (exercise.length === 0) throw new Error('exercise does not exist!'); + return true; + }) + .toInt(), +]; diff --git a/src/apps/ui/App.vue b/src/apps/ui/App.vue index 6f202b85..3c942455 100644 --- a/src/apps/ui/App.vue +++ b/src/apps/ui/App.vue @@ -1,6 +1,10 @@ diff --git a/src/apps/ui/pages/dashboard/Community.vue b/src/apps/ui/pages/dashboard/Community.vue index 7b858e9a..265653ef 100644 --- a/src/apps/ui/pages/dashboard/Community.vue +++ b/src/apps/ui/pages/dashboard/Community.vue @@ -196,41 +196,49 @@ async function getSessions() { diff --git a/src/apps/ui/pages/dashboard/Exercise.vue b/src/apps/ui/pages/dashboard/Exercise.vue new file mode 100644 index 00000000..51f5bd95 --- /dev/null +++ b/src/apps/ui/pages/dashboard/Exercise.vue @@ -0,0 +1,199 @@ + + + + + diff --git a/src/apps/ui/router.vue.js b/src/apps/ui/router.vue.js index 90df37a0..e3d9a0bd 100644 --- a/src/apps/ui/router.vue.js +++ b/src/apps/ui/router.vue.js @@ -31,6 +31,9 @@ import Blocks from './pages/dashboard/sessions/Blocks.vue'; import Categories from './pages/dashboard/sessions/Categories.vue'; import Exercises from './pages/dashboard/sessions/Exercises.vue'; +import Exercise from './pages/dashboard/Exercise.vue'; +import Block from './pages/dashboard/Block.vue'; + // --- settings --- import Settings from './pages/dashboard/settings/Settings.vue'; @@ -425,13 +428,37 @@ const routes = [ requiredAuth: true, }, }, + { + path: '/dashboard/exercises/:exercise_id', + name: 'Exercise', + component: Exercise, + props: (route) => ({ + exercise_id: Number(route.params.exercise_id), + }), + meta: { + layout: 'DashboardLayout', + requiredAuth: true, + }, + }, + { + path: '/dashboard/blocks/:block_id', + name: 'Block', + component: Block, + props: (route) => ({ + block_id: Number(route.params.block_id), + }), + meta: { + layout: 'DashboardLayout', + requiredAuth: true, + }, + }, { path: '/dashboard/sessions/:sid', name: 'SessionDetails', component: SessionDetails, - props: (sid) => { - return Number(sid); - }, + props: (route) => ({ + sid: Number(route.params.sid), + }), meta: { layout: 'DashboardLayout', requiredAuth: true,