Refactor permission settings and introduce user group management
All checks were successful
farmcontrol/farmcontrol-ws/pipeline/head This commit looks good
All checks were successful
farmcontrol/farmcontrol-ws/pipeline/head This commit looks good
- Replaced the existing permission settings schema with a new permission setting schema, enhancing the management of user permissions. - Updated user and user group schemas to include references to the new permission setting model, allowing for more flexible permission handling. - Added utility functions for resolving referenced documents and recalculating permissions, improving the overall permission management process. - Removed the old permission settings schema to streamline the codebase and reduce redundancy.
This commit is contained in:
parent
3742839737
commit
998bb725a2
@ -22,6 +22,16 @@ const cacheLogger = log4js.getLogger('Local Cache');
|
||||
logger.level = config.server.logLevel;
|
||||
cacheLogger.level = config.server.logLevel;
|
||||
|
||||
const mergeObjectUpdates = (target, source) =>
|
||||
_.mergeWith(target, source, (objValue, srcValue, key) => {
|
||||
if (Array.isArray(objValue) || Array.isArray(srcValue)) {
|
||||
return srcValue;
|
||||
}
|
||||
if (key === 'permissions' && srcValue !== undefined) {
|
||||
return srcValue;
|
||||
}
|
||||
});
|
||||
|
||||
// Default cache TTL in seconds (similar to previous in-memory cache)
|
||||
const CACHE_TTL_SECONDS = config.database?.redis?.ttlSeconds || 5;
|
||||
|
||||
@ -69,13 +79,13 @@ export const updateObjectCache = async ({
|
||||
for (const key of matchingKeys) {
|
||||
logger.trace('Updating object cache:', key);
|
||||
const cachedObject = (await redisServer.getKey(key)) || {};
|
||||
const mergedObject = _.merge(cachedObject, object);
|
||||
const mergedObject = mergeObjectUpdates(cachedObject, object);
|
||||
await redisServer.setKey(key, mergedObject, CACHE_TTL_SECONDS);
|
||||
mergedObjects.push(mergedObject);
|
||||
}
|
||||
|
||||
const cacheObject = (await redisServer.getKey(cacheKey)) || {};
|
||||
const mergedObject = _.merge(cacheObject, object);
|
||||
const mergedObject = mergeObjectUpdates(cacheObject, object);
|
||||
await redisServer.setKey(cacheKey, mergedObject, CACHE_TTL_SECONDS);
|
||||
|
||||
cacheLogger.trace('Updated:', {
|
||||
|
||||
@ -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',
|
||||
},
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user