From 016a01614a5a99a183869d3d5043cc7c7d49b29c Mon Sep 17 00:00:00 2001 From: Filip Borum Poulsen Date: Mon, 24 Apr 2023 17:51:52 +0200 Subject: [PATCH] Added trainer and center admin views --- client/src/components/CenterPopup.vue | 106 +++++++++++++++++++ client/src/components/NavBar.vue | 4 +- client/src/components/TrainerPopup.vue | 104 +++++++++++++++++++ client/src/interfaces/trainer.ts | 4 + client/src/router/index.ts | 16 +++ client/src/views/admin/Centers.vue | 119 ++++++++++++++++++++++ client/src/views/admin/Trainers.vue | 136 +++++++++++++++++++++++++ endpoints.md | 1 + server/src/middlewares/trainer.ts | 2 +- server/src/routes/trainer/trainer.ts | 89 ++++++++++++---- 10 files changed, 559 insertions(+), 22 deletions(-) create mode 100644 client/src/components/CenterPopup.vue create mode 100644 client/src/components/TrainerPopup.vue create mode 100644 client/src/views/admin/Centers.vue create mode 100644 client/src/views/admin/Trainers.vue diff --git a/client/src/components/CenterPopup.vue b/client/src/components/CenterPopup.vue new file mode 100644 index 0000000..ccc91f8 --- /dev/null +++ b/client/src/components/CenterPopup.vue @@ -0,0 +1,106 @@ + + + + + \ No newline at end of file diff --git a/client/src/components/NavBar.vue b/client/src/components/NavBar.vue index c20e4f2..a3adf1a 100644 --- a/client/src/components/NavBar.vue +++ b/client/src/components/NavBar.vue @@ -52,8 +52,8 @@ export default { Træningstimer Træningstimer Skema - Centers - Trainers + Trænere + Centre Profil diff --git a/client/src/components/TrainerPopup.vue b/client/src/components/TrainerPopup.vue new file mode 100644 index 0000000..c885843 --- /dev/null +++ b/client/src/components/TrainerPopup.vue @@ -0,0 +1,104 @@ + + + + + \ No newline at end of file diff --git a/client/src/interfaces/trainer.ts b/client/src/interfaces/trainer.ts index 60fa483..80fd3b4 100644 --- a/client/src/interfaces/trainer.ts +++ b/client/src/interfaces/trainer.ts @@ -4,4 +4,8 @@ export interface Trainer { last_name: string center_id: number center_name: string +} + +export interface TrainerWithHourlyPrice extends Trainer { + hourly_price: number } \ No newline at end of file diff --git a/client/src/router/index.ts b/client/src/router/index.ts index dd285fc..958689a 100644 --- a/client/src/router/index.ts +++ b/client/src/router/index.ts @@ -48,6 +48,22 @@ const router = createRouter({ // this generates a separate chunk (About.[hash].js) for this route // which is lazy-loaded when the route is visited. component: () => import('../views/trainer/TrainerOrdersView.vue') + }, + { + path: '/admin/center', + name: 'AdminCenters', + // route level code-splitting + // this generates a separate chunk (About.[hash].js) for this route + // which is lazy-loaded when the route is visited. + component: () => import('../views/admin/Centers.vue') + }, + { + path: '/admin/trainer', + name: 'AdminTrainers', + // route level code-splitting + // this generates a separate chunk (About.[hash].js) for this route + // which is lazy-loaded when the route is visited. + component: () => import('../views/admin/Trainers.vue') } ] }) diff --git a/client/src/views/admin/Centers.vue b/client/src/views/admin/Centers.vue new file mode 100644 index 0000000..d77c850 --- /dev/null +++ b/client/src/views/admin/Centers.vue @@ -0,0 +1,119 @@ + + + + + \ No newline at end of file diff --git a/client/src/views/admin/Trainers.vue b/client/src/views/admin/Trainers.vue new file mode 100644 index 0000000..0238ebb --- /dev/null +++ b/client/src/views/admin/Trainers.vue @@ -0,0 +1,136 @@ + + + + + \ No newline at end of file diff --git a/endpoints.md b/endpoints.md index ea897d1..3c881f4 100644 --- a/endpoints.md +++ b/endpoints.md @@ -9,6 +9,7 @@ | [x] | PUT | /center/:id | Admin can change center | Admin | | [ ] | DELETE | /center | Admin can delete center | Admin | | [x] | GET | /trainer | Get list of trainers | | +| [x] | GET | /admin/trainer | Get list of trainers with hourly price | Admin | | [x] | POST | /trainer | Admin can create trainer | Admin | | [ ] | PUT | /trainer | Trainer can change profile information | Trainer | | [x] | PUT | /trainer/:id | Admin can change trainer information | Admin | diff --git a/server/src/middlewares/trainer.ts b/server/src/middlewares/trainer.ts index 650f7be..91947e9 100644 --- a/server/src/middlewares/trainer.ts +++ b/server/src/middlewares/trainer.ts @@ -5,7 +5,7 @@ export async function trainerExists(req: Request, res: Response, next: NextFunct const trainerExistsResult = await client.query(` SELECT 1 FROM trainers where id = $1; `, [ - req.params.trainer_id + req.params.id ]); if (trainerExistsResult.rowCount < 1) { diff --git a/server/src/routes/trainer/trainer.ts b/server/src/routes/trainer/trainer.ts index 13de3e3..fe0613f 100644 --- a/server/src/routes/trainer/trainer.ts +++ b/server/src/routes/trainer/trainer.ts @@ -20,6 +20,10 @@ interface TrainerFilters { center?: number[] } +interface TrainerWithHourlyPrice extends Trainer { + hourly_price: number +} + router.get("/trainer", async (req: Request, res: Response) => { try { const validation = trainerFiltersSchema.validate(req.query, { abortEarly: false, stripUnknown: true }); @@ -33,8 +37,9 @@ router.get("/trainer", async (req: Request, res: Response) => { SELECT trainers.id, first_name, last_name, center_id, centers.name as center_name FROM trainers JOIN users ON trainers.user_id = users.id JOIN centers on trainers.center_id = centers.id - WHERE trainers.id = ANY($1) OR $2; - `,[ + WHERE trainers.id = ANY($1) OR $2 + ORDER BY first_name ASC; + `, [ trainerFilters.center !== undefined ? trainerFilters.center : [], trainerFilters.center === undefined ]); @@ -48,8 +53,44 @@ router.get("/trainer", async (req: Request, res: Response) => { } }) +router.get("/admin/trainer", AdminAuth, async (req: Request, res: Response) => { + try { + const validation = trainerFiltersSchema.validate(req.query, { abortEarly: false, stripUnknown: true }); + if (validation.error !== undefined) { + return res.status(400).send(validation.error.details); + } + + const trainerFilters: TrainerFilters = validation.value; + + const databaseResult = await client.query(` + SELECT + trainers.id, + first_name, + last_name, + center_id, + centers.name as center_name, + hourly_price + FROM trainers + JOIN users ON trainers.user_id = users.id + JOIN centers on trainers.center_id = centers.id + WHERE trainers.id = ANY($1) OR $2 + ORDER BY first_name ASC; + `, [ + trainerFilters.center !== undefined ? trainerFilters.center : [], + trainerFilters.center === undefined + ]); + + const trainers: TrainerWithHourlyPrice[] = databaseResult.rows; + + return res.status(200).send(trainers); + } catch (error: DatabaseError | Error | any) { + console.error(error); + return res.sendStatus(500); + } +}) + const createTrainerSchema = Joi.object({ - user_id: Joi.number().integer().positive().required(), + email: Joi.string().email().required(), center_id: Joi.number().integer().positive().required(), hourly_price: Joi.number().integer().positive().required() }); @@ -61,24 +102,34 @@ router.post("/trainer", AdminAuth, async (req: Request, res: Response) => { return res.status(400).send(validation.error.details); } - const databaseResult = await client.query(` - WITH inserted AS ( - INSERT INTO trainers (user_id, center_id, hourly_price) - VALUES ($1, $2, $3) - RETURNING * - ) - SELECT inserted.id, user_id, first_name, last_name, center_id, centers.name as center_name from inserted - JOIN users ON inserted.user_id = users.id - JOIN centers on inserted.center_id = centers.id; + const trainerLookup = await client.query(` + SELECT id FROM users + WHERE email = $1; + `, [ + validation.value.email + ]); + + if (trainerLookup.rows.length !== 1) + return res.status(400).send([{ + message: "\"email\" was not found", + path: [ + "email" + ], + type: "email.not_found" + }]); + + const user_id = trainerLookup.rows[0].id; + + await client.query(` + INSERT INTO trainers (user_id, center_id, hourly_price) + VALUES ($1, $2, $3); `, [ - validation.value.user_id, + user_id, validation.value.center_id, validation.value.hourly_price ]); - const trainers: Trainer[] = databaseResult.rows; - - return res.status(200).send(trainers); + return res.sendStatus(204); } catch (error: DatabaseError | Error | any) { if (error.constraint == "trainers_user_id_key") { return res.status(400).send([{ @@ -108,7 +159,7 @@ const updateTrainerSchema = Joi.object({ hourly_price: Joi.number().integer().positive().required() }); -router.put("/trainer/:trainer_id", AdminAuth, trainerExists, async (req: Request, res: Response) => { +router.put("/trainer/:id", AdminAuth, trainerExists, async (req: Request, res: Response) => { try { const validation = updateTrainerSchema.validate(req.body, { abortEarly: false }); if (validation.error !== undefined) { @@ -132,9 +183,9 @@ router.put("/trainer/:trainer_id", AdminAuth, trainerExists, async (req: Request req.params.id ]); - const trainers: Trainer[] = databaseResult.rows; + const trainer: Trainer = databaseResult.rows[0]; - return res.status(200).send(trainers); + return res.status(200).send(trainer); } catch (error: DatabaseError | Error | any) { if (error.constraint == "trainers_center_id_fkey") { return res.status(400).send([{