Add user group management functionality and refactor permission settings
All checks were successful
farmcontrol/farmcontrol-api/pipeline/head This commit looks good
All checks were successful
farmcontrol/farmcontrol-api/pipeline/head This commit looks good
This commit introduces a new feature for managing user groups within the application, including the creation of a `usergroup.schema.js` file for the Mongoose schema and corresponding route handlers for CRUD operations. Additionally, the permission settings functionality has been refactored to use a singular `permissionSetting` model instead of the previous plural form. Tests have been added to ensure the functionality of both user groups and permission settings services, enhancing the application's capability to manage user roles and permissions effectively.
This commit is contained in:
parent
806a32d306
commit
1c51766da6
@ -26,4 +26,31 @@ describe('applyPermissionSettingsList', () => {
|
||||
it('returns an empty object when nothing is explicitly set', () => {
|
||||
expect(applyPermissionSettingsList([{ permissions: { user: { info: null } } }])).toEqual({});
|
||||
});
|
||||
|
||||
it('applies group permissions first, then user permission settings', () => {
|
||||
const result = applyPermissionSettingsList([
|
||||
{
|
||||
permissions: {
|
||||
user: { info: true, edit: false },
|
||||
printer: { info: true },
|
||||
},
|
||||
},
|
||||
{
|
||||
permissions: {
|
||||
user: { delete: true },
|
||||
},
|
||||
},
|
||||
{
|
||||
permissions: {
|
||||
user: { edit: true },
|
||||
printer: { info: false },
|
||||
},
|
||||
},
|
||||
]);
|
||||
|
||||
expect(result).toEqual({
|
||||
user: { info: true, edit: true, delete: true },
|
||||
printer: { info: false },
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@ -45,10 +45,13 @@ const NEIGHBORS_CACHE_WINDOW = 25;
|
||||
const NEIGHBORS_CACHE_PREFIX = 'neighbors';
|
||||
|
||||
const mergeObjectUpdates = (target, source) =>
|
||||
_.mergeWith(target, source, (objValue, srcValue) => {
|
||||
_.mergeWith(target, source, (objValue, srcValue, key) => {
|
||||
if (Array.isArray(objValue) || Array.isArray(srcValue)) {
|
||||
return srcValue;
|
||||
}
|
||||
if (key === 'permissions' && srcValue !== undefined) {
|
||||
return srcValue;
|
||||
}
|
||||
});
|
||||
|
||||
export const retrieveObjectCache = async ({ model, id, populate = [] }) => {
|
||||
@ -1021,7 +1024,7 @@ export const getObject = async ({ model, id, populate }) => {
|
||||
return { error: 'Object not found.', code: 404 };
|
||||
}
|
||||
|
||||
const expanded = _.merge(cachedObject || {}, expandObjectIds(result));
|
||||
const expanded = mergeObjectUpdates(cachedObject || {}, expandObjectIds(result));
|
||||
|
||||
// Update cache with the expanded object
|
||||
await updateObjectCache({
|
||||
|
||||
@ -1,3 +1,5 @@
|
||||
import mongoose from 'mongoose';
|
||||
|
||||
export const applyPermissionSettingsList = (settingsList = []) => {
|
||||
const permissions = {};
|
||||
|
||||
@ -36,3 +38,25 @@ export const getPermissionSettingsId = (value) => {
|
||||
}
|
||||
return value;
|
||||
};
|
||||
|
||||
export const excludeIdFromList = (items = [], id) =>
|
||||
(items || []).filter((item) => String(getPermissionSettingsId(item)) !== String(id));
|
||||
|
||||
export const resolveReferencedDocs = async (modelName, items = []) => {
|
||||
const ids = (items || []).map(getPermissionSettingsId).filter(Boolean);
|
||||
if (ids.length === 0) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const docs = await mongoose
|
||||
.model(modelName)
|
||||
.find({ _id: { $in: ids } })
|
||||
.lean();
|
||||
const docsById = new Map(docs.map((doc) => [String(doc._id), doc]));
|
||||
|
||||
return ids.map((id) => docsById.get(String(id))).filter(Boolean);
|
||||
};
|
||||
|
||||
export const resolvePermissionSettings = async (owner) => {
|
||||
return resolveReferencedDocs('permissionSetting', owner?.permissionSettings);
|
||||
};
|
||||
|
||||
62
src/database/schemas/management/permissionsetting.schema.js
Normal file
62
src/database/schemas/management/permissionsetting.schema.js
Normal file
@ -0,0 +1,62 @@
|
||||
import mongoose from 'mongoose';
|
||||
import { generateId } from '../../utils.js';
|
||||
import { excludeIdFromList, getPermissionSettingsId } from '../../permissions.js';
|
||||
|
||||
const { Schema } = mongoose;
|
||||
|
||||
const permissionSettingSchema = new Schema(
|
||||
{
|
||||
_reference: { type: String, default: () => generateId()() },
|
||||
name: { type: String, required: true },
|
||||
permissions: { type: Schema.Types.Mixed, required: false, default: () => ({}) },
|
||||
},
|
||||
{ timestamps: true }
|
||||
);
|
||||
|
||||
permissionSettingSchema.index({ name: 'text' });
|
||||
|
||||
permissionSettingSchema.virtual('id').get(function () {
|
||||
return this._id;
|
||||
});
|
||||
|
||||
permissionSettingSchema.set('toJSON', { virtuals: true });
|
||||
|
||||
const recalculateAssignees = async (model, permissionSettingId, stillExists, user) => {
|
||||
const assigned = await model.find({ permissionSettings: permissionSettingId }).lean();
|
||||
|
||||
if (!stillExists) {
|
||||
await model.updateMany(
|
||||
{ permissionSettings: permissionSettingId },
|
||||
{ $pull: { permissionSettings: permissionSettingId } }
|
||||
);
|
||||
}
|
||||
|
||||
for (const item of assigned) {
|
||||
const nextItem = stillExists
|
||||
? item
|
||||
: {
|
||||
...item,
|
||||
permissionSettings: excludeIdFromList(item.permissionSettings, permissionSettingId),
|
||||
};
|
||||
await model.recalculate(nextItem, user);
|
||||
}
|
||||
};
|
||||
|
||||
permissionSettingSchema.statics.recalculate = async function (permissionSetting, user) {
|
||||
const permissionSettingId = getPermissionSettingsId(permissionSetting);
|
||||
if (!permissionSettingId) {
|
||||
return;
|
||||
}
|
||||
|
||||
const stillExists = await this.exists({ _id: permissionSettingId });
|
||||
const userGroupModel = mongoose.model('userGroup');
|
||||
const userModel = mongoose.model('user');
|
||||
|
||||
await recalculateAssignees(userGroupModel, permissionSettingId, stillExists, user);
|
||||
await recalculateAssignees(userModel, permissionSettingId, stillExists, user);
|
||||
};
|
||||
|
||||
export const permissionSettingModel = mongoose.model(
|
||||
'permissionSetting',
|
||||
permissionSettingSchema
|
||||
);
|
||||
@ -1,59 +0,0 @@
|
||||
import mongoose from 'mongoose';
|
||||
import { generateId } from '../../utils.js';
|
||||
import { getPermissionSettingsId } from '../../permissions.js';
|
||||
|
||||
const { Schema } = mongoose;
|
||||
|
||||
const permissionSettingsSchema = new Schema(
|
||||
{
|
||||
_reference: { type: String, default: () => generateId()() },
|
||||
name: { type: String, required: true },
|
||||
permissions: { type: Schema.Types.Mixed, required: false, default: () => ({}) },
|
||||
},
|
||||
{ timestamps: true }
|
||||
);
|
||||
|
||||
permissionSettingsSchema.index({ name: 'text' });
|
||||
|
||||
permissionSettingsSchema.virtual('id').get(function () {
|
||||
return this._id;
|
||||
});
|
||||
|
||||
permissionSettingsSchema.set('toJSON', { virtuals: true });
|
||||
|
||||
permissionSettingsSchema.statics.recalculate = async function (permissionSettings, user) {
|
||||
const permissionSettingsId = getPermissionSettingsId(permissionSettings);
|
||||
if (!permissionSettingsId) {
|
||||
return;
|
||||
}
|
||||
|
||||
const userModel = mongoose.model('user');
|
||||
const users = await userModel
|
||||
.find({ permissionSettings: permissionSettingsId })
|
||||
.lean();
|
||||
|
||||
const stillExists = await this.exists({ _id: permissionSettingsId });
|
||||
if (!stillExists) {
|
||||
await userModel.updateMany(
|
||||
{ permissionSettings: permissionSettingsId },
|
||||
{ $pull: { permissionSettings: permissionSettingsId } }
|
||||
);
|
||||
}
|
||||
|
||||
for (const assignedUser of users) {
|
||||
const nextUser = stillExists
|
||||
? assignedUser
|
||||
: {
|
||||
...assignedUser,
|
||||
permissionSettings: (assignedUser.permissionSettings || []).filter(
|
||||
(item) => String(getPermissionSettingsId(item)) !== String(permissionSettingsId)
|
||||
),
|
||||
};
|
||||
await userModel.recalculate(nextUser, user);
|
||||
}
|
||||
};
|
||||
|
||||
export const permissionSettingsModel = mongoose.model(
|
||||
'permissionSettings',
|
||||
permissionSettingsSchema
|
||||
);
|
||||
@ -1,6 +1,10 @@
|
||||
import mongoose from 'mongoose';
|
||||
import { generateId } from '../../utils.js';
|
||||
import { applyPermissionSettingsList, getPermissionSettingsId } from '../../permissions.js';
|
||||
import {
|
||||
applyPermissionSettingsList,
|
||||
resolvePermissionSettings,
|
||||
resolveReferencedDocs,
|
||||
} from '../../permissions.js';
|
||||
|
||||
const { Schema } = mongoose;
|
||||
|
||||
@ -14,8 +18,9 @@ const userSchema = new mongoose.Schema(
|
||||
email: { required: true, type: String },
|
||||
profileImage: { type: mongoose.SchemaTypes.ObjectId, ref: 'file', required: false },
|
||||
appPasswordHash: { type: String, required: false, select: false },
|
||||
groups: [{ type: Schema.Types.ObjectId, ref: 'userGroup', required: false }],
|
||||
permissionSettings: [
|
||||
{ type: Schema.Types.ObjectId, ref: 'permissionSettings', required: false },
|
||||
{ type: Schema.Types.ObjectId, ref: 'permissionSetting', required: false },
|
||||
],
|
||||
permissions: { type: Schema.Types.Mixed, required: false, default: () => ({}) },
|
||||
},
|
||||
@ -30,30 +35,18 @@ userSchema.virtual('id').get(function () {
|
||||
|
||||
userSchema.set('toJSON', { virtuals: true });
|
||||
|
||||
const resolvePermissionSettings = async (user) => {
|
||||
const permissionSettingsModel = mongoose.model('permissionSettings');
|
||||
const items = user?.permissionSettings || [];
|
||||
const ids = items.map(getPermissionSettingsId).filter(Boolean);
|
||||
if (ids.length === 0) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const docs = await permissionSettingsModel
|
||||
.find({ _id: { $in: ids } })
|
||||
.lean();
|
||||
const docsById = new Map(docs.map((doc) => [String(doc._id), doc]));
|
||||
|
||||
return ids.map((id) => docsById.get(String(id))).filter(Boolean);
|
||||
};
|
||||
|
||||
userSchema.statics.recalculate = async function (user, actingUser) {
|
||||
const userId = user?._id || user;
|
||||
if (!userId) {
|
||||
return;
|
||||
}
|
||||
|
||||
const groups = await resolveReferencedDocs('userGroup', user?.groups);
|
||||
const settings = await resolvePermissionSettings(user);
|
||||
const permissions = applyPermissionSettingsList(settings);
|
||||
const permissions = applyPermissionSettingsList([...groups, ...settings]);
|
||||
if (user && typeof user === 'object' && !user._bsontype) {
|
||||
user.permissions = permissions;
|
||||
}
|
||||
const { editObject } = await import('../../database.js');
|
||||
|
||||
await editObject({
|
||||
@ -61,7 +54,7 @@ userSchema.statics.recalculate = async function (user, actingUser) {
|
||||
id: userId,
|
||||
updateData: { permissions },
|
||||
user: actingUser,
|
||||
populate: ['profileImage', 'permissionSettings'],
|
||||
populate: ['profileImage', 'permissionSettings', 'groups'],
|
||||
recalculate: false,
|
||||
});
|
||||
};
|
||||
|
||||
74
src/database/schemas/management/usergroup.schema.js
Normal file
74
src/database/schemas/management/usergroup.schema.js
Normal file
@ -0,0 +1,74 @@
|
||||
import mongoose from 'mongoose';
|
||||
import { generateId } from '../../utils.js';
|
||||
import {
|
||||
applyPermissionSettingsList,
|
||||
excludeIdFromList,
|
||||
resolvePermissionSettings,
|
||||
} from '../../permissions.js';
|
||||
|
||||
const { Schema } = mongoose;
|
||||
|
||||
const userGroupSchema = new Schema(
|
||||
{
|
||||
_reference: { type: String, default: () => generateId()() },
|
||||
name: { type: String, required: true },
|
||||
permissionSettings: [
|
||||
{ type: Schema.Types.ObjectId, ref: 'permissionSetting', required: false },
|
||||
],
|
||||
permissions: { type: Schema.Types.Mixed, required: false, default: () => ({}) },
|
||||
},
|
||||
{ timestamps: true }
|
||||
);
|
||||
|
||||
userGroupSchema.index({ name: 'text' });
|
||||
|
||||
userGroupSchema.virtual('id').get(function () {
|
||||
return this._id;
|
||||
});
|
||||
|
||||
userGroupSchema.set('toJSON', { virtuals: true });
|
||||
|
||||
userGroupSchema.statics.recalculate = async function (userGroup, actingUser) {
|
||||
const userGroupId = userGroup?._id || userGroup;
|
||||
if (!userGroupId) {
|
||||
return;
|
||||
}
|
||||
|
||||
const stillExists = await this.exists({ _id: userGroupId });
|
||||
if (stillExists) {
|
||||
const settings = await resolvePermissionSettings(userGroup);
|
||||
const permissions = applyPermissionSettingsList(settings);
|
||||
if (userGroup && typeof userGroup === 'object' && !userGroup._bsontype) {
|
||||
userGroup.permissions = permissions;
|
||||
}
|
||||
const { editObject } = await import('../../database.js');
|
||||
|
||||
await editObject({
|
||||
model: this,
|
||||
id: userGroupId,
|
||||
updateData: { permissions },
|
||||
user: actingUser,
|
||||
populate: ['permissionSettings'],
|
||||
recalculate: false,
|
||||
});
|
||||
}
|
||||
|
||||
const userModel = mongoose.model('user');
|
||||
const users = await userModel.find({ groups: userGroupId }).lean();
|
||||
|
||||
if (!stillExists) {
|
||||
await userModel.updateMany({ groups: userGroupId }, { $pull: { groups: userGroupId } });
|
||||
}
|
||||
|
||||
for (const assignedUser of users) {
|
||||
const nextUser = stillExists
|
||||
? assignedUser
|
||||
: {
|
||||
...assignedUser,
|
||||
groups: excludeIdFromList(assignedUser.groups, userGroupId),
|
||||
};
|
||||
await userModel.recalculate(nextUser, actingUser);
|
||||
}
|
||||
};
|
||||
|
||||
export const userGroupModel = mongoose.model('userGroup', userGroupSchema);
|
||||
@ -24,7 +24,8 @@ import { stockLocationModel } from './inventory/stocklocation.schema.js';
|
||||
import { stockTransferModel } from './inventory/stocktransfer.schema.js';
|
||||
import { auditLogModel } from './management/auditlog.schema.js';
|
||||
import { userModel } from './management/user.schema.js';
|
||||
import { permissionSettingsModel } from './management/permissionsettings.schema.js';
|
||||
import { userGroupModel } from './management/usergroup.schema.js';
|
||||
import { permissionSettingModel } from './management/permissionsetting.schema.js';
|
||||
import { appPasswordModel } from './management/apppassword.schema.js';
|
||||
import { noteTypeModel } from './management/notetype.schema.js';
|
||||
import { noteModel } from './misc/note.schema.js';
|
||||
@ -213,10 +214,17 @@ export const models = {
|
||||
referenceField: '_reference',
|
||||
label: 'User',
|
||||
},
|
||||
PMS: {
|
||||
model: permissionSettingsModel,
|
||||
UGP: {
|
||||
model: userGroupModel,
|
||||
idField: '_id',
|
||||
type: 'permissionSettings',
|
||||
type: 'userGroup',
|
||||
referenceField: '_reference',
|
||||
label: 'User Group',
|
||||
},
|
||||
PMS: {
|
||||
model: permissionSettingModel,
|
||||
idField: '_id',
|
||||
type: 'permissionSetting',
|
||||
referenceField: '_reference',
|
||||
label: 'Permission Settings',
|
||||
},
|
||||
|
||||
@ -7,6 +7,7 @@ import { redisServer } from './database/redis.js';
|
||||
import {
|
||||
authRoutes,
|
||||
userRoutes,
|
||||
userGroupRoutes,
|
||||
permissionSettingsRoutes,
|
||||
appPasswordRoutes,
|
||||
fileRoutes,
|
||||
@ -155,6 +156,7 @@ app.get('/', function (req, res) {
|
||||
|
||||
app.use('/auth', authRoutes);
|
||||
app.use('/users', userRoutes);
|
||||
app.use('/usergroups', userGroupRoutes);
|
||||
app.use('/permissionsettings', permissionSettingsRoutes);
|
||||
app.use('/apppasswords', appPasswordRoutes);
|
||||
app.use('/files', fileRoutes);
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
import userRoutes from './management/users.js';
|
||||
import permissionSettingsRoutes from './management/permissionsettings.js';
|
||||
import userGroupRoutes from './management/usergroups.js';
|
||||
import permissionSettingsRoutes from './management/permissionsetting.js';
|
||||
import appPasswordRoutes from './management/apppasswords.js';
|
||||
import fileRoutes from './management/files.js';
|
||||
import authRoutes from './misc/auth.js';
|
||||
@ -61,6 +62,7 @@ import slicerRoutes from './misc/slicer.js';
|
||||
|
||||
export {
|
||||
userRoutes,
|
||||
userGroupRoutes,
|
||||
permissionSettingsRoutes,
|
||||
appPasswordRoutes,
|
||||
fileRoutes,
|
||||
|
||||
@ -19,7 +19,7 @@ import {
|
||||
searchPermissionSettingsRouteHandler,
|
||||
getPermissionSettingsPropertyValuesRouteHandler,
|
||||
getPermissionSettingsNeighborsRouteHandler,
|
||||
} from '../../services/management/permissionsettings.js';
|
||||
} from '../../services/management/permissionsetting.js';
|
||||
|
||||
router.get('/', isAuthenticated, async (req, res) => {
|
||||
const { page, limit, property, search, sort, order } = req.query;
|
||||
99
src/routes/management/usergroups.js
Normal file
99
src/routes/management/usergroups.js
Normal file
@ -0,0 +1,99 @@
|
||||
import express from 'express';
|
||||
import { isAuthenticated } from '../../keycloak.js';
|
||||
import { getFilter, convertPropertiesString, getSort } from '../../utils.js';
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
const listAllowedFilters = ['name', 'permissionSettings', 'createdAt', 'updatedAt', '_reference'];
|
||||
const listAllowedSorters = ['name', 'createdAt', '_id', 'updatedAt'];
|
||||
const propertiesAllowedFilters = ['name'];
|
||||
import {
|
||||
listUserGroupsRouteHandler,
|
||||
getUserGroupRouteHandler,
|
||||
editUserGroupRouteHandler,
|
||||
newUserGroupRouteHandler,
|
||||
deleteUserGroupRouteHandler,
|
||||
listUserGroupsByPropertiesRouteHandler,
|
||||
getUserGroupStatsRouteHandler,
|
||||
getUserGroupHistoryRouteHandler,
|
||||
searchUserGroupsRouteHandler,
|
||||
getUserGroupPropertyValuesRouteHandler,
|
||||
getUserGroupNeighborsRouteHandler,
|
||||
} from '../../services/management/usergroups.js';
|
||||
|
||||
router.get('/', isAuthenticated, async (req, res) => {
|
||||
const { page, limit, property, search, sort, order } = req.query;
|
||||
const filter = await getFilter(req.query, listAllowedFilters);
|
||||
listUserGroupsRouteHandler(
|
||||
req,
|
||||
res,
|
||||
page,
|
||||
limit,
|
||||
property,
|
||||
filter,
|
||||
search,
|
||||
getSort(sort, listAllowedSorters),
|
||||
order
|
||||
);
|
||||
});
|
||||
|
||||
router.get('/properties', isAuthenticated, async (req, res) => {
|
||||
let properties = convertPropertiesString(req.query.properties);
|
||||
const filter = await getFilter(req.query, propertiesAllowedFilters, false);
|
||||
var masterFilter = {};
|
||||
if (req.query.masterFilter) {
|
||||
masterFilter = JSON.parse(req.query.masterFilter);
|
||||
}
|
||||
listUserGroupsByPropertiesRouteHandler(req, res, properties, filter, masterFilter);
|
||||
});
|
||||
|
||||
router.get('/values', isAuthenticated, async (req, res) => {
|
||||
const { property } = req.query;
|
||||
getUserGroupPropertyValuesRouteHandler(req, res, property);
|
||||
});
|
||||
|
||||
router.get('/search', isAuthenticated, async (req, res) => {
|
||||
const { search } = req.query;
|
||||
searchUserGroupsRouteHandler(req, res, search);
|
||||
});
|
||||
|
||||
router.post('/', isAuthenticated, async (req, res) => {
|
||||
newUserGroupRouteHandler(req, res);
|
||||
});
|
||||
|
||||
router.get('/stats', isAuthenticated, async (req, res) => {
|
||||
getUserGroupStatsRouteHandler(req, res);
|
||||
});
|
||||
|
||||
router.get('/history', isAuthenticated, async (req, res) => {
|
||||
getUserGroupHistoryRouteHandler(req, res);
|
||||
});
|
||||
|
||||
router.get('/neighbors', isAuthenticated, async (req, res) => {
|
||||
const { property, search, sort, order, id } = req.query;
|
||||
const filter = await getFilter(req.query, listAllowedFilters);
|
||||
getUserGroupNeighborsRouteHandler(
|
||||
req,
|
||||
res,
|
||||
property,
|
||||
filter,
|
||||
search,
|
||||
getSort(sort, listAllowedSorters),
|
||||
order,
|
||||
id
|
||||
);
|
||||
});
|
||||
|
||||
router.get('/:id', isAuthenticated, async (req, res) => {
|
||||
getUserGroupRouteHandler(req, res);
|
||||
});
|
||||
|
||||
router.put('/:id', isAuthenticated, async (req, res) => {
|
||||
editUserGroupRouteHandler(req, res);
|
||||
});
|
||||
|
||||
router.delete('/:id', isAuthenticated, async (req, res) => {
|
||||
deleteUserGroupRouteHandler(req, res);
|
||||
});
|
||||
|
||||
export default router;
|
||||
@ -10,6 +10,7 @@ const listAllowedFilters = [
|
||||
'firstName',
|
||||
'lastName',
|
||||
'email',
|
||||
'groups',
|
||||
'permissionSettings',
|
||||
'createdAt',
|
||||
'updatedAt',
|
||||
|
||||
@ -14,8 +14,8 @@ jest.unstable_mockModule('../../../database/database.js', () => ({
|
||||
getObjectNeighbors: jest.fn(),
|
||||
}));
|
||||
|
||||
jest.unstable_mockModule('../../../database/schemas/management/permissionsettings.schema.js', () => ({
|
||||
permissionSettingsModel: { modelName: 'PermissionSettings' },
|
||||
jest.unstable_mockModule('../../../database/schemas/management/permissionsetting.schema.js', () => ({
|
||||
permissionSettingModel: { modelName: 'PermissionSetting' },
|
||||
}));
|
||||
|
||||
jest.unstable_mockModule('log4js', () => ({
|
||||
@ -35,13 +35,13 @@ const {
|
||||
getPermissionSettingsRouteHandler,
|
||||
newPermissionSettingsRouteHandler,
|
||||
editPermissionSettingsRouteHandler,
|
||||
} = await import('../permissionsettings.js');
|
||||
} = await import('../permissionsetting.js');
|
||||
|
||||
const { listObjects, getObject, editObject, newObject } = await import(
|
||||
'../../../database/database.js'
|
||||
);
|
||||
const { permissionSettingsModel } = await import(
|
||||
'../../../database/schemas/management/permissionsettings.schema.js'
|
||||
const { permissionSettingModel } = await import(
|
||||
'../../../database/schemas/management/permissionsetting.schema.js'
|
||||
);
|
||||
|
||||
describe('Permission Settings Service Route Handlers', () => {
|
||||
@ -69,7 +69,7 @@ describe('Permission Settings Service Route Handlers', () => {
|
||||
await listPermissionSettingsRouteHandler(req, res);
|
||||
|
||||
expect(listObjects).toHaveBeenCalledWith(
|
||||
expect.objectContaining({ model: permissionSettingsModel })
|
||||
expect.objectContaining({ model: permissionSettingModel })
|
||||
);
|
||||
expect(res.send).toHaveBeenCalledWith(mockResult);
|
||||
});
|
||||
@ -112,7 +112,7 @@ describe('Permission Settings Service Route Handlers', () => {
|
||||
|
||||
expect(getObject).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
model: permissionSettingsModel,
|
||||
model: permissionSettingModel,
|
||||
id: '123',
|
||||
})
|
||||
);
|
||||
130
src/services/management/__tests__/usergroups.test.js
Normal file
130
src/services/management/__tests__/usergroups.test.js
Normal file
@ -0,0 +1,130 @@
|
||||
import { jest } from '@jest/globals';
|
||||
|
||||
jest.unstable_mockModule('../../../database/database.js', () => ({
|
||||
searchObjects: jest.fn(),
|
||||
getPropertyValues: jest.fn(),
|
||||
listObjects: jest.fn(),
|
||||
getObject: jest.fn(),
|
||||
editObject: jest.fn(),
|
||||
newObject: jest.fn(),
|
||||
deleteObject: jest.fn(),
|
||||
listObjectsByProperties: jest.fn(),
|
||||
getModelStats: jest.fn(),
|
||||
getModelHistory: jest.fn(),
|
||||
getObjectNeighbors: jest.fn(),
|
||||
}));
|
||||
|
||||
jest.unstable_mockModule('../../../database/schemas/management/usergroup.schema.js', () => ({
|
||||
userGroupModel: { modelName: 'UserGroup' },
|
||||
}));
|
||||
|
||||
jest.unstable_mockModule('log4js', () => ({
|
||||
default: {
|
||||
getLogger: () => ({
|
||||
level: 'info',
|
||||
debug: jest.fn(),
|
||||
error: jest.fn(),
|
||||
warn: jest.fn(),
|
||||
trace: jest.fn(),
|
||||
}),
|
||||
},
|
||||
}));
|
||||
|
||||
const {
|
||||
listUserGroupsRouteHandler,
|
||||
getUserGroupRouteHandler,
|
||||
newUserGroupRouteHandler,
|
||||
editUserGroupRouteHandler,
|
||||
} = await import('../usergroups.js');
|
||||
|
||||
const { listObjects, getObject, editObject, newObject } = await import(
|
||||
'../../../database/database.js'
|
||||
);
|
||||
const { userGroupModel } = await import(
|
||||
'../../../database/schemas/management/usergroup.schema.js'
|
||||
);
|
||||
|
||||
describe('User Groups Service Route Handlers', () => {
|
||||
let req, res;
|
||||
|
||||
beforeEach(() => {
|
||||
req = {
|
||||
params: {},
|
||||
query: {},
|
||||
body: {},
|
||||
user: { id: 'test-user-id' },
|
||||
};
|
||||
res = {
|
||||
send: jest.fn(),
|
||||
status: jest.fn().mockReturnThis(),
|
||||
};
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
describe('listUserGroupsRouteHandler', () => {
|
||||
it('should list user groups', async () => {
|
||||
const mockResult = [{ _id: '1', name: 'Admins' }];
|
||||
listObjects.mockResolvedValue(mockResult);
|
||||
|
||||
await listUserGroupsRouteHandler(req, res);
|
||||
|
||||
expect(listObjects).toHaveBeenCalledWith(
|
||||
expect.objectContaining({ model: userGroupModel })
|
||||
);
|
||||
expect(res.send).toHaveBeenCalledWith(mockResult);
|
||||
});
|
||||
});
|
||||
|
||||
describe('newUserGroupRouteHandler', () => {
|
||||
it('should create a new user group', async () => {
|
||||
req.body = { name: 'Editors', permissionSettings: [{ _id: 'pms-1' }] };
|
||||
const mockUserGroup = { _id: '456', ...req.body };
|
||||
newObject.mockResolvedValue(mockUserGroup);
|
||||
|
||||
await newUserGroupRouteHandler(req, res);
|
||||
|
||||
expect(newObject).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
model: userGroupModel,
|
||||
newData: expect.objectContaining({
|
||||
name: 'Editors',
|
||||
permissionSettings: ['pms-1'],
|
||||
}),
|
||||
})
|
||||
);
|
||||
expect(res.send).toHaveBeenCalledWith(mockUserGroup);
|
||||
});
|
||||
});
|
||||
|
||||
describe('editUserGroupRouteHandler', () => {
|
||||
it('should update a user group', async () => {
|
||||
req.params.id = '507f1f77bcf86cd799439011';
|
||||
req.body = { name: 'Editors', permissionSettings: ['pms-1'] };
|
||||
const mockResult = { _id: '507f1f77bcf86cd799439011', ...req.body };
|
||||
editObject.mockResolvedValue(mockResult);
|
||||
|
||||
await editUserGroupRouteHandler(req, res);
|
||||
|
||||
expect(editObject).toHaveBeenCalled();
|
||||
expect(res.send).toHaveBeenCalledWith(mockResult);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getUserGroupRouteHandler', () => {
|
||||
it('should get a user group by id', async () => {
|
||||
req.params.id = '123';
|
||||
const mockResult = { _id: '123', name: 'Admins' };
|
||||
getObject.mockResolvedValue(mockResult);
|
||||
|
||||
await getUserGroupRouteHandler(req, res);
|
||||
|
||||
expect(getObject).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
model: userGroupModel,
|
||||
id: '123',
|
||||
})
|
||||
);
|
||||
expect(res.send).toHaveBeenCalledWith(mockResult);
|
||||
});
|
||||
});
|
||||
});
|
||||
@ -1,5 +1,5 @@
|
||||
import config from '../../config.js';
|
||||
import { permissionSettingsModel } from '../../database/schemas/management/permissionsettings.schema.js';
|
||||
import { permissionSettingModel } from '../../database/schemas/management/permissionsetting.schema.js';
|
||||
import log4js from 'log4js';
|
||||
import mongoose from 'mongoose';
|
||||
import {
|
||||
@ -16,7 +16,7 @@ import {
|
||||
getObjectNeighbors,
|
||||
} from '../../database/database.js';
|
||||
|
||||
const logger = log4js.getLogger('PermissionSettings');
|
||||
const logger = log4js.getLogger('PermissionSetting');
|
||||
logger.level = config.server.logLevel;
|
||||
|
||||
export const listPermissionSettingsRouteHandler = async (
|
||||
@ -31,7 +31,7 @@ export const listPermissionSettingsRouteHandler = async (
|
||||
order = 'ascend'
|
||||
) => {
|
||||
const result = await listObjects({
|
||||
model: permissionSettingsModel,
|
||||
model: permissionSettingModel,
|
||||
page,
|
||||
limit,
|
||||
property,
|
||||
@ -61,7 +61,7 @@ export const listPermissionSettingsByPropertiesRouteHandler = async (
|
||||
masterFilter = {}
|
||||
) => {
|
||||
const result = await listObjectsByProperties({
|
||||
model: permissionSettingsModel,
|
||||
model: permissionSettingModel,
|
||||
properties,
|
||||
filter,
|
||||
masterFilter,
|
||||
@ -79,7 +79,7 @@ export const listPermissionSettingsByPropertiesRouteHandler = async (
|
||||
|
||||
export const getPermissionSettingsPropertyValuesRouteHandler = async (req, res, property) => {
|
||||
const result = await getPropertyValues({
|
||||
model: permissionSettingsModel,
|
||||
model: permissionSettingModel,
|
||||
property,
|
||||
});
|
||||
res.send(result);
|
||||
@ -87,7 +87,7 @@ export const getPermissionSettingsPropertyValuesRouteHandler = async (req, res,
|
||||
|
||||
export const searchPermissionSettingsRouteHandler = async (req, res, search) => {
|
||||
const result = await searchObjects({
|
||||
model: permissionSettingsModel,
|
||||
model: permissionSettingModel,
|
||||
search,
|
||||
});
|
||||
res.send(result);
|
||||
@ -96,7 +96,7 @@ export const searchPermissionSettingsRouteHandler = async (req, res, search) =>
|
||||
export const getPermissionSettingsRouteHandler = async (req, res) => {
|
||||
const id = req.params.id;
|
||||
const result = await getObject({
|
||||
model: permissionSettingsModel,
|
||||
model: permissionSettingModel,
|
||||
id,
|
||||
});
|
||||
if (result?.error) {
|
||||
@ -118,7 +118,7 @@ export const editPermissionSettingsRouteHandler = async (req, res) => {
|
||||
permissions: req.body.permissions,
|
||||
};
|
||||
const result = await editObject({
|
||||
model: permissionSettingsModel,
|
||||
model: permissionSettingModel,
|
||||
id,
|
||||
updateData,
|
||||
user: req.user,
|
||||
@ -142,7 +142,7 @@ export const newPermissionSettingsRouteHandler = async (req, res) => {
|
||||
permissions: req.body.permissions,
|
||||
};
|
||||
const result = await newObject({
|
||||
model: permissionSettingsModel,
|
||||
model: permissionSettingModel,
|
||||
newData,
|
||||
user: req.user,
|
||||
});
|
||||
@ -162,7 +162,7 @@ export const deletePermissionSettingsRouteHandler = async (req, res) => {
|
||||
logger.trace(`Permission settings with ID: ${id}`);
|
||||
|
||||
const result = await deleteObject({
|
||||
model: permissionSettingsModel,
|
||||
model: permissionSettingModel,
|
||||
id,
|
||||
user: req.user,
|
||||
});
|
||||
@ -177,7 +177,7 @@ export const deletePermissionSettingsRouteHandler = async (req, res) => {
|
||||
};
|
||||
|
||||
export const getPermissionSettingsStatsRouteHandler = async (req, res) => {
|
||||
const result = await getModelStats({ model: permissionSettingsModel });
|
||||
const result = await getModelStats({ model: permissionSettingModel });
|
||||
if (result?.error) {
|
||||
logger.error('Error fetching permission settings stats:', result.error);
|
||||
return res.status(result.code).send(result);
|
||||
@ -189,7 +189,7 @@ export const getPermissionSettingsStatsRouteHandler = async (req, res) => {
|
||||
export const getPermissionSettingsHistoryRouteHandler = async (req, res) => {
|
||||
const from = req.query.from;
|
||||
const to = req.query.to;
|
||||
const result = await getModelHistory({ model: permissionSettingsModel, from, to });
|
||||
const result = await getModelHistory({ model: permissionSettingModel, from, to });
|
||||
if (result?.error) {
|
||||
logger.error('Error fetching permission settings history:', result.error);
|
||||
return res.status(result.code).send(result);
|
||||
@ -213,7 +213,7 @@ export const getPermissionSettingsNeighborsRouteHandler = async (
|
||||
}
|
||||
|
||||
const result = await getObjectNeighbors({
|
||||
model: permissionSettingsModel,
|
||||
model: permissionSettingModel,
|
||||
id,
|
||||
filter,
|
||||
search,
|
||||
240
src/services/management/usergroups.js
Normal file
240
src/services/management/usergroups.js
Normal file
@ -0,0 +1,240 @@
|
||||
import config from '../../config.js';
|
||||
import { userGroupModel } from '../../database/schemas/management/usergroup.schema.js';
|
||||
import log4js from 'log4js';
|
||||
import mongoose from 'mongoose';
|
||||
import {
|
||||
deleteObject,
|
||||
listObjects,
|
||||
getObject,
|
||||
editObject,
|
||||
newObject,
|
||||
listObjectsByProperties,
|
||||
getModelStats,
|
||||
getModelHistory,
|
||||
searchObjects,
|
||||
getPropertyValues,
|
||||
getObjectNeighbors,
|
||||
} from '../../database/database.js';
|
||||
|
||||
const logger = log4js.getLogger('UserGroups');
|
||||
logger.level = config.server.logLevel;
|
||||
|
||||
const USER_GROUP_POPULATE = ['permissionSettings'];
|
||||
|
||||
const toIdList = (value) =>
|
||||
Array.isArray(value) ? value.map((item) => item?._id || item) : value;
|
||||
|
||||
export const listUserGroupsRouteHandler = async (
|
||||
req,
|
||||
res,
|
||||
page = 1,
|
||||
limit = 25,
|
||||
property = '',
|
||||
filter = {},
|
||||
search = '',
|
||||
sort = '',
|
||||
order = 'ascend'
|
||||
) => {
|
||||
const result = await listObjects({
|
||||
model: userGroupModel,
|
||||
page,
|
||||
limit,
|
||||
property,
|
||||
filter,
|
||||
search,
|
||||
sort,
|
||||
order,
|
||||
populate: USER_GROUP_POPULATE,
|
||||
});
|
||||
|
||||
if (result?.error) {
|
||||
logger.error('Error listing user groups.');
|
||||
res.status(result.code).send(result);
|
||||
return;
|
||||
}
|
||||
|
||||
logger.debug(
|
||||
`List of user groups (Page ${page}, Limit ${limit}). Count: ${result.length}.`
|
||||
);
|
||||
res.send(result);
|
||||
};
|
||||
|
||||
export const listUserGroupsByPropertiesRouteHandler = async (
|
||||
req,
|
||||
res,
|
||||
properties = '',
|
||||
filter = {},
|
||||
masterFilter = {}
|
||||
) => {
|
||||
const result = await listObjectsByProperties({
|
||||
model: userGroupModel,
|
||||
properties,
|
||||
filter,
|
||||
masterFilter,
|
||||
});
|
||||
|
||||
if (result?.error) {
|
||||
logger.error('Error listing user groups.');
|
||||
res.status(result.code).send(result);
|
||||
return;
|
||||
}
|
||||
|
||||
logger.debug(`List of user groups. Count: ${result.length}`);
|
||||
res.send(result);
|
||||
};
|
||||
|
||||
export const getUserGroupPropertyValuesRouteHandler = async (req, res, property) => {
|
||||
const result = await getPropertyValues({
|
||||
model: userGroupModel,
|
||||
property,
|
||||
});
|
||||
res.send(result);
|
||||
};
|
||||
|
||||
export const searchUserGroupsRouteHandler = async (req, res, search) => {
|
||||
const result = await searchObjects({
|
||||
model: userGroupModel,
|
||||
search,
|
||||
});
|
||||
res.send(result);
|
||||
};
|
||||
|
||||
export const getUserGroupRouteHandler = async (req, res) => {
|
||||
const id = req.params.id;
|
||||
const result = await getObject({
|
||||
model: userGroupModel,
|
||||
id,
|
||||
populate: USER_GROUP_POPULATE,
|
||||
});
|
||||
if (result?.error) {
|
||||
logger.warn(`User group not found with supplied id.`);
|
||||
return res.status(result.code).send(result);
|
||||
}
|
||||
logger.debug(`Retreived user group with ID: ${id}`);
|
||||
res.send(result);
|
||||
};
|
||||
|
||||
export const editUserGroupRouteHandler = async (req, res) => {
|
||||
const id = new mongoose.Types.ObjectId(req.params.id);
|
||||
|
||||
logger.trace(`User group with ID: ${id}`);
|
||||
|
||||
const updateData = {
|
||||
updatedAt: new Date(),
|
||||
name: req.body.name,
|
||||
permissionSettings: toIdList(req.body.permissionSettings),
|
||||
};
|
||||
const result = await editObject({
|
||||
model: userGroupModel,
|
||||
id,
|
||||
updateData,
|
||||
user: req.user,
|
||||
populate: USER_GROUP_POPULATE,
|
||||
});
|
||||
|
||||
if (result.error) {
|
||||
logger.error('Error editing user group:', result.error);
|
||||
res.status(result).send(result);
|
||||
return;
|
||||
}
|
||||
|
||||
logger.debug(`Edited user group with ID: ${id}`);
|
||||
|
||||
res.send(result);
|
||||
};
|
||||
|
||||
export const newUserGroupRouteHandler = async (req, res) => {
|
||||
const newData = {
|
||||
updatedAt: new Date(),
|
||||
name: req.body.name,
|
||||
permissionSettings: toIdList(req.body.permissionSettings),
|
||||
permissions: req.body.permissions,
|
||||
};
|
||||
const result = await newObject({
|
||||
model: userGroupModel,
|
||||
newData,
|
||||
user: req.user,
|
||||
});
|
||||
if (result.error) {
|
||||
logger.error('No user group created:', result.error);
|
||||
return res.status(result.code).send(result);
|
||||
}
|
||||
|
||||
logger.debug(`New user group with ID: ${result._id}`);
|
||||
|
||||
res.send(result);
|
||||
};
|
||||
|
||||
export const deleteUserGroupRouteHandler = async (req, res) => {
|
||||
const id = new mongoose.Types.ObjectId(req.params.id);
|
||||
|
||||
logger.trace(`User group with ID: ${id}`);
|
||||
|
||||
const result = await deleteObject({
|
||||
model: userGroupModel,
|
||||
id,
|
||||
user: req.user,
|
||||
});
|
||||
if (result.error) {
|
||||
logger.error('No user group deleted:', result.error);
|
||||
return res.status(result.code).send(result);
|
||||
}
|
||||
|
||||
logger.debug(`Deleted user group with ID: ${result._id}`);
|
||||
|
||||
res.send(result);
|
||||
};
|
||||
|
||||
export const getUserGroupStatsRouteHandler = async (req, res) => {
|
||||
const result = await getModelStats({ model: userGroupModel });
|
||||
if (result?.error) {
|
||||
logger.error('Error fetching user group stats:', result.error);
|
||||
return res.status(result.code).send(result);
|
||||
}
|
||||
logger.trace('User group stats:', result);
|
||||
res.send(result);
|
||||
};
|
||||
|
||||
export const getUserGroupHistoryRouteHandler = async (req, res) => {
|
||||
const from = req.query.from;
|
||||
const to = req.query.to;
|
||||
const result = await getModelHistory({ model: userGroupModel, from, to });
|
||||
if (result?.error) {
|
||||
logger.error('Error fetching user group history:', result.error);
|
||||
return res.status(result.code).send(result);
|
||||
}
|
||||
logger.trace('User group history:', result);
|
||||
res.send(result);
|
||||
};
|
||||
|
||||
export const getUserGroupNeighborsRouteHandler = async (
|
||||
req,
|
||||
res,
|
||||
property = '',
|
||||
filter = {},
|
||||
search = '',
|
||||
sort = '',
|
||||
order = 'ascend',
|
||||
id
|
||||
) => {
|
||||
if (!id) {
|
||||
return res.status(400).send({ error: 'Missing id parameter', code: 400 });
|
||||
}
|
||||
|
||||
const result = await getObjectNeighbors({
|
||||
model: userGroupModel,
|
||||
id,
|
||||
filter,
|
||||
search,
|
||||
sort,
|
||||
order,
|
||||
});
|
||||
|
||||
if (result?.error) {
|
||||
logger.error('Error fetching userGroup neighbors.');
|
||||
return res.status(result.code).send(result);
|
||||
}
|
||||
|
||||
logger.debug(`Retrieved userGroup neighbors for ID: ${id}`);
|
||||
res.send(result);
|
||||
};
|
||||
@ -19,7 +19,7 @@ import {
|
||||
const logger = log4js.getLogger('Users');
|
||||
logger.level = config.server.logLevel;
|
||||
|
||||
const USER_POPULATE = ['profileImage', 'permissionSettings'];
|
||||
const USER_POPULATE = ['profileImage', 'permissionSettings', 'groups'];
|
||||
|
||||
export const listUsersRouteHandler = async (
|
||||
req,
|
||||
@ -122,6 +122,9 @@ export const editUserRouteHandler = async (req, res) => {
|
||||
lastName: req.body.lastName,
|
||||
email: req.body.email,
|
||||
profileImage: req.body.profileImage,
|
||||
groups: Array.isArray(req.body.groups)
|
||||
? req.body.groups.map((item) => item?._id || item)
|
||||
: req.body.groups,
|
||||
permissionSettings: Array.isArray(req.body.permissionSettings)
|
||||
? req.body.permissionSettings.map((item) => item?._id || item)
|
||||
: req.body.permissionSettings,
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user