From 834447da6cb4d19cc0f58ae292c667e8de2f4e16 Mon Sep 17 00:00:00 2001 From: MohammadHMazarei Date: Mon, 23 Dec 2024 22:13:06 +0330 Subject: [PATCH 01/55] ADD: bcryptjs jsonwebtoken validator packages --- package-lock.json | 17 ++++++++++++++++- package.json | 4 +++- 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index d450703..fa78f77 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,6 +9,7 @@ "version": "1.0.0", "license": "ISC", "dependencies": { + "bcryptjs": "^2.4.3", "body-parser": "^1.20.3", "chalk": "^5.3.0", "compression": "^1.7.5", @@ -17,7 +18,8 @@ "dotenv": "^16.4.7", "express": "^4.21.2", "jsonwebtoken": "^9.0.2", - "mongoose": "^8.9.0" + "mongoose": "^8.9.0", + "validator": "^13.12.0" }, "devDependencies": { "nodemon": "^3.1.9" @@ -88,6 +90,11 @@ "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", "dev": true }, + "node_modules/bcryptjs": { + "version": "2.4.3", + "resolved": "https://registry.npmjs.org/bcryptjs/-/bcryptjs-2.4.3.tgz", + "integrity": "sha512-V/Hy/X9Vt7f3BbPJEi8BdVFMByHi+jNXrYkW3huaybV/kQ0KJg0Y6PkEMbn+zeT+i+SiKZ/HMqJGIIt4LZDqNQ==" + }, "node_modules/binary-extensions": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", @@ -1494,6 +1501,14 @@ "node": ">= 0.4.0" } }, + "node_modules/validator": { + "version": "13.12.0", + "resolved": "https://registry.npmjs.org/validator/-/validator-13.12.0.tgz", + "integrity": "sha512-c1Q0mCiPlgdTVVVIJIrBuxNicYE+t/7oKeI9MWLj3fh/uq2Pxh/3eeWbVZ4OcGW1TUf53At0njHw5SMdA3tmMg==", + "engines": { + "node": ">= 0.10" + } + }, "node_modules/vary": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", diff --git a/package.json b/package.json index 3769713..c333c81 100644 --- a/package.json +++ b/package.json @@ -11,6 +11,7 @@ "license": "ISC", "description": "", "dependencies": { + "bcryptjs": "^2.4.3", "body-parser": "^1.20.3", "chalk": "^5.3.0", "compression": "^1.7.5", @@ -19,7 +20,8 @@ "dotenv": "^16.4.7", "express": "^4.21.2", "jsonwebtoken": "^9.0.2", - "mongoose": "^8.9.0" + "mongoose": "^8.9.0", + "validator": "^13.12.0" }, "devDependencies": { "nodemon": "^3.1.9" From 4542b46785c5f5ce5a990cd89daf0c633afe34f2 Mon Sep 17 00:00:00 2001 From: MohammadHMazarei Date: Mon, 23 Dec 2024 22:13:54 +0330 Subject: [PATCH 02/55] ADD: JWT_SECRET token sample for env.sample --- config/.env | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/config/.env b/config/.env index 55ad1db..44fe041 100644 --- a/config/.env +++ b/config/.env @@ -1,3 +1,4 @@ MONGODB_URL=mongodb://admin:password@mongodb:27018/marbm?authSource=admin NODE_ENV=development -PORT=3000 \ No newline at end of file +PORT=3000 +JWT_SECRET=Y@RJ$WT!@K0NS@MPL! \ No newline at end of file From 9280c60ceea93ff0c933b0d9bf47d5dbd01e4cf7 Mon Sep 17 00:00:00 2001 From: MohammadHMazarei Date: Mon, 23 Dec 2024 22:17:27 +0330 Subject: [PATCH 03/55] ADD: email, emailUnique, passwordChangedAt, passwordResetToken, and passwordResetExpires attributes --- components/user/userModel.js | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/components/user/userModel.js b/components/user/userModel.js index 2cb84f1..03de199 100644 --- a/components/user/userModel.js +++ b/components/user/userModel.js @@ -1,21 +1,37 @@ const mongoose = require('mongoose') const ObjectID = mongoose.Schema.Types.ObjectId +const validator = require('validator') const userSchema = new mongoose.Schema( { name: String, + email: { + type: String, + lowercase: true, + required: true, + validate: [validator.isEmail, 'Please provide a valid email'], + }, + emailUnique: { + type: String, + default: function () { + return this.email || `no-email-${this._id}` + }, + }, password: { type: String, required: true, trim: true, }, + passwordChangedAt: Date, + passwordResetToken: String, + passwordResetExpires: Date, isActive: { type: Boolean, default: true, }, role: { type: ObjectID, - ref: 'Role' + ref: 'Role', }, isAdmin: { type: Boolean, From f68505eeb5f6ab36204e5fb5cf04dd3ea96f3277 Mon Sep 17 00:00:00 2001 From: MohammadHMazarei Date: Mon, 23 Dec 2024 22:18:10 +0330 Subject: [PATCH 04/55] ADD: signup service logic --- components/user/userService.js | 42 ++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/components/user/userService.js b/components/user/userService.js index 09c3ee9..5ae580a 100644 --- a/components/user/userService.js +++ b/components/user/userService.js @@ -1,6 +1,47 @@ // userService.js const User = require('./userModel') const AppError = require('../../globalErrorHandler/AppError') +const jwt = require('jsonwebtoken') +const bcrypt = require('bcryptjs') +const maxAge = 60 * 60 * 24 * 90 + +const signToken = (id) => { + return jwt.sign({ id }, process.env.JWT_SECRET, { + expiresIn: maxAge, + }) +} + +const signup = async (name, email, password, passwordConfirm) => { + let userExist = await User.findOne({ email }, null, null) + + if (userExist) { + throw new AppError('UserAlreadyExist', 500, {}) + } + + if (password != passwordConfirm) throw new AppError('PasswordIncorrect', 401, {}) + + const hashedPassword = bcrypt.hashSync(password, 10) + + const user = await createUser({ + name, + email, + password: hashedPassword, + passwordConfirm: undefined, + }) + + const token = signToken(user._id) + const cookieOptions = { + expires: new Date(Date.now() + maxAge * 24 * 60 * 60 * 1000), + httpOnly: true, + } + + if (process.env.NODE_ENV === 'production') cookieOptions.secure = true + + // set user password to undefined before sending data + user.password = undefined + return { token, user, cookieOptions } +} + const loginUser = async (rawPassword) => { const user = await User.findOne({ rawPassword }, null, null) @@ -61,4 +102,5 @@ module.exports = { deleteUser, updateUser, createUser, + signup, } From 9063b30d381491bdb59c4b21f0aca6469217d39c Mon Sep 17 00:00:00 2001 From: MohammadHMazarei Date: Mon, 23 Dec 2024 22:18:42 +0330 Subject: [PATCH 05/55] ADD: signup handler --- components/user/userController.js | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/components/user/userController.js b/components/user/userController.js index 0af2808..47ecb6f 100644 --- a/components/user/userController.js +++ b/components/user/userController.js @@ -2,6 +2,17 @@ const catchAsync = require('../../globalErrorHandler/catchAsync') const userService = require('./userService') +exports.signup = catchAsync(async (req, res, next) => { + const { user, token, cookieOptions } = await userService.signup( + req.body.name, + req.body.email, + req.body.password, + req.body.passwordConfirm + ) + res.cookie('jwt', token, cookieOptions) + res.status(200).json({ status: 'success ', data: { user, token } }) +}) + exports.loginUser = catchAsync(async (req, res, next) => { const user = await userService.loginUser(req.body.rawPassword) const maxAge = 60 * 60 * 24 * 90 From 548fa8d0e28ca18fc1f79d0411bcd3f09212a8fd Mon Sep 17 00:00:00 2001 From: MohammadHMazarei Date: Mon, 23 Dec 2024 22:19:10 +0330 Subject: [PATCH 06/55] ADD: signup POST endpoint --- components/user/userRoutes.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/components/user/userRoutes.js b/components/user/userRoutes.js index 0013c05..ff9753a 100644 --- a/components/user/userRoutes.js +++ b/components/user/userRoutes.js @@ -5,10 +5,11 @@ const loginCheck = require('../../middlewares/loginCheck') const router = new express.Router() +router.post('/signup', userController.signup) router.post('/login', userController.loginUser) router.post('/logout', userController.logoutUser) -router.use(loginCheck.loginProtect) +// router.use(loginCheck.loginProtect) // router.use(loginCheck.adminProtect) router.get('/', userController.getAllUsers) From 4d45b4f2050eb37fe4c1536f78d6c00377a71ef0 Mon Sep 17 00:00:00 2001 From: MohammadHMazarei Date: Mon, 23 Dec 2024 23:09:04 +0330 Subject: [PATCH 07/55] ADD: min and select attribute for password --- components/user/userModel.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/components/user/userModel.js b/components/user/userModel.js index 03de199..1fb5820 100644 --- a/components/user/userModel.js +++ b/components/user/userModel.js @@ -21,6 +21,8 @@ const userSchema = new mongoose.Schema( type: String, required: true, trim: true, + min: 8, + select: false, }, passwordChangedAt: Date, passwordResetToken: String, From 3fe2377ce3b43bbd1f2fb8a662b54c7db6beca41 Mon Sep 17 00:00:00 2001 From: MohammadHMazarei Date: Mon, 23 Dec 2024 23:09:44 +0330 Subject: [PATCH 08/55] ADD: login service logic --- components/user/userService.js | 54 ++++++++++++++++++++++++++++++---- 1 file changed, 49 insertions(+), 5 deletions(-) diff --git a/components/user/userService.js b/components/user/userService.js index 5ae580a..2d55549 100644 --- a/components/user/userService.js +++ b/components/user/userService.js @@ -42,14 +42,58 @@ const signup = async (name, email, password, passwordConfirm) => { return { token, user, cookieOptions } } -const loginUser = async (rawPassword) => { - const user = await User.findOne({ rawPassword }, null, null) +const loginAttempt = async (phrase) => { + const attemptExist = await redisClient.exists(phrase + '#attempts') - if (!user) { - throw new AppError('WrongPassword', 404, {}) + if (attemptExist === 0) { + await redisClient.set( + phrase + '#attempts', + 0, + { + EX: 3600, + }, + null + ) } - return user + // if user phrase(email or phone) exist return error with expire time left for login + if (parseInt(await redisClient.get(phrase + '#attempts')) >= parseInt(process.env.LOGIN_ATTEMPT_LIMIT)) { + const limitTime = await redisClient.ttl(phrase + '#attempts') + const minutes = (limitTime / 60).toFixed(0) + throw new AppError('MaximumLoginReached', 500, { + minutes, + }) + } + + if (attemptExist === 1) { + const attempts = parseInt(await redisClient.get(phrase + '#attempts')) + await redisClient.set( + phrase + '#attempts', + attempts + 1 + '', + { + EX: 3600, + }, + null + ) + } +} + +const loginUser = async (email, password) => { + if (!email || !password) { + throw new AppError('ProvideEmailAndPassword', 400) + } + + const user = await User.findOne({ email }, null, null).select('+password') + + // if (email) { + // await loginAttempt(email) + // } + + if (!user || !(await bcrypt.compare(password, user.password))) { + throw new AppError('IncorrectEmailOrPassword', 401, {}) + } + + return { token: signToken(user._id), user } } const getAllUsers = async () => { From d368ae9af06a2b6944234c4dc9fac7a7b7e59e71 Mon Sep 17 00:00:00 2001 From: MohammadHMazarei Date: Mon, 23 Dec 2024 23:10:35 +0330 Subject: [PATCH 09/55] ADD: login handler --- components/user/userController.js | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/components/user/userController.js b/components/user/userController.js index 47ecb6f..c4cff2c 100644 --- a/components/user/userController.js +++ b/components/user/userController.js @@ -14,10 +14,13 @@ exports.signup = catchAsync(async (req, res, next) => { }) exports.loginUser = catchAsync(async (req, res, next) => { - const user = await userService.loginUser(req.body.rawPassword) + const { email, password } = req.body + const { token, user } = await userService.loginUser(email, password) const maxAge = 60 * 60 * 24 * 90 - res.cookie('raw-password', user.rawPassword, { + res.setHeader('Authorization', `Bearer ${token}`) + + res.cookie('belonging_token', token, { httpOnly: true, maxAge: maxAge * 1000, path: '/', @@ -25,7 +28,7 @@ exports.loginUser = catchAsync(async (req, res, next) => { res.status(200).json({ status: 'success', - data: user, + data: { user, token }, }) }) From 6dcd9b976e91a372026d8def1ff6e0da26c642fc Mon Sep 17 00:00:00 2001 From: MohammadHMazarei Date: Tue, 24 Dec 2024 19:25:55 +0330 Subject: [PATCH 10/55] ADD: redis setup and created a redis client --- setup/redis.js | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 setup/redis.js diff --git a/setup/redis.js b/setup/redis.js new file mode 100644 index 0000000..e3919cc --- /dev/null +++ b/setup/redis.js @@ -0,0 +1,18 @@ +const redis = require('redis') +const env = process.env.NODE_ENV || 'development' + +let connectionString +if (env === 'development') { + connectionString = process.env.REDIS_URL +} else if (env === 'test') { + connectionString = process.env.TEST_REDIS_URL +} else if (env === 'production') { + connectionString = process.env.REDIS_URL +} +const client = redis.createClient({ url: connectionString }) + +client.on('error', function (err) { + console.log(err) +}) + +module.exports = client From a7d7df8bf4b7d25a12670926d4ad83ca88085f6a Mon Sep 17 00:00:00 2001 From: MohammadHMazarei Date: Tue, 24 Dec 2024 19:26:44 +0330 Subject: [PATCH 11/55] ADD: redis client and connection --- setup/databaseConnection.js | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/setup/databaseConnection.js b/setup/databaseConnection.js index bda7ab6..b6d412e 100644 --- a/setup/databaseConnection.js +++ b/setup/databaseConnection.js @@ -1,4 +1,5 @@ const mongoose = require('mongoose') +const redisClient = require('./redis') const port = process.env.PORT || 3000 const env = process.env.NODE_ENV || 'development' module.exports = (app) => { @@ -14,13 +15,17 @@ module.exports = (app) => { .connect(connectionString, {}) .then(async function () { console.log('MongoDB Database Connected') - const MyModel = mongoose.model('MyCollection', new mongoose.Schema({name: String})); + const MyModel = mongoose.model('MyCollection', new mongoose.Schema({ name: String })) // Create a document to force MongoDB to actually create the DB and collection - await MyModel.create({name: 'test'}); + await MyModel.create({ name: 'test' }) - app.listen(port, function () { - console.log('Server Started On Port ' + port) + redisClient.connect().then((r) => { + console.log('Redis connection Established') + + app.listen(port, function () { + console.log('Server Started On Port ' + port) + }) }) }) .catch(function (err) { From 2efb05712e09787bf3bfa6188924090b9edb8caf Mon Sep 17 00:00:00 2001 From: MohammadHMazarei Date: Tue, 24 Dec 2024 19:27:12 +0330 Subject: [PATCH 12/55] ADD: redis package --- package-lock.json | 88 +++++++++++++++++++++++++++++++++++++++++++++++ package.json | 1 + 2 files changed, 89 insertions(+) diff --git a/package-lock.json b/package-lock.json index fa78f77..38b6e87 100644 --- a/package-lock.json +++ b/package-lock.json @@ -19,6 +19,7 @@ "express": "^4.21.2", "jsonwebtoken": "^9.0.2", "mongoose": "^8.9.0", + "redis": "^4.7.0", "validator": "^13.12.0" }, "devDependencies": { @@ -33,6 +34,59 @@ "sparse-bitfield": "^3.0.3" } }, + "node_modules/@redis/bloom": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@redis/bloom/-/bloom-1.2.0.tgz", + "integrity": "sha512-HG2DFjYKbpNmVXsa0keLHp/3leGJz1mjh09f2RLGGLQZzSHpkmZWuwJbAvo3QcRY8p80m5+ZdXZdYOSBLlp7Cg==", + "peerDependencies": { + "@redis/client": "^1.0.0" + } + }, + "node_modules/@redis/client": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@redis/client/-/client-1.6.0.tgz", + "integrity": "sha512-aR0uffYI700OEEH4gYnitAnv3vzVGXCFvYfdpu/CJKvk4pHfLPEy/JSZyrpQ+15WhXe1yJRXLtfQ84s4mEXnPg==", + "dependencies": { + "cluster-key-slot": "1.1.2", + "generic-pool": "3.9.0", + "yallist": "4.0.0" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/@redis/graph": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@redis/graph/-/graph-1.1.1.tgz", + "integrity": "sha512-FEMTcTHZozZciLRl6GiiIB4zGm5z5F3F6a6FZCyrfxdKOhFlGkiAqlexWMBzCi4DcRoyiOsuLfW+cjlGWyExOw==", + "peerDependencies": { + "@redis/client": "^1.0.0" + } + }, + "node_modules/@redis/json": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/@redis/json/-/json-1.0.7.tgz", + "integrity": "sha512-6UyXfjVaTBTJtKNG4/9Z8PSpKE6XgSyEb8iwaqDcy+uKrd/DGYHTWkUdnQDyzm727V7p21WUMhsqz5oy65kPcQ==", + "peerDependencies": { + "@redis/client": "^1.0.0" + } + }, + "node_modules/@redis/search": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@redis/search/-/search-1.2.0.tgz", + "integrity": "sha512-tYoDBbtqOVigEDMAcTGsRlMycIIjwMCgD8eR2t0NANeQmgK/lvxNAvYyb6bZDD4frHRhIHkJu2TBRvB0ERkOmw==", + "peerDependencies": { + "@redis/client": "^1.0.0" + } + }, + "node_modules/@redis/time-series": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@redis/time-series/-/time-series-1.1.0.tgz", + "integrity": "sha512-c1Q99M5ljsIuc4YdaCwfUEXsofakb9c8+Zse2qxTadu8TalLXuAESzLvFAvNVbkmSlvlzIQOLpBCmWI9wTOt+g==", + "peerDependencies": { + "@redis/client": "^1.0.0" + } + }, "node_modules/@types/webidl-conversions": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/@types/webidl-conversions/-/webidl-conversions-7.0.3.tgz", @@ -235,6 +289,14 @@ "fsevents": "~2.3.2" } }, + "node_modules/cluster-key-slot": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/cluster-key-slot/-/cluster-key-slot-1.1.2.tgz", + "integrity": "sha512-RMr0FhtfXemyinomL4hrWcYJxmX6deFdCxpJzhDttxgO1+bcCnkk+9drydLVDmAMG7NE6aN/fl4F7ucU/90gAA==", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/compressible": { "version": "2.0.18", "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz", @@ -555,6 +617,14 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/generic-pool": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/generic-pool/-/generic-pool-3.9.0.tgz", + "integrity": "sha512-hymDOu5B53XvN4QT9dBmZxPX4CWhBPPLguTZ9MMFeFa/Kg0xWVfylOVNlJji/E7yTZWFd/q9GO5TxDLq156D7g==", + "engines": { + "node": ">= 4" + } + }, "node_modules/get-intrinsic": { "version": "1.2.6", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.6.tgz", @@ -1224,6 +1294,19 @@ "node": ">=8.10.0" } }, + "node_modules/redis": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/redis/-/redis-4.7.0.tgz", + "integrity": "sha512-zvmkHEAdGMn+hMRXuMBtu4Vo5P6rHQjLoHftu+lBqq8ZTA3RCVC/WzD790bkKKiNFp7d5/9PcSD19fJyyRvOdQ==", + "dependencies": { + "@redis/bloom": "1.2.0", + "@redis/client": "1.6.0", + "@redis/graph": "1.1.1", + "@redis/json": "1.0.7", + "@redis/search": "1.2.0", + "@redis/time-series": "1.1.0" + } + }, "node_modules/safe-buffer": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", @@ -1536,6 +1619,11 @@ "engines": { "node": ">=16" } + }, + "node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" } } } diff --git a/package.json b/package.json index c333c81..5313ac8 100644 --- a/package.json +++ b/package.json @@ -21,6 +21,7 @@ "express": "^4.21.2", "jsonwebtoken": "^9.0.2", "mongoose": "^8.9.0", + "redis": "^4.7.0", "validator": "^13.12.0" }, "devDependencies": { From d1efe7752ba3062a0d822398c6db0dd20729d04b Mon Sep 17 00:00:00 2001 From: MohammadHMazarei Date: Tue, 24 Dec 2024 19:27:59 +0330 Subject: [PATCH 13/55] ADD: REDIS_URL and LOGIN_ATTEMPT_LIMIT --- config/.env | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/config/.env b/config/.env index 44fe041..6a66acd 100644 --- a/config/.env +++ b/config/.env @@ -1,4 +1,6 @@ MONGODB_URL=mongodb://admin:password@mongodb:27018/marbm?authSource=admin NODE_ENV=development PORT=3000 -JWT_SECRET=Y@RJ$WT!@K0NS@MPL! \ No newline at end of file +JWT_SECRET=Y@RJ$WT!@K0NS@MPL! +REDIS_URL= redis://:135792468@redis:6379/0 +LOGIN_ATTEMPT_LIMIT=5 \ No newline at end of file From 8cfea998a5fabfcb8537ae5fba42c5b677b8206e Mon Sep 17 00:00:00 2001 From: MohammadHMazarei Date: Tue, 24 Dec 2024 19:29:11 +0330 Subject: [PATCH 14/55] ADD: REDIS_URL to environment of api-server service --- docker-compose.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/docker-compose.yml b/docker-compose.yml index 2d0aab4..bf85114 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -7,6 +7,7 @@ services: - MONGODB_URL=mongodb://admin:*135792468*@mongo:27017/marbm?authSource=admin - NODE_ENV=development - PORT=3000 + - REDIS_URL=redis://:135792468@redis:6379/0 minio-server: image: minio/minio ports: From 8fae7769fdef268912a5d33f55abeffca186f756 Mon Sep 17 00:00:00 2001 From: MohammadHMazarei Date: Tue, 24 Dec 2024 19:29:59 +0330 Subject: [PATCH 15/55] ADD: redis client and inject the login-attempt logic into loginUser function --- components/user/userService.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/components/user/userService.js b/components/user/userService.js index 2d55549..ddd6dd3 100644 --- a/components/user/userService.js +++ b/components/user/userService.js @@ -1,6 +1,7 @@ // userService.js const User = require('./userModel') const AppError = require('../../globalErrorHandler/AppError') +const redisClient = require('../../setup/redis') const jwt = require('jsonwebtoken') const bcrypt = require('bcryptjs') const maxAge = 60 * 60 * 24 * 90 @@ -85,9 +86,9 @@ const loginUser = async (email, password) => { const user = await User.findOne({ email }, null, null).select('+password') - // if (email) { - // await loginAttempt(email) - // } + if (email) { + await loginAttempt(email) + } if (!user || !(await bcrypt.compare(password, user.password))) { throw new AppError('IncorrectEmailOrPassword', 401, {}) From 85a2dc7e7a9af0ebb57b8d60f6e812a89dd0b976 Mon Sep 17 00:00:00 2001 From: MohammadHMazarei Date: Tue, 24 Dec 2024 20:07:28 +0330 Subject: [PATCH 16/55] REMOVE: addresses from populate callback function in findById function called on user model --- middlewares/loginCheck.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/middlewares/loginCheck.js b/middlewares/loginCheck.js index 1a0e439..2ce6ab2 100644 --- a/middlewares/loginCheck.js +++ b/middlewares/loginCheck.js @@ -28,7 +28,7 @@ exports.loginProtect = async (req, res, next) => { } // 3) check if user still exist - const loggedInUser = await User.findById(decodedToken.id).populate('role addresses') + const loggedInUser = await User.findById(decodedToken.id).populate('role') if (!loggedInUser) { return next(new AppError('TokenUserNotExist', 401, {})) From 3faa550b1c7c102631b08fdb5ea67f513db1ff71 Mon Sep 17 00:00:00 2001 From: MohammadHMazarei Date: Tue, 24 Dec 2024 20:08:18 +0330 Subject: [PATCH 17/55] UPDATE: put endpoint to patch --- components/user/userRoutes.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/components/user/userRoutes.js b/components/user/userRoutes.js index ff9753a..da0800f 100644 --- a/components/user/userRoutes.js +++ b/components/user/userRoutes.js @@ -9,7 +9,7 @@ router.post('/signup', userController.signup) router.post('/login', userController.loginUser) router.post('/logout', userController.logoutUser) -// router.use(loginCheck.loginProtect) +router.use(loginCheck.loginProtect) // router.use(loginCheck.adminProtect) router.get('/', userController.getAllUsers) @@ -18,7 +18,7 @@ router.get('/:userId', userController.getUser) router.post('/', userController.createUser) -router.put('/:userId', userController.updateUser) +router.patch('/:userId', userController.updateUser) router.delete('/:userId', userController.deleteUser) From a8891f409629c1cf8e06fdac0daa99a6373ac697 Mon Sep 17 00:00:00 2001 From: MohammadHMazarei Date: Tue, 24 Dec 2024 20:08:59 +0330 Subject: [PATCH 18/55] FIX: getAllUsers logic --- components/user/userService.js | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/components/user/userService.js b/components/user/userService.js index ddd6dd3..7625d89 100644 --- a/components/user/userService.js +++ b/components/user/userService.js @@ -94,14 +94,13 @@ const loginUser = async (email, password) => { throw new AppError('IncorrectEmailOrPassword', 401, {}) } + // set user password to undefined before sending data + user.password = undefined return { token: signToken(user._id), user } } const getAllUsers = async () => { const users = await User.find({}, null, null) - for (let user of users) { - user.activatedCodes = await codeService.getCodeCount(user._id) - } return users } From bfb525f938a7a3e187a04f6f21e8403ac6c42e45 Mon Sep 17 00:00:00 2001 From: MohammadHMazarei Date: Tue, 24 Dec 2024 20:09:36 +0330 Subject: [PATCH 19/55] FIX: logoutUser logic --- components/user/userController.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/user/userController.js b/components/user/userController.js index c4cff2c..2918287 100644 --- a/components/user/userController.js +++ b/components/user/userController.js @@ -33,7 +33,7 @@ exports.loginUser = catchAsync(async (req, res, next) => { }) exports.logoutUser = catchAsync(async (req, res, next) => { - res.cookie('raw-password', '', { + res.cookie('belonging_token', '', { httpOnly: true, maxAge: 0, path: '/', From ee4458d5dda836ed31d358757cdb6a7bfba5fc72 Mon Sep 17 00:00:00 2001 From: MohammadHMazarei Date: Tue, 24 Dec 2024 21:17:37 +0330 Subject: [PATCH 20/55] ADD: SUPER_ADMIN_ID --- config/.env | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/config/.env b/config/.env index 6a66acd..9630419 100644 --- a/config/.env +++ b/config/.env @@ -3,4 +3,5 @@ NODE_ENV=development PORT=3000 JWT_SECRET=Y@RJ$WT!@K0NS@MPL! REDIS_URL= redis://:135792468@redis:6379/0 -LOGIN_ATTEMPT_LIMIT=5 \ No newline at end of file +LOGIN_ATTEMPT_LIMIT=5 +SUPER_ADMIN_ID=67686eab753cc552ee274e51 \ No newline at end of file From e02b567c16b29d7239378308cad22255de2a6382 Mon Sep 17 00:00:00 2001 From: MohammadHMazarei Date: Tue, 24 Dec 2024 21:18:33 +0330 Subject: [PATCH 21/55] FIX: createPermission and updatePermission logics --- components/permission/permissionController.js | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/components/permission/permissionController.js b/components/permission/permissionController.js index a8ef0e6..7f2ae4b 100644 --- a/components/permission/permissionController.js +++ b/components/permission/permissionController.js @@ -14,10 +14,7 @@ exports.getAllPermissions = catchAsync(async (req, res, next) => { }) exports.createPermission = catchAsync(async (req, res, next) => { - const permission = await permissionService.createPermission( - req.body.englishName, - req.body.persianName - ) + const permission = await permissionService.createPermission(req.body) res.status(201).json({ status: 'success', @@ -35,10 +32,10 @@ exports.getPermission = catchAsync(async (req, res, next) => { }) exports.updatePermission = catchAsync(async (req, res, next) => { - const updatePermission = await permissionService.updatePermission(data) + const updatePermission = await permissionService.updatePermission(req.params.id, req.body) res.status(200).json({ status: 'success', - data: permission, + data: updatePermission, }) }) From 3fef3759dda1cfd63d2178964a6223ac648da51b Mon Sep 17 00:00:00 2001 From: MohammadHMazarei Date: Tue, 24 Dec 2024 21:19:20 +0330 Subject: [PATCH 22/55] FIX: createPermission, updatePermission, and deletePermission logics --- components/permission/permissionService.js | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/components/permission/permissionService.js b/components/permission/permissionService.js index c928d90..69a71ad 100644 --- a/components/permission/permissionService.js +++ b/components/permission/permissionService.js @@ -5,11 +5,8 @@ module.exports.getAllPermissions = async () => { return Permission.find({}) } -module.exports.createPermission = async (englishName, persianName) => { - return await Permission.create({ - englishName, - persianName, - }) +module.exports.createPermission = async (data) => { + return await Permission.create(data) } module.exports.getPermission = async (permissionId) => { @@ -22,8 +19,8 @@ module.exports.getPermission = async (permissionId) => { return permission } -module.exports.updatePermission = async (data) => { - const permission = await Permission.findOneAndUpdate(data) +module.exports.updatePermission = async (permissionId, data) => { + const permission = await Permission.findOneAndUpdate({ _id: permissionId }, data, { new: true }) if (!permission) { throw AppError('NoPermissionFound', 404, {}) @@ -33,7 +30,11 @@ module.exports.updatePermission = async (data) => { } module.exports.deletePermission = async (permissionId) => { - await Permission.findOneAndDelete({ _id: permissionId }) + const permission = await Permission.findOneAndDelete({ _id: permissionId }) + + if (!permission) { + throw AppError('NoPermissionFound', 404, {}) + } return null } From fdd136ac4076e578f2a6ba8cf0bf38d23c798090 Mon Sep 17 00:00:00 2001 From: MohammadHMazarei Date: Wed, 25 Dec 2024 19:43:24 +0330 Subject: [PATCH 23/55] FIX: createRole, updateRole, and deleteRole logics --- components/role/roleService.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/components/role/roleService.js b/components/role/roleService.js index 917db0b..8b6ab87 100644 --- a/components/role/roleService.js +++ b/components/role/roleService.js @@ -6,7 +6,7 @@ module.exports.getAllRoles = async () => { } module.exports.createRole = async (data) => { - return Role.create(data, null) + return await Role.create(data) } module.exports.getRole = async (roleId) => { @@ -20,7 +20,7 @@ module.exports.getRole = async (roleId) => { } module.exports.updateRole = async (roleId, data) => { - const role = await Role.findOneAndUpdate({ _id: roleId},data, null) + const role = await Role.findOneAndUpdate({ _id: roleId }, data, { new: true }) if (!role) { throw new AppError('NoRoleFound', 404, {}) @@ -30,7 +30,7 @@ module.exports.updateRole = async (roleId, data) => { } module.exports.deleteRole = async (roleId) => { - const role = await Role.findByIdAndUpdate(roleId, { isActive: false }, null) + const role = await Role.findByIdAndUpdate(roleId, { isActive: false }, { new: true }) if (!role) { throw new AppError('NoRoleFound', 404, {}) From 0dc408f014a535b6feb860b6d57173637d139414 Mon Sep 17 00:00:00 2001 From: MohammadHMazarei Date: Wed, 25 Dec 2024 19:44:58 +0330 Subject: [PATCH 24/55] FIX: getRole, updateRole, and deleteRole handler --- components/role/roleController.js | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/components/role/roleController.js b/components/role/roleController.js index 3aed3e7..42e165e 100644 --- a/components/role/roleController.js +++ b/components/role/roleController.js @@ -14,10 +14,7 @@ exports.getAllRoles = catchAsync(async (req, res, next) => { }) exports.createRole = catchAsync(async (req, res, next) => { - const role = await roleService.createRole( - req.body.englishName, - req.body.persianName - ) + const role = await roleService.createRole(req.body) res.status(201).json({ status: 'success', @@ -26,7 +23,7 @@ exports.createRole = catchAsync(async (req, res, next) => { }) exports.getRole = catchAsync(async (req, res, next) => { - const role = await roleService.getRole(req.body.id) + const role = await roleService.getRole(req.params.roleId) res.status(200).json({ status: 'success', @@ -35,15 +32,15 @@ exports.getRole = catchAsync(async (req, res, next) => { }) exports.updateRole = catchAsync(async (req, res, next) => { - const updateRole = await roleService.updateRole(data) + const updateRole = await roleService.updateRole(req.params.roleId, req.body) res.status(200).json({ status: 'success', - data: role, + data: updateRole, }) }) exports.deleteRole = catchAsync(async (req, res, next) => { - await roleService.deleteRole(req.body.id) + await roleService.deleteRole(req.params.roleId) res.status(204).json({ status: 'success', From d3eb5c296cc227c3d3459c9fb3072715cc949d77 Mon Sep 17 00:00:00 2001 From: MohammadHMazarei Date: Wed, 25 Dec 2024 20:00:09 +0330 Subject: [PATCH 25/55] UPDATE: put endpoint to patch --- components/permission/permissionRoutes.js | 30 ++++------------------- 1 file changed, 5 insertions(+), 25 deletions(-) diff --git a/components/permission/permissionRoutes.js b/components/permission/permissionRoutes.js index 41bcff0..8d02c2c 100644 --- a/components/permission/permissionRoutes.js +++ b/components/permission/permissionRoutes.js @@ -7,34 +7,14 @@ const router = new express.Router() router.use(loginCheck.loginProtect) -router.get( - '/', - perm.requires('getallpermissions'), - permissionController.getAllPermissions -) +router.get('/', perm.requires('getallpermissions'), permissionController.getAllPermissions) -router.post( - '/', - perm.requires('createpermission'), - permissionController.createPermission -) +router.post('/', perm.requires('createpermission'), permissionController.createPermission) -router.get( - '/:id', - perm.requires('getpermission'), - permissionController.getPermission -) +router.get('/:id', perm.requires('getpermission'), permissionController.getPermission) -router.put( - '/:id', - perm.requires('updatepermission'), - permissionController.updatePermission -) +router.patch('/:id', perm.requires('updatepermission'), permissionController.updatePermission) -router.delete( - '/:id', - perm.requires('deletepermission'), - permissionController.deletePermission -) +router.delete('/:id', perm.requires('deletepermission'), permissionController.deletePermission) module.exports = router From 9fb74e3872d5ad75f981ea6b5ca704d6ba73ebab Mon Sep 17 00:00:00 2001 From: MohammadHMazarei Date: Wed, 25 Dec 2024 20:00:24 +0330 Subject: [PATCH 26/55] UPDATE: put endpoint to patch --- components/role/roleRoutes.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/role/roleRoutes.js b/components/role/roleRoutes.js index b824ad5..b836ca0 100644 --- a/components/role/roleRoutes.js +++ b/components/role/roleRoutes.js @@ -13,7 +13,7 @@ router.post('/admin/create', perm.requires('createarole'), roleController.create router.get('/admin/get-one/:roleId', perm.requires('getarole'), roleController.getRole) -router.put('/admin/update/:roleId', perm.requires('updatearole'), roleController.updateRole) +router.patch('/admin/update/:roleId', perm.requires('updatearole'), roleController.updateRole) router.delete('/admin/delete/:roleId', perm.requires('deletearole'), roleController.deleteRole) From dbffe586c3ed858b4e75bf6b05a314eb2e7bd767 Mon Sep 17 00:00:00 2001 From: MohammadHMazarei Date: Wed, 25 Dec 2024 20:00:49 +0330 Subject: [PATCH 27/55] UPDATE: SUPER_ADMIN_ID to SUPER_ADMIN_ROLE_ID --- config/.env | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/.env b/config/.env index 9630419..4642e61 100644 --- a/config/.env +++ b/config/.env @@ -4,4 +4,4 @@ PORT=3000 JWT_SECRET=Y@RJ$WT!@K0NS@MPL! REDIS_URL= redis://:135792468@redis:6379/0 LOGIN_ATTEMPT_LIMIT=5 -SUPER_ADMIN_ID=67686eab753cc552ee274e51 \ No newline at end of file +SUPER_ADMIN_ROLE_ID=676afd6d076b286f9395b05f \ No newline at end of file From b67ffb7f5e147c4e3076e4976b6cc83fdcff87c4 Mon Sep 17 00:00:00 2001 From: MohammadHMazarei Date: Sun, 29 Dec 2024 19:30:49 +0330 Subject: [PATCH 28/55] CREATE: category mongo model --- components/category/categoryModel.js | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 components/category/categoryModel.js diff --git a/components/category/categoryModel.js b/components/category/categoryModel.js new file mode 100644 index 0000000..e69de29 From aff7184e04f2d665086e575ce07be2c2087e57ef Mon Sep 17 00:00:00 2001 From: MohammadHMazarei Date: Sun, 29 Dec 2024 19:32:11 +0330 Subject: [PATCH 29/55] ADD: category model --- components/category/categoryModel.js | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/components/category/categoryModel.js b/components/category/categoryModel.js index e69de29..d4d0fb5 100644 --- a/components/category/categoryModel.js +++ b/components/category/categoryModel.js @@ -0,0 +1,28 @@ +const mongoose = require('mongoose') +const ObjectID = mongoose.Schema.Types.ObjectId + +const categorySchema = new mongoose.Schema( + { + nativeName: { + type: String, + required: true, + }, + englishName: { + type: String, + required: true, + unique: true, + }, + parentId: { + type: ObjectID, + ref: 'Category', + }, + }, + { + toJSON: { virtuals: true }, + toObject: { virtuals: true }, + timestamps: true, + } +) + +const Category = mongoose.model('Category', categorySchema) +module.exports = Category From f8c557d3507cc9711d34a67236e6ecb08361b140 Mon Sep 17 00:00:00 2001 From: MohammadHMazarei Date: Sun, 29 Dec 2024 19:32:58 +0330 Subject: [PATCH 30/55] CREATE: category service --- components/category/categoryService.js | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 components/category/categoryService.js diff --git a/components/category/categoryService.js b/components/category/categoryService.js new file mode 100644 index 0000000..e69de29 From f1f5b5413761f8647f4fe4f149937b19fb5b0dcf Mon Sep 17 00:00:00 2001 From: MohammadHMazarei Date: Sun, 29 Dec 2024 19:34:55 +0330 Subject: [PATCH 31/55] CREATE: category routes --- components/category/categoryRoutes.js | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 components/category/categoryRoutes.js diff --git a/components/category/categoryRoutes.js b/components/category/categoryRoutes.js new file mode 100644 index 0000000..e69de29 From dd64556d23853ef75de10d8bf7ffc0b43efae135 Mon Sep 17 00:00:00 2001 From: MohammadHMazarei Date: Sun, 29 Dec 2024 19:35:15 +0330 Subject: [PATCH 32/55] CREATE: category controller --- components/category/categoryController.js | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 components/category/categoryController.js diff --git a/components/category/categoryController.js b/components/category/categoryController.js new file mode 100644 index 0000000..e69de29 From d37d87bc36946db43b4a093bec985fa8a2ba9242 Mon Sep 17 00:00:00 2001 From: MohammadHMazarei Date: Sun, 29 Dec 2024 19:42:10 +0330 Subject: [PATCH 33/55] ADD: category routes to routerEndpoints --- setup/routerEndpoints.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/setup/routerEndpoints.js b/setup/routerEndpoints.js index 3f274f4..dab970b 100644 --- a/setup/routerEndpoints.js +++ b/setup/routerEndpoints.js @@ -5,6 +5,7 @@ const userRoutes = require('../components/user/userRoutes') const roleRoutes = require('../components/role/roleRoutes') const permissionRoutes = require('../components/permission/permissionRoutes') const belongingRoutes = require('../components/belonging/belongingRoutes') +const categoryRoutes = require('../components/category/categoryRoutes') // const codeRoutes = require('../components/code/codeRoutes') // const CRMRoutes = require('../components/pages/crmRoutes') // const LoginRoute = require('../components/pages/loginRoutes') @@ -15,5 +16,6 @@ routerEndpoints.use('/users', userRoutes) routerEndpoints.use('/permissions', permissionRoutes) routerEndpoints.use('/belongings', belongingRoutes) routerEndpoints.use('/roles', roleRoutes) +routerEndpoints.use('/categories', categoryRoutes) module.exports = routerEndpoints From ef71b0415f8e1a9aee0487aa065604df926e76c6 Mon Sep 17 00:00:00 2001 From: MohammadHMazarei Date: Sun, 29 Dec 2024 19:42:44 +0330 Subject: [PATCH 34/55] ADD: get all categories GET endpoint --- components/category/categoryRoutes.js | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/components/category/categoryRoutes.js b/components/category/categoryRoutes.js index e69de29..a052235 100644 --- a/components/category/categoryRoutes.js +++ b/components/category/categoryRoutes.js @@ -0,0 +1,16 @@ +const express = require('express') +const categoryController = require('./categoryController') +const perm = require('../../middlewares/permissionMiddleware') +const loginCheck = require('../../middlewares/loginCheck') + +const router = new express.Router() + +router.use(loginCheck.loginProtect) + +router.get( + '/admin/get-all', + // perm.requires('getallcategories'), + categoryController.getAllCategories +) + +module.exports = router From 6e6c40920188b077a3f0ea41df84dc82bc7958a9 Mon Sep 17 00:00:00 2001 From: MohammadHMazarei Date: Sun, 29 Dec 2024 19:44:47 +0330 Subject: [PATCH 35/55] ADD: get all categories logic --- components/category/categoryService.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/components/category/categoryService.js b/components/category/categoryService.js index e69de29..a5b3552 100644 --- a/components/category/categoryService.js +++ b/components/category/categoryService.js @@ -0,0 +1,6 @@ +const Category = require('./categoryModel') +const AppError = require('../../globalErrorHandler/AppError') + +module.exports.getAllCategories = async () => { + return Category.find().populate('parentId') +} From 67b0e810f5edd53753eb39b3c6b1ce3a98a9c39a Mon Sep 17 00:00:00 2001 From: MohammadHMazarei Date: Sun, 29 Dec 2024 19:45:20 +0330 Subject: [PATCH 36/55] ADD: get all category handler --- components/category/categoryController.js | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/components/category/categoryController.js b/components/category/categoryController.js index e69de29..e468c03 100644 --- a/components/category/categoryController.js +++ b/components/category/categoryController.js @@ -0,0 +1,14 @@ +const catchAsync = require('../../globalErrorHandler/catchAsync') +const AppError = require('../../globalErrorHandler/AppError') +const Category = require('./categoryModel') +const categoryService = require('./categoryService') + +exports.getAllCategories = catchAsync(async (req, res, next) => { + const categories = await categoryService.getAllCategories() + + res.status(200).json({ + status: 'success', + results: categories.length, + data: categories, + }) +}) From 183f458d9327d34ab89a1a90efb53e22e1148295 Mon Sep 17 00:00:00 2001 From: MohammadHMazarei Date: Sun, 29 Dec 2024 19:50:31 +0330 Subject: [PATCH 37/55] ADD: create a new category POST endpoint --- components/category/categoryRoutes.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/components/category/categoryRoutes.js b/components/category/categoryRoutes.js index a052235..496d9c2 100644 --- a/components/category/categoryRoutes.js +++ b/components/category/categoryRoutes.js @@ -13,4 +13,10 @@ router.get( categoryController.getAllCategories ) +router.post( + '/admin/create', + // perm.requires('createcategory'), + categoryController.createCategory +) + module.exports = router From 2fc9cad7fc5713e2a14e2004f7db93d210e42aad Mon Sep 17 00:00:00 2001 From: MohammadHMazarei Date: Sun, 29 Dec 2024 19:50:59 +0330 Subject: [PATCH 38/55] ADD: create a new category logic --- components/category/categoryService.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/components/category/categoryService.js b/components/category/categoryService.js index a5b3552..dc773f4 100644 --- a/components/category/categoryService.js +++ b/components/category/categoryService.js @@ -4,3 +4,8 @@ const AppError = require('../../globalErrorHandler/AppError') module.exports.getAllCategories = async () => { return Category.find().populate('parentId') } + +module.exports.createCategory = async (data) => { + const category = Category.create(data, null) + return category +} From ed884c405e5607eaabdf8b76c5348bae4051eca4 Mon Sep 17 00:00:00 2001 From: MohammadHMazarei Date: Sun, 29 Dec 2024 19:51:15 +0330 Subject: [PATCH 39/55] ADD: create a new category handler --- components/category/categoryController.js | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/components/category/categoryController.js b/components/category/categoryController.js index e468c03..c10e476 100644 --- a/components/category/categoryController.js +++ b/components/category/categoryController.js @@ -12,3 +12,12 @@ exports.getAllCategories = catchAsync(async (req, res, next) => { data: categories, }) }) + +exports.createCategory = catchAsync(async (req, res, next) => { + const category = await categoryService.createCategory(req.body) + + res.status(201).json({ + status: 'success', + data: category, + }) +}) From f1c7e378a70b47d9ed2293509cd50c0d79403d25 Mon Sep 17 00:00:00 2001 From: MohammadHMazarei Date: Sun, 29 Dec 2024 19:55:42 +0330 Subject: [PATCH 40/55] ADD: get one category by id GET endpoint --- components/category/categoryRoutes.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/components/category/categoryRoutes.js b/components/category/categoryRoutes.js index 496d9c2..9258db2 100644 --- a/components/category/categoryRoutes.js +++ b/components/category/categoryRoutes.js @@ -19,4 +19,10 @@ router.post( categoryController.createCategory ) +router.get( + '/admin/get-one/:categoryId', + // perm.requires('getcategory'), + categoryController.getCategory +) + module.exports = router From 91a3c7b3b8a612eed8be06df8d88663808781eff Mon Sep 17 00:00:00 2001 From: MohammadHMazarei Date: Sun, 29 Dec 2024 19:56:07 +0330 Subject: [PATCH 41/55] ADD: get one category logic --- components/category/categoryService.js | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/components/category/categoryService.js b/components/category/categoryService.js index dc773f4..fd37141 100644 --- a/components/category/categoryService.js +++ b/components/category/categoryService.js @@ -9,3 +9,13 @@ module.exports.createCategory = async (data) => { const category = Category.create(data, null) return category } + +module.exports.getCategory = async (categoryId) => { + const category = await Category.findById(categoryId) + + if (!category) { + throw new AppError('NoCategoryFound', 404, {}) + } + + return category +} From d4b96da9e35c1eaee73351fb6a941e084ac519d5 Mon Sep 17 00:00:00 2001 From: MohammadHMazarei Date: Sun, 29 Dec 2024 19:56:22 +0330 Subject: [PATCH 42/55] ADD: get one category handler --- components/category/categoryController.js | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/components/category/categoryController.js b/components/category/categoryController.js index c10e476..3ad1278 100644 --- a/components/category/categoryController.js +++ b/components/category/categoryController.js @@ -21,3 +21,12 @@ exports.createCategory = catchAsync(async (req, res, next) => { data: category, }) }) + +exports.getCategory = catchAsync(async (req, res, next) => { + const category = await categoryService.getCategory(req.params.categoryId) + + res.status(200).json({ + status: 'success', + data: category, + }) +}) From 6675a9d36a5395568e4eb13ce11602c3c640a899 Mon Sep 17 00:00:00 2001 From: MohammadHMazarei Date: Sun, 29 Dec 2024 20:04:29 +0330 Subject: [PATCH 43/55] ADD: update a category by id PATCH endpoint --- components/category/categoryRoutes.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/components/category/categoryRoutes.js b/components/category/categoryRoutes.js index 9258db2..f236437 100644 --- a/components/category/categoryRoutes.js +++ b/components/category/categoryRoutes.js @@ -25,4 +25,10 @@ router.get( categoryController.getCategory ) +router.patch( + '/admin/update/:categoryId', + // perm.requires('updatecategory'), + categoryController.updateCategory +) + module.exports = router From 9d99b89232e70ada693f42a4cf526030fdcc7159 Mon Sep 17 00:00:00 2001 From: MohammadHMazarei Date: Sun, 29 Dec 2024 20:04:44 +0330 Subject: [PATCH 44/55] ADD: update a category by id logic --- components/category/categoryService.js | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/components/category/categoryService.js b/components/category/categoryService.js index fd37141..505953e 100644 --- a/components/category/categoryService.js +++ b/components/category/categoryService.js @@ -19,3 +19,13 @@ module.exports.getCategory = async (categoryId) => { return category } + +module.exports.updateCategory = async (categoryId, data) => { + const category = await Category.findOneAndUpdate({ _id: categoryId }, data, { new: true }) + + if (!category) { + throw new AppError('NoCategoryFound', 404, {}) + } + + return category +} From f0ab38be23fd88d6ab9ddf71f3aa4a1772da1aaa Mon Sep 17 00:00:00 2001 From: MohammadHMazarei Date: Sun, 29 Dec 2024 20:05:08 +0330 Subject: [PATCH 45/55] ADD: update a category by id handler function --- components/category/categoryController.js | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/components/category/categoryController.js b/components/category/categoryController.js index 3ad1278..dc7874b 100644 --- a/components/category/categoryController.js +++ b/components/category/categoryController.js @@ -30,3 +30,12 @@ exports.getCategory = catchAsync(async (req, res, next) => { data: category, }) }) + +exports.updateCategory = catchAsync(async (req, res, next) => { + const category = await categoryService.updateCategory(req.params.categoryId, req.body) + + res.status(200).json({ + status: 'success', + data: category, + }) +}) From 56e18ab4020ed0706e8cb4d43c83e6c4bd102351 Mon Sep 17 00:00:00 2001 From: MohammadHMazarei Date: Sun, 29 Dec 2024 20:19:49 +0330 Subject: [PATCH 46/55] ADD: delete a category by id DELETE endpoint --- components/category/categoryRoutes.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/components/category/categoryRoutes.js b/components/category/categoryRoutes.js index f236437..39f8488 100644 --- a/components/category/categoryRoutes.js +++ b/components/category/categoryRoutes.js @@ -31,4 +31,10 @@ router.patch( categoryController.updateCategory ) +router.delete( + '/admin/delete/:categoryId', + // perm.requires('deletecategory'), + categoryController.deleteCategory +) + module.exports = router From bf653737b7e2863b4e3bc55ee387e9a1a0fa9359 Mon Sep 17 00:00:00 2001 From: MohammadHMazarei Date: Sun, 29 Dec 2024 20:20:10 +0330 Subject: [PATCH 47/55] ADD: delete a category by id logic function --- components/category/categoryService.js | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/components/category/categoryService.js b/components/category/categoryService.js index 505953e..ab548ce 100644 --- a/components/category/categoryService.js +++ b/components/category/categoryService.js @@ -29,3 +29,13 @@ module.exports.updateCategory = async (categoryId, data) => { return category } + +module.exports.deleteCategory = async (categoryId) => { + const category = await Category.findByIdAndDelete(categoryId) + + if (!category) { + throw new AppError('NoCategoryFound', 404, {}) + } + + return null +} From 935dace57b836ce27919fdc13771059484fa0675 Mon Sep 17 00:00:00 2001 From: MohammadHMazarei Date: Sun, 29 Dec 2024 20:20:22 +0330 Subject: [PATCH 48/55] ADD: delete a category by id handler function --- components/category/categoryController.js | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/components/category/categoryController.js b/components/category/categoryController.js index dc7874b..e26323e 100644 --- a/components/category/categoryController.js +++ b/components/category/categoryController.js @@ -39,3 +39,12 @@ exports.updateCategory = catchAsync(async (req, res, next) => { data: category, }) }) + +exports.deleteCategory = catchAsync(async (req, res, next) => { + await categoryService.deleteCategory(req.params.categoryId) + + res.status(204).json({ + status: 'success', + data: null, + }) +}) From 24983b2f3fd221bff1c36bf1e0911464b3892747 Mon Sep 17 00:00:00 2001 From: MohammadHMazarei Date: Sun, 29 Dec 2024 20:28:47 +0330 Subject: [PATCH 49/55] ADD: get category tree GET endpoint --- components/category/categoryRoutes.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/components/category/categoryRoutes.js b/components/category/categoryRoutes.js index 39f8488..494804f 100644 --- a/components/category/categoryRoutes.js +++ b/components/category/categoryRoutes.js @@ -7,6 +7,12 @@ const router = new express.Router() router.use(loginCheck.loginProtect) +router.get( + '/admin/get-tree', + // perm.requires('getcategorytree'), + categoryController.getCategoryTree +) + router.get( '/admin/get-all', // perm.requires('getallcategories'), From 04ae44b3c91d9452b8835e5b8ec192e9e53d1c3f Mon Sep 17 00:00:00 2001 From: MohammadHMazarei Date: Sun, 29 Dec 2024 20:29:07 +0330 Subject: [PATCH 50/55] ADD: get category tree logic function --- components/category/categoryService.js | 54 ++++++++++++++++++++++++++ 1 file changed, 54 insertions(+) diff --git a/components/category/categoryService.js b/components/category/categoryService.js index ab548ce..f22ed30 100644 --- a/components/category/categoryService.js +++ b/components/category/categoryService.js @@ -1,6 +1,60 @@ const Category = require('./categoryModel') const AppError = require('../../globalErrorHandler/AppError') +module.exports.getCategoryTree = async () => { + const nodes = await Category.find({}) + let tree = [] + for (let i = 0; i < nodes.length; i++) { + if (!nodes[i].parentId) { + const label = nodes[i].nativeName + const children = [] + const key = nodes[i]._id.toString() + const href = '/categories/admin/update/' + nodes[i]._id + const a_attr = { href } + const root = { label, children, key, a_attr } + tree.push(root) + } + } + + tree = recursiveFunction(tree, nodes) + return tree +} + +const recursiveFunction = (tree, categories) => { + if (tree.length > 0) { + for (let i = 0; i < tree.length; i++) { + let subTree = [] + for (let j = 0; j < categories.length; j++) { + if (tree[i].key === categories[j].parentId?.toString()) { + const label = categories[j].nativeName.toString() + const children = [] + const key = categories[j]._id.toString() + const href = '/categories/admin/update/' + categories[j]._id + const a_attr = { href } + subTree.push({ label, children, key, a_attr }) + } + let index = findCategoryIndex(tree, tree[i].label) + tree[index].children = subTree + } + } + for (let i = 0; i < tree.length; i++) { + let index = findCategoryIndex(tree, tree[i].label) + recursiveFunction(tree[index].children, categories) + } + } + return tree +} + +function findCategoryIndex(array, label) { + let index + for (let i = 0; i < array.length; i++) { + if (array[i].label === label) { + index = i + return index + } + } +} + module.exports.getAllCategories = async () => { return Category.find().populate('parentId') } From d9b87624747d78da4e798ee312710c2569a2655c Mon Sep 17 00:00:00 2001 From: MohammadHMazarei Date: Sun, 29 Dec 2024 20:29:17 +0330 Subject: [PATCH 51/55] ADD: get category tree handler function --- components/category/categoryController.js | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/components/category/categoryController.js b/components/category/categoryController.js index e26323e..6aef053 100644 --- a/components/category/categoryController.js +++ b/components/category/categoryController.js @@ -1,8 +1,15 @@ const catchAsync = require('../../globalErrorHandler/catchAsync') -const AppError = require('../../globalErrorHandler/AppError') -const Category = require('./categoryModel') const categoryService = require('./categoryService') +exports.getCategoryTree = catchAsync(async (req, res, next) => { + const categoryTree = await categoryService.getCategoryTree() + + res.status(200).json({ + status: 'success', + data: categoryTree, + }) +}) + exports.getAllCategories = catchAsync(async (req, res, next) => { const categories = await categoryService.getAllCategories() From bb24b16f74dd441834322982fd16b1db0d1158ae Mon Sep 17 00:00:00 2001 From: MohammadHMazarei Date: Sun, 29 Dec 2024 20:30:01 +0330 Subject: [PATCH 52/55] ADD: related permissions --- components/category/categoryRoutes.js | 36 +++++---------------------- 1 file changed, 6 insertions(+), 30 deletions(-) diff --git a/components/category/categoryRoutes.js b/components/category/categoryRoutes.js index 494804f..ba29d6a 100644 --- a/components/category/categoryRoutes.js +++ b/components/category/categoryRoutes.js @@ -7,40 +7,16 @@ const router = new express.Router() router.use(loginCheck.loginProtect) -router.get( - '/admin/get-tree', - // perm.requires('getcategorytree'), - categoryController.getCategoryTree -) +router.get('/admin/get-tree', perm.requires('getcategorytree'), categoryController.getCategoryTree) -router.get( - '/admin/get-all', - // perm.requires('getallcategories'), - categoryController.getAllCategories -) +router.get('/admin/get-all', perm.requires('getallcategories'), categoryController.getAllCategories) -router.post( - '/admin/create', - // perm.requires('createcategory'), - categoryController.createCategory -) +router.post('/admin/create', perm.requires('createcategory'), categoryController.createCategory) -router.get( - '/admin/get-one/:categoryId', - // perm.requires('getcategory'), - categoryController.getCategory -) +router.get('/admin/get-one/:categoryId', perm.requires('getcategory'), categoryController.getCategory) -router.patch( - '/admin/update/:categoryId', - // perm.requires('updatecategory'), - categoryController.updateCategory -) +router.patch('/admin/update/:categoryId', perm.requires('updatecategory'), categoryController.updateCategory) -router.delete( - '/admin/delete/:categoryId', - // perm.requires('deletecategory'), - categoryController.deleteCategory -) +router.delete('/admin/delete/:categoryId', perm.requires('deletecategory'), categoryController.deleteCategory) module.exports = router From 82b384401c1ff24c79c114c10bf45734f62e166b Mon Sep 17 00:00:00 2001 From: MohammadHMazarei Date: Sun, 29 Dec 2024 23:06:40 +0330 Subject: [PATCH 53/55] ADD: mongoose-sequence npm package --- package-lock.json | 23 +++++++++++++++++++++++ package.json | 1 + 2 files changed, 24 insertions(+) diff --git a/package-lock.json b/package-lock.json index 38b6e87..7d63afc 100644 --- a/package-lock.json +++ b/package-lock.json @@ -19,6 +19,7 @@ "express": "^4.21.2", "jsonwebtoken": "^9.0.2", "mongoose": "^8.9.0", + "mongoose-sequence": "^6.0.1", "redis": "^4.7.0", "validator": "^13.12.0" }, @@ -138,6 +139,11 @@ "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==" }, + "node_modules/async": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz", + "integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==" + }, "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", @@ -842,6 +848,11 @@ "node": ">=12.0.0" } }, + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" + }, "node_modules/lodash.includes": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", @@ -1039,6 +1050,18 @@ "url": "https://opencollective.com/mongoose" } }, + "node_modules/mongoose-sequence": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/mongoose-sequence/-/mongoose-sequence-6.0.1.tgz", + "integrity": "sha512-uXnLCW9pu2V49Xw8BmdXdeRugd2mv+ntu3nT2Bbm33pNRmmvHE2GKA+8BASKoQt960McLX4VL78wkb492f6MoQ==", + "dependencies": { + "async": "^3.2.5", + "lodash": "^4.17.21" + }, + "peerDependencies": { + "mongoose": ">=5" + } + }, "node_modules/mongoose/node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", diff --git a/package.json b/package.json index 5313ac8..36fa04b 100644 --- a/package.json +++ b/package.json @@ -21,6 +21,7 @@ "express": "^4.21.2", "jsonwebtoken": "^9.0.2", "mongoose": "^8.9.0", + "mongoose-sequence": "^6.0.1", "redis": "^4.7.0", "validator": "^13.12.0" }, From a63c44a7bd60d0e89ded5df693542ab6c579e67c Mon Sep 17 00:00:00 2001 From: MohammadHMazarei Date: Sun, 29 Dec 2024 23:07:30 +0330 Subject: [PATCH 54/55] ADD: isActive and Auto incremental id for numId --- components/belonging/belongingModel.js | 26 +++++++++++++++++++++----- 1 file changed, 21 insertions(+), 5 deletions(-) diff --git a/components/belonging/belongingModel.js b/components/belonging/belongingModel.js index 5ac887e..b8d9a8c 100644 --- a/components/belonging/belongingModel.js +++ b/components/belonging/belongingModel.js @@ -1,5 +1,6 @@ const mongoose = require('mongoose') const ObjectID = mongoose.Schema.Types.ObjectId +const AutoIncrement = require('mongoose-sequence')(mongoose) const belongingSchema = new mongoose.Schema( { @@ -10,14 +11,14 @@ const belongingSchema = new mongoose.Schema( }, belongingCategory: { type: ObjectID, - ref: 'Category' + ref: 'Category', }, projectCode: { type: String, required: true, default: false, }, - relatedProjects : [String], + relatedProjects: [String], location: { type: { type: String, @@ -29,12 +30,27 @@ const belongingSchema = new mongoose.Schema( required: true, }, }, - documents: [String] + documents: [String], + numId: { + type: Number, + unique: true, + }, + isActive: { + type: Boolean, + default: true, + select: false, + }, }, - { timestamps: true } + { + toJSON: { virtuals: true }, + toObject: { virtuals: true }, + timestamps: true, + } ) -belongingSchema.index({ location: '2dsphere' }); // Enable geospatial queries +belongingSchema.plugin(AutoIncrement, { inc_field: 'numId' }) + +belongingSchema.index({ location: '2dsphere' }) // Enable geospatial queries const Belonging = mongoose.model('Belonging', belongingSchema) module.exports = Belonging From b760b7819991307105a4cd9ccac7d1371998cea8 Mon Sep 17 00:00:00 2001 From: MohammadHMazarei Date: Sun, 29 Dec 2024 23:08:08 +0330 Subject: [PATCH 55/55] UPDATE: put endpoint to patch --- components/belonging/belongingRoutes.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/belonging/belongingRoutes.js b/components/belonging/belongingRoutes.js index 0e8fc7e..e3715e1 100644 --- a/components/belonging/belongingRoutes.js +++ b/components/belonging/belongingRoutes.js @@ -13,7 +13,7 @@ router.post('/admin/create', perm.requires('createabelonging'), belongingControl router.get('/admin/get-one/:belongingId', perm.requires('getabelonging'), belongingController.getBelonging) -router.put('/admin/update/:belongingId', perm.requires('updateabelonging'), belongingController.updateBelonging) +router.patch('/admin/update/:belongingId', perm.requires('updateabelonging'), belongingController.updateBelonging) router.delete('/admin/delete/:belongingId', perm.requires('deleteabelonging'), belongingController.deleteBelonging)