Spaces:
Running
Running
Hozifa Elgharbawy
commited on
Commit
·
cbe2a8f
1
Parent(s):
b499f38
feat: Update meal plan model fields to be optional
Browse files- src/common/models/meal-plan.model.ts +3 -3
- src/lib/services/crud.service.ts +5 -3
- src/modules/users/modules/auth/services/users-auth.service.ts +3 -0
- src/modules/users/modules/meal-plans/services/meal-plans.service.ts +15 -23
- src/modules/users/modules/user-registered-meal-plans/services/user-registered-meal-plans.service.ts +30 -21
- src/modules/users/modules/user-registered-workouts/services/user-registered-workouts.service.ts +1 -1
- src/modules/users/modules/workouts/services/workouts.service.ts +7 -3
- src/resources/meals.json +0 -0
- src/seeder/seed.ts +4 -3
- src/seeder/seeders/2-users.seeder.ts +4 -3
- src/seeder/seeders/9-meals.seeder.ts +1 -1
src/common/models/meal-plan.model.ts
CHANGED
@@ -24,10 +24,10 @@ const mealPlanSchema = new Schema({
|
|
24 |
description: { type: String, required: true },
|
25 |
duration: { type: Number, required: true },
|
26 |
level: { type: String, enum: FitnessLevel, required: true },
|
27 |
-
your_journey: { type: String, required:
|
28 |
key_features: [{
|
29 |
-
title: { type: String, required:
|
30 |
-
description: { type: String, required:
|
31 |
}],
|
32 |
days: [
|
33 |
{
|
|
|
24 |
description: { type: String, required: true },
|
25 |
duration: { type: Number, required: true },
|
26 |
level: { type: String, enum: FitnessLevel, required: true },
|
27 |
+
your_journey: { type: String, required: false },
|
28 |
key_features: [{
|
29 |
+
title: { type: String, required: false },
|
30 |
+
description: { type: String, required: false },
|
31 |
}],
|
32 |
days: [
|
33 |
{
|
src/lib/services/crud.service.ts
CHANGED
@@ -27,10 +27,12 @@ export const CrudService = <ModelDoc extends Document>(
|
|
27 |
|
28 |
async updateMany(
|
29 |
filter: FilterQuery<ModelDoc>,
|
30 |
-
data: AnyKeys<ModelDoc
|
|
|
31 |
): Promise<ModelDoc[]> {
|
32 |
filter = { ...crudOptions?.defaultFilter, ...filter };
|
33 |
-
|
|
|
34 |
await this.model.updateMany(filter, data);
|
35 |
return this.model.find(filter);
|
36 |
}
|
@@ -137,7 +139,7 @@ export const CrudService = <ModelDoc extends Document>(
|
|
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;
|
|
|
27 |
|
28 |
async updateMany(
|
29 |
filter: FilterQuery<ModelDoc>,
|
30 |
+
data: AnyKeys<ModelDoc>,
|
31 |
+
checkExists: boolean = true
|
32 |
): Promise<ModelDoc[]> {
|
33 |
filter = { ...crudOptions?.defaultFilter, ...filter };
|
34 |
+
if(checkExists)
|
35 |
+
await this.existsOrThrow(filter);
|
36 |
await this.model.updateMany(filter, data);
|
37 |
return this.model.find(filter);
|
38 |
}
|
|
|
139 |
options?: {
|
140 |
populateArray: any
|
141 |
}): Promise<ModelDoc | null> {
|
142 |
+
const queryInstruction = this.model.findOne(filter);
|
143 |
if (options?.populateArray) queryInstruction.populate(options.populateArray);
|
144 |
const document = await queryInstruction
|
145 |
return document;
|
src/modules/users/modules/auth/services/users-auth.service.ts
CHANGED
@@ -6,9 +6,11 @@ import { User } from "@common/models/user.model";
|
|
6 |
import { IUserRegister } from "@common/validations/user-register.validation";
|
7 |
import { CrudService } from "@lib/services/crud.service";
|
8 |
import { WorkoutService } from "../../workouts/services/workouts.service";
|
|
|
9 |
|
10 |
export class UsersAuthService extends CrudService(User) {
|
11 |
private workoutsService = new WorkoutService();
|
|
|
12 |
|
13 |
async register(createParams: IUserRegister) {
|
14 |
if (createParams.password !== createParams.confirmPassword) {
|
@@ -16,6 +18,7 @@ export class UsersAuthService extends CrudService(User) {
|
|
16 |
}
|
17 |
const user = await this.create(createParams);
|
18 |
await this.workoutsService.createModelWorkout(user);
|
|
|
19 |
return user;
|
20 |
}
|
21 |
|
|
|
6 |
import { IUserRegister } from "@common/validations/user-register.validation";
|
7 |
import { CrudService } from "@lib/services/crud.service";
|
8 |
import { WorkoutService } from "../../workouts/services/workouts.service";
|
9 |
+
import { MealPlansService } from "../../meal-plans/services/meal-plans.service";
|
10 |
|
11 |
export class UsersAuthService extends CrudService(User) {
|
12 |
private workoutsService = new WorkoutService();
|
13 |
+
private mealPlanService = new MealPlansService();
|
14 |
|
15 |
async register(createParams: IUserRegister) {
|
16 |
if (createParams.password !== createParams.confirmPassword) {
|
|
|
18 |
}
|
19 |
const user = await this.create(createParams);
|
20 |
await this.workoutsService.createModelWorkout(user);
|
21 |
+
await this.mealPlanService.createModelMealPlan(user);
|
22 |
return user;
|
23 |
}
|
24 |
|
src/modules/users/modules/meal-plans/services/meal-plans.service.ts
CHANGED
@@ -4,12 +4,13 @@ import { NutritionModel, INParams, INutritionPredictionItem } from "@lib/models/
|
|
4 |
import { calcAge } from "@lib/utils/age";
|
5 |
import { MealsService } from "../../meals/services/meals.service";
|
6 |
import { UserRegisteredMealPlansService } from "../../user-registered-meal-plans/services/user-registered-meal-plans.service";
|
|
|
7 |
|
8 |
export class MealPlansService extends CrudService(MealPlan) {
|
9 |
private mealsService = new MealsService();
|
10 |
private userRegisteredMealPlansService = new UserRegisteredMealPlansService();
|
11 |
|
12 |
-
public async createModelMealPlan(user:
|
13 |
let caloriesPerDay = 0;
|
14 |
if (user.gender === "male") {
|
15 |
caloriesPerDay = 10 * user.weight + 6.25 * user.height - 5 * calcAge(user.dob) + 5;
|
@@ -29,45 +30,36 @@ export class MealPlansService extends CrudService(MealPlan) {
|
|
29 |
|
30 |
const mealsNames = pMealPlan.flat().map((meal) => meal.Name);
|
31 |
const meals = await this.mealsService.listAll({ name: { $in: mealsNames } });
|
32 |
-
|
|
|
|
|
|
|
|
|
33 |
const mealPlan = await this.create({
|
34 |
aiGenerated: true,
|
35 |
image: "https://placehold.co/300x400",
|
36 |
-
description: `AI
|
|
|
|
|
37 |
duration: 28,
|
38 |
level: user.fitness_level,
|
39 |
-
your_journey: `Welcome to your personalized meal plan journey! As someone with a ${user.fitness_level} fitness level, this plan is tailored to meet your specific needs.`,
|
40 |
-
key_features: [
|
41 |
-
{
|
42 |
-
title: "Balanced Nutrition",
|
43 |
-
description: `Each meal provides a well-balanced mix of nutrients to support your health goals.`,
|
44 |
-
},
|
45 |
-
{
|
46 |
-
title: "Customizable Meals",
|
47 |
-
description: `Meals can be adjusted to fit your dietary preferences.`,
|
48 |
-
},
|
49 |
-
{
|
50 |
-
title: "Easy to Prepare",
|
51 |
-
description: `All meals are designed to be quick and easy to prepare.`,
|
52 |
-
},
|
53 |
-
{
|
54 |
-
title: "Variety and Flavor",
|
55 |
-
description: `Enjoy a diverse range of delicious meals.`,
|
56 |
-
},
|
57 |
-
],
|
58 |
days: pMealPlan.map((day, i) => ({
|
59 |
day_number: i + 1,
|
60 |
meals: day.map((m) => meals.find((meal) => meal.name === m.Name)?._id),
|
61 |
})),
|
62 |
});
|
|
|
|
|
63 |
|
64 |
await this.userRegisteredMealPlansService.createForUser(
|
65 |
{
|
66 |
-
|
67 |
},
|
68 |
user._id
|
69 |
);
|
70 |
|
|
|
|
|
71 |
return mealPlan;
|
72 |
}
|
73 |
}
|
|
|
4 |
import { calcAge } from "@lib/utils/age";
|
5 |
import { MealsService } from "../../meals/services/meals.service";
|
6 |
import { UserRegisteredMealPlansService } from "../../user-registered-meal-plans/services/user-registered-meal-plans.service";
|
7 |
+
import { UserDocument } from "@common/models/user.model";
|
8 |
|
9 |
export class MealPlansService extends CrudService(MealPlan) {
|
10 |
private mealsService = new MealsService();
|
11 |
private userRegisteredMealPlansService = new UserRegisteredMealPlansService();
|
12 |
|
13 |
+
public async createModelMealPlan(user: UserDocument) {
|
14 |
let caloriesPerDay = 0;
|
15 |
if (user.gender === "male") {
|
16 |
caloriesPerDay = 10 * user.weight + 6.25 * user.height - 5 * calcAge(user.dob) + 5;
|
|
|
30 |
|
31 |
const mealsNames = pMealPlan.flat().map((meal) => meal.Name);
|
32 |
const meals = await this.mealsService.listAll({ name: { $in: mealsNames } });
|
33 |
+
const todayDate = new Date().toLocaleDateString('en-US', {
|
34 |
+
year: 'numeric',
|
35 |
+
month: 'long',
|
36 |
+
day: 'numeric'
|
37 |
+
});
|
38 |
const mealPlan = await this.create({
|
39 |
aiGenerated: true,
|
40 |
image: "https://placehold.co/300x400",
|
41 |
+
description: `This AI-generated meal plan is designed specifically for you, considering your personal fitness goal of ${user.preferences.fitness_goal}.
|
42 |
+
Created on ${todayDate}, this plan is tailored to provide a balanced and nutritious diet that supports your workout frequency of ${user.preferences.workout_frequency} times per week.
|
43 |
+
Whether you prefer working out on ${user.preferences.preferred_days.join(", ")}, at ${user.preferences.workout_place}, or using ${user.preferences.preferred_equipment.join(", ")}, this meal plan will help you achieve your health and fitness goals. Enjoy a variety of delicious and nutritious meals selected just for you.`,
|
44 |
duration: 28,
|
45 |
level: user.fitness_level,
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
46 |
days: pMealPlan.map((day, i) => ({
|
47 |
day_number: i + 1,
|
48 |
meals: day.map((m) => meals.find((meal) => meal.name === m.Name)?._id),
|
49 |
})),
|
50 |
});
|
51 |
+
|
52 |
+
|
53 |
|
54 |
await this.userRegisteredMealPlansService.createForUser(
|
55 |
{
|
56 |
+
mealPlan: mealPlan._id,
|
57 |
},
|
58 |
user._id
|
59 |
);
|
60 |
|
61 |
+
|
62 |
+
|
63 |
return mealPlan;
|
64 |
}
|
65 |
}
|
src/modules/users/modules/user-registered-meal-plans/services/user-registered-meal-plans.service.ts
CHANGED
@@ -15,7 +15,7 @@ export class UserRegisteredMealPlansService extends CrudService(UserRegisteredMe
|
|
15 |
isActive: true,
|
16 |
}, {
|
17 |
isActive: false,
|
18 |
-
});
|
19 |
}
|
20 |
|
21 |
async createForUser(data: any, userId: string) {
|
@@ -24,7 +24,7 @@ export class UserRegisteredMealPlansService extends CrudService(UserRegisteredMe
|
|
24 |
});
|
25 |
|
26 |
await this.unregisterCurrentMealPlan(userId);
|
27 |
-
|
28 |
return await this.create({
|
29 |
...data,
|
30 |
user: userId,
|
@@ -32,25 +32,34 @@ export class UserRegisteredMealPlansService extends CrudService(UserRegisteredMe
|
|
32 |
isActive: true,
|
33 |
});
|
34 |
}
|
35 |
-
|
36 |
-
|
37 |
-
const mealPlan = await this.findOneOrFail({
|
38 |
-
_id: mealPlanProps.urwId,
|
39 |
-
user: userId,
|
40 |
-
});
|
41 |
|
|
|
|
|
|
|
|
|
42 |
|
43 |
-
|
44 |
-
|
45 |
-
|
46 |
-
|
47 |
-
|
48 |
-
|
49 |
-
|
50 |
-
|
51 |
-
|
52 |
-
|
53 |
-
|
54 |
-
|
55 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
56 |
}
|
|
|
15 |
isActive: true,
|
16 |
}, {
|
17 |
isActive: false,
|
18 |
+
}, false);
|
19 |
}
|
20 |
|
21 |
async createForUser(data: any, userId: string) {
|
|
|
24 |
});
|
25 |
|
26 |
await this.unregisterCurrentMealPlan(userId);
|
27 |
+
|
28 |
return await this.create({
|
29 |
...data,
|
30 |
user: userId,
|
|
|
32 |
isActive: true,
|
33 |
});
|
34 |
}
|
35 |
+
async updateForUser(mealPlanProps: { urwId: string; dayNumber: number }, data: any, userId: string) {
|
36 |
+
// find mealPlan
|
|
|
|
|
|
|
|
|
37 |
|
38 |
+
const mealPlan = await this.findOneOrFail({
|
39 |
+
_id: mealPlanProps.urwId,
|
40 |
+
user: userId,
|
41 |
+
});
|
42 |
|
43 |
+
// find day
|
44 |
+
const day = mealPlan.days.find(d => d.day_number === mealPlanProps.dayNumber);
|
45 |
+
if (!day) throw new HttpError(404, 'Workout Day Not Found');
|
46 |
+
const dayIndex = mealPlan.days.indexOf(day)
|
47 |
+
|
48 |
+
// update day
|
49 |
+
day.is_eaten = true;
|
50 |
+
mealPlan.days[dayIndex] = day;
|
51 |
+
|
52 |
+
// save changes
|
53 |
+
mealPlan.markModified('days');
|
54 |
+
|
55 |
+
const updatedMealPlan = await mealPlan.save();
|
56 |
+
|
57 |
+
// check if it's the last day
|
58 |
+
const lastDay = mealPlan.days[mealPlan.days.length - 1];
|
59 |
+
if (lastDay.day_number === mealPlanProps.dayNumber) {
|
60 |
+
console.log('This is the last day that was updated.');
|
61 |
+
}
|
62 |
+
|
63 |
+
return updatedMealPlan;
|
64 |
+
}
|
65 |
}
|
src/modules/users/modules/user-registered-workouts/services/user-registered-workouts.service.ts
CHANGED
@@ -18,7 +18,7 @@ export class UserRegisteredWorkoutsService extends CrudService(UserRegisteredWor
|
|
18 |
is_active: true,
|
19 |
}, {
|
20 |
is_active: false,
|
21 |
-
});
|
22 |
}
|
23 |
|
24 |
async createForUser(data: ICreateUserRegisteredWorkouts, userId: string) {
|
|
|
18 |
is_active: true,
|
19 |
}, {
|
20 |
is_active: false,
|
21 |
+
}, false);
|
22 |
}
|
23 |
|
24 |
async createForUser(data: ICreateUserRegisteredWorkouts, userId: string) {
|
src/modules/users/modules/workouts/services/workouts.service.ts
CHANGED
@@ -34,11 +34,15 @@ export class WorkoutService extends CrudService(Workout) {
|
|
34 |
|
35 |
const exercisesNames = pworkout.flat().map((e) => e.name);
|
36 |
const exercises = await this.exerciseService.listAll({ name: { $in: exercisesNames } });
|
37 |
-
|
|
|
|
|
|
|
|
|
38 |
const workout = await this.create({
|
39 |
aiGenerated: true,
|
40 |
-
name: `AI Generated Workout
|
41 |
-
description:
|
42 |
type: "AI Generated",
|
43 |
created_by: user._id,
|
44 |
image: "https://placehold.co/300x400",
|
|
|
34 |
|
35 |
const exercisesNames = pworkout.flat().map((e) => e.name);
|
36 |
const exercises = await this.exerciseService.listAll({ name: { $in: exercisesNames } });
|
37 |
+
const todayDate = new Date().toLocaleDateString('en-US', {
|
38 |
+
year: 'numeric',
|
39 |
+
month: 'long',
|
40 |
+
day: 'numeric'
|
41 |
+
});
|
42 |
const workout = await this.create({
|
43 |
aiGenerated: true,
|
44 |
+
name: `AI Generated Workout (${user.preferences.fitness_goal} - ${user.fitness_level}) - ${todayDate}`,
|
45 |
+
description: `This AI-generated workout plan, created on ${todayDate}, is tailored for your ${user.fitness_level.toLowerCase()} fitness level and ${user.preferences.fitness_goal.toLowerCase()} goal. It is designed to be performed ${user.preferences.workout_place === WorkoutPlace.GYM ? "at the gym" : "at home"} using your preferred equipment.`,
|
46 |
type: "AI Generated",
|
47 |
created_by: user._id,
|
48 |
image: "https://placehold.co/300x400",
|
src/resources/meals.json
CHANGED
The diff for this file is too large to render.
See raw diff
|
|
src/seeder/seed.ts
CHANGED
@@ -13,14 +13,14 @@ import { loadExercisesDataset } from "./helpers/load-exercises-dataset";
|
|
13 |
import { dbStore } from "./helpers/db-store";
|
14 |
import { loadMealsDataset } from "./helpers/load-meals-dataset";
|
15 |
|
16 |
-
const loadDatasets = async() => {
|
17 |
const exercisesDataset = await loadExercisesDataset();
|
18 |
const mealsDataset = loadMealsDataset();
|
19 |
const musclesDataset = Array.from(new Set(exercisesDataset.map((exercise) => exercise.target)));
|
20 |
const equipmentsDataset = Array.from(new Set(exercisesDataset.map((exercise) => exercise.equipment)));
|
21 |
-
const ingredientsArrays = mealsDataset.map(m=>m.RecipeIngredientParts);
|
22 |
const ingredientsNames = Array.from(new Set(ingredientsArrays.flat()))
|
23 |
-
|
24 |
dbStore.excerisesDataset = exercisesDataset;
|
25 |
dbStore.musclesDataset = musclesDataset;
|
26 |
dbStore.equipmentsDataset = equipmentsDataset;
|
@@ -53,6 +53,7 @@ const main = async () => {
|
|
53 |
return seederNames.some(name => path.basename(file).includes(name));
|
54 |
})
|
55 |
.sort();
|
|
|
56 |
|
57 |
// load datasets
|
58 |
await loadDatasets();
|
|
|
13 |
import { dbStore } from "./helpers/db-store";
|
14 |
import { loadMealsDataset } from "./helpers/load-meals-dataset";
|
15 |
|
16 |
+
const loadDatasets = async () => {
|
17 |
const exercisesDataset = await loadExercisesDataset();
|
18 |
const mealsDataset = loadMealsDataset();
|
19 |
const musclesDataset = Array.from(new Set(exercisesDataset.map((exercise) => exercise.target)));
|
20 |
const equipmentsDataset = Array.from(new Set(exercisesDataset.map((exercise) => exercise.equipment)));
|
21 |
+
const ingredientsArrays = mealsDataset.map(m => m.RecipeIngredientParts);
|
22 |
const ingredientsNames = Array.from(new Set(ingredientsArrays.flat()))
|
23 |
+
|
24 |
dbStore.excerisesDataset = exercisesDataset;
|
25 |
dbStore.musclesDataset = musclesDataset;
|
26 |
dbStore.equipmentsDataset = equipmentsDataset;
|
|
|
53 |
return seederNames.some(name => path.basename(file).includes(name));
|
54 |
})
|
55 |
.sort();
|
56 |
+
console.log(seedersFiles);
|
57 |
|
58 |
// load datasets
|
59 |
await loadDatasets();
|
src/seeder/seeders/2-users.seeder.ts
CHANGED
@@ -1,6 +1,7 @@
|
|
1 |
import { AuthenticatableType } from "@common/enums/authenticatable-type.enum";
|
2 |
import { FitnessGoal } from "@common/enums/fitness-goal.enum";
|
3 |
import { FitnessLevel } from "@common/enums/fitness-level.enum";
|
|
|
4 |
import { Injury } from "@common/enums/injury.enum";
|
5 |
import { PreferredDay } from "@common/enums/preferred-day.enum";
|
6 |
import { PreferredEquipment } from "@common/enums/preferred-equipment.enum";
|
@@ -16,9 +17,9 @@ export default seederWrapper(User, async () => {
|
|
16 |
email: `user-${i}@app.com`,
|
17 |
password: "password",
|
18 |
image: `https://placehold.co/300x400`,
|
19 |
-
|
20 |
-
|
21 |
-
|
22 |
height: 170,
|
23 |
weight: 70,
|
24 |
fitness_level: [FitnessLevel.BEGINNER, FitnessLevel.INTERMEDIATE, FitnessLevel.ADVANCED][i % 3],
|
|
|
1 |
import { AuthenticatableType } from "@common/enums/authenticatable-type.enum";
|
2 |
import { FitnessGoal } from "@common/enums/fitness-goal.enum";
|
3 |
import { FitnessLevel } from "@common/enums/fitness-level.enum";
|
4 |
+
import { Gender } from "@common/enums/gender.enum";
|
5 |
import { Injury } from "@common/enums/injury.enum";
|
6 |
import { PreferredDay } from "@common/enums/preferred-day.enum";
|
7 |
import { PreferredEquipment } from "@common/enums/preferred-equipment.enum";
|
|
|
17 |
email: `user-${i}@app.com`,
|
18 |
password: "password",
|
19 |
image: `https://placehold.co/300x400`,
|
20 |
+
gender: (i as number % 2 === 0) ?
|
21 |
+
Gender.MALE :
|
22 |
+
Gender.FEMALE,
|
23 |
height: 170,
|
24 |
weight: 70,
|
25 |
fitness_level: [FitnessLevel.BEGINNER, FitnessLevel.INTERMEDIATE, FitnessLevel.ADVANCED][i % 3],
|
src/seeder/seeders/9-meals.seeder.ts
CHANGED
@@ -22,7 +22,7 @@ export default seederWrapper(Meal, async () => {
|
|
22 |
const data = await Promise.all(dbStore.mealsDataset.map(async (mealJson) => ({
|
23 |
name: mealJson.Name,
|
24 |
created_at: new Date(),
|
25 |
-
image:
|
26 |
ingredients: mealJson.RecipeIngredientParts.map(name => ingredientsIds.find(i => i.name === name)._id),
|
27 |
calories: mealJson.Calories,
|
28 |
carbs: mealJson.CarbohydrateContent,
|
|
|
22 |
const data = await Promise.all(dbStore.mealsDataset.map(async (mealJson) => ({
|
23 |
name: mealJson.Name,
|
24 |
created_at: new Date(),
|
25 |
+
image: mealJson.Images[0],
|
26 |
ingredients: mealJson.RecipeIngredientParts.map(name => ingredientsIds.find(i => i.name === name)._id),
|
27 |
calories: mealJson.Calories,
|
28 |
carbs: mealJson.CarbohydrateContent,
|