Spaces:
Running
Running
Merge branch 'main' into models-server
Browse files- src/common/enums/activity-type.enum.ts +3 -0
- src/common/models/activity.model.ts +62 -0
- src/common/models/meal.model.ts +2 -0
- src/common/serializers/meal-planPopulate.serialization.ts +11 -0
- src/common/serializers/meal.serialization.ts +4 -0
- src/common/serializers/mealPopulate.serialization.ts +4 -0
- src/common/serializers/user-registered-meal-planPopulate.serialization.ts +5 -5
- src/lib/events/events-manager.ts +72 -0
- src/modules/console/modules/meals/validations/create-meals.validation.ts +7 -0
- src/modules/console/modules/meals/validations/update-meals.validation.ts +5 -0
- src/modules/users/modules/home/controllers/home-nutriguide.controller.ts +7 -5
- src/modules/users/modules/meals/controller/meals.controller.ts +23 -1
- src/modules/users/modules/meals/events/eat-custom-meal.event.ts +18 -0
- src/modules/users/modules/meals/services/meals.service.ts +24 -1
- src/modules/users/modules/meals/validations/eat-custom-meal.validation.ts +18 -0
- src/modules/users/modules/user-registered-meal-plans/controller/user-registered-meal-plans.controller.ts +25 -2
- src/modules/users/modules/user-registered-meal-plans/services/user-registered-meal-plans.service.ts +26 -1
- src/resources/meals1.json +0 -0
- src/resources/meals2.json +0 -0
- src/seeder/seeders/9-meals.seeder.ts +2 -0
src/common/enums/activity-type.enum.ts
ADDED
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
1 |
+
export enum ActivityType {
|
2 |
+
EAT_CUSTOM_MEAL = "eat-custom-meal",
|
3 |
+
}
|
src/common/models/activity.model.ts
ADDED
@@ -0,0 +1,62 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import mongoose, { Types } from "mongoose";
|
2 |
+
import { MealDocument } from "./meal.model";
|
3 |
+
import { MealPlanDocument } from "./meal-plan.model";
|
4 |
+
import { WorkoutDocument } from "./workout.model";
|
5 |
+
import { ExerciseDocument } from "./exercise.model";
|
6 |
+
import { ActivityType } from "@common/enums/activity-type.enum";
|
7 |
+
const { Schema } = mongoose;
|
8 |
+
|
9 |
+
export type RelatedItem = | MealDocument
|
10 |
+
| MealPlanDocument
|
11 |
+
| WorkoutDocument
|
12 |
+
| ExerciseDocument
|
13 |
+
| {
|
14 |
+
ingredients: {
|
15 |
+
id: string,
|
16 |
+
noServings: number
|
17 |
+
}[];
|
18 |
+
}
|
19 |
+
export type AMetaData = {};
|
20 |
+
|
21 |
+
export interface IActivity {
|
22 |
+
related_item?: RelatedItem;
|
23 |
+
meta_data?: AMetaData;
|
24 |
+
activity_type: ActivityType;
|
25 |
+
related_id?: string | Types.ObjectId;
|
26 |
+
user_id: string | Types.ObjectId;
|
27 |
+
created_at?: Date;
|
28 |
+
}
|
29 |
+
|
30 |
+
const activitySchema = new Schema({
|
31 |
+
related_item: {
|
32 |
+
type: Schema.Types.Mixed,
|
33 |
+
required: false,
|
34 |
+
},
|
35 |
+
meta_data: {
|
36 |
+
type: Schema.Types.Mixed,
|
37 |
+
default: {},
|
38 |
+
},
|
39 |
+
activity_type: {
|
40 |
+
type: String,
|
41 |
+
enum: Object.values(ActivityType),
|
42 |
+
},
|
43 |
+
related_id: {
|
44 |
+
type: Schema.Types.ObjectId,
|
45 |
+
required: false,
|
46 |
+
},
|
47 |
+
user_id: {
|
48 |
+
type: Schema.Types.ObjectId,
|
49 |
+
required: true,
|
50 |
+
},
|
51 |
+
created_at: {
|
52 |
+
type: Date,
|
53 |
+
default: Date.now,
|
54 |
+
},
|
55 |
+
});
|
56 |
+
|
57 |
+
export type ActivityDocument = IActivity & mongoose.Document;
|
58 |
+
|
59 |
+
export const Activity = mongoose.model<ActivityDocument>(
|
60 |
+
"activities",
|
61 |
+
activitySchema
|
62 |
+
);
|
src/common/models/meal.model.ts
CHANGED
@@ -4,6 +4,7 @@ import { MealType } from "@common/enums/meal-type.enum";
|
|
4 |
export interface IMeal {
|
5 |
name: string;
|
6 |
created_at: Date;
|
|
|
7 |
ingredients: string[];
|
8 |
calories: number;
|
9 |
carbs: number;
|
@@ -15,6 +16,7 @@ export interface IMeal {
|
|
15 |
const mealSchema = new Schema({
|
16 |
name: { type: String, required: true, unique: true, dropDups: true },
|
17 |
created_at: { type: Date, default: Date.now() },
|
|
|
18 |
type: {
|
19 |
type: String,
|
20 |
enum: MealType,
|
|
|
4 |
export interface IMeal {
|
5 |
name: string;
|
6 |
created_at: Date;
|
7 |
+
image: string;
|
8 |
ingredients: string[];
|
9 |
calories: number;
|
10 |
carbs: number;
|
|
|
16 |
const mealSchema = new Schema({
|
17 |
name: { type: String, required: true, unique: true, dropDups: true },
|
18 |
created_at: { type: Date, default: Date.now() },
|
19 |
+
image: { type: String, required: true },
|
20 |
type: {
|
21 |
type: String,
|
22 |
enum: MealType,
|
src/common/serializers/meal-planPopulate.serialization.ts
CHANGED
@@ -97,4 +97,15 @@ export class ListMealPlanSerialization {
|
|
97 |
)
|
98 |
key_features: any;
|
99 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
100 |
}
|
|
|
97 |
)
|
98 |
key_features: any;
|
99 |
|
100 |
+
}
|
101 |
+
|
102 |
+
|
103 |
+
export class MSerialization {
|
104 |
+
@Expose({ name: "_id" })
|
105 |
+
@SwaggerResponseProperty({ type: "string" })
|
106 |
+
id: string;
|
107 |
+
|
108 |
+
@Expose()
|
109 |
+
@SwaggerResponseProperty({ type: "string" })
|
110 |
+
image: string;
|
111 |
}
|
src/common/serializers/meal.serialization.ts
CHANGED
@@ -15,6 +15,10 @@ export class MealSerialization {
|
|
15 |
@SwaggerResponseProperty({ type: "string" })
|
16 |
created_at: Date;
|
17 |
|
|
|
|
|
|
|
|
|
18 |
@Expose()
|
19 |
@SwaggerResponseProperty({ type: ["string"] })
|
20 |
ingredients: any;
|
|
|
15 |
@SwaggerResponseProperty({ type: "string" })
|
16 |
created_at: Date;
|
17 |
|
18 |
+
@Expose()
|
19 |
+
@SwaggerResponseProperty({ type: "string" })
|
20 |
+
image: string;
|
21 |
+
|
22 |
@Expose()
|
23 |
@SwaggerResponseProperty({ type: ["string"] })
|
24 |
ingredients: any;
|
src/common/serializers/mealPopulate.serialization.ts
CHANGED
@@ -17,6 +17,10 @@ export class MealPopulateSerialization {
|
|
17 |
@SwaggerResponseProperty({ type: "string" })
|
18 |
created_at: Date;
|
19 |
|
|
|
|
|
|
|
|
|
20 |
@Expose()
|
21 |
@SwaggerResponseProperty({ type: [IngredientSerialization] })
|
22 |
@Transform(
|
|
|
17 |
@SwaggerResponseProperty({ type: "string" })
|
18 |
created_at: Date;
|
19 |
|
20 |
+
@Expose()
|
21 |
+
@SwaggerResponseProperty({ type: "string" })
|
22 |
+
image: string;
|
23 |
+
|
24 |
@Expose()
|
25 |
@SwaggerResponseProperty({ type: [IngredientSerialization] })
|
26 |
@Transform(
|
src/common/serializers/user-registered-meal-planPopulate.serialization.ts
CHANGED
@@ -1,8 +1,8 @@
|
|
1 |
import { Expose, Transform } from "class-transformer";
|
2 |
import { serialize } from "@helpers/serialize";
|
3 |
import { SwaggerResponseProperty } from "@lib/decorators/swagger-response-property.decorator";
|
4 |
-
import { ListMealPlanSerialization } from "
|
5 |
-
import {
|
6 |
import { MealPlanSerialization } from "./meal-plan.serialization";
|
7 |
|
8 |
|
@@ -13,9 +13,9 @@ class MealDaysPopulate {
|
|
13 |
day_number: number;
|
14 |
|
15 |
@Expose({ name: "meals" })
|
16 |
-
@SwaggerResponseProperty({ type: [
|
17 |
@Transform(
|
18 |
-
({ value }) => serialize(value,
|
19 |
)
|
20 |
meals: any;
|
21 |
|
@@ -65,7 +65,7 @@ export class GetMyMealPlanSerialization {
|
|
65 |
|
66 |
@Expose()
|
67 |
@SwaggerResponseProperty({ type: ListMealPlanSerialization })
|
68 |
-
meal_plan:
|
69 |
|
70 |
@Expose()
|
71 |
@SwaggerResponseProperty({ type: "boolean" })
|
|
|
1 |
import { Expose, Transform } from "class-transformer";
|
2 |
import { serialize } from "@helpers/serialize";
|
3 |
import { SwaggerResponseProperty } from "@lib/decorators/swagger-response-property.decorator";
|
4 |
+
import { ListMealPlanSerialization } from "@common/serializers/meal-planPopulate.serialization";
|
5 |
+
import { MealPopulateSerialization } from "./mealPopulate.serialization";
|
6 |
import { MealPlanSerialization } from "./meal-plan.serialization";
|
7 |
|
8 |
|
|
|
13 |
day_number: number;
|
14 |
|
15 |
@Expose({ name: "meals" })
|
16 |
+
@SwaggerResponseProperty({ type: [MealPopulateSerialization] })
|
17 |
@Transform(
|
18 |
+
({ value }) => serialize(value, MealPopulateSerialization)
|
19 |
)
|
20 |
meals: any;
|
21 |
|
|
|
65 |
|
66 |
@Expose()
|
67 |
@SwaggerResponseProperty({ type: ListMealPlanSerialization })
|
68 |
+
meal_plan: any;
|
69 |
|
70 |
@Expose()
|
71 |
@SwaggerResponseProperty({ type: "boolean" })
|
src/lib/events/events-manager.ts
ADDED
@@ -0,0 +1,72 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import { asyncHandler } from '@helpers/async-handler';
|
2 |
+
import EventEmitter from 'node:events';
|
3 |
+
|
4 |
+
/**
|
5 |
+
* EventsManager is a singleton class that manages events.
|
6 |
+
* It uses the EventEmitter class from Node.js to handle events.
|
7 |
+
*/
|
8 |
+
export class EventsManager {
|
9 |
+
private static instance: EventsManager;
|
10 |
+
private emitter: EventEmitter;
|
11 |
+
|
12 |
+
private constructor() {
|
13 |
+
this.emitter = new EventEmitter();
|
14 |
+
}
|
15 |
+
|
16 |
+
public static getInstance(): EventsManager {
|
17 |
+
if (!EventsManager.instance) {
|
18 |
+
EventsManager.instance = new EventsManager();
|
19 |
+
}
|
20 |
+
|
21 |
+
return EventsManager.instance;
|
22 |
+
}
|
23 |
+
|
24 |
+
/**
|
25 |
+
* Register an event listener.
|
26 |
+
* @param event The event name.
|
27 |
+
* @param listener The listener function.
|
28 |
+
*/
|
29 |
+
public static on(event: string, listener: (...args: any[]) => void | Promise<void>): void {
|
30 |
+
try {
|
31 |
+
EventsManager.getInstance().emitter.on(event, asyncHandler(listener));
|
32 |
+
} catch (error) {
|
33 |
+
console.error(error);
|
34 |
+
}
|
35 |
+
}
|
36 |
+
|
37 |
+
/**
|
38 |
+
* Remove an event listener.
|
39 |
+
* @param event The event name.
|
40 |
+
* @param listener The listener function.
|
41 |
+
*/
|
42 |
+
public static emit(event: string, ...args: any[]): void {
|
43 |
+
EventsManager.getInstance().emitter.emit(event, ...args);
|
44 |
+
}
|
45 |
+
|
46 |
+
/**
|
47 |
+
* Create a queue to store events.
|
48 |
+
* @returns An EventsQueue object.
|
49 |
+
* @see EventsQueue
|
50 |
+
*/
|
51 |
+
public createQueue() {
|
52 |
+
return new class EventsQueue {
|
53 |
+
private queue: any[] = [];
|
54 |
+
|
55 |
+
public add(event: string, ...args: any[]): void {
|
56 |
+
this.queue.push({ event, args });
|
57 |
+
}
|
58 |
+
|
59 |
+
public clear() {
|
60 |
+
this.queue = [];
|
61 |
+
}
|
62 |
+
|
63 |
+
public process() {
|
64 |
+
this.queue.forEach((item) => {
|
65 |
+
EventsManager.emit(item.event, ...item.args);
|
66 |
+
});
|
67 |
+
|
68 |
+
this.clear();
|
69 |
+
}
|
70 |
+
}
|
71 |
+
}
|
72 |
+
}
|
src/modules/console/modules/meals/validations/create-meals.validation.ts
CHANGED
@@ -5,6 +5,7 @@ import { MealType } from "@common/enums/meal-type.enum";
|
|
5 |
export interface ICreateMeal {
|
6 |
name: string;
|
7 |
created_at?: Date;
|
|
|
8 |
ingredients: string[];
|
9 |
calories: number;
|
10 |
carbs: number;
|
@@ -24,6 +25,12 @@ export const createMealSchema = createSchema<ICreateMeal>({
|
|
24 |
"date.empty": "created_at can not be empty",
|
25 |
}),
|
26 |
|
|
|
|
|
|
|
|
|
|
|
|
|
27 |
calories: joi.number().empty().required().messages({
|
28 |
"number.base": "please enter a valid calories",
|
29 |
"any.required": "calories is required",
|
|
|
5 |
export interface ICreateMeal {
|
6 |
name: string;
|
7 |
created_at?: Date;
|
8 |
+
image: string;
|
9 |
ingredients: string[];
|
10 |
calories: number;
|
11 |
carbs: number;
|
|
|
25 |
"date.empty": "created_at can not be empty",
|
26 |
}),
|
27 |
|
28 |
+
image: joi.string().empty().required().messages({
|
29 |
+
"string.base": "please enter a valid image",
|
30 |
+
"any.required": "image is required",
|
31 |
+
"string.empty": "image can not be empty",
|
32 |
+
}),
|
33 |
+
|
34 |
calories: joi.number().empty().required().messages({
|
35 |
"number.base": "please enter a valid calories",
|
36 |
"any.required": "calories is required",
|
src/modules/console/modules/meals/validations/update-meals.validation.ts
CHANGED
@@ -5,6 +5,7 @@ import { MealType } from "@common/enums/meal-type.enum";
|
|
5 |
export interface IUpdateMeal {
|
6 |
name?: string;
|
7 |
created_at?: Date;
|
|
|
8 |
ingredients?: [string];
|
9 |
calories?: number;
|
10 |
carbs?: number;
|
@@ -22,6 +23,10 @@ export const updateMealSchema = createSchema<IUpdateMeal>({
|
|
22 |
"date.base": "please enter a valid created_at",
|
23 |
"date.empty": "created_at can not be empty",
|
24 |
}),
|
|
|
|
|
|
|
|
|
25 |
ingredients: joi.array().items(joi.string()).optional().messages({
|
26 |
"array.base": "please enter a valid ingredients",
|
27 |
}),
|
|
|
5 |
export interface IUpdateMeal {
|
6 |
name?: string;
|
7 |
created_at?: Date;
|
8 |
+
image?: string;
|
9 |
ingredients?: [string];
|
10 |
calories?: number;
|
11 |
carbs?: number;
|
|
|
23 |
"date.base": "please enter a valid created_at",
|
24 |
"date.empty": "created_at can not be empty",
|
25 |
}),
|
26 |
+
image: joi.string().empty().optional().messages({
|
27 |
+
"string.base": "please enter a valid image",
|
28 |
+
"string.empty": "image can not be empty",
|
29 |
+
}),
|
30 |
ingredients: joi.array().items(joi.string()).optional().messages({
|
31 |
"array.base": "please enter a valid ingredients",
|
32 |
}),
|
src/modules/users/modules/home/controllers/home-nutriguide.controller.ts
CHANGED
@@ -15,7 +15,7 @@ import { UserHomeService } from "../services/user-home.service";
|
|
15 |
import { IUserRequest } from "@common/interfaces/user-request.interface";
|
16 |
import { UserHomeYourDailyIntakeSerialization } from "../responses/user-home-your-daily-intake.serialization";
|
17 |
import { UserNutriHomeDailyGoalsSerialization } from "../responses/user-nutri-home-daily-goals.serialization";
|
18 |
-
import {
|
19 |
import { UserRegisteredMealPlansService } from "../../user-registered-meal-plans/services/user-registered-meal-plans.service";
|
20 |
import { SwaggerRequest } from "@lib/decorators/swagger-request.decorator";
|
21 |
|
@@ -37,7 +37,7 @@ export class homeNutriGuideController extends BaseController {
|
|
37 |
}
|
38 |
|
39 |
@SwaggerGet("/today-meals")
|
40 |
-
@SwaggerResponse(
|
41 |
@SwaggerSummary("Get today's meals")
|
42 |
@SwaggerDescription("Get today's meals for the user.")
|
43 |
getTodayMeals = async (req: IUserRequest, res: Response): Promise<Response> => {
|
@@ -48,11 +48,13 @@ export class homeNutriGuideController extends BaseController {
|
|
48 |
{
|
49 |
populateArray: [
|
50 |
{ path: "meal_plan", select: "-days" },
|
51 |
-
{
|
|
|
|
|
|
|
52 |
],
|
53 |
}
|
54 |
);
|
55 |
-
console.log(data);
|
56 |
|
57 |
|
58 |
const dayToEat = data.days.find(day => day.is_eaten === false);
|
@@ -61,7 +63,7 @@ export class homeNutriGuideController extends BaseController {
|
|
61 |
data.days = [dayToEat];
|
62 |
return JsonResponse.success(
|
63 |
{
|
64 |
-
data: serialize(data,
|
65 |
},
|
66 |
res
|
67 |
);
|
|
|
15 |
import { IUserRequest } from "@common/interfaces/user-request.interface";
|
16 |
import { UserHomeYourDailyIntakeSerialization } from "../responses/user-home-your-daily-intake.serialization";
|
17 |
import { UserNutriHomeDailyGoalsSerialization } from "../responses/user-nutri-home-daily-goals.serialization";
|
18 |
+
import { GetMyMealPlanSerialization } from "@common/serializers/user-registered-meal-planPopulate.serialization";
|
19 |
import { UserRegisteredMealPlansService } from "../../user-registered-meal-plans/services/user-registered-meal-plans.service";
|
20 |
import { SwaggerRequest } from "@lib/decorators/swagger-request.decorator";
|
21 |
|
|
|
37 |
}
|
38 |
|
39 |
@SwaggerGet("/today-meals")
|
40 |
+
@SwaggerResponse(GetMyMealPlanSerialization)
|
41 |
@SwaggerSummary("Get today's meals")
|
42 |
@SwaggerDescription("Get today's meals for the user.")
|
43 |
getTodayMeals = async (req: IUserRequest, res: Response): Promise<Response> => {
|
|
|
48 |
{
|
49 |
populateArray: [
|
50 |
{ path: "meal_plan", select: "-days" },
|
51 |
+
{
|
52 |
+
path: "days.meals",
|
53 |
+
populate: { path: "ingredients" }
|
54 |
+
}
|
55 |
],
|
56 |
}
|
57 |
);
|
|
|
58 |
|
59 |
|
60 |
const dayToEat = data.days.find(day => day.is_eaten === false);
|
|
|
63 |
data.days = [dayToEat];
|
64 |
return JsonResponse.success(
|
65 |
{
|
66 |
+
data: serialize(data, GetMyMealPlanSerialization),
|
67 |
},
|
68 |
res
|
69 |
);
|
src/modules/users/modules/meals/controller/meals.controller.ts
CHANGED
@@ -9,11 +9,15 @@ import { Controller } from "@lib/decorators/controller.decorator";
|
|
9 |
import { serialize } from "@helpers/serialize";
|
10 |
import { ControllerMiddleware } from "@lib/decorators/controller-middleware.decorator";
|
11 |
import { UsersGuardMiddleware } from "modules/users/common/guards/users.guard";
|
12 |
-
import { SwaggerGet } from "@lib/decorators/swagger-routes.decorator";
|
13 |
import { SwaggerResponse } from "@lib/decorators/swagger-response.decorator";
|
14 |
import { SwaggerSummary } from "@lib/decorators/swagger-summary.decorator";
|
15 |
import { SwaggerDescription } from "@lib/decorators/swagger-description.decorator";
|
16 |
import { SwaggerQuery } from "@lib/decorators/swagger-query.decorator";
|
|
|
|
|
|
|
|
|
17 |
|
18 |
|
19 |
@Controller("/user/meals")
|
@@ -23,6 +27,7 @@ export class UsersMealsController extends BaseController {
|
|
23 |
|
24 |
setRoutes(): void {
|
25 |
this.router.get("/", asyncHandler(this.list));
|
|
|
26 |
}
|
27 |
|
28 |
@SwaggerGet()
|
@@ -52,4 +57,21 @@ export class UsersMealsController extends BaseController {
|
|
52 |
res
|
53 |
);
|
54 |
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
55 |
}
|
|
|
|
9 |
import { serialize } from "@helpers/serialize";
|
10 |
import { ControllerMiddleware } from "@lib/decorators/controller-middleware.decorator";
|
11 |
import { UsersGuardMiddleware } from "modules/users/common/guards/users.guard";
|
12 |
+
import { SwaggerGet, SwaggerPost } from "@lib/decorators/swagger-routes.decorator";
|
13 |
import { SwaggerResponse } from "@lib/decorators/swagger-response.decorator";
|
14 |
import { SwaggerSummary } from "@lib/decorators/swagger-summary.decorator";
|
15 |
import { SwaggerDescription } from "@lib/decorators/swagger-description.decorator";
|
16 |
import { SwaggerQuery } from "@lib/decorators/swagger-query.decorator";
|
17 |
+
import { IUserRequest } from "@common/interfaces/user-request.interface";
|
18 |
+
import { IEatCustomMeal, eatCustomMealSchema } from "../validations/eat-custom-meal.validation";
|
19 |
+
import { bodyValidator } from "@helpers/validation.helper";
|
20 |
+
import { SwaggerRequest } from "@lib/decorators/swagger-request.decorator";
|
21 |
|
22 |
|
23 |
@Controller("/user/meals")
|
|
|
27 |
|
28 |
setRoutes(): void {
|
29 |
this.router.get("/", asyncHandler(this.list));
|
30 |
+
this.router.post("/eat-custom-meal", bodyValidator(eatCustomMealSchema), asyncHandler(this.eatCustomMeal));
|
31 |
}
|
32 |
|
33 |
@SwaggerGet()
|
|
|
57 |
res
|
58 |
);
|
59 |
};
|
60 |
+
|
61 |
+
@SwaggerPost('/eat-custom-meal')
|
62 |
+
@SwaggerResponse({})
|
63 |
+
@SwaggerRequest(eatCustomMealSchema)
|
64 |
+
@SwaggerSummary("Eat custom meal")
|
65 |
+
@SwaggerDescription("Eat custom meal")
|
66 |
+
eatCustomMeal = async (req: IUserRequest, res: Response) => {
|
67 |
+
await this.mealsService.eatCustomMeal(req.jwtPayload.id, req.body as IEatCustomMeal);
|
68 |
+
|
69 |
+
return JsonResponse.success(
|
70 |
+
{
|
71 |
+
message: "Meal created successfully",
|
72 |
+
},
|
73 |
+
res
|
74 |
+
);
|
75 |
+
};
|
76 |
}
|
77 |
+
|
src/modules/users/modules/meals/events/eat-custom-meal.event.ts
ADDED
@@ -0,0 +1,18 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import { ActivityType } from "@common/enums/activity-type.enum";
|
2 |
+
import { Activity, IActivity } from "@common/models/activity.model";
|
3 |
+
import { EventsManager } from "@lib/events/events-manager";
|
4 |
+
import { IEatCustomMeal } from "../validations/eat-custom-meal.validation";
|
5 |
+
|
6 |
+
export class EatCustomMealEvent {
|
7 |
+
constructor(public userId: string, public ingredients: IEatCustomMeal['ingredients']) {}
|
8 |
+
}
|
9 |
+
|
10 |
+
EventsManager.on(ActivityType.EAT_CUSTOM_MEAL, (event: EatCustomMealEvent) => {
|
11 |
+
console.log(`User with id ${event.userId} ate a custom meal with ingredients: ${event.ingredients.map(i => i.id).join(", ")}`);
|
12 |
+
|
13 |
+
Activity.create({
|
14 |
+
user_id: event.userId,
|
15 |
+
activity_type: ActivityType.EAT_CUSTOM_MEAL,
|
16 |
+
related_item: { ingredients: event.ingredients },
|
17 |
+
} satisfies IActivity).catch(console.error);
|
18 |
+
});
|
src/modules/users/modules/meals/services/meals.service.ts
CHANGED
@@ -1,4 +1,27 @@
|
|
1 |
import { Meal } from "@common/models/meal.model";
|
2 |
import { CrudService } from "@lib/services/crud.service";
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
3 |
|
4 |
-
export class MealsService extends CrudService(Meal) {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
import { Meal } from "@common/models/meal.model";
|
2 |
import { CrudService } from "@lib/services/crud.service";
|
3 |
+
import { IEatCustomMeal } from "../validations/eat-custom-meal.validation";
|
4 |
+
import { EventsManager } from "@lib/events/events-manager";
|
5 |
+
import { Ingredient } from "@common/models/ingredient.model";
|
6 |
+
import { HttpError } from "@lib/error-handling/http-error";
|
7 |
+
import { ActivityType } from "@common/enums/activity-type.enum";
|
8 |
+
import { EatCustomMealEvent } from "../events/eat-custom-meal.event";
|
9 |
+
import { Types } from "mongoose";
|
10 |
|
11 |
+
export class MealsService extends CrudService(Meal) {
|
12 |
+
async eatCustomMeal(id: string, body: IEatCustomMeal) {
|
13 |
+
// Validate ingredients
|
14 |
+
await Promise.all(
|
15 |
+
body.ingredients.map(async i => {
|
16 |
+
const ing = await Ingredient.findOne({ _id: new Types.ObjectId(i.id) });
|
17 |
+
if(!ing) throw new HttpError(404, `Ingredient with id ${i} not found`);
|
18 |
+
return {
|
19 |
+
ingredient: ing,
|
20 |
+
noServings: i.noServings,
|
21 |
+
};
|
22 |
+
})
|
23 |
+
)
|
24 |
+
|
25 |
+
EventsManager.emit(ActivityType.EAT_CUSTOM_MEAL, new EatCustomMealEvent(id, body.ingredients));
|
26 |
+
}
|
27 |
+
}
|
src/modules/users/modules/meals/validations/eat-custom-meal.validation.ts
ADDED
@@ -0,0 +1,18 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import * as joi from "joi";
|
2 |
+
import { createSchema } from "@helpers/create-schema";
|
3 |
+
|
4 |
+
export interface IEatCustomMeal {
|
5 |
+
ingredients: {
|
6 |
+
id: string;
|
7 |
+
noServings: number;
|
8 |
+
}[];
|
9 |
+
}
|
10 |
+
|
11 |
+
export const eatCustomMealKeys = {
|
12 |
+
ingredients: joi.array().items(joi.object({
|
13 |
+
id: joi.string().required(),
|
14 |
+
noServings: joi.number().required(),
|
15 |
+
})).required(),
|
16 |
+
};
|
17 |
+
|
18 |
+
export const eatCustomMealSchema = createSchema<IEatCustomMeal>(eatCustomMealKeys);
|
src/modules/users/modules/user-registered-meal-plans/controller/user-registered-meal-plans.controller.ts
CHANGED
@@ -9,6 +9,8 @@ import { GetMyMealPlanSerialization } from "@common/serializers/user-registered-
|
|
9 |
import { ControllerMiddleware } from "@lib/decorators/controller-middleware.decorator";
|
10 |
import { UsersGuardMiddleware } from "modules/users/common/guards/users.guard";
|
11 |
import { SwaggerGet } from "@lib/decorators/swagger-routes.decorator";
|
|
|
|
|
12 |
import { SwaggerResponse } from "@lib/decorators/swagger-response.decorator";
|
13 |
import { SwaggerSummary } from "@lib/decorators/swagger-summary.decorator";
|
14 |
import { SwaggerDescription } from "@lib/decorators/swagger-description.decorator";
|
@@ -23,6 +25,10 @@ export class UsersRegisteredMealPlansController extends BaseController {
|
|
23 |
|
24 |
setRoutes(): void {
|
25 |
this.router.get("/", asyncHandler(this.get));
|
|
|
|
|
|
|
|
|
26 |
}
|
27 |
|
28 |
@SwaggerGet()
|
@@ -34,8 +40,8 @@ export class UsersRegisteredMealPlansController extends BaseController {
|
|
34 |
{ user: req.jwtPayload.id, isActive: true },
|
35 |
{
|
36 |
populateArray: [
|
37 |
-
{ path: "meal_plan", select: "-days"},
|
38 |
-
{ path: "days.meals"}
|
39 |
],
|
40 |
});
|
41 |
|
@@ -46,4 +52,21 @@ export class UsersRegisteredMealPlansController extends BaseController {
|
|
46 |
res
|
47 |
);
|
48 |
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
49 |
}
|
|
|
9 |
import { ControllerMiddleware } from "@lib/decorators/controller-middleware.decorator";
|
10 |
import { UsersGuardMiddleware } from "modules/users/common/guards/users.guard";
|
11 |
import { SwaggerGet } from "@lib/decorators/swagger-routes.decorator";
|
12 |
+
import { SwaggerPost } from "@lib/decorators/swagger-routes.decorator";
|
13 |
+
import { SwaggerRequest } from "@lib/decorators/swagger-request.decorator";
|
14 |
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";
|
|
|
25 |
|
26 |
setRoutes(): void {
|
27 |
this.router.get("/", asyncHandler(this.get));
|
28 |
+
this.router.post(
|
29 |
+
"/",
|
30 |
+
asyncHandler(this.create)
|
31 |
+
);
|
32 |
}
|
33 |
|
34 |
@SwaggerGet()
|
|
|
40 |
{ user: req.jwtPayload.id, isActive: true },
|
41 |
{
|
42 |
populateArray: [
|
43 |
+
{ path: "meal_plan", select: "-days" },
|
44 |
+
{ path: "days.meals", populate: { path: "ingredients" } }
|
45 |
],
|
46 |
});
|
47 |
|
|
|
52 |
res
|
53 |
);
|
54 |
};
|
55 |
+
|
56 |
+
|
57 |
+
@SwaggerPost()
|
58 |
+
@SwaggerResponse(GetMyMealPlanSerialization)
|
59 |
+
@SwaggerRequest({ mealPlan: "string" })
|
60 |
+
@SwaggerSummary("create my meal plan")
|
61 |
+
@SwaggerDescription("Create a new meal plan for the user")
|
62 |
+
create = async (req: userRequest, res: Response) => {
|
63 |
+
const data = await this.userRegisteredMealPlansService.createForUser(req.body, req.jwtPayload.id);
|
64 |
+
return JsonResponse.success(
|
65 |
+
{
|
66 |
+
status: 201,
|
67 |
+
data: serialize(data.toJSON(), GetMyMealPlanSerialization),
|
68 |
+
},
|
69 |
+
res
|
70 |
+
);
|
71 |
+
};
|
72 |
}
|
src/modules/users/modules/user-registered-meal-plans/services/user-registered-meal-plans.service.ts
CHANGED
@@ -1,10 +1,35 @@
|
|
1 |
import { UserRegisteredMealPlan } from "@common/models/user-registered-meal-plan.model";
|
2 |
import { CrudService } from "@lib/services/crud.service";
|
3 |
-
import {
|
4 |
import { HttpError } from "@lib/error-handling/http-error";
|
5 |
|
6 |
|
7 |
export class UserRegisteredMealPlansService extends CrudService(UserRegisteredMealPlan) {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
8 |
async updateForUser(mealPlanProps: {urwId: string; dayNumber: number}, data: any, userId: string) {
|
9 |
// find workout
|
10 |
const mealPlan = await this.findOneOrFail({
|
|
|
1 |
import { UserRegisteredMealPlan } from "@common/models/user-registered-meal-plan.model";
|
2 |
import { CrudService } from "@lib/services/crud.service";
|
3 |
+
import { MealPlansService } from "../../meal-plans/services/meal-plans.service";
|
4 |
import { HttpError } from "@lib/error-handling/http-error";
|
5 |
|
6 |
|
7 |
export class UserRegisteredMealPlansService extends CrudService(UserRegisteredMealPlan) {
|
8 |
+
private mealPlansService: MealPlansService = new MealPlansService();
|
9 |
+
|
10 |
+
async unregisterCurrentMealPlan(userId: string) {
|
11 |
+
return await this.updateMany({
|
12 |
+
user: userId,
|
13 |
+
isActive: true,
|
14 |
+
}, {
|
15 |
+
isActive: false,
|
16 |
+
});
|
17 |
+
}
|
18 |
+
|
19 |
+
async createForUser(data: any, userId: string) {
|
20 |
+
const mealPlan = await this.mealPlansService.findOneOrFail({
|
21 |
+
_id: data.mealPlan,
|
22 |
+
});
|
23 |
+
|
24 |
+
await this.unregisterCurrentMealPlan(userId);
|
25 |
+
|
26 |
+
return await this.create({
|
27 |
+
...data,
|
28 |
+
user: userId,
|
29 |
+
days: mealPlan.days,
|
30 |
+
isActive: true,
|
31 |
+
});
|
32 |
+
}
|
33 |
async updateForUser(mealPlanProps: {urwId: string; dayNumber: number}, data: any, userId: string) {
|
34 |
// find workout
|
35 |
const mealPlan = await this.findOneOrFail({
|
src/resources/meals1.json
DELETED
The diff for this file is too large to render.
See raw diff
|
|
src/resources/meals2.json
DELETED
The diff for this file is too large to render.
See raw diff
|
|
src/seeder/seeders/9-meals.seeder.ts
CHANGED
@@ -21,6 +21,8 @@ export default seederWrapper(Meal, async () => {
|
|
21 |
console.log('preping meals data...')
|
22 |
const data = await Promise.all(dbStore.mealsDataset.map(async (mealJson) => ({
|
23 |
name: mealJson.Name,
|
|
|
|
|
24 |
ingredients: mealJson.RecipeIngredientParts.map(name => ingredientsIds.find(i => i.name === name)._id),
|
25 |
calories: mealJson.Calories,
|
26 |
carbs: mealJson.CarbohydrateContent,
|
|
|
21 |
console.log('preping meals data...')
|
22 |
const data = await Promise.all(dbStore.mealsDataset.map(async (mealJson) => ({
|
23 |
name: mealJson.Name,
|
24 |
+
created_at: new Date(),
|
25 |
+
image: `https://placehold.co/300x400`,
|
26 |
ingredients: mealJson.RecipeIngredientParts.map(name => ingredientsIds.find(i => i.name === name)._id),
|
27 |
calories: mealJson.Calories,
|
28 |
carbs: mealJson.CarbohydrateContent,
|