Spaces:
Running
Running
Commit
·
cbdd38a
1
Parent(s):
d8b75ae
feat: exercises seeders
Browse files- package-lock.json +15 -1
- package.json +1 -0
- src/common/enums/exercise-type.enum.ts +4 -0
- src/common/models/exercise.model.ts +12 -5
- src/common/serializers/exercise.serialization.ts +4 -0
- src/common/serializers/exercisePopulate.serialization.ts +4 -0
- src/resources/exercises.csv +0 -0
- src/seeder/helpers/db-store.ts +12 -2
- src/seeder/helpers/load-exercises-dataset.ts +68 -0
- src/seeder/seed.ts +17 -1
- src/seeder/seeders/{admins.seeder.ts → 1-admins.seeder.ts} +0 -0
- src/seeder/seeders/{users.seeder.ts → 2-users.seeder.ts} +0 -0
- src/seeder/seeders/3-muscles.seeder.ts +9 -0
- src/seeder/seeders/4-equipments.seeder.ts +8 -0
- src/seeder/seeders/5-exercises.seeder.ts +47 -0
package-lock.json
CHANGED
@@ -14,6 +14,7 @@
|
|
14 |
"bcrypt": "^5.1.1",
|
15 |
"class-transformer": "^0.5.1",
|
16 |
"cors": "^2.8.5",
|
|
|
17 |
"dotenv": "^16.3.1",
|
18 |
"express": "^4.18.2",
|
19 |
"express-joi-validation": "^5.0.1",
|
@@ -799,6 +800,20 @@
|
|
799 |
"node": ">= 8"
|
800 |
}
|
801 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
802 |
"node_modules/debug": {
|
803 |
"version": "4.3.4",
|
804 |
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
|
@@ -1800,7 +1815,6 @@
|
|
1800 |
"version": "1.2.8",
|
1801 |
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz",
|
1802 |
"integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==",
|
1803 |
-
"dev": true,
|
1804 |
"funding": {
|
1805 |
"url": "https://github.com/sponsors/ljharb"
|
1806 |
}
|
|
|
14 |
"bcrypt": "^5.1.1",
|
15 |
"class-transformer": "^0.5.1",
|
16 |
"cors": "^2.8.5",
|
17 |
+
"csv-parser": "^3.0.0",
|
18 |
"dotenv": "^16.3.1",
|
19 |
"express": "^4.18.2",
|
20 |
"express-joi-validation": "^5.0.1",
|
|
|
800 |
"node": ">= 8"
|
801 |
}
|
802 |
},
|
803 |
+
"node_modules/csv-parser": {
|
804 |
+
"version": "3.0.0",
|
805 |
+
"resolved": "https://registry.npmjs.org/csv-parser/-/csv-parser-3.0.0.tgz",
|
806 |
+
"integrity": "sha512-s6OYSXAK3IdKqYO33y09jhypG/bSDHPuyCme/IdEHfWpLf/jKcpitVFyOC6UemgGk8v7Q5u2XE0vvwmanxhGlQ==",
|
807 |
+
"dependencies": {
|
808 |
+
"minimist": "^1.2.0"
|
809 |
+
},
|
810 |
+
"bin": {
|
811 |
+
"csv-parser": "bin/csv-parser"
|
812 |
+
},
|
813 |
+
"engines": {
|
814 |
+
"node": ">= 10"
|
815 |
+
}
|
816 |
+
},
|
817 |
"node_modules/debug": {
|
818 |
"version": "4.3.4",
|
819 |
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
|
|
|
1815 |
"version": "1.2.8",
|
1816 |
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz",
|
1817 |
"integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==",
|
|
|
1818 |
"funding": {
|
1819 |
"url": "https://github.com/sponsors/ljharb"
|
1820 |
}
|
package.json
CHANGED
@@ -28,6 +28,7 @@
|
|
28 |
"bcrypt": "^5.1.1",
|
29 |
"class-transformer": "^0.5.1",
|
30 |
"cors": "^2.8.5",
|
|
|
31 |
"dotenv": "^16.3.1",
|
32 |
"express": "^4.18.2",
|
33 |
"express-joi-validation": "^5.0.1",
|
|
|
28 |
"bcrypt": "^5.1.1",
|
29 |
"class-transformer": "^0.5.1",
|
30 |
"cors": "^2.8.5",
|
31 |
+
"csv-parser": "^3.0.0",
|
32 |
"dotenv": "^16.3.1",
|
33 |
"express": "^4.18.2",
|
34 |
"express-joi-validation": "^5.0.1",
|
src/common/enums/exercise-type.enum.ts
ADDED
@@ -0,0 +1,4 @@
|
|
|
|
|
|
|
|
|
|
|
1 |
+
export enum ExerciseType {
|
2 |
+
DURATION = 'duration',
|
3 |
+
WEIGHT = 'weight',
|
4 |
+
}
|
src/common/models/exercise.model.ts
CHANGED
@@ -1,3 +1,4 @@
|
|
|
|
1 |
import mongoose, { ObjectId } from "mongoose";
|
2 |
const { Schema } = mongoose;
|
3 |
|
@@ -5,12 +6,13 @@ export interface IExercise {
|
|
5 |
name: string;
|
6 |
category: string;
|
7 |
duration?: number | null;
|
|
|
8 |
expectedDurationRange: {
|
9 |
min: number;
|
10 |
max: number;
|
11 |
};
|
12 |
-
reps
|
13 |
-
sets
|
14 |
instructions: string;
|
15 |
benefits: string;
|
16 |
targetMuscles: {
|
@@ -25,16 +27,21 @@ export interface IExercise {
|
|
25 |
};
|
26 |
}
|
27 |
|
28 |
-
const exerciseSchema = new Schema({
|
29 |
name: { type: String, required: true, unique: true, dropDups: true },
|
30 |
category: { type: String, required: true },
|
31 |
duration: { type: Number, required: false },
|
|
|
|
|
|
|
|
|
|
|
32 |
expectedDurationRange: {
|
33 |
min: { type: Number, required: true },
|
34 |
max: { type: Number, required: true },
|
35 |
},
|
36 |
-
reps: { type: Number, required:
|
37 |
-
sets: { type: Number, required:
|
38 |
instructions: { type: String, required: true },
|
39 |
benefits: { type: String, required: true },
|
40 |
targetMuscles: {
|
|
|
1 |
+
import { ExerciseType } from "@common/enums/exercise-type.enum";
|
2 |
import mongoose, { ObjectId } from "mongoose";
|
3 |
const { Schema } = mongoose;
|
4 |
|
|
|
6 |
name: string;
|
7 |
category: string;
|
8 |
duration?: number | null;
|
9 |
+
exerciseType: ExerciseType;
|
10 |
expectedDurationRange: {
|
11 |
min: number;
|
12 |
max: number;
|
13 |
};
|
14 |
+
reps?: number;
|
15 |
+
sets?: number;
|
16 |
instructions: string;
|
17 |
benefits: string;
|
18 |
targetMuscles: {
|
|
|
27 |
};
|
28 |
}
|
29 |
|
30 |
+
const exerciseSchema = new Schema<IExercise>({
|
31 |
name: { type: String, required: true, unique: true, dropDups: true },
|
32 |
category: { type: String, required: true },
|
33 |
duration: { type: Number, required: false },
|
34 |
+
exerciseType: {
|
35 |
+
type: String,
|
36 |
+
required: false,
|
37 |
+
enum: ExerciseType
|
38 |
+
},
|
39 |
expectedDurationRange: {
|
40 |
min: { type: Number, required: true },
|
41 |
max: { type: Number, required: true },
|
42 |
},
|
43 |
+
reps: { type: Number, required: false },
|
44 |
+
sets: { type: Number, required: false },
|
45 |
instructions: { type: String, required: true },
|
46 |
benefits: { type: String, required: true },
|
47 |
targetMuscles: {
|
src/common/serializers/exercise.serialization.ts
CHANGED
@@ -45,6 +45,10 @@ export class ExerciseSerialization {
|
|
45 |
@SwaggerResponseProperty({ type: "string" })
|
46 |
category: string;
|
47 |
|
|
|
|
|
|
|
|
|
48 |
@Expose()
|
49 |
@SwaggerResponseProperty({ type: "number" })
|
50 |
duration: number | null;
|
|
|
45 |
@SwaggerResponseProperty({ type: "string" })
|
46 |
category: string;
|
47 |
|
48 |
+
@Expose()
|
49 |
+
@SwaggerResponseProperty({ type: "string" })
|
50 |
+
exerciseType: string;
|
51 |
+
|
52 |
@Expose()
|
53 |
@SwaggerResponseProperty({ type: "number" })
|
54 |
duration: number | null;
|
src/common/serializers/exercisePopulate.serialization.ts
CHANGED
@@ -47,6 +47,10 @@ export class ExercisePopulateSerialization {
|
|
47 |
@SwaggerResponseProperty({ type: "string" })
|
48 |
category: string;
|
49 |
|
|
|
|
|
|
|
|
|
50 |
@Expose()
|
51 |
@SwaggerResponseProperty({ type: "number" })
|
52 |
duration: number | null;
|
|
|
47 |
@SwaggerResponseProperty({ type: "string" })
|
48 |
category: string;
|
49 |
|
50 |
+
@Expose()
|
51 |
+
@SwaggerResponseProperty({ type: "string" })
|
52 |
+
exerciseType: string;
|
53 |
+
|
54 |
@Expose()
|
55 |
@SwaggerResponseProperty({ type: "number" })
|
56 |
duration: number | null;
|
src/resources/exercises.csv
ADDED
The diff for this file is too large to render.
See raw diff
|
|
src/seeder/helpers/db-store.ts
CHANGED
@@ -1,3 +1,13 @@
|
|
1 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2 |
dbConnected: false,
|
3 |
-
|
|
|
|
|
|
|
|
1 |
+
import { IExerciseCSV } from './load-exercises-dataset';
|
2 |
+
|
3 |
+
export const dbStore: {
|
4 |
+
dbConnected: boolean;
|
5 |
+
excerisesDataset: IExerciseCSV[];
|
6 |
+
musclesDataset: IExerciseCSV['target'][];
|
7 |
+
equipmentsDataset: IExerciseCSV['equipment'][];
|
8 |
+
} = {
|
9 |
dbConnected: false,
|
10 |
+
excerisesDataset: [],
|
11 |
+
musclesDataset: [],
|
12 |
+
equipmentsDataset: [],
|
13 |
+
}
|
src/seeder/helpers/load-exercises-dataset.ts
ADDED
@@ -0,0 +1,68 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import { ExerciseType } from "@common/enums/exercise-type.enum";
|
2 |
+
import * as path from "path";
|
3 |
+
const csv = require('csv-parser')
|
4 |
+
const fs = require('fs')
|
5 |
+
|
6 |
+
type EBodyPart = "waist" | "upper legs" | "upper arms" | "lower legs" | "chest" | "lower arms" | "back" | "neck" | "shoulders" | "cardio";
|
7 |
+
type EType = ExerciseType;
|
8 |
+
type EGoal = "Gain Muscle" | "Get Fitter & Lose Weight";
|
9 |
+
type ELevel = "Beginner" | "Intermediate" | "Advanced";
|
10 |
+
type EEquipment = "band" | "barbell" | "kettlebell" | "dumbbell" | "weighted" | "cable" | "Machine" | "body weight" | "medicine ball" | "Exercise Ball" | "Foam Roll" | "ez barbell" | "leverage machine" | "assisted" | "rope" | "sled machine" | "upper body ergometer" | "olympic barbell" | "bosu ball" | "skierg machine" | "hammer" | "smith machine" | "wheel roller" | "stationary bike" | "tire" | "trap bar" | "elliptical machine" | "stepmill machine";
|
11 |
+
type ETarget = "abs" | "adductors" | "abductors" | "biceps" | "calves" | "pectorals" | "forearms" | "glutes" | "hamstrings" | "lats" | "Lower Back" | "Middle Back" | "traps" | "levator scapulae" | "quads" | "delts" | "triceps" | "cardiovascular system" | "spine" | "upper back" | "serratus anterior";
|
12 |
+
|
13 |
+
export interface IExerciseCSV {
|
14 |
+
name: string;
|
15 |
+
target: ETarget;
|
16 |
+
equipment: EEquipment;
|
17 |
+
level: ELevel;
|
18 |
+
gym: boolean;
|
19 |
+
home: boolean;
|
20 |
+
goal: EGoal;
|
21 |
+
bodyPart: EBodyPart;
|
22 |
+
type: EType;
|
23 |
+
sets: number;
|
24 |
+
}
|
25 |
+
|
26 |
+
|
27 |
+
const filePath = path.join(__dirname, '../../resources/exercises.csv');
|
28 |
+
|
29 |
+
export const loadExercisesDataset = async (): Promise<IExerciseCSV[]> => {
|
30 |
+
let results: IExerciseCSV[] = [];
|
31 |
+
|
32 |
+
await new Promise((resolve) => {
|
33 |
+
fs
|
34 |
+
.createReadStream(filePath)
|
35 |
+
.pipe(csv())
|
36 |
+
.on('data', (data: any) => {
|
37 |
+
// skip empty rows
|
38 |
+
if (Object.values(data).some((v: any) => !v)) {
|
39 |
+
return;
|
40 |
+
}
|
41 |
+
if(!data.type){
|
42 |
+
console.log("type is null", data);
|
43 |
+
}
|
44 |
+
|
45 |
+
results.push({
|
46 |
+
...data,
|
47 |
+
gym: data.gym == '1',
|
48 |
+
home: data.home == '1',
|
49 |
+
sets: parseInt(data.sets)
|
50 |
+
})
|
51 |
+
})
|
52 |
+
.on('end', () => {
|
53 |
+
resolve(results);
|
54 |
+
});
|
55 |
+
});
|
56 |
+
|
57 |
+
// remove duplicates by name
|
58 |
+
const uniqueNames = new Set(results.map(e => e.name));
|
59 |
+
results = results.filter(e => {
|
60 |
+
const found = uniqueNames.has(e.name)
|
61 |
+
uniqueNames.delete(e.name);
|
62 |
+
return found;
|
63 |
+
});
|
64 |
+
|
65 |
+
console.log(`Loaded ${results.length} exercises from dataset`);
|
66 |
+
|
67 |
+
return results;
|
68 |
+
}
|
src/seeder/seed.ts
CHANGED
@@ -9,6 +9,18 @@
|
|
9 |
|
10 |
import * as glob from "glob";
|
11 |
import path from "path";
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
12 |
|
13 |
const main = async () => {
|
14 |
// get cli arguments
|
@@ -33,7 +45,11 @@ const main = async () => {
|
|
33 |
}
|
34 |
|
35 |
return seederNames.includes(path.basename(file));
|
36 |
-
})
|
|
|
|
|
|
|
|
|
37 |
|
38 |
// run all seeders
|
39 |
let count = 0;
|
|
|
9 |
|
10 |
import * as glob from "glob";
|
11 |
import path from "path";
|
12 |
+
import { loadExercisesDataset } from "./helpers/load-exercises-dataset";
|
13 |
+
import { dbStore } from "./helpers/db-store";
|
14 |
+
|
15 |
+
const loadDatasets = async() => {
|
16 |
+
const exercisesDataset = await loadExercisesDataset();
|
17 |
+
const musclesDataset = Array.from(new Set(exercisesDataset.map((exercise) => exercise.target)));
|
18 |
+
const equipmentsDataset = Array.from(new Set(exercisesDataset.map((exercise) => exercise.equipment)))
|
19 |
+
|
20 |
+
dbStore.excerisesDataset = exercisesDataset;
|
21 |
+
dbStore.musclesDataset = musclesDataset;
|
22 |
+
dbStore.equipmentsDataset = equipmentsDataset;
|
23 |
+
}
|
24 |
|
25 |
const main = async () => {
|
26 |
// get cli arguments
|
|
|
45 |
}
|
46 |
|
47 |
return seederNames.includes(path.basename(file));
|
48 |
+
})
|
49 |
+
.sort();
|
50 |
+
|
51 |
+
// load datasets
|
52 |
+
await loadDatasets();
|
53 |
|
54 |
// run all seeders
|
55 |
let count = 0;
|
src/seeder/seeders/{admins.seeder.ts → 1-admins.seeder.ts}
RENAMED
File without changes
|
src/seeder/seeders/{users.seeder.ts → 2-users.seeder.ts}
RENAMED
File without changes
|
src/seeder/seeders/3-muscles.seeder.ts
ADDED
@@ -0,0 +1,9 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import { Muscle } from "@common/models/muscle.model";
|
2 |
+
import { dbStore } from "seeder/helpers/db-store";
|
3 |
+
import { seederWrapper } from "seeder/helpers/seeder-wrapper";
|
4 |
+
|
5 |
+
export default seederWrapper(Muscle, async () => {
|
6 |
+
await Promise.all(dbStore.musclesDataset.map(async function (m) {
|
7 |
+
return Muscle.create({ name: m, image: `https://placehold.co/600x400`})
|
8 |
+
}))
|
9 |
+
})
|
src/seeder/seeders/4-equipments.seeder.ts
ADDED
@@ -0,0 +1,8 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import { Equipment } from "@common/models/equipment.model";
|
2 |
+
import { dbStore } from "seeder/helpers/db-store";
|
3 |
+
import { seederWrapper } from "seeder/helpers/seeder-wrapper";
|
4 |
+
|
5 |
+
export default seederWrapper(Equipment, async () => { await Promise.all(dbStore.equipmentsDataset.map(async function (e) {
|
6 |
+
return Equipment.create({ name: e, image: `https://placehold.co/600x400`})
|
7 |
+
}))
|
8 |
+
})
|
src/seeder/seeders/5-exercises.seeder.ts
ADDED
@@ -0,0 +1,47 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import { ExerciseType } from "@common/enums/exercise-type.enum";
|
2 |
+
import { Equipment } from "@common/models/equipment.model";
|
3 |
+
import { Exercise, IExercise } from "@common/models/exercise.model";
|
4 |
+
import { Muscle } from "@common/models/muscle.model";
|
5 |
+
import { dbStore } from "seeder/helpers/db-store";
|
6 |
+
import { IExerciseCSV } from "seeder/helpers/load-exercises-dataset";
|
7 |
+
import { seederWrapper } from "seeder/helpers/seeder-wrapper";
|
8 |
+
|
9 |
+
export default seederWrapper(Exercise, async () => {
|
10 |
+
console.log('preparing exercises data... (this may take a while)');
|
11 |
+
const data = await Promise.all(dbStore.excerisesDataset.map(async function (e: IExerciseCSV) {
|
12 |
+
return {
|
13 |
+
name: e.name,
|
14 |
+
category: "unknown",
|
15 |
+
exerciseType: e.type,
|
16 |
+
...(
|
17 |
+
e.type === ExerciseType.WEIGHT &&
|
18 |
+
{
|
19 |
+
reps: Math.floor(Math.random() * 10),
|
20 |
+
sets: e.sets,
|
21 |
+
}
|
22 |
+
||
|
23 |
+
{
|
24 |
+
duration: Math.floor(Math.random() * 100),
|
25 |
+
}
|
26 |
+
),
|
27 |
+
expectedDurationRange: {
|
28 |
+
min: Math.floor(Math.random() * 10),
|
29 |
+
max: 10 + Math.floor(Math.random() * 10),
|
30 |
+
},
|
31 |
+
instructions: "Do this exercise",
|
32 |
+
benefits: "You will get stronger",
|
33 |
+
targetMuscles: {
|
34 |
+
primary: (await Muscle.findOne({ name: e.target }).exec())._id,
|
35 |
+
secondary: (await Muscle.findOne({ name: e.target }).exec())._id,
|
36 |
+
},
|
37 |
+
equipments: [(await Equipment.findOne({name: e.equipment}).exec())._id],
|
38 |
+
coverImage: "https://placehold.co/600x400",
|
39 |
+
media: {
|
40 |
+
type: 'image',
|
41 |
+
url: "https://placehold.co/600x400",
|
42 |
+
}
|
43 |
+
} satisfies IExercise;
|
44 |
+
}))
|
45 |
+
console.log('inserting exercises...');
|
46 |
+
await Exercise.insertMany(data);
|
47 |
+
})
|