diff --git a/src/config/constants.js b/src/config/constants.js index a7151a4..6950070 100644 --- a/src/config/constants.js +++ b/src/config/constants.js @@ -65,11 +65,29 @@ const ItemTransactionTypes = [ "RESERVE", "CHECK-IN", "CHECK-OUT", - "RESERVE" + "REPORT", + "ADJUST", ]; const ReportItemForTypes = ["LOCATION", "ISSUE", "INCIDENT"]; +const AllUIModules = [ + "Home::Explore Inventory", + "Home::Scan", + "Home::Receiving", + "Home::Shipping", + "Setup::Warehouse design", + "Setup::Inventory Definition", + "Setup::User & Access", + "Setup::Labelling", + "Report::Warehouse design", + "Report::Inventory Definition", + "Report::User & Access", + "Report::Labelling", + "Messages", + "Settings", +]; + module.exports = { UserActions, InventoryScopes, @@ -88,4 +106,5 @@ module.exports = { AUTHORIZATION_FAILURE_ERROR_MESSAGE, ItemTransactionTypes, ReportItemForTypes, + AllUIModules, }; diff --git a/src/config/db/connect.js b/src/config/db/connect.js new file mode 100644 index 0000000..121d027 --- /dev/null +++ b/src/config/db/connect.js @@ -0,0 +1,23 @@ +const mongoose = require("mongoose"); + +const { MONGODB_URI } = require("../env"); + + +const connect = async () => { + console.log("Connecting to MongoDB ..."); + await mongoose + .connect(MONGODB_URI, { + useNewUrlParser: true, + useUnifiedTopology: true, + }) + .then(() => { + console.log("Connected to MongoDB at: ", MONGODB_URI); + }) + .catch(console.error); + + mongoose.set("debug", true); +}; + +module.exports = { + connect, +}; diff --git a/src/config/db/seed-auth-data.js b/src/config/db/seed-auth-data.js new file mode 100644 index 0000000..f4c0f21 --- /dev/null +++ b/src/config/db/seed-auth-data.js @@ -0,0 +1,13 @@ +const db = require("./connect"); +const UserPermission = require("../../models/UserPermission"); +const { AllUIModules } = require("../constants"); +(async () => { + await db.connect(); + for (const UIModule of AllUIModules) { + const modulePermission = await UserPermission.findOne({ name: UIModule, allowedUIModules: [UIModule] }); + if (!modulePermission) { + await UserPermission.create({ name: UIModule, allowedUIModules: [UIModule] }); + } + } + process.exit(1); +})(); diff --git a/src/controller/user.controller.js b/src/controller/user.controller.js index f52323a..25deba4 100644 --- a/src/controller/user.controller.js +++ b/src/controller/user.controller.js @@ -6,6 +6,7 @@ const User = require("./../models/User"); const { JWT_SECRET, JWT_REFRESH_EXPIRY_TIME, JWT_ACCESS_EXPIRY_TIME } = require("./../config/env"); const UserRole = require("../models/UserRole"); const UserPermission = require("../models/UserPermission"); +const { AllUIModules } = require("../config/constants"); const createAccessToken = (id) => { return jwt.sign({ id }, JWT_SECRET, { @@ -123,4 +124,111 @@ module.exports = { ); res.send({ success: true, data: response }); }, + + getUIAccessControl: async (req, res, next) => { + try { + const user = res.locals.user; + + const userUIPermissions = []; + + for (const role of user.roles) { + for (const permission of role.permissions) { + if (AllUIModules.includes(permission.name) && !userUIPermissions.includes(permission.name)) { + userUIPermissions.push(permission.name); + } + } + } + + for (const permission of user.permissions) { + if (AllUIModules.includes(permission.name) && !userUIPermissions.includes(permission.name)) { + userUIPermissions.push(permission.name); + } + } + + res.send({ success: true, data: userUIPermissions }); + } catch (error) { + next(error); + } + }, + getAllUsers: async (req, res, next) => { + try { + let { page, perPage } = req.query; + page = page ? parseInt(page) : 0; + perPage = perPage ? parseInt(perPage) : 10; + + const result = await User.find( + {}, + { id: 1, fullName: 1, email: 1, roles: 1, permissions: 1, createdBy: 1 }, + { skip: page * perPage, limit: perPage } + ) + .populate({ path: "roles", populate: "permissions" }) + .populate("permissions") + .populate("createdBy"); + res.send({ success: true, data: result }); + } catch (error) { + next(error); + } + }, + getUserById: async (req, res, next) => { + const { id } = req.params; + if (!mongoose.isValidObjectId(id)) { + res.status(400).send({ success: false, error: "Invalid data for user ID" }); + } + + try { + const result = await User.findOne({ _id: id }, { id: 1, fullName: 1, email: 1, roles: 1, permissions: 1, createdBy: 1 }) + .populate({ path: "roles", populate: "permissions" }) + .populate("permissions") + .populate("createdBy"); + res.send({ success: true, data: result }); + } catch (error) { + next(error); + } + }, + createUser: async (req, res, next) => { + const { email, fullName, password } = req.body; + try { + const salt = await bcrypt.genSalt(); + const newUser = { + email: email, + fullName: fullName, + password: await bcrypt.hash(password, salt), + createdBy: res.locals.user, + }; + + const user = await User.create(newUser); + console.log({ msg: "new user created", user }); + + res.send({ success: true, data: user }); + } catch (err) { + console.log(err); + next(err); + } + }, + updateUser: async (req, res, next) => { + const { id } = req.params; + if (!mongoose.isValidObjectId(id)) { + res.status(400).send({ success: false, error: "Invalid data for user ID" }); + } + + const { email, fullName, password } = req.body; + try { + const user = await User.findById(id); + if (user) { + res.status(404).send({ success: false, error: "User not found" }); + } + const salt = await bcrypt.genSalt(); + + if (email) user.email = email; + if (fullName) user.fullName = fullName; + if (password) user.password = await bcrypt.hash(password, salt); + + await user.save(); + + res.send({ success: true, data: user }); + } catch (err) { + console.log(err); + next(err); + } + }, }; diff --git a/src/controller/user.router.js b/src/controller/user.router.js index 9840f0a..1695c5b 100644 --- a/src/controller/user.router.js +++ b/src/controller/user.router.js @@ -4,7 +4,14 @@ const { SuperAdminCheck, AuthenticateMiddleware } = require("./utils/authorize") router.post("/register", controller.registerUser); router.post("/login", controller.loginUser); + +router.get("/all", AuthenticateMiddleware, SuperAdminCheck, controller.getAllUsers); +router.get("/:id", AuthenticateMiddleware, SuperAdminCheck, controller.getUserById); +router.post("/create", AuthenticateMiddleware, SuperAdminCheck, controller.createUser); +router.post("/:id", AuthenticateMiddleware, SuperAdminCheck, controller.updateUser); + router.post("/:user/add-access", AuthenticateMiddleware, SuperAdminCheck, controller.addUserAccessControl); router.post("/:user/remove-access", AuthenticateMiddleware, SuperAdminCheck, controller.removeUserAccessControl); +router.get("/allowed-ui-modules", AuthenticateMiddleware, controller.getUIAccessControl); module.exports = router; diff --git a/src/index.js b/src/index.js index e3d6e9e..726f7a0 100644 --- a/src/index.js +++ b/src/index.js @@ -4,26 +4,18 @@ const helmet = require("helmet"); const cors = require("cors"); const morgan = require("morgan"); -const mongoose = require("mongoose"); + const { router } = require("./controller"); const { API_PORT, - MONGODB_URI, } = require("./config/env"); +const db = require("./config/db/connect"); + (async () => { console.log("Connecting to MongoDB ..."); - await mongoose - .connect(MONGODB_URI, { - useNewUrlParser: true, - useUnifiedTopology: true, - }) - .then(() => { - console.log("Connected to MongoDB at: ", MONGODB_URI); - }) - .catch(console.error); - mongoose.set("debug", true); + await db.connect(); const app = express(); diff --git a/src/models/User.js b/src/models/User.js index 4e7349c..8eab1a0 100644 --- a/src/models/User.js +++ b/src/models/User.js @@ -48,6 +48,10 @@ const schema = new mongoose.Schema( ref: "UserPermission", }, ], + createdBy: { + type: mongoose.Schema.Types.ObjectId, + ref: "User", + } }, { timestamps: true, diff --git a/src/models/UserPermission.js b/src/models/UserPermission.js index be2cf93..6059f8b 100644 --- a/src/models/UserPermission.js +++ b/src/models/UserPermission.js @@ -1,9 +1,5 @@ const mongoose = require("mongoose"); -const { - UserActions, - WarehouseScopes, - InventoryScopes, -} = require("./../config/constants"); +const { UserActions, WarehouseScopes, InventoryScopes, AllUIModules } = require("./../config/constants"); const schema = new mongoose.Schema( { @@ -37,6 +33,12 @@ const schema = new mongoose.Schema( }, }, ], + allowedUIModules: [ + { + type: String, + enum: AllUIModules, + }, + ], actions: [ { type: String,