From 6f88edf950f49dd5cda220d0af0f3865c4289a41 Mon Sep 17 00:00:00 2001 From: Foyzul Karim Date: Thu, 31 Mar 2022 22:13:22 +1100 Subject: [PATCH 1/3] work in progress --- server/setup/migrate.js | 6 + server/setup/permissions.js | 27 +++- server/setup/permissions.jsonc | 176 ++++++++++++++++++++- server/setup/resources.js | 34 +++- server/setup/roles.js | 22 ++- server/src/modules/auth/index.js | 15 +- server/src/modules/auth/model.js | 5 +- server/src/modules/auth/service.js | 40 +---- server/src/modules/auth/user-controller.js | 104 ++---------- server/src/modules/customer/model.js | 10 ++ server/src/modules/permission/model.js | 33 ++-- server/src/modules/product/model.js | 10 ++ server/src/modules/resource/model.js | 10 ++ server/src/modules/role/model.js | 10 ++ 14 files changed, 354 insertions(+), 148 deletions(-) diff --git a/server/setup/migrate.js b/server/setup/migrate.js index 52ca1f4..88b8fa8 100644 --- a/server/setup/migrate.js +++ b/server/setup/migrate.js @@ -3,6 +3,9 @@ require("dotenv").config(); const logger = require("../src/core/logger"); const { migrate: userMigrate } = require("./users"); +const { migrate: resourceMigrate } = require("./resources"); +const { migrate: roleMigrate } = require("./roles"); +const { migrate: permissionMigrate } = require("./permissions"); logger.info("Migration starting"); const isMongoDbUrl = JSON.parse( @@ -18,6 +21,9 @@ const migrate = async () => { await mongoose.connect(uri, options); logger.info("Connected to MongoDB"); await userMigrate(logger); + await resourceMigrate(logger); + await roleMigrate(logger); + await permissionMigrate(logger); logger.info(`Migration finished`); process.exit(0); }; diff --git a/server/setup/permissions.js b/server/setup/permissions.js index 3e5f930..20611f9 100644 --- a/server/setup/permissions.js +++ b/server/setup/permissions.js @@ -3,7 +3,12 @@ const parser = require("jsonc-parser"); const dataStr = fs.readFileSync("./setup/permissions.jsonc", "utf8"); -const { save, searchOne, update } = require("../src/core/repository"); +const { + save, + searchOne, + update, + updateAll, +} = require("../src/core/repository"); const { name: permissionModel } = require("../src/modules/permission/model"); const { name: resourceModel } = require("../src/modules/resource/model"); @@ -51,4 +56,22 @@ const seed = async (logger) => { logger.info(`Seeding users finished`); }; -module.exports = { seed }; +const migrate = async (logger) => { + logger.info(`Starting migration of permissions`); + const superadminUser = await searchOne({ username: "superadmin" }, "User"); + if (!superadminUser) { + throw new Error("Superadmin user not found"); + } + + await updateAll( + {}, + { + createdBy: superadminUser._id, + updatedBy: superadminUser._id, + }, + permissionModel + ); + logger.info(`Migration of permissions finished`); +}; + +module.exports = { seed, migrate }; diff --git a/server/setup/permissions.jsonc b/server/setup/permissions.jsonc index dfa0fa3..4a09c27 100644 --- a/server/setup/permissions.jsonc +++ b/server/setup/permissions.jsonc @@ -238,6 +238,120 @@ // Admin users api permissions + // admin roles api permissions + + // /api/roles/detail for Admin + { + "resourceName": "/api/roles/detail", + "resourceAlias": "Roles detail API", + "roleName": "admin", + "roleAlias": "Admin", + "isAllowed": true, + "isDisabled": false + }, + // /api/roles/create for Admin + { + "resourceName": "/api/roles/create", + "resourceAlias": "Roles create API", + "roleName": "admin", + "roleAlias": "Admin", + "isAllowed": true, + "isDisabled": false + }, + // /api/roles/update for Admin + { + "resourceName": "/api/roles/update", + "resourceAlias": "Roles update API", + "roleName": "admin", + "roleAlias": "Admin", + "isAllowed": true, + "isDisabled": false + }, + // /api/roles/search for Admin + { + "resourceName": "/api/roles/search", + "resourceAlias": "Roles search API", + "roleName": "admin", + "roleAlias": "Admin", + "isAllowed": true, + "isDisabled": false + }, + // /api/roles/count for Admin + { + "resourceName": "/api/roles/count", + "resourceAlias": "Roles count API", + "roleName": "admin", + "roleAlias": "Admin", + "isAllowed": true, + "isDisabled": false + }, + // /api/roles/delete for Admin + { + "resourceName": "/api/roles/delete", + "resourceAlias": "Roles delete API", + "roleName": "admin", + "roleAlias": "Admin", + "isAllowed": true, + "isDisabled": false + }, + + // admin permissions api permissions + + // /api/permissions/detail for Admin + { + "resourceName": "/api/permissions/detail", + "resourceAlias": "Permissions detail API", + "roleName": "admin", + "roleAlias": "Admin", + "isAllowed": true, + "isDisabled": false + }, + // /api/permissions/create for Admin + { + "resourceName": "/api/permissions/create", + "resourceAlias": "Permissions create API", + "roleName": "admin", + "roleAlias": "Admin", + "isAllowed": true, + "isDisabled": false + }, + // /api/permissions/update for admin + { + "resourceName": "/api/permissions/update", + "resourceAlias": "Permissions update API", + "roleName": "admin", + "roleAlias": "Admin", + "isAllowed": true, + "isDisabled": false + }, + // /api/permissions/search for admin + { + "resourceName": "/api/permissions/search", + "resourceAlias": "Permissions search API", + "roleName": "admin", + "roleAlias": "Admin", + "isAllowed": true, + "isDisabled": false + }, + // /api/permissions/count for admin + { + "resourceName": "/api/permissions/count", + "resourceAlias": "Permissions count API", + "roleName": "admin", + "roleAlias": "Admin", + "isAllowed": true, + "isDisabled": false + }, + // /api/permissions/delete for admin + { + "resourceName": "/api/permissions/delete", + "resourceAlias": "Permissions delete API", + "roleName": "admin", + "roleAlias": "Admin", + "isAllowed": true, + "isDisabled": false + }, + // /api/users/detail for admin { "resourceName": "/api/users/detail", @@ -301,7 +415,7 @@ "isAllowed": true, "isDisabled": false }, - // /api/roles/search for superadmin + // /api/roles/search for admin { "resourceName": "/api/roles/search", "resourceAlias": "Roles search API", @@ -460,6 +574,36 @@ "isDisabled": false }, + // admin permissions for roles pages + + // /roles permissions for admin + { + "resourceName": "/roles", + "resourceAlias": "Roles menu", + "roleName": "admin", + "roleAlias": "Admin", + "isAllowed": true, + "isDisabled": false + }, + // /roles/list permissions for admin + { + "resourceName": "/roles/list", + "resourceAlias": "Roles list", + "roleName": "admin", + "roleAlias": "Admin", + "isAllowed": true, + "isDisabled": false + }, + // /roles/new permission for admin + { + "resourceName": "/roles/new", + "resourceAlias": "Roles new", + "roleName": "admin", + "roleAlias": "Admin", + "isAllowed": true, + "isDisabled": false + }, + // superadmin permissions for resources pages // /resources permissions for superadmin @@ -520,6 +664,36 @@ "isDisabled": false }, + // admin permissions for permissions pages + + // /permissions permissions for admin + { + "resourceName": "/permissions", + "resourceAlias": "Permissions menu", + "roleName": "admin", + "roleAlias": "Admin", + "isAllowed": true, + "isDisabled": false + }, + // /permissions/list permissions for admin + { + "resourceName": "/permissions/list", + "resourceAlias": "Permissions list", + "roleName": "admin", + "roleAlias": "Admin", + "isAllowed": true, + "isDisabled": false + }, + // /permissions/new permission for admin + { + "resourceName": "/permissions/new", + "resourceAlias": "Permissions new", + "roleName": "admin", + "roleAlias": "Admin", + "isAllowed": true, + "isDisabled": false + }, + // user permissions for products pages // /products permissions for user diff --git a/server/setup/resources.js b/server/setup/resources.js index 960ebc2..1bfa638 100644 --- a/server/setup/resources.js +++ b/server/setup/resources.js @@ -2,25 +2,43 @@ const fs = require("fs"); const parser = require("jsonc-parser"); const dataStr = fs.readFileSync("./setup/resources.jsonc", "utf8"); -const { save, searchOne } = require("../src/core/repository"); -const { name: model } = require("../src/modules/resource/model"); +const { save, searchOne, updateAll } = require("../src/core/repository"); +const { name: modelName } = require("../src/modules/resource/model"); // const model = "Resource"; const seed = async (logger) => { const data = parser.parse(dataStr); await Promise.all( data.map(async (item) => { - logger.info(`Checking if ${model} ${item.name} exists`); - const itemExists = await searchOne({ name: item.name }, model); + logger.info(`Checking if ${modelName} ${item.name} exists`); + const itemExists = await searchOne({ name: item.name }, modelName); if (!itemExists) { - const savedItem = await save(item, model); + const savedItem = await save(item, modelName); logger.info(`Saved role id: ${savedItem._id}`); } else { - logger.info(`${model} ${item.name} already exists`); + logger.info(`${modelName} ${item.name} already exists`); } }) ); - logger.info(`${model} seeding finished`); + logger.info(`${modelName} seeding finished`); }; -module.exports = { seed }; +const migrate = async (logger) => { + logger.info(`${modelName} starting`); + const superadminUser = await searchOne({ username: "superadmin" }, "User"); + if (!superadminUser) { + throw new Error("Superadmin user not found"); + } + + await updateAll( + {}, + { + createdBy: superadminUser._id, + updatedBy: superadminUser._id, + }, + modelName + ); + logger.info(`${modelName} seeding finished`); +}; + +module.exports = { seed, migrate }; diff --git a/server/setup/roles.js b/server/setup/roles.js index f0343de..4075819 100644 --- a/server/setup/roles.js +++ b/server/setup/roles.js @@ -1,5 +1,5 @@ const data = require("./roles.json"); -const { save, searchOne } = require("../src/core/repository"); +const { save, searchOne, updateAll } = require("../src/core/repository"); const { modelName } = require("../src/modules/role/service"); const seed = async (logger) => { @@ -18,4 +18,22 @@ const seed = async (logger) => { logger.info(`Seeding ${modelName} finished`); }; -module.exports = { seed }; +const migrate = async (logger) => { + logger.info(`Starting migration of ${modelName}`); + const superadminUser = await searchOne({ name: "superadmin" }, modelName); + if (!superadminUser) { + throw new Error(`Superadmin user not found`); + } + + await updateAll( + {}, + { + createdBy: superadminUser._id, + updatedBy: superadminUser._id, + }, + modelName + ); + logger.info(`Migration of ${modelName} finished`); +}; + +module.exports = { seed, migrate }; diff --git a/server/src/modules/auth/index.js b/server/src/modules/auth/index.js index a313e77..d62bd2b 100644 --- a/server/src/modules/auth/index.js +++ b/server/src/modules/auth/index.js @@ -5,9 +5,22 @@ const { authorizeRequest, } = require("../../common/middlewares"); +const { name: ModelName } = require("./model"); + +const processRequest = async (req, res, next) => { + req.modelName = ModelName; + return next(); +}; + const init = async (app) => { app.use("/api/auth", authRoutes); - app.use("/api/users", authenticateRequest, authorizeRequest, userRoutes); + app.use( + "/api/users", + authenticateRequest, + authorizeRequest, + processRequest, + userRoutes + ); return app; }; diff --git a/server/src/modules/auth/model.js b/server/src/modules/auth/model.js index b96af94..49ffd22 100644 --- a/server/src/modules/auth/model.js +++ b/server/src/modules/auth/model.js @@ -51,7 +51,8 @@ userSchema.post("save", (error, doc, next) => { } }); -const User = mongoose.model("User", userSchema); +const ModelName = "User"; +const User = mongoose.model(ModelName, userSchema); async function getPasswordHash(password) { const hash = await bcrypt.hash(password, 10); @@ -75,4 +76,4 @@ User.setPassword = async (model, newPassword) => { return { passwordHash, ...model }; }; -module.exports = User; +module.exports = { Model: User, name: ModelName }; diff --git a/server/src/modules/auth/service.js b/server/src/modules/auth/service.js index 4400f0e..9b2a269 100644 --- a/server/src/modules/auth/service.js +++ b/server/src/modules/auth/service.js @@ -4,15 +4,13 @@ const { NotFound } = require("../../common/errors"); const { save, getById, - update, searchOne, dynamicSearch, updateAll, + update, } = require("../../core/repository"); -const Model = require("./model"); - -const ModelName = "User"; +const { Model, name: ModelName } = require("./model"); const changePassword = async (user, newPassword) => { const id = user._id; @@ -75,7 +73,7 @@ const tryCreateUser = async (user) => { return id; }; -const prepareQuery = (payload) => { +const getQuery = (payload) => { const createdBySubQuery = { $or: [ { createdBy: ObjectId(payload.userId) }, @@ -102,20 +100,6 @@ const prepareQuery = (payload) => { return query; }; -const search = async (payload) => { - const query = prepareQuery(payload); - const data = await Model.collection.find(query).skip(0).limit(20); - const items = { data: await data.toArray(), total: 200 }; - return items; -}; - -const count = async (payload) => { - const query = prepareQuery(payload); - const t = await Model.collection.find(query).count(); - const items = { total: t }; - return items; -}; - const searchPermissions = async (roleId) => { const permissions = await dynamicSearch( { @@ -127,18 +111,6 @@ const searchPermissions = async (roleId) => { return permissions; }; -// const searchClientPermissions = async (roleId) => { -// const clientResources = await dynamicSearch({ type: "client" }, "Resource"); -// const permissions = await dynamicSearch( -// { -// roleId: ObjectId(roleId), -// isAllowed: true, -// }, -// "Permission" -// ); -// return permissions; -// }; - const getPermittedUserById = async (id, userId) => { const user = await getById(id, ModelName); if (user) { @@ -154,16 +126,16 @@ const getPermittedUserById = async (id, userId) => { module.exports = { save, - update, getById: getPermittedUserById, searchOne, changePassword, checkUser, createUser, getByUsername, - search, - count, tryCreateUser, searchPermissions, updateAll, + update, + getQuery, + ModelName, }; diff --git a/server/src/modules/auth/user-controller.js b/server/src/modules/auth/user-controller.js index 5f00f36..f895fe3 100644 --- a/server/src/modules/auth/user-controller.js +++ b/server/src/modules/auth/user-controller.js @@ -1,52 +1,19 @@ /* eslint-disable no-undef */ const express = require("express"); +const { tryCreateUser, searchOne, getQuery, ModelName } = require("./service"); const { - update, - deleteById, - getById, - search, - count, - tryCreateUser, - searchOne, -} = require("./service"); + getByIdHandler, + updateHandler, + searchHandler: baseSearchHandler, + countHandler: baseCountHandler, + deleteHandler, +} = require("../../core/controller"); const { validateUserUpdate, validateUserCreate } = require("./request"); const { handleValidation } = require("../../common/middlewares"); -const { NotFound } = require("../../common/errors"); const router = express.Router(); -const ModelName = "User"; -const getHandler = async (req, res, next) => { - try { - const items = [ - { id: 1, name: "User 1" }, - { id: 2, name: "User 2" }, - ]; - const result = { - data: items, - total: items.length, - success: true, - }; - return res.status(200).send(result); - } catch (error) { - return next(error, req, res); - } -}; - -const getByIdHandler = async (req, res, next) => { - try { - const { id } = req.query; - const item = await getById(id, req.user.id); - if (item) { - return res.status(200).send(item); - } - throw new NotFound(`${ModelName} not found by the id: ${id}`); - } catch (error) { - return next(error, req, res); - } -}; - -const postHandler = async (req, res, next) => { +const saveHandler = async (req, res, next) => { try { const user = req.body; const id = await tryCreateUser(user); @@ -65,53 +32,15 @@ const postHandler = async (req, res, next) => { }; const searchHandler = async (req, res, next) => { - try { - if (!req.body.pageSize) { - req.body.pageSize = 10; - } - if (!req.body.current) { - req.body.current = 1; - } - const query = { ...req.body, userId: req.user.id }; - const result = await search(query); - const response = { success: true, ...result }; - return res.status(200).send(response); - } catch (error) { - return next(error, req, res); - } + const query = { ...req.body, userId: req.user.id }; + req.searchQuery = getQuery(query); + return baseSearchHandler(req, res, next); }; const countHandler = async (req, res, next) => { - try { - const query = { ...req.body, userId: req.user.id }; - const result = await count(query); - const response = { success: true, ...result }; - return res.status(200).send(response); - } catch (error) { - return next(error, req, res); - } -}; - -const putHandler = async (req, res, next) => { - try { - const { body } = req; - const id = await update(body, ModelName); - return res.status(200).send(id); - } catch (error) { - return next(error, req, res); - } -}; - -const deleteHandler = async (req, res, next) => { - try { - const { id } = req.query; - await deleteById(id, ModelName); - return res - .status(200) - .send({ success: true, message: `${ModelName} deleted` }); - } catch (error) { - return next(error, req, res); - } + const query = { ...req.body, userId: req.user.id }; + req.searchQuery = getQuery(query); + return baseCountHandler(req, res, next); }; const checkUserHandler = async (req, res) => { @@ -124,10 +53,9 @@ const checkUserHandler = async (req, res) => { return res.status(200).send({ status: "error", message: "User not found" }); }; -router.get("/", getHandler); router.get("/detail", getByIdHandler); -router.post("/create", handleValidation(validateUserCreate), postHandler); -router.put("/update", handleValidation(validateUserUpdate), putHandler); +router.post("/create", handleValidation(validateUserCreate), saveHandler); +router.put("/update", handleValidation(validateUserUpdate), updateHandler); router.post("/search", searchHandler); router.post("/count", countHandler); router.delete("/delete", deleteHandler); diff --git a/server/src/modules/customer/model.js b/server/src/modules/customer/model.js index 51fe0e9..9fb54b8 100644 --- a/server/src/modules/customer/model.js +++ b/server/src/modules/customer/model.js @@ -11,6 +11,16 @@ const customerSchema = new mongoose.Schema( state: { type: String, required: true }, postcode: { type: String, required: true }, country: { type: String, required: true }, + createdBy: { + type: mongoose.Schema.Types.ObjectId, + required: true, + default: "000000000000", + }, + updatedBy: { + type: mongoose.Schema.Types.ObjectId, + required: true, + default: "000000000000", + }, }, { timestamps: true } ); diff --git a/server/src/modules/permission/model.js b/server/src/modules/permission/model.js index dfd1f1f..30a8617 100644 --- a/server/src/modules/permission/model.js +++ b/server/src/modules/permission/model.js @@ -2,16 +2,29 @@ const mongoose = require("mongoose"); // Schema -const permissionSchema = new mongoose.Schema({ - roleId: { type: mongoose.Schema.Types.ObjectId, ref: "Role" }, - roleName: { type: String, required: true }, - roleAlias: { type: String, required: true }, - resourceId: { type: mongoose.Schema.Types.ObjectId, ref: "Resource" }, - resourceName: { type: String, required: true }, - resourceAlias: { type: String, required: true }, - isAllowed: { type: Boolean, required: true }, - isDisabled: { type: Boolean, required: true }, -}); +const permissionSchema = new mongoose.Schema( + { + roleId: { type: mongoose.Schema.Types.ObjectId, ref: "Role" }, + roleName: { type: String, required: true }, + roleAlias: { type: String, required: true }, + resourceId: { type: mongoose.Schema.Types.ObjectId, ref: "Resource" }, + resourceName: { type: String, required: true }, + resourceAlias: { type: String, required: true }, + isAllowed: { type: Boolean, required: true }, + isDisabled: { type: Boolean, required: true }, + createdBy: { + type: mongoose.Schema.Types.ObjectId, + required: true, + default: "000000000000", + }, + updatedBy: { + type: mongoose.Schema.Types.ObjectId, + required: true, + default: "000000000000", + }, + }, + { timestamps: true } +); // reference model const ModelName = "Permission"; diff --git a/server/src/modules/product/model.js b/server/src/modules/product/model.js index bb8a23e..9b10c8e 100644 --- a/server/src/modules/product/model.js +++ b/server/src/modules/product/model.js @@ -11,6 +11,16 @@ const schema = new mongoose.Schema( size: { type: Number, required: true }, manufacturingDate: { type: Date, required: true }, expiryDate: { type: Date, required: true }, + createdBy: { + type: mongoose.Schema.Types.ObjectId, + required: true, + default: "000000000000", + }, + updatedBy: { + type: mongoose.Schema.Types.ObjectId, + required: true, + default: "000000000000", + }, }, { timestamps: true } ); diff --git a/server/src/modules/resource/model.js b/server/src/modules/resource/model.js index ced0b23..524368a 100644 --- a/server/src/modules/resource/model.js +++ b/server/src/modules/resource/model.js @@ -6,6 +6,16 @@ const schema = new mongoose.Schema( name: { type: String, unique: true, required: true }, alias: { type: String, unique: true, required: true }, type: { type: String, required: true }, + createdBy: { + type: mongoose.Schema.Types.ObjectId, + required: true, + default: "000000000000", + }, + updatedBy: { + type: mongoose.Schema.Types.ObjectId, + required: true, + default: "000000000000", + }, }, { timestamps: true } ); diff --git a/server/src/modules/role/model.js b/server/src/modules/role/model.js index e162171..f371286 100644 --- a/server/src/modules/role/model.js +++ b/server/src/modules/role/model.js @@ -8,6 +8,16 @@ const schema = new mongoose.Schema( isAdmin: { type: Boolean }, alias: { type: String, unique: true, required: true }, permissions: [{ type: mongoose.Schema.Types.ObjectId, ref: "Permission" }], + createdBy: { + type: mongoose.Schema.Types.ObjectId, + required: true, + default: "000000000000", + }, + updatedBy: { + type: mongoose.Schema.Types.ObjectId, + required: true, + default: "000000000000", + }, }, { timestamps: true } ); From bf5c22568648870b0dde9d1b277f789a43e2b897 Mon Sep 17 00:00:00 2001 From: Foyzul Karim Date: Thu, 31 Mar 2022 23:46:30 +1100 Subject: [PATCH 2/3] Implemented logic to load only own data --- .../src/pages/role/role-entry/index.jsx | 12 ++- client-pro/src/pages/role/role-list/index.jsx | 76 +++++++++++-------- client-pro/src/pages/user/user-list/index.jsx | 7 +- server/setup/permissions.js | 2 +- server/setup/permissions.jsonc | 50 +++++++++++- server/setup/roles.js | 4 +- server/setup/users.js | 2 +- server/src/modules/permission/controller.js | 4 +- server/src/modules/permission/service.js | 19 ++--- server/src/modules/role/controller.js | 4 +- server/src/modules/role/model.js | 13 ++++ server/src/modules/role/request.js | 10 ++- server/src/modules/role/service.js | 16 +++- 13 files changed, 160 insertions(+), 59 deletions(-) diff --git a/client-pro/src/pages/role/role-entry/index.jsx b/client-pro/src/pages/role/role-entry/index.jsx index 00dec5c..402330e 100644 --- a/client-pro/src/pages/role/role-entry/index.jsx +++ b/client-pro/src/pages/role/role-entry/index.jsx @@ -30,7 +30,17 @@ const EntryForm = (props) => { const onFinish = async (values) => { console.log(values, form); - run(values); + // run(values); + const result = await save(values); + console.log(result); + + if (result instanceof Error) { + message.error(result.message); + } + else { + message.success(result.message); + form.resetFields(); + } }; return ( diff --git a/client-pro/src/pages/role/role-list/index.jsx b/client-pro/src/pages/role/role-list/index.jsx index 2fb8df2..e4616e2 100644 --- a/client-pro/src/pages/role/role-list/index.jsx +++ b/client-pro/src/pages/role/role-list/index.jsx @@ -3,9 +3,52 @@ import { Button, message, Pagination, Form, Row, Col, Input, DatePicker, Modal } import React, { useState, useRef, useEffect } from 'react'; import { PageContainer, } from '@ant-design/pro-layout'; import ProTable from '@ant-design/pro-table'; -import { history } from 'umi'; +import { history, useAccess } from 'umi'; import { count, search, remove } from '../service'; +const DeleteButton = (props) => { + + const { confirm } = Modal; + const { elementId } = props; + + const showDeleteConfirm = (item) => { + confirm({ + title: `Do you Want to delete ${item.name}?`, + icon: , + content: `${item.name} will be deleted permanently.`, + okText: 'Yes', + okType: 'danger', + cancelText: 'No', + onOk: async () => { + console.log('OK'); + const r = await remove(item._id); + if (r.success) { + message.success(r.message); + setFetchRoles(true); + } + }, + onCancel() { + console.log('Cancel'); + }, + }); + }; + + const access = useAccess(); + const isVisible = access.canShow(elementId); + if (isVisible) { + const isDisabled = access.isDisabled(elementId); + return isDisabled ? Delete : { + showDeleteConfirm(props.record); + }} + > + Delete + ; + } + return null; +} + const TableList = () => { const actionRef = useRef(); @@ -36,28 +79,6 @@ const TableList = () => { } }; - const showDeleteConfirm = (product) => { - confirm({ - title: `Do you Want to delete ${product.name}?`, - icon: , - content: `${product.name} will be deleted permanently.`, - okText: 'Yes', - okType: 'danger', - cancelText: 'No', - onOk: async () => { - console.log('OK'); - const r = await remove(product._id); - if (r.success) { - message.success(r.message); - setFetchRoles(true); - } - }, - onCancel() { - console.log('Cancel'); - }, - }); - }; - const fetchRoleCount = async () => { const result = await count({ ...searchObject }); setTotal(result.total); @@ -124,14 +145,7 @@ const TableList = () => { dataIndex: 'option', valueType: 'option', render: (_, record) => [ - { - showDeleteConfirm(record); - }} - > - Delete - , + , ], }, ]; diff --git a/client-pro/src/pages/user/user-list/index.jsx b/client-pro/src/pages/user/user-list/index.jsx index af1b9a5..b13d621 100644 --- a/client-pro/src/pages/user/user-list/index.jsx +++ b/client-pro/src/pages/user/user-list/index.jsx @@ -9,6 +9,7 @@ import { count, search, remove } from '../service'; const DeleteButton = (props) => { const { confirm } = Modal; + const { elementId } = props; const showDeleteConfirm = (product) => { confirm({ @@ -33,9 +34,9 @@ const DeleteButton = (props) => { }; const access = useAccess(); - const isVisible = access.canShow('user-list-delete-btn'); + const isVisible = access.canShow(elementId); if (isVisible) { - const isDisabled = access.isDisabled('user-list-delete-btn'); + const isDisabled = access.isDisabled(elementId); return isDisabled ? Delete : { @@ -147,7 +148,7 @@ const TableList = () => { dataIndex: 'option', valueType: 'option', render: (_, record) => [ - , + , ], }, ]; diff --git a/server/setup/permissions.js b/server/setup/permissions.js index 20611f9..4b47fe2 100644 --- a/server/setup/permissions.js +++ b/server/setup/permissions.js @@ -64,7 +64,7 @@ const migrate = async (logger) => { } await updateAll( - {}, + { createdBy: { $exists: false } }, { createdBy: superadminUser._id, updatedBy: superadminUser._id, diff --git a/server/setup/permissions.jsonc b/server/setup/permissions.jsonc index 4a09c27..eacf9b1 100644 --- a/server/setup/permissions.jsonc +++ b/server/setup/permissions.jsonc @@ -291,6 +291,17 @@ "resourceAlias": "Roles delete API", "roleName": "admin", "roleAlias": "Admin", + "isAllowed": false, + "isDisabled": false + }, + + // admin permissions for resources + // /api/resources/search for admin + { + "resourceName": "/api/resources/search", + "resourceAlias": "Resources search API", + "roleName": "admin", + "roleAlias": "Admin", "isAllowed": true, "isDisabled": false }, @@ -348,7 +359,7 @@ "resourceAlias": "Permissions delete API", "roleName": "admin", "roleAlias": "Admin", - "isAllowed": true, + "isAllowed": false, "isDisabled": false }, @@ -724,6 +735,7 @@ "isDisabled": false }, + // user list delete button { "resourceName": "user-list-delete-btn", "resourceAlias": "Delete user button", @@ -739,5 +751,41 @@ "roleAlias": "Superadmin", "isAllowed": true, "isDisabled": false + }, + + // role list delete button + { + "resourceName": "role-list-delete-btn", + "resourceAlias": "Delete role button", + "roleName": "admin", + "roleAlias": "Admin", + "isAllowed": true, + "isDisabled": true + }, + { + "resourceName": "role-list-delete-btn", + "resourceAlias": "Delete role button", + "roleName": "superadmin", + "roleAlias": "Superadmin", + "isAllowed": true, + "isDisabled": false + }, + + // permission list delete button + { + "resourceName": "permission-list-delete-btn", + "resourceAlias": "Delete permission button", + "roleName": "admin", + "roleAlias": "Admin", + "isAllowed": true, + "isDisabled": true + }, + { + "resourceName": "permission-list-delete-btn", + "resourceAlias": "Delete permission button", + "roleName": "superadmin", + "roleAlias": "Superadmin", + "isAllowed": true, + "isDisabled": false } ] diff --git a/server/setup/roles.js b/server/setup/roles.js index 4075819..69e1ac9 100644 --- a/server/setup/roles.js +++ b/server/setup/roles.js @@ -20,13 +20,13 @@ const seed = async (logger) => { const migrate = async (logger) => { logger.info(`Starting migration of ${modelName}`); - const superadminUser = await searchOne({ name: "superadmin" }, modelName); + const superadminUser = await searchOne({ username: "superadmin" }, "User"); if (!superadminUser) { throw new Error(`Superadmin user not found`); } await updateAll( - {}, + { createdBy: { $exists: false } }, { createdBy: superadminUser._id, updatedBy: superadminUser._id, diff --git a/server/setup/users.js b/server/setup/users.js index 78e2ca1..d0c5f2f 100644 --- a/server/setup/users.js +++ b/server/setup/users.js @@ -48,7 +48,7 @@ const migrate = async (logger) => { } const response = await updateAll( - {}, + { createdBy: { $exists: false } }, { createdBy: superadminUser._id, updatedBy: superadminUser._id, diff --git a/server/src/modules/permission/controller.js b/server/src/modules/permission/controller.js index 43698fe..b63b510 100644 --- a/server/src/modules/permission/controller.js +++ b/server/src/modules/permission/controller.js @@ -14,12 +14,12 @@ const { handleValidation } = require("../../common/middlewares"); const router = express.Router(); const searchHandler = async (req, res, next) => { - req.searchQuery = getQuery(req.body); + req.searchQuery = getQuery({ ...req.body, userId: req.user.id }); return baseSearchHandler(req, res, next); }; const countHandler = async (req, res, next) => { - req.searchQuery = getQuery(req.body); + req.searchQuery = getQuery({ ...req.body, userId: req.user.id }); return baseCountHandler(req, res, next); }; diff --git a/server/src/modules/permission/service.js b/server/src/modules/permission/service.js index 20bec52..9997f02 100644 --- a/server/src/modules/permission/service.js +++ b/server/src/modules/permission/service.js @@ -2,10 +2,14 @@ const { ObjectId } = require("mongoose").Types; const { name } = require("./model"); const getQuery = (payload) => { + const createdBySubQuery = { createdBy: ObjectId(payload.userId) }; + const subQueries = []; + subQueries.push(createdBySubQuery); let query = {}; let roleQuery = {}; if (payload.roleId) { roleQuery = { roleId: ObjectId(payload.roleId) }; + subQueries.push(roleQuery); } let nameQuery = []; @@ -14,19 +18,16 @@ const getQuery = (payload) => { $or: [ { roleAlias: { $regex: payload.name, $options: "i" } }, { resourceAlias: { $regex: payload.name, $options: "i" } }, + { roleName: { $regex: payload.name, $options: "i" } }, + { resourceName: { $regex: payload.name, $options: "i" } }, ], }; + subQueries.push(nameQuery); } - if (payload.name && payload.roleId) { - query = { - $and: [roleQuery, nameQuery], - }; - } else if (payload.name) { - query = nameQuery; - } else if (payload.roleId) { - query = roleQuery; - } + query = { + $and: [...subQueries], + }; return query; }; diff --git a/server/src/modules/role/controller.js b/server/src/modules/role/controller.js index 43698fe..b63b510 100644 --- a/server/src/modules/role/controller.js +++ b/server/src/modules/role/controller.js @@ -14,12 +14,12 @@ const { handleValidation } = require("../../common/middlewares"); const router = express.Router(); const searchHandler = async (req, res, next) => { - req.searchQuery = getQuery(req.body); + req.searchQuery = getQuery({ ...req.body, userId: req.user.id }); return baseSearchHandler(req, res, next); }; const countHandler = async (req, res, next) => { - req.searchQuery = getQuery(req.body); + req.searchQuery = getQuery({ ...req.body, userId: req.user.id }); return baseCountHandler(req, res, next); }; diff --git a/server/src/modules/role/model.js b/server/src/modules/role/model.js index f371286..1b2e6c4 100644 --- a/server/src/modules/role/model.js +++ b/server/src/modules/role/model.js @@ -1,4 +1,5 @@ const mongoose = require("mongoose"); +const { MongoError } = require("../../common/errors"); // schema const schema = new mongoose.Schema( @@ -34,6 +35,18 @@ schema.index({ updatedAt: 1 }); schema.index({ isSuperAdmin: 1 }); schema.index({ isAdmin: 1 }); +schema.post("save", (error, doc, next) => { + if (error.name === "MongoError" && error.code === 11000) { + // if error.message contains the substring 'duplicate key error' then it's a duplicate username + if (error.message.includes("duplicate key error")) { + const errorMessage = `Name already exists`; + next(new MongoError(errorMessage)); + } else next(new MongoError(error.message)); + } else { + next(); + } +}); + const ModelName = "Role"; // reference model // const Role = mongoose.model("Role", schema); diff --git a/server/src/modules/role/request.js b/server/src/modules/role/request.js index dce588f..4c14859 100644 --- a/server/src/modules/role/request.js +++ b/server/src/modules/role/request.js @@ -6,9 +6,15 @@ const schema = Joi.object().keys({ alias: Joi.string().required(), }); -const validate = (data) => { +const validate = (data, user) => { const result = schema.validate(data); - result.value = { isSuperAdmin: false, isAdmin: false, ...data }; + result.value = { + ...data, + isSuperAdmin: false, + isAdmin: false, + createdBy: user.id, + updatedBy: user.id, + }; return result; }; diff --git a/server/src/modules/role/service.js b/server/src/modules/role/service.js index 0d5430b..663a6fe 100644 --- a/server/src/modules/role/service.js +++ b/server/src/modules/role/service.js @@ -1,12 +1,20 @@ +const { ObjectId } = require("mongoose").Types; const { name } = require("./model"); const getQuery = (payload) => { - let query = {}; + const createdBySubQuery = { createdBy: ObjectId(payload.userId) }; + + let query = createdBySubQuery; if (payload.name) { query = { - $or: [ - { name: { $regex: payload.name, $options: "i" } }, - { alias: { $regex: payload.name, $options: "i" } }, + $and: [ + createdBySubQuery, + { + $or: [ + { name: { $regex: payload.name, $options: "i" } }, + { alias: { $regex: payload.name, $options: "i" } }, + ], + }, ], }; } From 01ce22e4c4a39d5c663a904a07bbd67ab7ca7b09 Mon Sep 17 00:00:00 2001 From: Foyzul Karim Date: Fri, 1 Apr 2022 20:06:28 +1100 Subject: [PATCH 3/3] Fixed data seeding and load dropdowns --- client-pro/src/pages/permission/entry/index.jsx | 4 +++- server/src/core/controller.js | 10 +++++++++- server/src/core/repository.js | 11 +++++++++++ server/src/modules/auth/service.js | 9 ++++++--- server/src/modules/permission/request.js | 8 ++++++-- 5 files changed, 35 insertions(+), 7 deletions(-) diff --git a/client-pro/src/pages/permission/entry/index.jsx b/client-pro/src/pages/permission/entry/index.jsx index 104f3cb..e70573d 100644 --- a/client-pro/src/pages/permission/entry/index.jsx +++ b/client-pro/src/pages/permission/entry/index.jsx @@ -1,5 +1,5 @@ import React from 'react'; -import { Form, Card, message } from 'antd'; +import { Form, Card, message, AutoComplete } from 'antd'; import ProForm, { ProFormCheckbox, ProFormSelect, @@ -12,6 +12,7 @@ const EntryForm = (props) => { const [form] = Form.useForm(); const [role, setRole] = React.useState(null); const [resource, setResource] = React.useState(null); + const [resources, setResources] = React.useState([]); // get roles const fetchRoles = async () => { @@ -24,6 +25,7 @@ const EntryForm = (props) => { const fetchResources = async () => { const result = await getResources(); const options = result.data.map(r => ({ label: r.alias, value: r._id, resource: r })); + setResources(options); return options; }; diff --git a/server/src/core/controller.js b/server/src/core/controller.js index e027951..a030c8f 100644 --- a/server/src/core/controller.js +++ b/server/src/core/controller.js @@ -2,6 +2,7 @@ const { NotFound } = require("../common/errors"); const { getById, search, + getDropdownData, count, save, update, @@ -27,7 +28,14 @@ const searchHandler = async (req, res, next) => { const ModelName = req.modelName; const { body } = req; req.log.info({ body }, `search ${ModelName}`); - const data = await search(body, req.searchQuery, ModelName); + const data = + body.pageSize === -1 + ? await getDropdownData( + req.searchQuery, + { alias: 1, name: 1 }, + ModelName + ) + : await search(body, req.searchQuery, ModelName); return res.status(200).send({ data, total: 0 }); } catch (error) { return next(error, req, res); diff --git a/server/src/core/repository.js b/server/src/core/repository.js index f5dadea..3ad212c 100644 --- a/server/src/core/repository.js +++ b/server/src/core/repository.js @@ -82,6 +82,16 @@ const search = async (payload, query, modelName) => { return data; }; +const getDropdownData = async (query, project, modelName) => { + const data = await mongoose.models[modelName] + .find(query) + .select(project) + .sort(project) + .lean() + .exec(); + return data; +}; + module.exports = { save, update, @@ -93,4 +103,5 @@ module.exports = { getSortClause, count, search, + getDropdownData, }; diff --git a/server/src/modules/auth/service.js b/server/src/modules/auth/service.js index 9b2a269..71e740d 100644 --- a/server/src/modules/auth/service.js +++ b/server/src/modules/auth/service.js @@ -60,9 +60,12 @@ const tryCreateUser = async (user) => { const { username, phoneNumber, email } = user; const query = { $or: [ - { phoneNumber: { $regex: phoneNumber, $options: "i" } }, - { email: { $regex: email, $options: "i" } }, - { username: { $regex: username, $options: "i" } }, + // { phoneNumber: { $regex: phoneNumber, $options: "i" } }, + // { email: { $regex: email, $options: "i" } }, + // { username: { $regex: username, $options: "i" } }, + { phoneNumber }, + { email }, + { username }, ], }; const item = await Model.findOne(query); diff --git a/server/src/modules/permission/request.js b/server/src/modules/permission/request.js index ac98e98..bc39a03 100644 --- a/server/src/modules/permission/request.js +++ b/server/src/modules/permission/request.js @@ -12,9 +12,13 @@ const schema = Joi.object().keys({ isDisabled: Joi.bool().required(), }); -const validate = (data) => { +const validate = (data, user) => { const result = schema.validate(data); - result.value = data; + result.value = { + ...data, + createdBy: user.id, + updatedBy: user.id, + }; return result; };