Commit b3781a4a authored by Kulpybaev Ilyas's avatar Kulpybaev Ilyas

Урок-94

parent a9467abb
{ {
"name": "node_template", "name": "node_template",
"version": "1.0.0", "version": "2.1.5",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "node_template", "name": "node_template",
"version": "1.0.0", "version": "2.1.5",
"license": "ISC", "license": "ISC",
"dependencies": { "dependencies": {
"bcrypt": "^5.1.1", "bcrypt": "^5.1.1",
...@@ -25,6 +25,7 @@ ...@@ -25,6 +25,7 @@
"typescript": "^5.1.6" "typescript": "^5.1.6"
}, },
"devDependencies": { "devDependencies": {
"@faker-js/faker": "^8.4.1",
"@types/bcrypt": "^5.0.2", "@types/bcrypt": "^5.0.2",
"@types/cors": "^2.8.17", "@types/cors": "^2.8.17",
"@types/express": "^4.17.17", "@types/express": "^4.17.17",
...@@ -39,7 +40,8 @@ ...@@ -39,7 +40,8 @@
"eslint-plugin-prettier": "^5.0.0", "eslint-plugin-prettier": "^5.0.0",
"nodemon": "^3.0.1", "nodemon": "^3.0.1",
"prettier": "^3.0.0", "prettier": "^3.0.0",
"tsconfig-paths": "^4.2.0" "tsconfig-paths": "^4.2.0",
"typeorm-extension": "^3.5.1"
} }
}, },
"node_modules/@aashutoshrathi/word-wrap": { "node_modules/@aashutoshrathi/word-wrap": {
...@@ -180,6 +182,22 @@ ...@@ -180,6 +182,22 @@
"node": "^12.22.0 || ^14.17.0 || >=16.0.0" "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
} }
}, },
"node_modules/@faker-js/faker": {
"version": "8.4.1",
"resolved": "https://registry.npmjs.org/@faker-js/faker/-/faker-8.4.1.tgz",
"integrity": "sha512-XQ3cU+Q8Uqmrbf2e0cIC/QN43sTBSC8KF12u29Mb47tWrt2hAgBXSgpZMj4Ao8Uk0iJcU99QsOCaIL8934obCg==",
"dev": true,
"funding": [
{
"type": "opencollective",
"url": "https://opencollective.com/fakerjs"
}
],
"engines": {
"node": "^14.17.0 || ^16.13.0 || >=18.0.0",
"npm": ">=6.14.13"
}
},
"node_modules/@humanwhocodes/config-array": { "node_modules/@humanwhocodes/config-array": {
"version": "0.11.14", "version": "0.11.14",
"resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.14.tgz", "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.14.tgz",
...@@ -1372,6 +1390,15 @@ ...@@ -1372,6 +1390,15 @@
"typedarray": "^0.0.6" "typedarray": "^0.0.6"
} }
}, },
"node_modules/consola": {
"version": "3.2.3",
"resolved": "https://registry.npmjs.org/consola/-/consola-3.2.3.tgz",
"integrity": "sha512-I5qxpzLv+sJhTVEoLYNcTW+bThDCPsit0vLNKShZx6rLtpilNpmmeTPaeqJb9ZE9dV3DGaeby6Vuhrw38WjeyQ==",
"dev": true,
"engines": {
"node": "^14.18.0 || >=16.10.0"
}
},
"node_modules/console-control-strings": { "node_modules/console-control-strings": {
"version": "1.1.0", "version": "1.1.0",
"resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz",
...@@ -1508,6 +1535,12 @@ ...@@ -1508,6 +1535,12 @@
"node": ">= 0.8" "node": ">= 0.8"
} }
}, },
"node_modules/destr": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/destr/-/destr-2.0.3.tgz",
"integrity": "sha512-2N3BOUU4gYMpTP24s5rF5iP7BDr7uNTCs4ozw3kf/eKfvWSIu93GEBi5m427YoyJoeOzQ5smuu4nNAPGb8idSQ==",
"dev": true
},
"node_modules/destroy": { "node_modules/destroy": {
"version": "1.2.0", "version": "1.2.0",
"resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz",
...@@ -1572,6 +1605,12 @@ ...@@ -1572,6 +1605,12 @@
"resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz",
"integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==" "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA=="
}, },
"node_modules/ebec": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/ebec/-/ebec-2.3.0.tgz",
"integrity": "sha512-bt+0tSL7223VU3PSVi0vtNLZ8pO1AfWolcPPMk2a/a5H+o/ZU9ky0n3A0zhrR4qzJTN61uPsGIO4ShhOukdzxA==",
"dev": true
},
"node_modules/ee-first": { "node_modules/ee-first": {
"version": "1.1.1", "version": "1.1.1",
"resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
...@@ -1590,6 +1629,18 @@ ...@@ -1590,6 +1629,18 @@
"node": ">= 0.8" "node": ">= 0.8"
} }
}, },
"node_modules/envix": {
"version": "1.5.0",
"resolved": "https://registry.npmjs.org/envix/-/envix-1.5.0.tgz",
"integrity": "sha512-IOxTKT+tffjxgvX2O5nq6enbkv6kBQ/QdMy18bZWo0P0rKPvsRp2/EypIPwTvJfnmk3VdOlq/KcRSZCswefM/w==",
"dev": true,
"dependencies": {
"std-env": "^3.7.0"
},
"engines": {
"node": ">=18.0.0"
}
},
"node_modules/es-define-property": { "node_modules/es-define-property": {
"version": "1.0.0", "version": "1.0.0",
"resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz", "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz",
...@@ -2032,6 +2083,15 @@ ...@@ -2032,6 +2083,15 @@
"url": "https://github.com/sponsors/sindresorhus" "url": "https://github.com/sponsors/sindresorhus"
} }
}, },
"node_modules/flat": {
"version": "5.0.2",
"resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz",
"integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==",
"dev": true,
"bin": {
"flat": "cli.js"
}
},
"node_modules/flat-cache": { "node_modules/flat-cache": {
"version": "3.2.0", "version": "3.2.0",
"resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.2.0.tgz", "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.2.0.tgz",
...@@ -2568,6 +2628,15 @@ ...@@ -2568,6 +2628,15 @@
"@pkgjs/parseargs": "^0.11.0" "@pkgjs/parseargs": "^0.11.0"
} }
}, },
"node_modules/jiti": {
"version": "1.21.0",
"resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.0.tgz",
"integrity": "sha512-gFqAIbuKyyso/3G2qhiO2OM6shY6EPP/R0+mkDbyspxKazh8BXDC5FiFsUjlczgdNz/vfra0da2y+aHrusLG/Q==",
"dev": true,
"bin": {
"jiti": "bin/jiti.js"
}
},
"node_modules/js-yaml": { "node_modules/js-yaml": {
"version": "4.1.0", "version": "4.1.0",
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
...@@ -2651,6 +2720,32 @@ ...@@ -2651,6 +2720,32 @@
"url": "https://github.com/sponsors/sindresorhus" "url": "https://github.com/sponsors/sindresorhus"
} }
}, },
"node_modules/locter": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/locter/-/locter-2.1.0.tgz",
"integrity": "sha512-QUPPtb6CQ3hOacDZq2kc6KMzYn9z6r9B2RtFJTBD9nqxmyQJVYnTNZNqY6Z5NcJfwsGEgJLddnfFpofg7EJMDg==",
"dev": true,
"dependencies": {
"destr": "^2.0.3",
"ebec": "^2.3.0",
"fast-glob": "^3.3.2",
"flat": "^5.0.2",
"jiti": "^1.21.0",
"yaml": "^2.4.1"
}
},
"node_modules/locter/node_modules/yaml": {
"version": "2.4.2",
"resolved": "https://registry.npmjs.org/yaml/-/yaml-2.4.2.tgz",
"integrity": "sha512-B3VqDZ+JAg1nZpaEmWtTXUlBneoGx6CPM9b0TENK6aoSu5t73dItudwdgmi6tHlIZZId4dZ9skcAQ2UbcyAeVA==",
"dev": true,
"bin": {
"yaml": "bin.mjs"
},
"engines": {
"node": ">= 14"
}
},
"node_modules/lodash.get": { "node_modules/lodash.get": {
"version": "4.4.2", "version": "4.4.2",
"resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz", "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz",
...@@ -2677,6 +2772,15 @@ ...@@ -2677,6 +2772,15 @@
"resolved": "https://registry.npmjs.org/long/-/long-5.2.3.tgz", "resolved": "https://registry.npmjs.org/long/-/long-5.2.3.tgz",
"integrity": "sha512-lcHwpNoggQTObv5apGNCTdJrO69eHOZMi4BNC+rTLER8iHAqGrUVeLh/irVIM7zTw2bOXA8T6uNPeujwOLg/2Q==" "integrity": "sha512-lcHwpNoggQTObv5apGNCTdJrO69eHOZMi4BNC+rTLER8iHAqGrUVeLh/irVIM7zTw2bOXA8T6uNPeujwOLg/2Q=="
}, },
"node_modules/lower-case": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/lower-case/-/lower-case-2.0.2.tgz",
"integrity": "sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==",
"dev": true,
"dependencies": {
"tslib": "^2.0.3"
}
},
"node_modules/lru-cache": { "node_modules/lru-cache": {
"version": "6.0.0", "version": "6.0.0",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
...@@ -2954,6 +3058,16 @@ ...@@ -2954,6 +3058,16 @@
"node": ">= 0.6" "node": ">= 0.6"
} }
}, },
"node_modules/no-case": {
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/no-case/-/no-case-3.0.4.tgz",
"integrity": "sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==",
"dev": true,
"dependencies": {
"lower-case": "^2.0.2",
"tslib": "^2.0.3"
}
},
"node_modules/node-addon-api": { "node_modules/node-addon-api": {
"version": "5.1.0", "version": "5.1.0",
"resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-5.1.0.tgz", "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-5.1.0.tgz",
...@@ -3210,6 +3324,16 @@ ...@@ -3210,6 +3324,16 @@
"node": ">= 0.8" "node": ">= 0.8"
} }
}, },
"node_modules/pascal-case": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/pascal-case/-/pascal-case-3.1.2.tgz",
"integrity": "sha512-uWlGT3YSnK9x3BQJaOdcZwrnV6hPpd8jFH1/ucpiLRPh/2zCVJKS19E4GvYHvaCcACn3foXZ0cLB9Wrx1KGe5g==",
"dev": true,
"dependencies": {
"no-case": "^3.0.4",
"tslib": "^2.0.3"
}
},
"node_modules/path-exists": { "node_modules/path-exists": {
"version": "4.0.0", "version": "4.0.0",
"resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
...@@ -3394,6 +3518,25 @@ ...@@ -3394,6 +3518,25 @@
"node": ">= 0.6" "node": ">= 0.6"
} }
}, },
"node_modules/rapiq": {
"version": "0.9.0",
"resolved": "https://registry.npmjs.org/rapiq/-/rapiq-0.9.0.tgz",
"integrity": "sha512-k4oT4RarFBrlLMJ49xUTeQpa/us0uU4I70D/UEnK3FWQ4GENzei01rEQAmvPKAIzACo4NMW+YcYJ7EVfSa7EFg==",
"dev": true,
"dependencies": {
"ebec": "^1.1.0",
"smob": "^1.4.0"
}
},
"node_modules/rapiq/node_modules/ebec": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/ebec/-/ebec-1.1.1.tgz",
"integrity": "sha512-JZ1vcvPQtR+8LGbZmbjG21IxLQq/v47iheJqn2F6yB2CgnGfn8ZVg3myHrf3buIZS8UCwQK0jOSIb3oHX7aH8g==",
"dev": true,
"dependencies": {
"smob": "^1.4.0"
}
},
"node_modules/raw-body": { "node_modules/raw-body": {
"version": "2.5.2", "version": "2.5.2",
"resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz",
...@@ -3440,9 +3583,9 @@ ...@@ -3440,9 +3583,9 @@
} }
}, },
"node_modules/reflect-metadata": { "node_modules/reflect-metadata": {
"version": "0.2.1", "version": "0.2.2",
"resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.2.1.tgz", "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.2.2.tgz",
"integrity": "sha512-i5lLI6iw9AU3Uu4szRNPPEkomnkjRTaVt9hy/bn5g/oSzekBSMeLZblcjP74AW0vBabqERLLIrz+gR8QYR54Tw==" "integrity": "sha512-urBwgfrvVP/eAyXx4hluJivBKzuEbSQs9rKWCrCkbSxNv8mxPcUZKeuoF3Uy4mJl3Lwprp6yy5/39VWigZ4K6Q=="
}, },
"node_modules/require-directory": { "node_modules/require-directory": {
"version": "2.1.1", "version": "2.1.1",
...@@ -3712,6 +3855,12 @@ ...@@ -3712,6 +3855,12 @@
"node": ">=8" "node": ">=8"
} }
}, },
"node_modules/smob": {
"version": "1.5.0",
"resolved": "https://registry.npmjs.org/smob/-/smob-1.5.0.tgz",
"integrity": "sha512-g6T+p7QO8npa+/hNx9ohv1E5pVCmWrVCUzUXJyLdMmftX6ER0oiWY/w9knEonLpnOp6b6FenKnMfR8gqwWdwig==",
"dev": true
},
"node_modules/sqlstring": { "node_modules/sqlstring": {
"version": "2.3.3", "version": "2.3.3",
"resolved": "https://registry.npmjs.org/sqlstring/-/sqlstring-2.3.3.tgz", "resolved": "https://registry.npmjs.org/sqlstring/-/sqlstring-2.3.3.tgz",
...@@ -3728,6 +3877,12 @@ ...@@ -3728,6 +3877,12 @@
"node": ">= 0.8" "node": ">= 0.8"
} }
}, },
"node_modules/std-env": {
"version": "3.7.0",
"resolved": "https://registry.npmjs.org/std-env/-/std-env-3.7.0.tgz",
"integrity": "sha512-JPbdCEQLj1w5GilpiHAx3qJvFndqybBysA3qUOnznweH4QbNYUsW/ea8QzSrnh0vNsezMMw5bcVool8lM0gwzg==",
"dev": true
},
"node_modules/streamsearch": { "node_modules/streamsearch": {
"version": "1.1.0", "version": "1.1.0",
"resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz", "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz",
...@@ -4251,6 +4406,33 @@ ...@@ -4251,6 +4406,33 @@
} }
} }
}, },
"node_modules/typeorm-extension": {
"version": "3.5.1",
"resolved": "https://registry.npmjs.org/typeorm-extension/-/typeorm-extension-3.5.1.tgz",
"integrity": "sha512-gykF1eBattSIt+F0+134c+j+AFODYCb/6uIjmFCNzAoc63UW5j7d25JnzDah10viWnDi99BY03UUIxRcQWcR+w==",
"dev": true,
"dependencies": {
"@faker-js/faker": "^8.4.1",
"consola": "^3.2.3",
"envix": "^1.5.0",
"locter": "^2.1.0",
"pascal-case": "^3.1.2",
"rapiq": "^0.9.0",
"reflect-metadata": "^0.2.2",
"smob": "^1.5.0",
"yargs": "^17.7.2"
},
"bin": {
"typeorm-extension": "bin/cli.cjs",
"typeorm-extension-esm": "bin/cli.mjs"
},
"engines": {
"node": ">=14.0.0"
},
"peerDependencies": {
"typeorm": "~0.3.0"
}
},
"node_modules/typeorm/node_modules/glob": { "node_modules/typeorm/node_modules/glob": {
"version": "10.3.12", "version": "10.3.12",
"resolved": "https://registry.npmjs.org/glob/-/glob-10.3.12.tgz", "resolved": "https://registry.npmjs.org/glob/-/glob-10.3.12.tgz",
......
...@@ -7,7 +7,8 @@ ...@@ -7,7 +7,8 @@
"test": "echo \"Error: no test specified\" && exit 1", "test": "echo \"Error: no test specified\" && exit 1",
"dev": "nodemon", "dev": "nodemon",
"lint": "eslint", "lint": "eslint",
"lint:fix": "eslint --fix" "lint:fix": "eslint --fix",
"seed": "node -r tsconfig-paths/register -r ts-node/register src/database/init.seeds.ts"
}, },
"keywords": [], "keywords": [],
"author": "", "author": "",
...@@ -29,6 +30,7 @@ ...@@ -29,6 +30,7 @@
"typescript": "^5.1.6" "typescript": "^5.1.6"
}, },
"devDependencies": { "devDependencies": {
"@faker-js/faker": "^8.4.1",
"@types/bcrypt": "^5.0.2", "@types/bcrypt": "^5.0.2",
"@types/cors": "^2.8.17", "@types/cors": "^2.8.17",
"@types/express": "^4.17.17", "@types/express": "^4.17.17",
...@@ -43,6 +45,7 @@ ...@@ -43,6 +45,7 @@
"eslint-plugin-prettier": "^5.0.0", "eslint-plugin-prettier": "^5.0.0",
"nodemon": "^3.0.1", "nodemon": "^3.0.1",
"prettier": "^3.0.0", "prettier": "^3.0.0",
"tsconfig-paths": "^4.2.0" "tsconfig-paths": "^4.2.0",
"typeorm-extension": "^3.5.1"
} }
} }
import { DataSource } from 'typeorm'; import { DataSource, DataSourceOptions } from 'typeorm';
import { Product } from '@/entities/product.entity'; import { Product } from '@/entities/product.entity';
import { Category } from '@/entities/category.entity'; import { Category } from '@/entities/category.entity';
import { User } from '@/entities/user.entity'; import { User } from '@/entities/user.entity';
import { SeederOptions } from 'typeorm-extension';
import { userFactory } from '@/database/factories/user.factory';
import MainSeeder from '@/database/seeds/main.seeder';
import { categoriesFactory } from '@/database/factories/category.factory';
import { productFactory } from '@/database/factories/product.factory';
export const appDataSource = new DataSource({ const options: DataSourceOptions & SeederOptions = {
type: 'mysql', type: 'mysql',
host: 'localhost', host: 'localhost',
port: 3306, port: 3306,
...@@ -13,4 +18,7 @@ export const appDataSource = new DataSource({ ...@@ -13,4 +18,7 @@ export const appDataSource = new DataSource({
synchronize: true, synchronize: true,
logging: true, logging: true,
entities: [Product, Category, User], entities: [Product, Category, User],
}); seeds: [MainSeeder],
factories: [userFactory, categoriesFactory, productFactory],
};
export const appDataSource = new DataSource(options);
...@@ -5,6 +5,7 @@ import { RegisterUserDto } from '@/dto/register-user.dto'; ...@@ -5,6 +5,7 @@ import { RegisterUserDto } from '@/dto/register-user.dto';
import { SignInUserDto } from '@/dto/sign-in-user.dto'; import { SignInUserDto } from '@/dto/sign-in-user.dto';
import { validate } from 'class-validator'; import { validate } from 'class-validator';
import { formatErrors } from '@/helpers/formatErrors'; import { formatErrors } from '@/helpers/formatErrors';
import { IRequestWithUser } from '@/interfaces/IRequestWithUser';
export class AuthController { export class AuthController {
private service: AuthService; private service: AuthService;
...@@ -60,4 +61,15 @@ export class AuthController { ...@@ -60,4 +61,15 @@ export class AuthController {
return res.status(500).send({ error: { message: 'Internal server error' } }); return res.status(500).send({ error: { message: 'Internal server error' } });
} }
}; };
logout: RequestHandler = async (req: IRequestWithUser, res) => {
if (!req.user?.token) return res.send({ message: 'success' });
try {
const { token } = req.user;
await this.service.logout(token);
} catch (e) {
return res.status(500).send({ error: { message: 'Internal server error' } });
}
return res.send({ message: 'success' });
};
} }
import { setSeederFactory } from 'typeorm-extension';
import { Category } from '@/entities/category.entity';
import { Faker } from '@faker-js/faker';
export const categoriesFactory = setSeederFactory(Category, (faker: Faker) => {
const category = new Category();
category.title = faker.commerce.department();
category.description = faker.lorem.sentence();
return category;
});
import { setSeederFactory } from 'typeorm-extension';
import { Product } from '@/entities/product.entity';
import { Faker } from '@faker-js/faker';
export const productFactory = setSeederFactory(Product, (faker: Faker) => {
const product = new Product();
product.title = faker.commerce.productName();
product.price = faker.number.int({ min: 100, max: 2000 });
product.description = faker.lorem.sentence();
return product;
});
import { Faker } from '@faker-js/faker';
import { setSeederFactory } from 'typeorm-extension';
import { User } from '../../entities/user.entity';
export const userFactory = setSeederFactory(User, (faker: Faker) => {
const user = new User();
user.username = faker.internet.userName();
user.displayName = faker.person.firstName();
user.password = 'password';
user.generateToken();
return user;
});
import { runSeeders } from 'typeorm-extension';
import { appDataSource } from '@/config/dataSource';
appDataSource.initialize().then(async () => {
await appDataSource.synchronize(true);
await runSeeders(appDataSource);
process.exit();
});
import { Seeder, SeederFactoryManager } from 'typeorm-extension';
import { DataSource } from 'typeorm';
import { User } from '@/entities/user.entity';
import { Category } from '@/entities/category.entity';
import { Product } from '@/entities/product.entity';
import { faker } from '@faker-js/faker';
export default class MainSeeder implements Seeder {
async run(dataSource: DataSource, factoryManager: SeederFactoryManager): Promise<void> {
const userFactory = factoryManager.get(User);
const categoryFactory = factoryManager.get(Category);
const productFactory = factoryManager.get(Product);
await userFactory.saveMany(7);
await userFactory.save({ displayName: 'User', username: 'user', password: '123456' });
await userFactory.save({ displayName: 'Admin', username: 'admin', password: '123456', role: 'admin' });
const categories = await categoryFactory.saveMany(3);
await productFactory.saveMany(4, { category: faker.helpers.arrayElement(categories) });
await productFactory.saveMany(4, { category: faker.helpers.arrayElement(categories) });
await productFactory.saveMany(4, { category: faker.helpers.arrayElement(categories) });
}
}
import { Seeder, SeederFactoryManager } from 'typeorm-extension';
import { DataSource } from 'typeorm';
import { User } from '@/entities/user.entity';
export default class UserSeeder implements Seeder {
async run(dataSource: DataSource, factoryManager: SeederFactoryManager): Promise<void> {
const userFactory = factoryManager.get(User);
await userFactory.saveMany(10);
}
}
import { Column, Entity, PrimaryGeneratedColumn, Unique } from 'typeorm'; import { BeforeInsert, Column, Entity, PrimaryGeneratedColumn, Unique } from 'typeorm';
import bcrypt from 'bcrypt'; import bcrypt from 'bcrypt';
@Entity('users') @Entity('users')
...@@ -19,6 +19,9 @@ export class User { ...@@ -19,6 +19,9 @@ export class User {
@Column() @Column()
token!: string; token!: string;
@Column({ default: 'user' })
role!: 'user' | 'admin';
async comparePassword(password: string): Promise<boolean> { async comparePassword(password: string): Promise<boolean> {
if (this.password) return await bcrypt.compare(password, this.password); if (this.password) return await bcrypt.compare(password, this.password);
return false; return false;
...@@ -27,4 +30,14 @@ export class User { ...@@ -27,4 +30,14 @@ export class User {
generateToken() { generateToken() {
this.token = crypto.randomUUID(); this.token = crypto.randomUUID();
} }
@BeforeInsert()
async hashPassword() {
const SALT_WORK_FACTORY = 10;
if (this.password) {
const salt = await bcrypt.genSalt(SALT_WORK_FACTORY);
const hashedPassword = await bcrypt.hash(this.password, salt);
this.password = hashedPassword;
}
}
} }
import { IUser } from '@/interfaces/IUser.interface';
import { Request } from 'express';
export interface IRequestWithUser extends Request {
user?: IUser;
}
...@@ -3,4 +3,6 @@ export interface IUser { ...@@ -3,4 +3,6 @@ export interface IUser {
username: string; username: string;
password?: string; password?: string;
displayName: string; displayName: string;
token?: string;
role: 'user' | 'admin';
} }
import { NextFunction, Request, Response } from 'express';
import { AuthService } from '@/services/auth.service';
import { IRequestWithUser } from '@/interfaces/IRequestWithUser';
import { IUser } from '@/interfaces/IUser.interface';
const service = new AuthService();
export const authValidate = async (req: Request, res: Response, next: NextFunction) => {
const token = req.header('Authorization');
if (!token) {
return res.status(401).send({ error: { message: 'Token not passed' } });
}
const user = await service.getUserByToken(token);
if (!user) {
return res.status(401).send({ error: { message: 'Invalid token' } });
}
(req as IRequestWithUser).user = user as unknown as IUser;
next();
return;
};
import { IRequestWithUser } from '@/interfaces/IRequestWithUser';
import { NextFunction, Response } from 'express';
export function checkRole(...allowedRoles: string[]) {
return (req: IRequestWithUser, res: Response, next: NextFunction) => {
const user = req.user;
if (user && allowedRoles.includes(user.role)) {
next();
} else {
res.status(403).send({ error: 'Permission denied' });
}
};
}
...@@ -4,9 +4,7 @@ import { appDataSource } from '@/config/dataSource'; ...@@ -4,9 +4,7 @@ import { appDataSource } from '@/config/dataSource';
import { SignInUserDto } from '@/dto/sign-in-user.dto'; import { SignInUserDto } from '@/dto/sign-in-user.dto';
import { RegisterUserDto } from '@/dto/register-user.dto'; import { RegisterUserDto } from '@/dto/register-user.dto';
import { IUser } from '@/interfaces/IUser.interface'; import { IUser } from '@/interfaces/IUser.interface';
import bcrypt from 'bcrypt';
const SALT_WORK_FACTORY = 10;
export class UserRepository extends Repository<User> { export class UserRepository extends Repository<User> {
constructor() { constructor() {
super(User, appDataSource.createEntityManager()); super(User, appDataSource.createEntityManager());
...@@ -14,7 +12,7 @@ export class UserRepository extends Repository<User> { ...@@ -14,7 +12,7 @@ export class UserRepository extends Repository<User> {
async signIn(signInUserDto: SignInUserDto): Promise<User> { async signIn(signInUserDto: SignInUserDto): Promise<User> {
const user = await this.findOne({ const user = await this.findOne({
select: ['username', 'displayName', 'id', 'password'], select: ['username', 'displayName', 'id', 'password', 'role'],
where: { username: signInUserDto.username }, where: { username: signInUserDto.username },
}); });
...@@ -30,9 +28,7 @@ export class UserRepository extends Repository<User> { ...@@ -30,9 +28,7 @@ export class UserRepository extends Repository<User> {
} }
async register(registerUserDto: RegisterUserDto): Promise<IUser> { async register(registerUserDto: RegisterUserDto): Promise<IUser> {
const salt = await bcrypt.genSalt(SALT_WORK_FACTORY);
const userData = await this.create(registerUserDto); const userData = await this.create(registerUserDto);
userData.password = await bcrypt.hash(registerUserDto.password, salt);
userData.generateToken(); userData.generateToken();
const user = await this.save(userData); const user = await this.save(userData);
delete user.password; delete user.password;
...@@ -40,7 +36,15 @@ export class UserRepository extends Repository<User> { ...@@ -40,7 +36,15 @@ export class UserRepository extends Repository<User> {
return user; return user;
} }
async getUserByToken(token: string): Promise<IUser | null> { async getUserByToken(token: string): Promise<User | null> {
return await this.findOneBy({ token }); return await this.findOneBy({ token });
} }
async clearToken(token: string) {
const user = await this.getUserByToken(token);
if (user) {
user.generateToken();
await this.save(user);
}
}
} }
import { IRoute } from '@/interfaces/IRoute.interface'; import { IRoute } from '@/interfaces/IRoute.interface';
import { Router } from 'express'; import { Router } from 'express';
import { AuthController } from '@/controllers/auth.controller'; import { AuthController } from '@/controllers/auth.controller';
import { authValidate } from '@/middlewares/authValidate';
export class AuthRoute implements IRoute { export class AuthRoute implements IRoute {
public path = '/auth'; public path = '/auth';
...@@ -16,5 +17,6 @@ export class AuthRoute implements IRoute { ...@@ -16,5 +17,6 @@ export class AuthRoute implements IRoute {
this.router.post('/register', this.controller.register); this.router.post('/register', this.controller.register);
this.router.post('/sign-in', this.controller.signIn); this.router.post('/sign-in', this.controller.signIn);
this.router.get('/secret', this.controller.secret); this.router.get('/secret', this.controller.secret);
this.router.delete('/logout', authValidate, this.controller.logout);
} }
} }
import { IRoute } from '@/interfaces/IRoute.interface'; import { IRoute } from '@/interfaces/IRoute.interface';
import { Router } from 'express'; import { Router } from 'express';
import { CategoryController } from '@/controllers/category.controller'; import { CategoryController } from '@/controllers/category.controller';
import { authValidate } from '@/middlewares/authValidate';
import { checkRole } from '@/middlewares/checkRole';
export class CategoryRoute implements IRoute { export class CategoryRoute implements IRoute {
public path = '/categories'; public path = '/categories';
...@@ -14,6 +16,6 @@ export class CategoryRoute implements IRoute { ...@@ -14,6 +16,6 @@ export class CategoryRoute implements IRoute {
private init() { private init() {
this.router.get('/', this.controller.getCategories); this.router.get('/', this.controller.getCategories);
this.router.post('/', this.controller.createCategory); this.router.post('/', authValidate, checkRole('admin'), this.controller.createCategory);
} }
} }
...@@ -2,6 +2,8 @@ import { Router } from 'express'; ...@@ -2,6 +2,8 @@ import { Router } from 'express';
import { IRoute } from '@/interfaces/IRoute.interface'; import { IRoute } from '@/interfaces/IRoute.interface';
import { ProductController } from '@/controllers/product.controller'; import { ProductController } from '@/controllers/product.controller';
import { upload } from '@/middlewares/upload'; import { upload } from '@/middlewares/upload';
import { authValidate } from '@/middlewares/authValidate';
import { checkRole } from '@/middlewares/checkRole';
export class ProductRoute implements IRoute { export class ProductRoute implements IRoute {
public path = '/products'; public path = '/products';
...@@ -16,6 +18,6 @@ export class ProductRoute implements IRoute { ...@@ -16,6 +18,6 @@ export class ProductRoute implements IRoute {
private init() { private init() {
this.router.get('/', this.controller.getProducts); this.router.get('/', this.controller.getProducts);
this.router.get('/:id', this.controller.getProduct); this.router.get('/:id', this.controller.getProduct);
this.router.post('/', upload.single('image'), this.controller.createProduct); this.router.post('/', authValidate, checkRole('admin'), upload.single('image'), this.controller.createProduct);
} }
} }
...@@ -21,4 +21,8 @@ export class AuthService { ...@@ -21,4 +21,8 @@ export class AuthService {
getUserByToken = async (token: string): Promise<IUser | null> => { getUserByToken = async (token: string): Promise<IUser | null> => {
return await this.repository.getUserByToken(token); return await this.repository.getUserByToken(token);
}; };
logout = async (token: string) => {
await this.repository.clearToken(token);
};
} }
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment