moahmedwafy commited on
Commit
fc659a3
·
1 Parent(s): 7c6de6f

feat: support model creating workouts

Browse files
src/common/models/user.model.ts CHANGED
@@ -17,7 +17,7 @@ export interface IUser {
17
  password: string;
18
  image: string;
19
  role: AuthenticatableType;
20
- gender: string;
21
  dob: Date;
22
  height: number;
23
  weight: number;
 
17
  password: string;
18
  image: string;
19
  role: AuthenticatableType;
20
+ gender: Gender;
21
  dob: Date;
22
  height: number;
23
  weight: number;
src/common/models/workout.model.ts CHANGED
@@ -30,7 +30,8 @@ export interface IWorkout {
30
  },
31
  ],
32
  },
33
- ]
 
34
  }
35
 
36
  const workoutSchema = new Schema({
@@ -60,10 +61,11 @@ const workoutSchema = new Schema({
60
  },
61
  ],
62
  },
63
- ]
 
64
  });
65
 
66
 
67
  export type WorkoutDocument = IWorkout & mongoose.Document;
68
 
69
- export const Workout = mongoose.model<WorkoutDocument>("workouts", workoutSchema);
 
30
  },
31
  ],
32
  },
33
+ ],
34
+ aiGenerated: boolean;
35
  }
36
 
37
  const workoutSchema = new Schema({
 
61
  },
62
  ],
63
  },
64
+ ],
65
+ aiGenerated: { type: Boolean, required: true, default: false },
66
  });
67
 
68
 
69
  export type WorkoutDocument = IWorkout & mongoose.Document;
70
 
71
+ export const Workout = mongoose.model<WorkoutDocument>("workouts", workoutSchema);
src/lib/services/crud.service.ts CHANGED
@@ -3,7 +3,10 @@ import { AnyKeys, Document, FilterQuery, Model } from "mongoose";
3
 
4
 
