Spaces:
Running
Running
Commit
·
21a9b1b
1
Parent(s):
8a85206
feat: eat custom meal
Browse files- src/common/enums/activity-type.enum.ts +3 -1
- src/common/models/activity.model.ts +14 -12
- src/lib/events/events-manager.ts +12 -6
- 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/common/enums/activity-type.enum.ts
CHANGED
@@ -1 +1,3 @@
|
|
1 |
-
export enum ActivityType {
|
|
|
|
|
|
1 |
+
export enum ActivityType {
|
2 |
+
EAT_CUSTOM_MEAL = "eat-custom-meal",
|
3 |
+
}
|
src/common/models/activity.model.ts
CHANGED
@@ -5,24 +5,26 @@ 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 |
-
//related_item
|
9 |
-
//meta_data
|
10 |
-
//activity type
|
11 |
-
//related id
|
12 |
-
//user id
|
13 |
-
//created_//at
|
14 |
-
//
|
15 |
|
16 |
-
export type RelatedItem =
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
17 |
export type AMetaData = {};
|
18 |
|
19 |
export interface IActivity {
|
20 |
related_item?: RelatedItem;
|
21 |
-
meta_data
|
22 |
activity_type: ActivityType;
|
23 |
-
related_id
|
24 |
user_id: string | Types.ObjectId;
|
25 |
-
created_at
|
26 |
}
|
27 |
|
28 |
const activitySchema = new Schema({
|
@@ -40,7 +42,7 @@ const activitySchema = new Schema({
|
|
40 |
},
|
41 |
related_id: {
|
42 |
type: Schema.Types.ObjectId,
|
43 |
-
required:
|
44 |
},
|
45 |
user_id: {
|
46 |
type: Schema.Types.ObjectId,
|
|
|
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({
|
|
|
42 |
},
|
43 |
related_id: {
|
44 |
type: Schema.Types.ObjectId,
|
45 |
+
required: false,
|
46 |
},
|
47 |
user_id: {
|
48 |
type: Schema.Types.ObjectId,
|
src/lib/events/events-manager.ts
CHANGED
@@ -1,3 +1,4 @@
|
|
|
|
1 |
import EventEmitter from 'node:events';
|
2 |
|
3 |
/**
|
@@ -12,7 +13,7 @@ export class EventsManager {
|
|
12 |
this.emitter = new EventEmitter();
|
13 |
}
|
14 |
|
15 |
-
|
16 |
if (!EventsManager.instance) {
|
17 |
EventsManager.instance = new EventsManager();
|
18 |
}
|
@@ -25,8 +26,12 @@ export class EventsManager {
|
|
25 |
* @param event The event name.
|
26 |
* @param listener The listener function.
|
27 |
*/
|
28 |
-
public on(event: string, listener: (...args: any[]) => void): void {
|
29 |
-
|
|
|
|
|
|
|
|
|
30 |
}
|
31 |
|
32 |
/**
|
@@ -34,13 +39,14 @@ export class EventsManager {
|
|
34 |
* @param event The event name.
|
35 |
* @param listener The listener function.
|
36 |
*/
|
37 |
-
public emit(event: string, ...args: any[]): void {
|
38 |
-
|
39 |
}
|
40 |
|
41 |
/**
|
42 |
* Create a queue to store events.
|
43 |
* @returns An EventsQueue object.
|
|
|
44 |
*/
|
45 |
public createQueue() {
|
46 |
return new class EventsQueue {
|
@@ -56,7 +62,7 @@ export class EventsManager {
|
|
56 |
|
57 |
public process() {
|
58 |
this.queue.forEach((item) => {
|
59 |
-
EventsManager.
|
60 |
});
|
61 |
|
62 |
this.clear();
|
|
|
1 |
+
import { asyncHandler } from '@helpers/async-handler';
|
2 |
import EventEmitter from 'node:events';
|
3 |
|
4 |
/**
|
|
|
13 |
this.emitter = new EventEmitter();
|
14 |
}
|
15 |
|
16 |
+
public static getInstance(): EventsManager {
|
17 |
if (!EventsManager.instance) {
|
18 |
EventsManager.instance = new EventsManager();
|
19 |
}
|
|
|
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 |
/**
|
|
|
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 {
|
|
|
62 |
|
63 |
public process() {
|
64 |
this.queue.forEach((item) => {
|
65 |
+
EventsManager.emit(item.event, ...item.args);
|
66 |
});
|
67 |
|
68 |
this.clear();
|
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);
|