moahmedwafy commited on
Commit
c822de3
·
unverified ·
2 Parent(s): fe2d2df 2f0e1b7

Merge pull request #22 from Modarb-Ai-Trainer:refactors

Browse files
Files changed (46) hide show
  1. package-lock.json +44 -10
  2. package.json +2 -1
  3. src/common/enums/authenticatable-type.enum.ts +4 -0
  4. src/common/enums/fitness-goal.enum.ts +5 -0
  5. src/common/enums/fitness-level.enum.ts +5 -0
  6. src/common/enums/gender.enum.ts +4 -0
  7. src/common/enums/injury.enum.ts +7 -0
  8. src/common/enums/preferred-day.enum.ts +9 -0
  9. src/common/enums/preferred-equipment.enum.ts +7 -0
  10. src/{modules/console/admins/enums/roles.enum.ts → common/enums/role.enum.ts} +0 -0
  11. src/common/enums/workout-place.enum.ts +5 -0
  12. src/common/interfaces/jwt-payload.interface.ts +15 -0
  13. src/{modules/common/templates → common}/models/template.model.ts +0 -0
  14. src/common/models/user.model.ts +103 -0
  15. src/common/validations/user-register.validation.ts +193 -0
  16. src/configs/config.ts +1 -1
  17. src/configs/env.ts +0 -25
  18. src/helpers/create-schema.ts +7 -0
  19. src/helpers/jwt.helper.ts +15 -15
  20. src/helpers/pagination.ts +11 -0
  21. src/helpers/validation.helper.ts +1 -20
  22. src/lib/error-handling/http-error.ts +13 -0
  23. src/lib/responses/json-response.ts +13 -15
  24. src/lib/services/crud.service.ts +68 -0
  25. src/modules/common/users/enums/roles.enum.ts +0 -45
  26. src/modules/common/users/models/user.model.ts +0 -88
  27. src/modules/common/users/services/users.base.service.ts +0 -114
  28. src/modules/common/users/validation/user-register.validation.ts +0 -140
  29. src/modules/console/admins/services/admins.service.ts +0 -267
  30. src/modules/console/admins/validations/create-admin.validation.ts +0 -47
  31. src/modules/console/common/guards/admins.guard.ts +45 -0
  32. src/modules/console/{admins → common}/models/admin.model.ts +3 -7
  33. src/modules/console/{admins → modules/admins}/controllers/admins.controller.ts +53 -25
  34. src/modules/console/modules/admins/services/admins.service.ts +4 -0
  35. src/modules/console/modules/admins/validations/create-admin.validation.ts +55 -0
  36. src/modules/console/modules/users/controllers/users.controller.ts +29 -0
  37. src/modules/console/modules/users/services/users.service.ts +4 -0
  38. src/modules/console/users/controllers/users.controller.ts +0 -37
  39. src/modules/console/users/services/users.service.ts +0 -3
  40. src/modules/user/auth/controllers/auth.controller.ts +0 -73
  41. src/modules/user/auth/services/users.service.ts +0 -87
  42. src/modules/user/auth/validation/user.Validation.ts +0 -26
  43. src/modules/users/auth/controllers/auth.controller.ts +43 -0
  44. src/modules/users/auth/services/users.service.ts +24 -0
  45. src/modules/users/auth/validation/login.validation.ts +31 -0
  46. tsconfig.json +0 -1
package-lock.json CHANGED
@@ -9,6 +9,7 @@
9
  "version": "1.0.0",
10
  "license": "ISC",
11
  "dependencies": {
 
12
  "@types/glob": "^8.1.0",
13
  "bcrypt": "^5.1.1",
14
  "cors": "^2.8.5",
@@ -18,7 +19,7 @@
18
  "glob": "^10.3.10",
19
  "http": "^0.0.1-security",
20
  "i": "^0.3.7",
21
- "joi": "^17.11.0",
22
  "jsonwebtoken": "^9.0.2",
23
  "mongoose": "^8.0.3",
24
  "npm": "^10.2.5",
@@ -45,11 +46,44 @@
45
  "node": ">=12"
46
  }
47
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
48
  "node_modules/@hapi/hoek": {
49
  "version": "9.3.0",
50
  "resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-9.3.0.tgz",
51
  "integrity": "sha512-/c6rf4UJlmHlC9b5BaNvzAcFv7HZ2QHaV0D4/HNlBdvFnvQq8RI4kYdhyPCl7Xj+oWvTWQ8ujhqS53LIgAe6KQ=="
52
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
53
  "node_modules/@hapi/topo": {
54
  "version": "5.1.0",
55
  "resolved": "https://registry.npmjs.org/@hapi/topo/-/topo-5.1.0.tgz",
@@ -228,9 +262,9 @@
228
  }
229
  },
230
  "node_modules/@sideway/address": {
231
- "version": "4.1.4",
232
- "resolved": "https://registry.npmjs.org/@sideway/address/-/address-4.1.4.tgz",
233
- "integrity": "sha512-7vwq+rOHVWjyXxVlR76Agnvhy8I9rpzjosTESvmhNeXOXdZZB15Fl+TI9x1SiHZH5Jv2wTGduSxFDIaq0m3DUw==",
234
  "dependencies": {
235
  "@hapi/hoek": "^9.0.0"
236
  }
@@ -1449,13 +1483,13 @@
1449
  }
1450
  },
1451
  "node_modules/joi": {
1452
- "version": "17.11.0",
1453
- "resolved": "https://registry.npmjs.org/joi/-/joi-17.11.0.tgz",
1454
- "integrity": "sha512-NgB+lZLNoqISVy1rZocE9PZI36bL/77ie924Ri43yEvi9GUUMPeyVIr8KdFTMUlby1p0PBYMk9spIxEUQYqrJQ==",
1455
  "dependencies": {
1456
- "@hapi/hoek": "^9.0.0",
1457
- "@hapi/topo": "^5.0.0",
1458
- "@sideway/address": "^4.1.3",
1459
  "@sideway/formula": "^3.0.1",
1460
  "@sideway/pinpoint": "^2.0.0"
1461
  }
 
9
  "version": "1.0.0",
10
  "license": "ISC",
11
  "dependencies": {
12
+ "@hapi/joi": "^17.1.1",
13
  "@types/glob": "^8.1.0",
14
  "bcrypt": "^5.1.1",
15
  "cors": "^2.8.5",
 
19
  "glob": "^10.3.10",
20
  "http": "^0.0.1-security",
21
  "i": "^0.3.7",
22
+ "joi": "^17.12.1",
23
  "jsonwebtoken": "^9.0.2",
24
  "mongoose": "^8.0.3",
25
  "npm": "^10.2.5",
 
46
  "node": ">=12"
47
  }
48
  },
49
+ "node_modules/@hapi/address": {
50
+ "version": "4.1.0",
51
+ "resolved": "https://registry.npmjs.org/@hapi/address/-/address-4.1.0.tgz",
52
+ "integrity": "sha512-SkszZf13HVgGmChdHo/PxchnSaCJ6cetVqLzyciudzZRT0jcOouIF/Q93mgjw8cce+D+4F4C1Z/WrfFN+O3VHQ==",
53
+ "deprecated": "Moved to 'npm install @sideway/address'",
54
+ "dependencies": {
55
+ "@hapi/hoek": "^9.0.0"
56
+ }
57
+ },
58
+ "node_modules/@hapi/formula": {
59
+ "version": "2.0.0",
60
+ "resolved": "https://registry.npmjs.org/@hapi/formula/-/formula-2.0.0.tgz",
61
+ "integrity": "sha512-V87P8fv7PI0LH7LiVi8Lkf3x+KCO7pQozXRssAHNXXL9L1K+uyu4XypLXwxqVDKgyQai6qj3/KteNlrqDx4W5A==",
62
+ "deprecated": "Moved to 'npm install @sideway/formula'"
63
+ },
64
  "node_modules/@hapi/hoek": {
65
  "version": "9.3.0",
66
  "resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-9.3.0.tgz",
67
  "integrity": "sha512-/c6rf4UJlmHlC9b5BaNvzAcFv7HZ2QHaV0D4/HNlBdvFnvQq8RI4kYdhyPCl7Xj+oWvTWQ8ujhqS53LIgAe6KQ=="
68
  },
69
+ "node_modules/@hapi/joi": {
70
+ "version": "17.1.1",
71
+ "resolved": "https://registry.npmjs.org/@hapi/joi/-/joi-17.1.1.tgz",
72
+ "integrity": "sha512-p4DKeZAoeZW4g3u7ZeRo+vCDuSDgSvtsB/NpfjXEHTUjSeINAi/RrVOWiVQ1isaoLzMvFEhe8n5065mQq1AdQg==",
73
+ "deprecated": "Switch to 'npm install joi'",
74
+ "dependencies": {
75
+ "@hapi/address": "^4.0.1",
76
+ "@hapi/formula": "^2.0.0",
77
+ "@hapi/hoek": "^9.0.0",
78
+ "@hapi/pinpoint": "^2.0.0",
79
+ "@hapi/topo": "^5.0.0"
80
+ }
81
+ },
82
+ "node_modules/@hapi/pinpoint": {
83
+ "version": "2.0.1",
84
+ "resolved": "https://registry.npmjs.org/@hapi/pinpoint/-/pinpoint-2.0.1.tgz",
85
+ "integrity": "sha512-EKQmr16tM8s16vTT3cA5L0kZZcTMU5DUOZTuvpnY738m+jyP3JIUj+Mm1xc1rsLkGBQ/gVnfKYPwOmPg1tUR4Q=="
86
+ },
87
  "node_modules/@hapi/topo": {
88
  "version": "5.1.0",
89
  "resolved": "https://registry.npmjs.org/@hapi/topo/-/topo-5.1.0.tgz",
 
262
  }
263
  },