5
  export const CrudService = <ModelDoc extends Document>(
6
- model: Model<ModelDoc>
 
 
 
7
  ) => {
8
  return class CrudServiceClass {
9
  protected model: Model<ModelDoc> = model;
@@ -16,6 +19,7 @@ export const CrudService = <ModelDoc extends Document>(
16
  filter: FilterQuery<ModelDoc>,
17
  data: AnyKeys<ModelDoc>
18
  ): Promise<ModelDoc> {
 
19
  await this.existsOrThrow(filter);
20
  await this.model.updateOne(filter, data);
21
  return this.findOneOrFail(filter);
@@ -25,12 +29,14 @@ export const CrudService = <ModelDoc extends Document>(
25
  filter: FilterQuery<ModelDoc>,
26
  data: AnyKeys<ModelDoc>
27
  ): Promise<ModelDoc[]> {
 
28
  await this.existsOrThrow(filter);
29
  await this.model.updateMany(filter, data);
30
  return this.model.find(filter);
31
  }
32
 
33
  async deleteOne(filter: FilterQuery<ModelDoc>): Promise<ModelDoc> {
 
34
  await this.existsOrThrow(filter);
35
  return this.model.findOneAndDelete(filter);
36
  }
@@ -57,6 +63,8 @@ export const CrudService = <ModelDoc extends Document>(
57
  };
58
  }> {
59
  if (options?.filterOptions) filter = { ...filter, ...options.filterOptions };
 
 
60
  const queryInstruction = this.model
61
  .find(filter)
62
  .limit(paginationOptions.limit)
@@ -74,6 +82,18 @@ export const CrudService = <ModelDoc extends Document>(
74
  return { docs, paginationData };
75
  }
76
 
 
 
 
 
 
 
 
 
 
 
 
 
77
  async search(
78
  filter: FilterQuery<ModelDoc>,
79
  paginationOptions: {
@@ -94,6 +114,7 @@ export const CrudService = <ModelDoc extends Document>(
94
  perPage: number;
95
  };
96
  }> {
 
97
  const queryInstruction = this.model
98
  .find(filter)
99
  .limit(paginationOptions.limit)
@@ -116,7 +137,7 @@ export const CrudService = <ModelDoc extends Document>(
116
  options?: {
117
  populateArray: any
118
  }): Promise<ModelDoc | null> {
119
- const queryInstruction = this.model.findOne(filter);
120
  if (options?.populateArray) queryInstruction.populate(options.populateArray);
121
  const document = await queryInstruction
122
  return document;
 
3
 
4
 
5
  export const CrudService = <ModelDoc extends Document>(
6
+ model: Model<ModelDoc>,
7
+ crudOptions?: {
8
+ defaultFilter?: FilterQuery<ModelDoc>;
9
+ }
10
  ) => {
11
  return class CrudServiceClass {
12
  protected model: Model<ModelDoc> = model;
 
19
  filter: FilterQuery<ModelDoc>,
20
  data: AnyKeys<ModelDoc>
21
  ): Promise<ModelDoc> {
22
+ filter = { ...crudOptions?.defaultFilter, ...filter };
23
  await this.existsOrThrow(filter);
24
  await this.model.updateOne(filter, data);
25
  return this.findOneOrFail(filter);
 
29
  filter: FilterQuery<ModelDoc>,
30
  data: AnyKeys<ModelDoc>
31
  ): Promise<ModelDoc[]> {
32
+ filter = { ...crudOptions?.defaultFilter, ...filter };
33
  await this.existsOrThrow(filter);
34
  await this.model.updateMany(filter, data);
35
  return this.model.find(filter);
36
  }
37
 
38
  async deleteOne(filter: FilterQuery<ModelDoc>): Promise<ModelDoc> {
39
+ filter = { ...crudOptions?.defaultFilter, ...filter };
40
  await this.existsOrThrow(filter);
41
  return this.model.findOneAndDelete(filter);
42
  }
 
63
  };
64
  }> {
65
  if (options?.filterOptions) filter = { ...filter, ...options.filterOptions };
66
+ filter = { ...crudOptions?.defaultFilter, ...filter };
67
+
68
  const queryInstruction = this.model
69
  .find(filter)
70
  .limit(paginationOptions.limit)
 
82
  return { docs, paginationData };
83
  }
84
 
85
+ async listAll(
86
+ filter: FilterQuery<ModelDoc>,
87
+ options?: {
88
+ populateArray: any
89
+ },
90
+ ): Promise<ModelDoc[]> {
91
+ filter = { ...crudOptions?.defaultFilter, ...filter };
92
+ const queryInstruction = this.model.find(filter);
93
+ if (options?.populateArray) queryInstruction.populate(options.populateArray);
94
+ return queryInstruction;
95
+ }
96
+
97
  async search(
98
  filter: FilterQuery<ModelDoc>,
99
  paginationOptions: {
 
114
  perPage: number;
115
  };
116
  }> {
117
+ filter = { ...crudOptions?.defaultFilter, ...filter };
118
  const queryInstruction = this.model
119
  .find(filter)
120
  .limit(paginationOptions.limit)
 
137
  options?: {
138
  populateArray: any
139
  }): Promise<ModelDoc | null> {
140
+ const queryInstruction = this.model.findOne();
141
  if (options?.populateArray) queryInstruction.populate(options.populateArray);
142
  const document = await queryInstruction
143
  return document;
src/lib/utils/age.ts ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ export const calcAge = (dob: Date): number => {
2
+ const diff = Date.now() - dob.getTime();
3
+ const ageDate = new Date(diff);
4
+ return Math.abs(ageDate.getUTCFullYear() - 1970);
5
+ }
src/modules/users/modules/user-registered-workouts/controllers/user-registered-workouts.controller.ts CHANGED
@@ -1,5 +1,5 @@
1
  import { UserRegisteredWorkoutsService } from "../services/user-registered-workouts.service";
2
- import { Request, Response } from "express";
3
  import { JsonResponse } from "@lib/responses/json-response";
4
  import { parsePaginationQuery } from "@helpers/pagination";
5
  import { bodyValidator } from "@helpers/validation.helper";
@@ -21,13 +21,10 @@ import { SwaggerSummary } from "@lib/decorators/swagger-summary.decorator";
21
  import { SwaggerDescription } from "@lib/decorators/swagger-description.decorator";
22
  import { SwaggerResponse } from "@lib/decorators/swagger-response.decorator";
23
  import { SwaggerRequest } from "@lib/decorators/swagger-request.decorator";import { updateUserRegisteredWorkoutsSchema } from "../validations/update-user-registered-workouts.validation";
 
24
  4
25
 
26
 
27
- interface userRequest extends Request {
28
- jwtPayload?: any;
29
- }
30
-
31
  @Controller("/user/myWorkouts")
32
  @ControllerMiddleware(UsersGuardMiddleware())
33
  export class userRegisteredWorkoutsController extends BaseController {
@@ -52,7 +49,7 @@ export class userRegisteredWorkoutsController extends BaseController {
52
  @SwaggerResponse([UserRegisteredWorkoutsPopulateSerialization])
53
  @SwaggerSummary("List my workouts")
54
  @SwaggerDescription("List all user registered workouts (workouts that the user had started)")
55
- list = async (req: userRequest, res: Response) => {
56
  const paginationQuery = parsePaginationQuery(req.query);
57
  const { docs, paginationData } =
58
  await this.userRegisteredWorkoutsService.list(
@@ -79,7 +76,7 @@ export class userRegisteredWorkoutsController extends BaseController {
79
  @SwaggerResponse(UserRegisteredWorkoutsPopulateSerialization)
80
  @SwaggerSummary("today's workout && my trainer --> my plan && weekly")
81
  @SwaggerDescription("Get a single workout from user registered workouts (workouts that the user had started)")
82
- get = async (req: userRequest, res: Response) => {
83
  const data = await this.userRegisteredWorkoutsService.findOneOrFail(
84
  { _id: req.params.id },
85
  {
@@ -107,7 +104,7 @@ export class userRegisteredWorkoutsController extends BaseController {
107
  @SwaggerRequest(createUserRegisteredWorkoutsSchema)
108
  @SwaggerSummary("Create workout")
109
  @SwaggerDescription("Create a new workout for the user")
110
- create = async (req: userRequest, res: Response) => {
111
  const data = await this.userRegisteredWorkoutsService.createForUser(req.body, req.jwtPayload.id);
112
  return JsonResponse.success(
113
  {
@@ -123,7 +120,7 @@ export class userRegisteredWorkoutsController extends BaseController {
123
  @SwaggerRequest(updateUserRegisteredWorkoutsSchema)
124
  @SwaggerSummary("Update Workout Progress")
125
  @SwaggerDescription("Update the progress of a workout")
126
- updateProgress = async (req: userRequest, res: Response) => {
127
  const urwId: string = req.params.id;
128
  const weekNumber: number = Number(req.params.week);
129
  const dayNumber: number = Number(req.params.day);
 
1
  import { UserRegisteredWorkoutsService } from "../services/user-registered-workouts.service";
2
+ import { Response } from "express";
3
  import { JsonResponse } from "@lib/responses/json-response";
4
  import { parsePaginationQuery } from "@helpers/pagination";
5
  import { bodyValidator } from "@helpers/validation.helper";
 
21
  import { SwaggerDescription } from "@lib/decorators/swagger-description.decorator";
22
  import { SwaggerResponse } from "@lib/decorators/swagger-response.decorator";
23
  import { SwaggerRequest } from "@lib/decorators/swagger-request.decorator";import { updateUserRegisteredWorkoutsSchema } from "../validations/update-user-registered-workouts.validation";
24
+ import { IUserRequest } from "@common/interfaces/user-request.interface";
25
  4
26
 
27
 
 
 
 
 
28
  @Controller("/user/myWorkouts")
29
  @ControllerMiddleware(UsersGuardMiddleware())
30
  export class userRegisteredWorkoutsController extends BaseController {
 
49
  @SwaggerResponse([UserRegisteredWorkoutsPopulateSerialization])
50
  @SwaggerSummary("List my workouts")
51
  @SwaggerDescription("List all user registered workouts (workouts that the user had started)")
52
+ list = async (req: IUserRequest, res: Response) => {
53
  const paginationQuery = parsePaginationQuery(req.query);
54
  const { docs, paginationData } =
55
  await this.userRegisteredWorkoutsService.list(
 
76
  @SwaggerResponse(UserRegisteredWorkoutsPopulateSerialization)
77
  @SwaggerSummary("today's workout && my trainer --> my plan && weekly")
78
  @SwaggerDescription("Get a single workout from user registered workouts (workouts that the user had started)")
79
+ get = async (req: IUserRequest, res: Response) => {
80
  const data = await this.userRegisteredWorkoutsService.findOneOrFail(
81
  { _id: req.params.id },
82
  {
 
104
  @SwaggerRequest(createUserRegisteredWorkoutsSchema)
105
  @SwaggerSummary("Create workout")
106
  @SwaggerDescription("Create a new workout for the user")
107
+ create = async (req: IUserRequest, res: Response) => {
108
  const data = await this.userRegisteredWorkoutsService.createForUser(req.body, req.jwtPayload.id);
109
  return JsonResponse.success(
110
  {
 
120
  @SwaggerRequest(updateUserRegisteredWorkoutsSchema)
121
  @SwaggerSummary("Update Workout Progress")
122
  @SwaggerDescription("Update the progress of a workout")
123
+ updateProgress = async (req: IUserRequest, res: Response) => {
124
  const urwId: string = req.params.id;
125
  const weekNumber: number = Number(req.params.week);
126
  const dayNumber: number = Number(req.params.day);
src/modules/users/modules/user-registered-workouts/services/user-registered-workouts.service.ts CHANGED
@@ -1,17 +1,33 @@
1
  import { UserRegisteredWorkout } from "@common/models/user-registered-workout.model";
2
  import { CrudService } from "@lib/services/crud.service";
3
  import { ICreateUserRegisteredWorkouts } from "../validations/create-user-registered-workouts.validation";
4
- import { WorkoutService } from "../../workouts/services/workouts.service";
5
  import { IUpdateUserRegisteredWorkouts } from "../validations/update-user-registered-workouts.validation";
6
  import { HttpError } from "@lib/error-handling/http-error";
 
7
 
8
- export class UserRegisteredWorkoutsService extends CrudService(UserRegisteredWorkout) {
9
- private workoutsService: WorkoutService = new WorkoutService();
 
 
 
 
 
 
 
 
 
 
 
 
 
10
 
11
  async createForUser(data: ICreateUserRegisteredWorkouts, userId: string) {
12
  const workout = await this.workoutsService.findOneOrFail({
13
  _id: data.workout,
14
  });
 
 
 
15
  return await this.create({
16
  ...data,
17
  user: userId,
 
1
  import { UserRegisteredWorkout } from "@common/models/user-registered-workout.model";
2
  import { CrudService } from "@lib/services/crud.service";
3
  import { ICreateUserRegisteredWorkouts } from "../validations/create-user-registered-workouts.validation";
 
4
  import { IUpdateUserRegisteredWorkouts } from "../validations/update-user-registered-workouts.validation";
5
  import { HttpError } from "@lib/error-handling/http-error";
6
+ import { Workout } from "@common/models/workout.model";
7
 
8
+ export class UserRegisteredWorkoutsService extends CrudService(UserRegisteredWorkout, {
9
+ defaultFilter: {
10
+ is_active: true,
11
+ },
12
+ }) {
13
+ private workoutsService = new (CrudService(Workout))()
14
+
15
+ async unregisterCurrentWorkout(userId: string) {
16
+ return await this.updateMany({
17
+ user: userId,
18
+ is_active: true,
19
+ }, {
20
+ is_active: false,
21
+ });
22
+ }
23
 
24
  async createForUser(data: ICreateUserRegisteredWorkouts, userId: string) {
25
  const workout = await this.workoutsService.findOneOrFail({
26
  _id: data.workout,
27
  });
28
+
29
+ await this.unregisterCurrentWorkout(userId);
30
+
31
  return await this.create({
32
  ...data,
33
  user: userId,
src/modules/users/modules/workouts/controllers/workouts.controller.ts CHANGED
@@ -15,6 +15,7 @@ import { SwaggerResponse } from "@lib/decorators/swagger-response.decorator";
15
  import { SwaggerSummary } from "@lib/decorators/swagger-summary.decorator";
16
  import { SwaggerDescription } from "@lib/decorators/swagger-description.decorator";
17
  import { SwaggerQuery } from "@lib/decorators/swagger-query.decorator";
 
18
 
19
 
20
  @Controller("/user/workouts")
@@ -37,7 +38,7 @@ export class UsersWorkoutController extends BaseController {
37
  filterName: "string",
38
  filterVal: "string",
39
  })
40
- list = async (req: Request, res: Response): Promise<Response> => {
41
  const paginationQuery = parsePaginationQuery(req.query);
42
 
43
  let filterName = req.query.filterName, filterVal = req.query.filterVal;
@@ -48,7 +49,13 @@ export class UsersWorkoutController extends BaseController {
48
  }
49
 
50
  const { docs, paginationData } = await this.workoutsService.list(
51
- filter,
 
 
 
 
 
 
52
  paginationQuery
53
  );
54
 
 
15
  import { SwaggerSummary } from "@lib/decorators/swagger-summary.decorator";
16
  import { SwaggerDescription } from "@lib/decorators/swagger-description.decorator";
17
  import { SwaggerQuery } from "@lib/decorators/swagger-query.decorator";
18
+ import { IUserRequest } from "@common/interfaces/user-request.interface";
19
 
20
 
21
  @Controller("/user/workouts")
 
38
  filterName: "string",
39
  filterVal: "string",
40
  })
41
+ list = async (req: IUserRequest, res: Response): Promise<Response> => {
42
  const paginationQuery = parsePaginationQuery(req.query);
43
 
44
  let filterName = req.query.filterName, filterVal = req.query.filterVal;
 
49
  }
50
 
51
  const { docs, paginationData } = await this.workoutsService.list(
52
+ {
53
+ ...filter,
54
+ $or: [
55
+ { aiGenerated: true, created_by: req.jwtPayload.id },
56
+ { aiGenerated: false },
57
+ ],
58
+ },
59
  paginationQuery
60
  );
61
 
src/modules/users/modules/workouts/services/workouts.service.ts CHANGED
@@ -1,4 +1,71 @@
 
 
1
  import { Workout } from "@common/models/workout.model";
 
2
  import { CrudService } from "@lib/services/crud.service";
 
 
 
3
 
4
- export class WorkoutService extends CrudService(Workout) {}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { WorkoutPlace } from "@common/enums/workout-place.enum";
2
+ import { UserDocument } from "@common/models/user.model";
3
  import { Workout } from "@common/models/workout.model";
4
+ import { FitnessModel, IFWParams, IFitnessPredictionItem } from "@lib/models/fitness-model";
5
  import { CrudService } from "@lib/services/crud.service";
6
+ import { calcAge } from "@lib/utils/age";
7
+ import { ExerciseService } from "../../exercises/services/exercises.service";
8
+ import { UserRegisteredWorkoutsService } from "../../user-registered-workouts/services/user-registered-workouts.service";
9
 
10
+ export class WorkoutService extends CrudService(Workout, {
11
+ defaultFilter: {
12
+ aiGenerated: false,
13
+ },
14
+ }) {
15
+ private exerciseService = new ExerciseService();
16
+ private userRegisteredWorkoutsService = new UserRegisteredWorkoutsService();
17
+
18
+ public async createModelWorkout(user: UserDocument) {
19
+ const params: IFWParams = {
20
+ home_or_gym: user.preferences.workout_place === WorkoutPlace.GYM ? 1 : 0,
21
+ level: user.fitness_level,
22
+ goal: user.preferences.fitness_goal,
23
+ gender: user.gender,
24
+ age: calcAge(user.dob),
25
+ feedback: false,
26
+ old_weight: user.weight,
27
+ equipments: user.preferences.preferred_equipment,
28
+ };
29
+
30
+ const pworkout = await FitnessModel.predictWorkout(params);
31
+
32
+ // partition the workout days into weeks
33
+ // each week has 7 days
34
+ const weeks: IFitnessPredictionItem[][][] = [];
35
+ for (let i = 0; i < pworkout.length; i += 7) {
36
+ weeks.push(pworkout.slice(i, i + 7));
37
+ }
38
+
39
+ const exercisesNames = pworkout.flat().map((e) => e.name);
40
+ const exercises = await this.exerciseService.listAll({ name: { $in: exercisesNames } });
41
+
42
+ const workout = await this.create({
43
+ aiGenerated: true,
44
+ name: `AI Generated Workout for ${user.name} (${new Date().toLocaleDateString()})`,
45
+ description: `AI Generated Workout for ${user.name} (${new Date().toLocaleDateString()})`,
46
+ type: "AI Generated",
47
+ created_by: user._id,
48
+ image: "https://placehold.co/300x400",
49
+ fitness_level: user.fitness_level,
50
+ fitness_goal: user.preferences.fitness_goal,
51
+ place: [user.preferences.workout_place],
52
+ min_per_day: 30,
53
+ total_number_days: pworkout.flat().length,
54
+ template_weeks: weeks.map((week, i) => ({
55
+ week_number: i + 1,
56
+ week_name: `Week ${i + 1}`,
57
+ week_description: `Week ${i + 1}`,
58
+ days: week.map((day, j) => ({
59
+ day_number: j + 1,
60
+ total_number_exercises: day.length,
61
+ day_type: "full_body", // #TODO: Change this
62
+ exercises: day.map((e) => exercises.find((ex) => ex.name === e.name)?._id),
63
+ })),
64
+ })),
65
+ });
66
+
67
+ await this.userRegisteredWorkoutsService.createForUser({
68
+ workout: workout._id,
69
+ }, user._id);
70
+ }
71
+ }