264
  "node_modules/@sideway/address": {
265
+ "version": "4.1.5",
266
+ "resolved": "https://registry.npmjs.org/@sideway/address/-/address-4.1.5.tgz",
267
+ "integrity": "sha512-IqO/DUQHUkPeixNQ8n0JA6102hT9CmaljNTPmQ1u8MEhBo/R4Q8eKLN/vGZxuebwOroDB4cbpjheD4+/sKFK4Q==",
268
  "dependencies": {
269
  "@hapi/hoek": "^9.0.0"
270
  }
 
1483
  }
1484
  },
1485
  "node_modules/joi": {
1486
+ "version": "17.12.1",
1487
+ "resolved": "https://registry.npmjs.org/joi/-/joi-17.12.1.tgz",
1488
+ "integrity": "sha512-vtxmq+Lsc5SlfqotnfVjlViWfOL9nt/avKNbKYizwf6gsCfq9NYY/ceYRMFD8XDdrjJ9abJyScWmhmIiy+XRtQ==",
1489
  "dependencies": {
1490
+ "@hapi/hoek": "^9.3.0",
1491
+ "@hapi/topo": "^5.1.0",
1492
+ "@sideway/address": "^4.1.5",
1493
  "@sideway/formula": "^3.0.1",
1494
  "@sideway/pinpoint": "^2.0.0"
1495
  }
package.json CHANGED
@@ -19,6 +19,7 @@
19
  "typescript": "^5.3.3"
20
  },
21
  "dependencies": {
 
22
  "@types/glob": "^8.1.0",
23
  "bcrypt": "^5.1.1",
24
  "cors": "^2.8.5",
@@ -28,7 +29,7 @@
28
  "glob": "^10.3.10",
29
  "http": "^0.0.1-security",
30
  "i": "^0.3.7",
31
- "joi": "^17.11.0",
32
  "jsonwebtoken": "^9.0.2",
33
  "mongoose": "^8.0.3",
34
  "npm": "^10.2.5",
 
19
  "typescript": "^5.3.3"
20
  },
21
  "dependencies": {
22
+ "@hapi/joi": "^17.1.1",
23
  "@types/glob": "^8.1.0",
24
  "bcrypt": "^5.1.1",
25
  "cors": "^2.8.5",
 
29
  "glob": "^10.3.10",
30
  "http": "^0.0.1-security",
31
  "i": "^0.3.7",
32
+ "joi": "^17.12.1",
33
  "jsonwebtoken": "^9.0.2",
34
  "mongoose": "^8.0.3",
35
  "npm": "^10.2.5",
src/common/enums/authenticatable-type.enum.ts ADDED
@@ -0,0 +1,4 @@
 
 
 
 
 
1
+ export enum AuthenticatableType {
2
+ USER = "user",
3
+ ADMIN = "admin",
4
+ }
src/common/enums/fitness-goal.enum.ts ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ export enum FitnessGoal {
2
+ LOSE_WEIGHT = "lose weight",
3
+ GAIN_MUSCLE = "gain muscle",
4
+ GET_FITTER = "get fitter",
5
+ }
src/common/enums/fitness-level.enum.ts ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ export enum FitnessLevel {
2
+ BEGINNER = "beginner",
3
+ INTERMEDIATE = "intermediate",
4
+ ADVANCED = "advanced",
5
+ }
src/common/enums/gender.enum.ts ADDED
@@ -0,0 +1,4 @@
 
 
 
 
 
1
+ export enum Gender {
2
+ MALE = "male",
3
+ FEMALE = "female",
4
+ }
src/common/enums/injury.enum.ts ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ export enum Injury {
2
+ NECK = "neck",
3
+ SHOULDERS = "shoulders",
4
+ BACK = "back",
5
+ ARMS = "arms",
6
+ KNEES = "knees",
7
+ }
src/common/enums/preferred-day.enum.ts ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ export enum PreferredDay {
2
+ SATURDAY = "saturday",
3
+ SUNDAY = "sunday",
4
+ MONDAY = "monday",
5
+ TUESDAY = "tuesday",
6
+ WEDNESDAY = "wednesday",
7
+ THURSDAY = "thursday",
8
+ FRIDAY = "friday",
9
+ }
src/common/enums/preferred-equipment.enum.ts ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ export enum PreferredEquipment {
2
+ BARBELLS = "barbells",
3
+ DUMBBELLS = "dumbbells",
4
+ GYM_MACHINES = "gym machines",
5
+ RESISTANCE_BAND = "resistance band",
6
+ BODYWEIGHT = "bodyweight",
7
+ }
src/{modules/console/admins/enums/roles.enum.ts → common/enums/role.enum.ts} RENAMED
File without changes
src/common/enums/workout-place.enum.ts ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ export enum WorkoutPlace {
2
+ GYM = "gym",
3
+ HOME = "home",
4
+ BOTH = "both",
5
+ }
src/common/interfaces/jwt-payload.interface.ts ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { Role } from "@common/enums/role.enum";
2
+
3
+ export type IJwtLoginPayload = {
4
+ id: string;
5
+ email: string;
6
+ name: string;
7
+ } & (
8
+ | {
9
+ role: Role;
10
+ type: "admin";
11
+ }
12
+ | {
13
+ type: "user";
14
+ }
15
+ );
src/{modules/common/templates → common}/models/template.model.ts RENAMED
File without changes
src/common/models/user.model.ts ADDED
@@ -0,0 +1,103 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import mongoose from "mongoose";
2
+ import bcrypt from "bcrypt";
3
+ import { AuthenticatableType } from "@common/enums/authenticatable-type.enum";
4
+ import { FitnessGoal } from "@common/enums/fitness-goal.enum";
5
+ import { FitnessLevel } from "@common/enums/fitness-level.enum";
6
+ import { Gender } from "@common/enums/gender.enum";
7
+ import { Injury } from "@common/enums/injury.enum";
8
+ import { PreferredDay } from "@common/enums/preferred-day.enum";
9
+ import { PreferredEquipment } from "@common/enums/preferred-equipment.enum";
10
+ import { WorkoutPlace } from "@common/enums/workout-place.enum";
11
+ export const saltrounds = 5;
12
+ const { Schema } = mongoose;
13
+
14
+ export interface IUser {
15
+ name: string;
16
+ email: string;
17
+ password: string;
18
+ image: object;
19
+ role: AuthenticatableType;
20
+ gender: string;
21
+ dob: Date;
22
+ height: number;
23
+ weight: number;
24
+ fitness_level: string;
25
+ preferences: {
26
+ fitness_goal: FitnessGoal;
27
+ target_weight: number;
28
+ workout_frequency: number;
29
+ preferred_days: [PreferredDay];
30
+ workout_place: WorkoutPlace;
31
+ preferred_equipment: [PreferredEquipment];
32
+ };
33
+ injuries: [Injury];
34
+ }
35
+
36
+ const userSchema = new Schema({
37
+ name: { type: String, required: true },
38
+ email: { type: String, required: true, unique: true, dropDups: true },
39
+ password: { type: String, required: true },
40
+ image: { type: Object },
41
+ gender: {
42
+ type: String,
43
+ enum: Gender,
44
+ required: true,
45
+ },
46
+ height: { type: Number, required: true },
47
+ weight: { type: Number, required: true },
48
+ fitness_level: {
49
+ type: String,
50
+ enum: FitnessLevel,
51
+ required: true,
52
+ },
53
+ preferences: {
54
+ fitness_goal: {
55
+ type: String,
56
+ enum: FitnessGoal,
57
+ required: true,
58
+ },
59
+ target_weight: { type: Number, required: true },
60
+ workout_frequency: { type: Number, required: true },
61
+ preferred_days: [
62
+ {
63
+ type: String,
64
+ enum: PreferredDay,
65
+ required: true,
66
+ },
67
+ ],
68
+ workout_place: {
69
+ type: String,
70
+ enum: WorkoutPlace,
71
+ required: true,
72
+ },
73
+ preferred_equipment: [
74
+ {
75
+ type: String,
76
+ enum: PreferredEquipment,
77
+ required: true,
78
+ },
79
+ ],
80
+ },
81
+ injuries: [
82
+ {
83
+ type: String,
84
+ enum: Injury,
85
+ required: true,
86
+ },
87
+ ],
88
+ dob: { type: Date },
89
+ role: {
90
+ type: String,
91
+ enum: AuthenticatableType,
92
+ default: AuthenticatableType.USER,
93
+ },
94
+ });
95
+
96
+ userSchema.pre("save", async function (next) {
97
+ this.password = await bcrypt.hash(this.password, saltrounds);
98
+ next();
99
+ });
100
+
101
+ export type UserDocument = IUser & mongoose.Document;
102
+
103
+ export const userModel = mongoose.model<UserDocument>("users", userSchema);
src/common/validations/user-register.validation.ts ADDED
@@ -0,0 +1,193 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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";
8
+ import { WorkoutPlace } from "@common/enums/workout-place.enum";
9
+ import * as joi from "joi";
10
+ import { createSchema } from "src/helpers/create-schema";
11
+
12
+ export interface IUserRegister {
13
+ name: string;
14
+ email: string;
15
+ password: string;
16
+ confirmPassword: string;
17
+ image?: {
18
+ url: string;
19
+ public_id: string;
20
+ };
21
+ gender: string;
22
+ height: number;
23
+ weight: number;
24
+ fitness_level: string;
25
+ preferences?: {
26
+ fitness_goal: string;
27
+ target_weight: number;
28
+ workout_frequency: number;
29
+ preferred_days: string[];
30
+ workout_place: string;
31
+ preferred_equipment: string[];
32
+ };
33
+ injuries: string[];
34
+ dob?: Date;
35
+ }
36
+
37
+ export const userRegisterKeys = {
38
+ name: joi.string().empty().required().messages({
39
+ "string.base": "please enter a valid name",
40
+ "any.required": "name is required",
41
+ "string.empty": "name can not be empty",
42
+ }),
43
+ email: joi
44
+ .string()
45
+ .required()
46
+ .email({
47
+ minDomainSegments: 2,
48
+ tlds: { allow: ["com", "net", "org", "eg", "io"] },
49
+ })
50
+ .empty()
51
+ .messages({
52
+ "string.email": "please enter a valid email",
53
+ "any.required": "email must be entered",
54
+ "string.empty": "email can not be empty",
55
+ }),
56
+ password: joi.string().empty().min(8).required().messages({
57
+ "string.base": "please enter a valid password",
58
+ "any.required": "password must be entered",
59
+ "string.empty": "password cannot be empty",
60
+ "string.min": "password must be at least 8 characters",
61
+ }),
62
+ confirmPassword: joi.string().empty().min(8).required().messages({
63
+ "string.base": "please enter a valid password",
64
+ "any.required": "password must be entered",
65
+ "string.empty": "password cannot be empty",
66
+ "string.min": "password must be at least 8 characters",
67
+ }),
68
+ image: joi
69
+ .object()
70
+ .optional()
71
+ .keys({
72
+ url: joi.string().optional().messages({
73
+ "string.base": "please enter a valid url",
74
+ }),
75
+ public_id: joi.string().optional().messages({
76
+ "string.base": "please enter a valid public_id",
77
+ }),
78
+ }),
79
+ gender: joi
80
+ .string()
81
+ .valid(...Object.values(Gender))
82
+ .empty()
83
+ .required()
84
+ .messages({
85
+ "string.base": "please enter a valid gender",
86
+ "any.required": "gender must be entered",
87
+ "string.empty": "gender cannot be empty",
88
+ }),
89
+ height: joi.number().empty().required().messages({
90
+ "number.base": "please enter a valid height number",
91
+ "any.required": "height must be entered",
92
+ "number.empty": "height cannot be empty",
93
+ }),
94
+ weight: joi.number().empty().required().messages({
95
+ "number.base": "please enter a valid weight number",
96
+ "any.required": "weight must be entered",
97
+ "number.empty": "weight cannot be empty",
98
+ }),
99
+ fitness_level: joi
100
+ .string()
101
+ .valid(...Object.values(FitnessLevel))
102
+ .empty()
103
+ .required()
104
+ .messages({
105
+ "string.base": "please enter a valid fitness_level",
106
+ "any.required": "fitness_level must be entered",
107
+ "string.empty": "fitness_level cannot be empty",
108
+ }),
109
+ preferences: joi
110
+ .object()
111
+ .optional()
112
+ .keys({
113
+ fitness_goal: joi
114
+ .string()
115
+ .valid(...Object.values(FitnessGoal))
116
+ .empty()
117
+ .required()
118
+ .messages({
119
+ "string.base": "please enter a valid fitness_goal",
120
+ "any.required": "fitness_goal must be entered",
121
+ "string.empty": "fitness_goal cannot be empty",
122
+ }),
123
+ target_weight: joi.number().empty().required().messages({
124
+ "number.base": "please enter a valid target_weight number",
125
+ "any.required": "target_weight must be entered",
126
+ "number.empty": "target_weight cannot be empty",
127
+ }),
128
+ workout_frequency: joi.number().empty().required().messages({
129
+ "number.base": "please enter a valid workout_frequency number",
130
+ "any.required": "workout_frequency must be entered",
131
+ "number.empty": "workout_frequency cannot be empty",
132
+ }),
133
+ preferred_days: joi
134
+ .array()
135
+ .valid(...Object.values(PreferredDay))
136
+ .empty()
137
+ .required()
138
+ .items(
139
+ joi.string().empty().required().messages({
140
+ "string.base": "please enter a valid preferred_days",
141
+ "any.required": "preferred_days must be entered",
142
+ "string.empty": "preferred_days cannot be empty",
143
+ })
144
+ ),
145
+ workout_place: joi
146
+ .string()
147
+ .valid(...Object.values(WorkoutPlace))
148
+ .empty()
149
+ .required()
150
+ .messages({
151
+ "string.base": "please enter a valid workout_place",
152
+ "any.required": "workout_place must be entered",
153
+ "string.empty": "workout_place cannot be empty",
154
+ }),
155
+ preferred_equipment: joi
156
+ .array()
157
+ .valid(...Object.values(PreferredEquipment))
158
+ .empty()
159
+ .required()
160
+ .items(
161
+ joi.string().empty().required().messages({
162
+ "string.base": "please enter a valid preferred_equipment",
163
+ "any.required": "preferred_equipment must be entered",
164
+ "string.empty": "preferred_equipment cannot be empty",
165
+ })
166
+ ),
167
+ }),
168
+ injuries: joi
169
+ .array()
170
+ .valid(...Object.values(Injury))
171
+ .empty()
172
+ .required()
173
+ .items(
174
+ joi.string().empty().required().messages({
175
+ "string.base": "please enter a valid injuries",
176
+ "any.required": "injuries must be entered",
177
+ "string.empty": "injuries cannot be empty",
178
+ })
179
+ ),
180
+ dob: joi.date().empty().optional().messages({
181
+ "date.base": "please enter a valid date",
182
+ }),
183
+ role: joi
184
+ .string()
185
+ .valid(...Object.values(AuthenticatableType))
186
+ .optional()
187
+ .messages({
188
+ "string.base": "please enter a valid role",
189
+ "string.empty": "role cannot be empty",
190
+ }),
191
+ };
192
+
193
+ export const userRegisterSchema = createSchema<IUserRegister>(userRegisterKeys);
src/configs/config.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { Env } from "./env";
2
  import dotenv from "dotenv";
 
3
  dotenv.config();
4
 
5
  export interface Config {
 
 
1
  import dotenv from "dotenv";
2
+ import { Env } from "src/lib/env/env";
3
  dotenv.config();
4
 
5
  export interface Config {
src/configs/env.ts DELETED
@@ -1,25 +0,0 @@
1
- export class EnvValue {
2
- constructor(public value: string | number | boolean) {}
3
-
4
- toString(): string {
5
- return String(this.value);
6
- }
7
- toNumber(): number {
8
- return Number(this.value);
9
- }
10
- toBoolean(): boolean {
11
- return this.value === "true";
12
- }
13
- }
14
-
15
- export class Env {
16
- static get(key: string, defaultValue?: string | number | boolean): EnvValue {
17
- const value = process.env[key] || defaultValue;
18
-
19
- if (!value) {
20
- throw new Error(`Environment variable ${key} not found`);
21
- }
22
-
23
- return new EnvValue(value);
24
- }
25
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
src/helpers/create-schema.ts ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ import * as joi from "joi";
2
+
3
+ export const createSchema = <T>(
4
+ schema: joi.SchemaMap<T>
5
+ ): joi.ObjectSchema<T> => {
6
+ return joi.object().required().keys(schema);
7
+ };
src/helpers/jwt.helper.ts CHANGED
@@ -1,7 +1,7 @@
1
  import jwt from "jsonwebtoken";
2
  import { config } from "../configs/config";
3
 
4
- export class jwtHelper {
5
  static generateToken(payload: any) {
6
  return jwt.sign(payload, config.jwt.secret, {
7
  expiresIn: config.jwt.expiresIn,
@@ -12,23 +12,23 @@ export class jwtHelper {
12
  return (req: any, res: any, next: any) => {
13
  let authHeader = req.headers["authorization"];
14
  const token = authHeader && authHeader.split(" ")[1];
15
- if (token) {
16
- jwt.verify(token, config.jwt.secret, (err: any, tokenData: any) => {
17
- if (err)
18
- return res
19
- .status(403)
20
- .json({ success: false, code: 403, message: "Invalid Token!" });
21
- if (!role.includes(tokenData.role))
22
- return res
23
- .status(401)
24
- .json({ success: false, code: 401, message: "Unauthorized" });
25
- req.tokenData = tokenData;
26
- next();
27
- });
28
- } else
29
  return res
30
  .status(401)
31
  .json({ success: false, code: 401, message: "Unauthorized" });
 
 
 
 
 
 
 
 
 
 
 
 
 
32
  };
33
  }
34
  }
 
1
  import jwt from "jsonwebtoken";
2
  import { config } from "../configs/config";
3
 
4
+ export class JwtHelper {
5
  static generateToken(payload: any) {
6
  return jwt.sign(payload, config.jwt.secret, {
7
  expiresIn: config.jwt.expiresIn,
 
12
  return (req: any, res: any, next: any) => {
13
  let authHeader = req.headers["authorization"];
14
  const token = authHeader && authHeader.split(" ")[1];
15
+ if (!token) {
 
 
 
 
 
 
 
 
 
 
 
 
 
16
  return res
17
  .status(401)
18
  .json({ success: false, code: 401, message: "Unauthorized" });
19
+ }
20
+ jwt.verify(token, config.jwt.secret, (err: any, tokenData: any) => {
21
+ if (err)
22
+ return res
23
+ .status(403)
24
+ .json({ success: false, code: 403, message: "Invalid Token!" });
25
+ if (!role.includes(tokenData.role))
26
+ return res
27
+ .status(401)
28
+ .json({ success: false, code: 401, message: "Unauthorized" });
29
+ req.tokenData = tokenData;
30
+ next();
31
+ });
32
  };
33
  }
34
  }
src/helpers/pagination.ts ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { Request } from "express";
2
+
3
+ export const parsePaginationQuery = (query: Request["query"]) => {
4
+ const limit = query.take && parseInt(query.limit as string);
5
+ const skip = query.skip && parseInt(query.skip as string);
6
+
7
+ return {
8
+ limit,
9
+ skip,
10
+ };
11
+ };
src/helpers/validation.helper.ts CHANGED
@@ -1,22 +1,3 @@
1
- // export const validator = (schema: any) => {
2
- // return (req: any, res: any, next: any) => {
3
- // try {
4
- // let validationResult = schema.body.validate(req.body);
5
- // var validation = [];
6
- // if (validationResult.error) {
7
- // validation.push(validationResult.error.details[0].message);
8
- // }
9
- // if (validation.length) {
10
- // return res.status(400).json({ success: false, error: validation.join(), code: 400 });
11
- // }
12
- // next();
13
- // } catch (err) {
14
- // console.log(`err`, err);
15
- // return res.status(400).json({ success: false, error: "Bad Request!", code: 400 });
16
- // }
17
- // };
18
- // };
19
-
20
  import { NextFunction, Request, Response } from "express";
21
  import { createValidator } from "express-joi-validation";
22
  import Joi from "joi";
@@ -32,7 +13,7 @@ export const paramsValidator = (schemaOrParam: Joi.Schema | string) =>
32
 
33
  export const validationErrorHandler = (
34
  err,
35
- req: Request,
36
  res: Response,
37
  next: NextFunction
38
  ) => {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  import { NextFunction, Request, Response } from "express";
2
  import { createValidator } from "express-joi-validation";
3
  import Joi from "joi";
 
13
 
14
  export const validationErrorHandler = (
15
  err,
16
+ _req: Request,
17
  res: Response,
18
  next: NextFunction
19
  ) => {
src/lib/error-handling/http-error.ts ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import http from "http";
2
+
3
+ export class HttpError extends Error {
4
+ status: number;
5
+ constructor(status: number, message?: string | object) {
6
+ if (typeof message === "object") {
7
+ message = JSON.stringify(message);
8
+ }
9
+
10
+ super(message || http.STATUS_CODES[status] || "Error");
11
+ this.status = status;
12
+ }
13
+ }
src/lib/responses/json-response.ts CHANGED
@@ -1,23 +1,21 @@
1
- export class JsonResponse {
2
- public status: number;
3
- public message: string;
4
- public data: Record<string, any> | Record<string, any>[];
5
- public meta?: {
6
  total: number;
7
  page: number;
8
  perPage: number;
9
  };
 
 
 
 
 
 
 
10
 
11
- constructor(props: {
12
- status?: number;
13
- message?: string;
14
- data?: Record<string, any> | Record<string, any>[];
15
- meta?: {
16
- total: number;
17
- page: number;
18
- perPage: number;
19
- };
20
- }) {
21
  this.status = props.status || 200;
22
  this.message = props.message || "Success";
23
  this.data = props.data || {};
 
1
+ interface JsonResponseProps {
2
+ status?: number;
3
+ message?: string;
4
+ data?: Record<string, any> | Record<string, any>[];
5
+ meta?: {
6
  total: number;
7
  page: number;
8
  perPage: number;
9
  };
10
+ }
11
+
12
+ export class JsonResponse {
13
+ public status: JsonResponseProps["status"];
14
+ public message: JsonResponseProps["message"];
15
+ public data: JsonResponseProps["data"];
16
+ public meta?: JsonResponseProps["meta"];
17
 
18
+ constructor(props: JsonResponseProps) {
 
 
 
 
 
 
 
 
 
19
  this.status = props.status || 200;
20
  this.message = props.message || "Success";
21
  this.data = props.data || {};
src/lib/services/crud.service.ts ADDED
@@ -0,0 +1,68 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { HttpError } from "@lib/error-handling/http-error";
2
+ import { AnyKeys, Document, FilterQuery, Model } from "mongoose";
3
+
4
+ export const CrudService = <ModelDoc extends Document>(
5
+ model: Model<ModelDoc>
6
+ ) => {
7
+ return class CrudServiceClass {
8
+ protected model: Model<ModelDoc> = model;
9
+
10
+ async create(data: AnyKeys<ModelDoc>): Promise<ModelDoc> {
11
+ return this.model.create(data);
12
+ }
13
+
14
+ async update(
15
+ filter: FilterQuery<ModelDoc>,
16
+ data: AnyKeys<ModelDoc>
17
+ ): Promise<ModelDoc> {
18
+ return this.model.findOneAndUpdate(filter, data, { new: true });
19
+ }
20
+
21
+ async delete(filter: FilterQuery<ModelDoc>): Promise<ModelDoc> {
22
+ return this.model.findOneAndDelete(filter);
23
+ }
24
+
25
+ async list(
26
+ filter: FilterQuery<ModelDoc>,
27
+ paginationOptions: {
28
+ limit?: number;
29
+ skip?: number;
30
+ } = {
31
+ limit: 10,
32
+ skip: 0,
33
+ }
34
+ ): Promise<{
35
+ docs: ModelDoc[];
36
+ paginationData: {
37
+ total: number;
38
+ page: number;
39
+ perPage: number;
40
+ };
41
+ }> {
42
+ const docs = await this.model
43
+ .find(filter)
44
+ .limit(paginationOptions.limit)
45
+ .skip(paginationOptions.skip);
46
+
47
+ const total = await this.model.countDocuments(filter);
48
+ const paginationData = {
49
+ total: total,
50
+ page: paginationOptions.skip,
51
+ perPage: paginationOptions.limit,
52
+ };
53
+
54
+ return { docs, paginationData };
55
+ }
56
+
57
+ async findOne(filter: FilterQuery<ModelDoc>): Promise<ModelDoc | null> {
58
+ return this.model.findOne(filter);
59
+ }
60
+
61
+ async findOneOrFail(filter: FilterQuery<ModelDoc>): Promise<ModelDoc> {
62
+ const document = await this.findOne(filter);
63
+ if (!document) throw new HttpError(404, "No Matching Result Found.");
64
+
65
+ return document;
66
+ }
67
+ };
68
+ };
src/modules/common/users/enums/roles.enum.ts DELETED
@@ -1,45 +0,0 @@
1
- export enum Role {
2
- USER = "user"
3
- }
4
- export enum Gender {
5
- MALE = "male",
6
- FEMALE = "female"
7
- }
8
- export enum FitnessLevel {
9
- BEGINNER = "beginner",
10
- INTERMEDIATE = "intermediate",
11
- ADVANCED = "advanced"
12
- }
13
- export enum FitnessGoal {
14
- LOSE_WEIGHT = "lose weight",
15
- GAIN_MUSCLE = "gain muscle",
16
- GET_FITTER = "get fitter"
17
- }
18
- export enum WorkoutPlace {
19
- GYM = "gym",
20
- HOME = "home",
21
- BOTH = "both"
22
- }
23
- export enum PreferredDay {
24
- SATURDAY = "saturday",
25
- SUNDAY = "sunday",
26
- MONDAY = "monday",
27
- TUESDAY = "tuesday",
28
- WEDNESDAY = "wednesday",
29
- THURSDAY = "thursday",
30
- FRIDAY = "friday"
31
- }
32
- export enum PreferredEquipment {
33
- BARBELLS = "barbells",
34
- DUMBBELLS = "dumbbells",
35
- GYM_MACHINES = "gym machines",
36
- RESISTANCE_BAND = "resistance band",
37
- BODYWEIGHT = "bodyweight"
38
- }
39
- export enum Injurie {
40
- NECK = "neck",
41
- SHOULDERS = "shoulders",
42
- BACK = "back",
43
- ARMS = "arms",
44
- KNEES = "knees"
45
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
src/modules/common/users/models/user.model.ts DELETED
@@ -1,88 +0,0 @@
1
- import mongoose from "mongoose";
2
- import bcrypt from "bcrypt";
3
- export const saltrounds = 5;
4
- import { Role, Gender, FitnessLevel, FitnessGoal, WorkoutPlace, PreferredDay, PreferredEquipment, Injurie } from "../enums/roles.enum";
5
- const { Schema } = mongoose;
6
-
7
- export interface IUser {
8
- name: string;
9
- email: string;
10
- password: string;
11
- image: object;
12
- role: Role;
13
- gender: string;
14
- dob: Date;
15
- height: number;
16
- weight: number;
17
- fitness_level: string;
18
- preferences: {
19
- fitness_goal: FitnessGoal;
20
- target_weight: number;
21
- workout_frequency: number;
22
- preferred_days: [PreferredDay];
23
- workout_place: WorkoutPlace;
24
- preferred_equipment: [PreferredEquipment];
25
- };
26
- injuries: [Injurie];
27
- }
28
-
29
- const userSchema = new Schema({
30
- name: { type: String, required: true },
31
- email: { type: String, required: true, unique: true, dropDups: true },
32
- password: { type: String, required: true },
33
- image: { type: Object },
34
- gender: {
35
- type: String,
36
- enum: Gender,
37
- required: true
38
- },
39
- height: { type: Number, required: true },
40
- weight: { type: Number, required: true },
41
- fitness_level: {
42
- type: String,
43
- enum: FitnessLevel,
44
- required: true
45
- },
46
- preferences: {
47
- fitness_goal: {
48
- type: String,
49
- enum: FitnessGoal,
50
- required: true
51
- },
52
- target_weight: { type: Number, required: true },
53
- workout_frequency: { type: Number, required: true },
54
- preferred_days: [{
55
- type: String,
56
- enum: PreferredDay,
57
- required: true
58
- }],
59
- workout_place: {
60
- type: String,
61
- enum: WorkoutPlace,
62
- required: true
63
- },
64
- preferred_equipment: [{
65
- type: String,
66
- enum: PreferredEquipment,
67
- required: true
68
- }]
69
- },
70
- injuries: [{
71
- type: String,
72
- enum: Injurie,
73
- required: true
74
- }],
75
- dob: { type: Date },
76
- role: {
77
- type: String,
78
- enum: Role,
79
- default: Role.USER
80
- }
81
- });
82
-
83
- userSchema.pre("save", async function (next) {
84
- this.password = await bcrypt.hash(this.password, saltrounds);
85
- next();
86
- });
87
-
88
- export const userModel = mongoose.model<IUser>("users", userSchema);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
src/modules/common/users/services/users.base.service.ts DELETED
@@ -1,114 +0,0 @@
1
- import { userModel } from "../models/user.model";
2
-
3
- export abstract class UsersBaseService {
4
- async find(filterObject) {
5
- try {
6
- const resultObject = await userModel.findOne(filterObject).lean();
7
-
8
- if (!resultObject)
9
- return {
10
- success: false,
11
- code: 404,
12
- error: "No Matching Result Found.",
13
- };
14
-
15
- return {
16
- success: true,
17
- code: 200,
18
- record: resultObject,
19
- };
20
- } catch (err) {
21
- console.log(`err.message`, err.message);
22
- return {
23
- success: false,
24
- code: 500,
25
- error: err.message,
26
- };
27
- }
28
- }
29
-
30
- async create(form: any) {
31
- try {
32
- if (form.email) {
33
- form.email = form.email.toLowerCase();
34
- let user = await this.find({ email: form.email });
35
- if (user.success)
36
- return {
37
- success: false,
38
- error: "This email already exists",
39
- code: 409,
40
- };
41
- }
42
- let newUser = new userModel(form);
43
- await newUser.save();
44
- return {
45
- success: true,
46
- code: 201,
47
- };
48
- } catch (err) {
49
- console.log(`err.message`, err.message);
50
- return {
51
- success: false,
52
- code: 500,
53
- error: err.message,
54
- };
55
- }
56
- }
57
-
58
- async get(filterObject) {
59
- try {
60
- const resultObject = await userModel
61
- .findOne(filterObject)
62
- .lean()
63
- .select("-password");
64
- if (!resultObject)
65
- return {
66
- success: false,
67
- code: 404,
68
- error: "No Matching Result Found.",
69
- };
70
- return {
71
- success: true,
72
- code: 200,
73
- record: resultObject,
74
- };
75
- } catch (err) {
76
- console.log(`err.message`, err.message);
77
- return {
78
- success: false,
79
- code: 500,
80
- error: err.message,
81
- };
82
- }
83
- }
84
-
85
- async list(filterObject) {
86
- try {
87
- const resultArray = await userModel
88
- .find(filterObject)
89
- .lean()
90
- .select("-password");
91
-
92
- if (!resultArray)
93
- return {
94
- success: false,
95
- code: 404,
96
- error: "No Matching Result Found.",
97
- };
98
- const count = await userModel.countDocuments(filterObject);
99
- return {
100
- success: true,
101
- code: 200,
102
- record: resultArray,
103
- count,
104
- };
105
- } catch (err) {
106
- console.log(`err.message`, err.message);
107
- return {
108
- success: false,
109
- code: 500,
110
- error: "Unexpected Error Happened.",
111
- };
112
- }
113
- }
114
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
src/modules/common/users/validation/user-register.validation.ts DELETED
@@ -1,140 +0,0 @@
1
- import * as joi from "joi";
2
- import { Role, Gender, FitnessLevel, FitnessGoal, WorkoutPlace, PreferredDay, PreferredEquipment, Injurie } from "../enums/roles.enum";
3
-
4
- export const userRegisterValidation = joi
5
- .object()
6
- .required()
7
- .keys({
8
- name: joi.string().empty().required().messages({
9
- "string.base": "please enter a valid name",
10
- "any.required": "name is required",
11
- "string.empty": "name can not be empty",
12
- }),
13
- email: joi
14
- .string()
15
- .required()
16
- .email({
17
- minDomainSegments: 2,
18
- tlds: { allow: ["com", "net", "org", "eg", "io"] },
19
- })
20
- .empty()
21
- .messages({
22
- "string.email": "please enter a valid email",
23
- "any.required": "email must be entered",
24
- "string.empty": "email can not be empty",
25
- }),
26
- password: joi.string().empty().min(8).required().messages({
27
- "string.base": "please enter a valid password",
28
- "any.required": "password must be entered",
29
- "string.empty": "password cannot be empty",
30
- "string.min": "password must be at least 8 characters",
31
- }),
32
- confirmPassword: joi.string().empty().min(8).required().messages({
33
- "string.base": "please enter a valid password",
34
- "any.required": "password must be entered",
35
- "string.empty": "password cannot be empty",
36
- "string.min": "password must be at least 8 characters",
37
- }),
38
- image: joi
39
- .object()
40
- .optional()
41
- .keys({
42
- url: joi.string().optional().messages({
43
- "string.base": "please enter a valid url",
44
- }),
45
- public_id: joi.string().optional().messages({
46
- "string.base": "please enter a valid public_id",
47
- }),
48
- }),
49
- gender: joi.string().valid(...Object.values(Gender)).empty().required().messages({
50
- "string.base": "please enter a valid gender",
51
- "any.required": "gender must be entered",
52
- "string.empty": "gender cannot be empty",
53
- }),
54
- height: joi.number().empty().required().messages({
55
- "number.base": "please enter a valid height number",
56
- "any.required": "height must be entered",
57
- "number.empty": "height cannot be empty",
58
- }),
59
- weight: joi.number().empty().required().messages({
60
- "number.base": "please enter a valid weight number",
61
- "any.required": "weight must be entered",
62
- "number.empty": "weight cannot be empty",
63
- }),
64
- fitness_level: joi.string().valid(...Object.values(FitnessLevel)).empty().required().messages({
65
- "string.base": "please enter a valid fitness_level",
66
- "any.required": "fitness_level must be entered",
67
- "string.empty": "fitness_level cannot be empty",
68
- }),
69
- preferences: joi
70
- .object()
71
- .optional()
72
- .keys({
73
- fitness_goal: joi.string().valid(...Object.values(FitnessGoal)).empty().required().messages({
74
- "string.base": "please enter a valid fitness_goal",
75
- "any.required": "fitness_goal must be entered",
76
- "string.empty": "fitness_goal cannot be empty",
77
- }),
78
- target_weight: joi.number().empty().required().messages({
79
- "number.base": "please enter a valid target_weight number",
80
- "any.required": "target_weight must be entered",
81
- "number.empty": "target_weight cannot be empty",
82
- }),
83
- workout_frequency: joi.number().empty().required().messages({
84
- "number.base": "please enter a valid workout_frequency number",
85
- "any.required": "workout_frequency must be entered",
86
- "number.empty": "workout_frequency cannot be empty",
87
- }),
88
- preferred_days: joi
89
- .array()
90
- .valid(...Object.values(PreferredDay))
91
- .empty()
92
- .required()
93
- .items(
94
- joi.string().empty().required().messages({
95
- "string.base": "please enter a valid preferred_days",
96
- "any.required": "preferred_days must be entered",
97
- "string.empty": "preferred_days cannot be empty",
98
- })
99
- ),
100
- workout_place: joi.string().valid(...Object.values(WorkoutPlace)).empty().required().messages({
101
- "string.base": "please enter a valid workout_place",
102
- "any.required": "workout_place must be entered",
103
- "string.empty": "workout_place cannot be empty",
104
- }),
105
- preferred_equipment: joi
106
- .array()
107
- .valid(...Object.values(PreferredEquipment))
108
- .empty()
109
- .required()
110
- .items(
111
- joi.string().empty().required().messages({
112
- "string.base": "please enter a valid preferred_equipment",
113
- "any.required": "preferred_equipment must be entered",
114
- "string.empty": "preferred_equipment cannot be empty",
115
- })
116
- ),
117
- }),
118
- injuries: joi
119
- .array()
120
- .valid(...Object.values(Injurie))
121
- .empty()
122
- .required()
123
- .items(
124
- joi.string().empty().required().messages({
125
- "string.base": "please enter a valid injuries",
126
- "any.required": "injuries must be entered",
127
- "string.empty": "injuries cannot be empty",
128
- })
129
- ),
130
- dob: joi.date().empty().optional().messages({
131
- "date.base": "please enter a valid date",
132
- }),
133
- role: joi
134
- .string()
135
- .valid(...Object.values(Role))
136
- .optional().messages({
137
- "string.base": "please enter a valid role",
138
- "string.empty": "role cannot be empty"
139
- }),
140
- });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
src/modules/console/admins/services/admins.service.ts DELETED
@@ -1,267 +0,0 @@
1
- import bcrypt from "bcrypt";
2
- import { Admin, IAdmin } from "../models/admin.model";
3
- import { config } from "../../../../configs/config";
4
- import { FilterQuery } from "mongoose";
5
-
6
- export class AdminsService {
7
- async find(filterObject) {
8
- try {
9
- const resultObject = await Admin.findOne(filterObject).lean();
10
-
11
- if (!resultObject)
12
- return {
13
- success: false,
14
- code: 404,
15
- error: "No Matching Result Found.",
16
- };
17
-
18
- return {
19
- success: true,
20
- code: 200,
21
- record: resultObject,
22
- };
23
- } catch (err) {
24
- console.log(`err.message`, err.message);
25
- return {
26
- success: false,
27
- code: 500,
28
- error: "Unexpected Error Happened.",
29
- };
30
- }
31
- }
32
-
33
- async get(filterObject: FilterQuery<IAdmin>) {
34
- try {
35
- const resultObject = await Admin.findOne(filterObject)
36
- .lean()
37
- .select("-password");
38
- if (!resultObject)
39
- return {
40
- success: false,
41
- code: 404,
42
- error: "No Matching Result Found.",
43
- };
44
- return {
45
- success: true,
46
- code: 200,
47
- record: resultObject,
48
- };
49
- } catch (err) {
50
- console.log(`err.message`, err.message);
51
- return {
52
- success: false,
53
- code: 500,
54
- error: "Unexpected Error Happened.",
55
- };
56
- }
57
- }
58
-
59
- async list(filterObject) {
60
- try {
61
- const resultArray = await Admin.find(filterObject)
62
- .lean()
63
- .select("-password");
64
-
65
- if (!resultArray)
66
- return {
67
- success: false,
68
- code: 404,
69
- error: "No Matching Result Found.",
70
- };
71
- const count = await Admin.countDocuments(filterObject);
72
- return {
73
- success: true,
74
- code: 200,
75
- record: resultArray,
76
- count,
77
- };
78
- } catch (err) {
79
- console.log(`err.message`, err.message);
80
- return {
81
- success: false,
82
- code: 500,
83
- error: "Unexpected Error Happened.",
84
- };
85
- }
86
- }
87
-
88
- async create(formObject) {
89
- try {
90
- if (formObject.email) formObject.email = formObject.email.toLowerCase();
91
- const resultObject = new Admin(formObject);
92
- await resultObject.save();
93
-
94
- if (!resultObject)
95
- return {
96
- success: false,
97
- code: 500,
98
- error: "Unexpected Error Happened.",
99
- };
100
-
101
- return {
102
- success: true,
103
- code: 201,
104
- record: resultObject,
105
- };
106
- } catch (err) {
107
- console.log(`err.message`, err.message);
108
- return {
109
- success: false,
110
- code: 500,
111
- error: "Unexpected Error Happened.",
112
- };
113
- }
114
- }
115
-
116
- async update(_id, formObject) {
117
- try {
118
- const existingObject = await this.find({ _id });
119
- if (!existingObject.success)
120
- return {
121
- success: false,
122
- code: 404,
123
- error: "No Matching Result Found.",
124
- };
125
- if (formObject.email) {
126
- formObject.email = formObject.email.toLowerCase();
127
- const duplicate = await this.find({ email: formObject.email });
128
- if (
129
- duplicate.success &&
130
- duplicate.record._id.toString() !=
131
- existingObject.record._id.toString()
132
- )
133
- return {
134
- success: false,
135
- error: "This Email is taken by another user",
136
- code: 409,
137
- };
138
- }
139
-
140
- const resultObject = await Admin.findByIdAndUpdate({ _id }, formObject);
141
-
142
- if (!resultObject)
143
- return {
144
- success: false,
145
- code: 500,
146
- error: "Unexpected Error Happened.",
147
- };
148
-
149
- return {
150
- success: true,
151
- code: 200,
152
- record: resultObject,
153
- };
154
- } catch (err) {
155
- console.log(`err.message`, err.message);
156
- return {
157
- success: false,
158
- code: 500,
159
- error: "Unexpected Error Happened.",
160
- };
161
- }
162
- }
163
-
164
- async remove(_id) {
165
- try {
166
- const resultObject = await Admin.findByIdAndDelete({ _id });
167
- if (!resultObject)
168
- return {
169
- success: false,
170
- code: 404,
171
- error: "No Matching Result Found.",
172
- };
173
-
174
- return {
175
- success: true,
176
- code: 200,
177
- };
178
- } catch (err) {
179
- console.log(`err.message`, err.message);
180
- return {
181
- success: false,
182
- code: 500,
183
- error: "Unexpected Error Happened.",
184
- };
185
- }
186
- }
187
-
188
- async comparePassword(emailString, passwordString) {
189
- try {
190
- emailString = emailString.toLowerCase();
191
- const existingObject = await this.find({ email: emailString });
192
-
193
- if (!existingObject.success)
194
- return {
195
- success: false,
196
- code: 404,
197
- error: "No Matching Result Found.",
198
- };
199
-
200
- const matchingPasswords = await bcrypt.compare(
201
- passwordString,
202
- existingObject.record.password
203
- );
204
- if (!matchingPasswords)
205
- return {
206
- success: false,
207
- code: 409,
208
- error: "Incorrect Password.",
209
- };
210
-
211
- return {
212
- success: true,
213
- record: existingObject.record,
214
- code: 200,
215
- };
216
- } catch (err) {
217
- console.log(`err.message`, err.message);
218
- return {
219
- success: false,
220
- code: 500,
221
- error: "Unexpected Error Happened.",
222
- };
223
- }
224
- }
225
-
226
- async resetPassword(emailString, newPasswordString) {
227
- try {
228
- emailString = emailString.toLowerCase();
229
- const existingObject = await this.find({ email: emailString });
230
-
231
- if (!existingObject.success)
232
- return {
233
- success: false,
234
- code: 404,
235
- error: "No Matching Result Found.",
236
- };
237
-
238
- const hashedPassword = await bcrypt.hash(
239
- newPasswordString,
240
- config.saltRounds
241
- );
242
- const resultObject = await Admin.findOneAndUpdate(
243
- { email: emailString },
244
- { password: hashedPassword }
245
- );
246
-
247
- if (!resultObject)
248
- return {
249
- success: false,
250
- code: 500,
251
- error: "Unexpected Error Happened.",
252
- };
253
-
254
- return {
255
- success: true,
256
- code: 200,
257
- };
258
- } catch (err) {
259
- console.log(`err.message`, err.message);
260
- return {
261
- success: false,
262
- code: 500,
263
- error: "Unexpected Error Happened.",
264
- };
265
- }
266
- }
267
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
src/modules/console/admins/validations/create-admin.validation.ts DELETED
@@ -1,47 +0,0 @@
1
- import * as joi from "joi";
2
- import { Role } from "../enums/roles.enum";
3
-
4
- export const createAdminSchema = joi
5
- .object()
6
- .required()
7
- .keys({
8
- name: joi.string().empty().required().messages({
9
- "string.base": "please enter a valid name",
10
- "any.required": "name is required",
11
- "string.empty": "name can not be empty",
12
- }),
13
- email: joi
14
- .string()
15
- .required()
16
- .email({
17
- minDomainSegments: 2,
18
- tlds: { allow: ["com", "net", "org", "eg", "io"] },
19
- })
20
- .empty()
21
- .messages({
22
- "string.email": "please enter a valid email",
23
- "any.required": "email must be entered",
24
- "string.empty": "email can not be empty",
25
- }),
26
- password: joi.string().empty().min(8).required().messages({
27
- "string.base": "please enter a valid password",
28
- "any.required": "password must be entered",
29
- "string.empty": "password cannot be empty",
30
- "string.min": "password must be at least 8 characters",
31
- }),
32
- dob: joi.date().empty().optional().messages({
33
- "date.base": "please enter a valid date",
34
- }),
35
- role: joi
36
- .string()
37
- .valid(...Object.values(Role))
38
- .optional().messages({
39
- "string.base": "please enter a valid role",
40
- "string.empty": "role cannot be empty"
41
- }),
42
- gender: joi.string().empty().required().messages({
43
- "string.base": "please enter a valid gender",
44
- "any.required": "gender must be entered",
45
- "string.empty": "gender cannot be empty"
46
- })
47
- });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
src/modules/console/common/guards/admins.guard.ts ADDED
@@ -0,0 +1,45 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { JwtPayload, verify } from "jsonwebtoken";
2
+ import { Request } from "express";
3
+ import { Role } from "@common/enums/role.enum";
4
+ import { HttpError } from "@lib/error-handling/http-error";
5
+ import { config } from "@configs/config";
6
+ import { IJwtLoginPayload } from "@common/interfaces/jwt-payload.interface";
7
+
8
+ type AdminGuardMiddlewareProps = {
9
+ roles?: Role[];
10
+ };
11
+
12
+ export const AdminGuardMiddleware =
13
+ (props?: AdminGuardMiddlewareProps) => (req: Request, res, next) => {
14
+ // get token from cookie
15
+ const token = req.headers.authorization?.split(" ")[1];
16
+ let payload: IJwtLoginPayload;
17
+
18
+ // validate token
19
+ if (!token) {
20
+ throw new HttpError(401, "Unauthorized");
21
+ }
22
+
23
+ try {
24
+ payload = verify(token, config.jwt.secret);
25
+ } catch (err) {
26
+ throw new HttpError(401, "Unauthorized");
27
+ }
28
+
29
+ if (payload.type !== "admin") {
30
+ throw new HttpError(401, "Unauthorized");
31
+ }
32
+
33
+ // check roles
34
+ if (props?.roles && props?.roles.length > 0) {
35
+ if (!props.roles.includes(payload.role)) {
36
+ throw new HttpError(401, "Unauthorized");
37
+ }
38
+ }
39
+
40
+ // inject payload in request
41
+ (req as unknown as { jwtPayload: JwtPayload }).jwtPayload = payload;
42
+
43
+ // go on
44
+ next();
45
+ };
src/modules/console/{admins → common}/models/admin.model.ts RENAMED
@@ -1,6 +1,5 @@
1
  import mongoose from "mongoose";
2
  import bcrypt from "bcrypt";
3
- import { Role } from "../enums/roles.enum";
4
  import { config } from "../../../../configs/config";
5
  const { Schema } = mongoose;
6
 
@@ -9,7 +8,6 @@ export interface IAdmin {
9
  email: string;
10
  password: string;
11
  image: object;
12
- role: Role;
13
  gender: string;
14
  dob: Date;
15
  }
@@ -19,10 +17,6 @@ const AdminSchema = new Schema({
19
  email: { type: String, required: true, unique: true, dropDups: true },
20
  password: { type: String, required: true },
21
  image: { type: Object, default: {} },
22
- role: {
23
- type: String,
24
- enum: Role,
25
- },
26
  gender: { type: String, required: true },
27
  dob: { type: Date },
28
  });
@@ -32,4 +26,6 @@ AdminSchema.pre("save", async function (next) {
32
  next();
33
  });
34
 
35
- export const Admin = mongoose.model<IAdmin>("admins", AdminSchema);
 
 
 
1
  import mongoose from "mongoose";
2
  import bcrypt from "bcrypt";
 
3
  import { config } from "../../../../configs/config";
4
  const { Schema } = mongoose;
5
 
 
8
  email: string;
9
  password: string;
10
  image: object;
 
11
  gender: string;
12
  dob: Date;
13
  }
 
17
  email: { type: String, required: true, unique: true, dropDups: true },
18
  password: { type: String, required: true },
19
  image: { type: Object, default: {} },
 
 
 
 
20
  gender: { type: String, required: true },
21
  dob: { type: Date },
22
  });
 
26
  next();
27
  });
28
 
29
+ export type AdminDocument = mongoose.Document & IAdmin;
30
+
31
+ export const Admin = mongoose.model<AdminDocument>("admins", AdminSchema);
src/modules/console/{admins → modules/admins}/controllers/admins.controller.ts RENAMED
@@ -1,15 +1,18 @@
1
- import { Request, Response, Router } from "express";
2
- import { BaseController } from "../../../../lib/controllers/controller.base";
 
 
 
3
  import { AdminsService } from "../services/admins.service";
4
  import { createAdminSchema } from "../validations/create-admin.validation";
5
- import {
6
- bodyValidator,
7
- paramsValidator,
8
- } from "../../../../helpers/validation.helper";
9
- import { asyncHandler } from "../../../../helpers/async-handler";
10
- import { Prefix } from "../../../../lib/decorators/prefix.decorator";
11
 
12
  @Prefix("/console/admins")
 
13
  export class AdminsController extends BaseController {
14
  private adminsService = new AdminsService();
15
 
@@ -34,36 +37,61 @@ export class AdminsController extends BaseController {
34
  );
35
  }
36
 
37
- list = (_, res: Response) => {
38
- this.adminsService
39
- .list({})
40
- .then((result) => {
41
- res.status(result.code).json(result);
42
- })
43
- .catch((err) => {
44
- res.status(500).json(err);
45
- });
 
 
46
  };
47
 
48
  get = async (req: Request, res: Response) => {
49
- const data = await this.adminsService.get({
50
  _id: req.params.id,
51
  });
52
- res.json(data);
 
 
 
53
  };
54
 
55
  create = async (req: Request, res: Response) => {
56
- const data = await this.adminsService.create(req.body);
57
- res.json(data);
 
 
 
58
  };
59
 
60
  update = async (req: Request, res: Response) => {
61
- const data = await this.adminsService.update(req.params.id, req.body);
62
- res.json(data);
 
 
 
 
 
 
 
 
 
 
63
  };
64
 
65
  delete = async (req: Request, res: Response) => {
66
- const data = await this.adminsService.remove(req.params.id);
67
- res.json(data);
 
 
 
 
 
 
 
68
  };
69
  }
 
1
+ import { asyncHandler } from "@helpers/async-handler";
2
+ import { paramsValidator, bodyValidator } from "@helpers/validation.helper";
3
+ import { BaseController } from "@lib/controllers/controller.base";
4
+ import { Prefix } from "@lib/decorators/prefix.decorator";
5
+ import { Request, Response } from "express";
6
  import { AdminsService } from "../services/admins.service";
7
  import { createAdminSchema } from "../validations/create-admin.validation";
8
+ import { parsePaginationQuery } from "@helpers/pagination";
9
+ import { JsonResponse } from "@lib/responses/json-response";
10
+ import { ControllerMiddleware } from "@lib/decorators/controller-middleware.decorator";
11
+ import { AdminGuardMiddleware } from "src/modules/console/common/guards/admins.guard";
12
+ import { Role } from "@common/enums/role.enum";
 
13
 
14
  @Prefix("/console/admins")
15
+ @ControllerMiddleware(AdminGuardMiddleware({ roles: [Role.SUPER_ADMIN] }))
16
  export class AdminsController extends BaseController {
17
  private adminsService = new AdminsService();
18
 
 
37
  );
38
  }
39
 
40
+ list = async (req: Request, res: Response) => {
41
+ const paginationQuery = parsePaginationQuery(req.query);
42
+ const { docs, paginationData } = await this.adminsService.list(
43
+ {},
44
+ paginationQuery
45
+ );
46
+ const response = new JsonResponse({
47
+ data: docs,
48
+ meta: paginationData,
49
+ });
50
+ return res.json(response);
51
  };
52
 
53
  get = async (req: Request, res: Response) => {
54
+ const data = await this.adminsService.findOneOrFail({
55
  _id: req.params.id,
56
  });
57
+ const response = new JsonResponse({
58
+ data,
59
+ });
60
+ res.json(response);
61
  };
62
 
63
  create = async (req: Request, res: Response) => {
64
+ const admin = await this.adminsService.create(req.body);
65
+ const response = new JsonResponse({
66
+ data: admin,
67
+ });
68
+ res.json(response);
69
  };
70
 
71
  update = async (req: Request, res: Response) => {
72
+ const admin = await this.adminsService.update(
73
+ {
74
+ _id: req.params.id,
75
+ },
76
+ req.body
77
+ );
78
+
79
+ const response = new JsonResponse({
80
+ data: admin,
81
+ });
82
+
83
+ res.json(response);
84
  };
85
 
86
  delete = async (req: Request, res: Response) => {
87
+ const admin = await this.adminsService.delete({
88
+ _id: req.params.id,
89
+ });
90
+
91
+ const response = new JsonResponse({
92
+ data: admin,
93
+ });
94
+
95
+ res.json(response);
96
  };
97
  }
src/modules/console/modules/admins/services/admins.service.ts ADDED
@@ -0,0 +1,4 @@
 
 
 
 
 
1
+ import { Admin } from "../../../common/models/admin.model";
2
+ import { CrudService } from "@lib/services/crud.service";
3
+
4
+ export class AdminsService extends CrudService(Admin) {}
src/modules/console/modules/admins/validations/create-admin.validation.ts ADDED
@@ -0,0 +1,55 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { Role } from "@common/enums/role.enum";
2
+ import * as joi from "joi";
3
+ import { createSchema } from "src/helpers/create-schema";
4
+
5
+ export interface ICreateAdmin {
6
+ name: string;
7
+ email: string;
8
+ password: string;
9
+ dob: Date;
10
+ role?: Role;
11
+ gender: string;
12
+ }
13
+
14
+ export const createAdminSchema = createSchema<ICreateAdmin>({
15
+ name: joi.string().empty().required().messages({
16
+ "string.base": "please enter a valid name",
17
+ "any.required": "name is required",
18
+ "string.empty": "name can not be empty",
19
+ }),
20
+ email: joi
21
+ .string()
22
+ .required()
23
+ .email({
24
+ minDomainSegments: 2,
25
+ tlds: { allow: ["com", "net", "org", "eg", "io"] },
26
+ })
27
+ .empty()
28
+ .messages({
29
+ "string.email": "please enter a valid email",
30
+ "any.required": "email must be entered",
31
+ "string.empty": "email can not be empty",
32
+ }),
33
+ password: joi.string().empty().min(8).required().messages({
34
+ "string.base": "please enter a valid password",
35
+ "any.required": "password must be entered",
36
+ "string.empty": "password cannot be empty",
37
+ "string.min": "password must be at least 8 characters",
38
+ }),
39
+ dob: joi.date().empty().optional().messages({
40
+ "date.base": "please enter a valid date",
41
+ }),
42
+ role: joi
43
+ .string()
44
+ .valid(...Object.values(Role))
45
+ .optional()
46
+ .messages({
47
+ "string.base": "please enter a valid role",
48
+ "string.empty": "role cannot be empty",
49
+ }),
50
+ gender: joi.string().empty().required().messages({
51
+ "string.base": "please enter a valid gender",
52
+ "any.required": "gender must be entered",
53
+ "string.empty": "gender cannot be empty",
54
+ }),
55
+ });
src/modules/console/modules/users/controllers/users.controller.ts ADDED
@@ -0,0 +1,29 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { userRegisterSchema } from "src/common/validations/user-register.validation";
2
+ import { UsersService } from "../services/users.service";
3
+ import { JsonResponse } from "src/lib/responses/json-response";
4
+ import { Request, Response } from "express";
5
+ import { asyncHandler } from "@helpers/async-handler";
6
+ import { bodyValidator } from "@helpers/validation.helper";
7
+ import { BaseController } from "@lib/controllers/controller.base";
8
+ import { Prefix } from "@lib/decorators/prefix.decorator";
9
+
10
+ @Prefix("/console/users")
11
+ export class AdminUsersController extends BaseController {
12
+ private usersService: UsersService = new UsersService();
13
+
14
+ setRoutes() {
15
+ this.router.post(
16
+ "/create",
17
+ bodyValidator(userRegisterSchema),
18
+ asyncHandler(this.create)
19
+ );
20
+ }
21
+
22
+ create = async (req: Request, res: Response) => {
23
+ let user = await this.usersService.create(req.body);
24
+ const response = new JsonResponse({
25
+ data: user,
26
+ });
27
+ return res.json(response);
28
+ };
29
+ }
src/modules/console/modules/users/services/users.service.ts ADDED
@@ -0,0 +1,4 @@
 
 
 
 
 
1
+ import { userModel } from "@common/models/user.model";
2
+ import { CrudService } from "@lib/services/crud.service";
3
+
4
+ export class UsersService extends CrudService(userModel) {}
src/modules/console/users/controllers/users.controller.ts DELETED
@@ -1,37 +0,0 @@
1
- import { asyncHandler } from "../../../../helpers/async-handler";
2
- import { jwtHelper } from "../../../../helpers/jwt.helper";
3
- import { bodyValidator } from "../../../../helpers/validation.helper";
4
- import { BaseController } from "../../../../lib/controllers/controller.base";
5
- import { Prefix } from "../../../../lib/decorators/prefix.decorator";
6
- import { userRegisterValidation } from "../../../common/users/validation/user-register.validation";
7
- import { UsersService } from "../services/users.service";
8
-
9
- const allowedRoles = ["superAdmin", "admin"];
10
-
11
- @Prefix("/console/users")
12
- export class AdminUsersController extends BaseController {
13
- private usersService: UsersService = new UsersService();
14
-
15
- setRoutes() {
16
- this.router.post(
17
- "/create",
18
- jwtHelper.verifyToken(allowedRoles),
19
- bodyValidator(userRegisterValidation),
20
- asyncHandler(this.create)
21
- );
22
- }
23
-
24
- create = async (req, res) => {
25
- try {
26
- let result = await this.usersService.create(req.body);
27
- return res.status(result.code).json(result);
28
- } catch (err) {
29
- console.log(`err.message`, err.message);
30
- return res.status(500).json({
31
- success: false,
32
- code: 500,
33
- error: err.message,
34
- });
35
- }
36
- };
37
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
src/modules/console/users/services/users.service.ts DELETED
@@ -1,3 +0,0 @@
1
- import { UsersBaseService } from "../../../common/users/services/users.base.service";
2
-
3
- export class UsersService extends UsersBaseService {}
 
 
 
 
src/modules/user/auth/controllers/auth.controller.ts DELETED
@@ -1,73 +0,0 @@
1
- import { UsersService } from "../services/users.service";
2
- import { jwtHelper } from "../../../../helpers/jwt.helper";
3
- import { BaseController } from "../../../../lib/controllers/controller.base";
4
- import { bodyValidator } from "../../../../helpers/validation.helper";
5
- import { userRegisterValidation } from "../../../common/users/validation/user-register.validation";
6
- import { loginValidation } from "../validation/user.Validation";
7
- import { asyncHandler } from "../../../../helpers/async-handler";
8
- import { Prefix } from "../../../../lib/decorators/prefix.decorator";
9
-
10
- @Prefix("/user/auth")
11
- export class AuthController extends BaseController {
12
- private usersService = new UsersService();
13
-
14
- setRoutes(): void {
15
- this.router.post(
16
- "/register",
17
- bodyValidator(userRegisterValidation),
18
- asyncHandler(this.register)
19
- );
20
- this.router.post(
21
- "/login",
22
- bodyValidator(loginValidation),
23
- asyncHandler(this.login)
24
- );
25
- }
26
-
27
- register = async (req, res) => {
28
- try {
29
- let result = await this.usersService.create(req.body);
30
- return res.status(result.code).json(result);
31
- } catch (err) {
32
- console.log(`err.message`, err.message);
33
- return res.status(500).json({
34
- success: false,
35
- code: 500,
36
- error: err.message,
37
- });
38
- }
39
- };
40
-
41
- login = async (req, res) => {
42
- try {
43
- const { email, password } = req.body;
44
- let result: {
45
- success: boolean;
46
- code: number;
47
- record?: any;
48
- message?: string;
49
- } = await this.usersService.comparePassword(email, password);
50
- if (!result.success) return res.status(result.code).json(result);
51
- let payload = {
52
- _id: result.record?._id,
53
- name: result.record?.name,
54
- email: result.record?.email,
55
- number: result.record?.number,
56
- role: result.record?.role,
57
- };
58
- const token = jwtHelper.generateToken(payload);
59
- return res.status(result.code).json({
60
- success: result.success,
61
- token,
62
- code: result.code,
63
- record: result.record,
64
- });
65
- } catch (err) {
66
- console.log(`err.message`, err.message);
67
- return res.status(500).json({
68
- success: false,
69
- message: err.message,
70
- });
71
- }
72
- };
73
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
src/modules/user/auth/services/users.service.ts DELETED
@@ -1,87 +0,0 @@
1
- import { UsersBaseService } from "../../../common/users/services/users.base.service";
2
- import bcrypt from "bcrypt";
3
-
4
- export class UsersService extends UsersBaseService {
5
- async comparePassword(email: string, password: string) {
6
- try {
7
- if (email != undefined) {
8
- email = email.toLowerCase();
9
- }
10
- let result = await this.find({ email });
11
- if (!result.success) return result;
12
-
13
- let match = await bcrypt.compare(password, result.record.password);
14
- delete result.record.password;
15
-
16
- if (!match)
17
- return {
18
- success: false,
19
- code: 409,
20
- message: "password isn't correct",
21
- };
22
-
23
- return {
24
- success: true,
25
- code: 200,
26
- record: result.record,
27
- };
28
- } catch (err) {
29
- console.log(`err.message`, err.message);
30
- return {
31
- success: false,
32
- code: 500,
33
- error: err.message,
34
- };
35
- }
36
- }
37
-
38
- // async resetPassword(_id: any, currentPassword: string, newPassword: string, confirmPassword: string) {
39
- // try {
40
- // let user = await UserBaseService.find({ _id })
41
- // let saltrouds = 5;
42
- // let oldPassword = user.record.password;
43
- // let ok = await bcrypt.compare(currentPassword, oldPassword)
44
-
45
- // if (user.success) {
46
-
47
- // if (!ok) return {
48
- // success: false,
49
- // code: 409,
50
- // message: "current password isn't correct"
51
- // };
52
-
53
- // if (newPassword == currentPassword) return {
54
- // success: false,
55
- // code: 409,
56
- // message: "new password must be different from current password"
57
- // };
58
-
59
- // if (newPassword != confirmPassword) return {
60
- // success: false,
61
- // code: 409,
62
- // message: "passwords don't match"
63
- // };
64
-
65
- // const hashedPassword = await bcrypt.hash(newPassword, saltrouds)
66
- // await userModel.findByIdAndUpdate(_id, { password: hashedPassword })
67
- // return {
68
- // success: true,
69
- // code: 200,
70
- // message: "password changed successfully"
71
- // };
72
- // }
73
- // else return {
74
- // success: false,
75
- // code: 404,
76
- // error: user.error
77
- // };
78
- // } catch (err) {
79
- // console.log(`err.message`, err.message);
80
- // return {
81
- // success: false,
82
- // code: 500,
83
- // error: err.message
84
- // };
85
- // }
86
- // }
87
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
src/modules/user/auth/validation/user.Validation.ts DELETED
@@ -1,26 +0,0 @@
1
- import joi from "joi";
2
-
3
- export const loginValidation = joi
4
- .object()
5
- .required()
6
- .keys({
7
- email: joi
8
- .string()
9
- .required()
10
- .email({
11
- minDomainSegments: 2,
12
- tlds: { allow: ["com", "net", "org", "eg", "io"] },
13
- })
14
- .empty()
15
- .messages({
16
- "string.email": "please enter a valid email",
17
- "any.required": "email must be entered",
18
- "string.empty": "email can not be empty",
19
- }),
20
- password: joi.string().empty().min(8).required().messages({
21
- "string.base": "please enter a valid password",
22
- "any.required": "password must be entered",
23
- "string.empty": "password cannot be empty",
24
- "string.min": "password must be at least 8 characters",
25
- }),
26
- });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
src/modules/users/auth/controllers/auth.controller.ts ADDED
@@ -0,0 +1,43 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { UsersAuthService } from "../services/users.service";
2
+ import { loginValidationSchema } from "../validation/login.validation";
3
+ import { Request, Response } from "express";
4
+ import { JsonResponse } from "src/lib/responses/json-response";
5
+ import { userRegisterSchema, IUserRegister } from "src/common/validations/user-register.validation";
6
+ import { asyncHandler } from "@helpers/async-handler";
7
+ import { bodyValidator } from "@helpers/validation.helper";
8
+ import { BaseController } from "@lib/controllers/controller.base";
9
+ import { Prefix } from "@lib/decorators/prefix.decorator";
10
+
11
+ @Prefix("/users/auth")
12
+ export class UsersAuthController extends BaseController {
13
+ private authService = new UsersAuthService();
14
+
15
+ setRoutes(): void {
16
+ this.router.post(
17
+ "/register",
18
+ bodyValidator(userRegisterSchema),
19
+ asyncHandler(this.register)
20
+ );
21
+ this.router.post(
22
+ "/login",
23
+ bodyValidator(loginValidationSchema),
24
+ asyncHandler(this.login)
25
+ );
26
+ }
27
+
28
+ register = async (req: Request, res: Response) => {
29
+ const user = await this.authService.register(req.body as IUserRegister);
30
+ const response = new JsonResponse({
31
+ data: user,
32
+ });
33
+ return res.json(response);
34
+ };
35
+
36
+ login = async (req: Request, res: Response) => {
37
+ const { user, token } = await this.authService.login(req.body);
38
+ const response = new JsonResponse({
39
+ data: { user, token },
40
+ });
41
+ return res.json(response);
42
+ };
43
+ }
src/modules/users/auth/services/users.service.ts ADDED
@@ -0,0 +1,24 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import bcrypt from "bcrypt";
2
+ import { ILogin } from "../validation/login.validation";
3
+ import { HttpError } from "src/lib/error-handling/http-error";
4
+ import { JwtHelper } from "src/helpers/jwt.helper";
5
+ import { userModel } from "src/common/models/user.model";
6
+ import { IUserRegister } from "src/common/validations/user-register.validation";
7
+ import { CrudService } from "@lib/services/crud.service";
8
+
9
+ export class UsersAuthService extends CrudService(userModel) {
10
+ async register(createParams: IUserRegister) {
11
+ return this.create(createParams);
12
+ }
13
+
14
+ async login(loginRequest: ILogin) {
15
+ const user = await this.findOneOrFail({ email: loginRequest.email });
16
+ const isPasswordCorrect = await bcrypt.compare(
17
+ loginRequest.password,
18
+ user.password
19
+ );
20
+ if (!isPasswordCorrect) throw new HttpError(401, "Incorrect Password");
21
+ const token = JwtHelper.generateToken({ id: user._id, role: user.role });
22
+ return { user, token };
23
+ }
24
+ }
src/modules/users/auth/validation/login.validation.ts ADDED
@@ -0,0 +1,31 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import joi from "joi";
2
+ import { createSchema } from "src/helpers/create-schema";
3
+
4
+ export interface ILogin {
5
+ email: string;
6
+ password: string;
7
+ }
8
+
9
+ export const loginValidationKeys = {
10
+ email: joi
11
+ .string()
12
+ .required()
13
+ .email({
14
+ minDomainSegments: 2,
15
+ tlds: { allow: ["com", "net", "org", "eg", "io"] },
16
+ })
17
+ .empty()
18
+ .messages({
19
+ "string.email": "please enter a valid email",
20
+ "any.required": "email must be entered",
21
+ "string.empty": "email can not be empty",
22
+ }),
23
+ password: joi.string().empty().min(8).required().messages({
24
+ "string.base": "please enter a valid password",
25
+ "any.required": "password must be entered",
26
+ "string.empty": "password cannot be empty",
27
+ "string.min": "password must be at least 8 characters",
28
+ }),
29
+ };
30
+
31
+ export const loginValidationSchema = createSchema<ILogin>(loginValidationKeys);
tsconfig.json CHANGED
@@ -10,7 +10,6 @@
10
  "experimentalDecorators": true,
11
  "baseUrl": ".",
12
  "paths": {
13
- "src/*": ["src/*"],
14
  "@lib/*": ["src/lib/*"],
15
  "@common/*": ["src/common/*"],
16
  "@configs/*": ["src/configs/*"],
 
10
  "experimentalDecorators": true,
11
  "baseUrl": ".",
12
  "paths": {
 
13
  "@lib/*": ["src/lib/*"],
14
  "@common/*": ["src/common/*"],
15
  "@configs/*": ["src/configs/*"],