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;
|
logger.level = config.server.logLevel;
|
||||||
cacheLogger.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)
|
// Default cache TTL in seconds (similar to previous in-memory cache)
|
||||||
const CACHE_TTL_SECONDS = config.database?.redis?.ttlSeconds || 5;
|
const CACHE_TTL_SECONDS = config.database?.redis?.ttlSeconds || 5;
|
||||||
|
|
||||||
@ -69,13 +79,13 @@ export const updateObjectCache = async ({
|
|||||||
for (const key of matchingKeys) {
|
for (const key of matchingKeys) {
|
||||||
logger.trace('Updating object cache:', key);
|
logger.trace('Updating object cache:', key);
|
||||||
const cachedObject = (await redisServer.getKey(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);
|
await redisServer.setKey(key, mergedObject, CACHE_TTL_SECONDS);
|
||||||
mergedObjects.push(mergedObject);
|
mergedObjects.push(mergedObject);
|
||||||
}
|
}
|
||||||
|
|
||||||
const cacheObject = (await redisServer.getKey(cacheKey)) || {};
|
const cacheObject = (await redisServer.getKey(cacheKey)) || {};
|
||||||
const mergedObject = _.merge(cacheObject, object);
|
const mergedObject = mergeObjectUpdates(cacheObject, object);
|
||||||
await redisServer.setKey(cacheKey, mergedObject, CACHE_TTL_SECONDS);
|
await redisServer.setKey(cacheKey, mergedObject, CACHE_TTL_SECONDS);
|
||||||
|
|
||||||
cacheLogger.trace('Updated:', {
|
cacheLogger.trace('Updated:', {
|
||||||
|
|||||||
@ -1,3 +1,5 @@
|
|||||||
|
import mongoose from 'mongoose';
|
||||||
|
|
||||||
export const applyPermissionSettingsList = (settingsList = []) => {
|
export const applyPermissionSettingsList = (settingsList = []) => {
|
||||||
const permissions = {};
|
const permissions = {};
|
||||||
|
|
||||||
@ -36,3 +38,25 @@ export const getPermissionSettingsId = (value) => {
|
|||||||
}
|
}
|
||||||
return 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 mongoose from 'mongoose';
|
||||||
import { generateId } from '../../utils.js';
|
import { generateId } from '../../utils.js';
|
||||||
import { applyPermissionSettingsList, getPermissionSettingsId } from '../../permissions.js';
|
import {
|
||||||
|
applyPermissionSettingsList,
|
||||||
|
resolvePermissionSettings,
|
||||||
|
resolveReferencedDocs,
|
||||||
|
} from '../../permissions.js';
|
||||||
|
|
||||||
const { Schema } = mongoose;
|
const { Schema } = mongoose;
|
||||||
|
|
||||||
@ -14,8 +18,9 @@ const userSchema = new mongoose.Schema(
|
|||||||
email: { required: true, type: String },
|
email: { required: true, type: String },
|
||||||
profileImage: { type: mongoose.SchemaTypes.ObjectId, ref: 'file', required: false },
|
profileImage: { type: mongoose.SchemaTypes.ObjectId, ref: 'file', required: false },
|
||||||
appPasswordHash: { type: String, required: false, select: false },
|
appPasswordHash: { type: String, required: false, select: false },
|
||||||
|
groups: [{ type: Schema.Types.ObjectId, ref: 'userGroup', required: false }],
|
||||||
permissionSettings: [
|
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: () => ({}) },
|
permissions: { type: Schema.Types.Mixed, required: false, default: () => ({}) },
|
||||||
},
|
},
|
||||||
@ -30,30 +35,18 @@ userSchema.virtual('id').get(function () {
|
|||||||
|
|
||||||
userSchema.set('toJSON', { virtuals: true });
|
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) {
|
userSchema.statics.recalculate = async function (user, actingUser) {
|
||||||
const userId = user?._id || user;
|
const userId = user?._id || user;
|
||||||
if (!userId) {
|
if (!userId) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const groups = await resolveReferencedDocs('userGroup', user?.groups);
|
||||||
const settings = await resolvePermissionSettings(user);
|
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');
|
const { editObject } = await import('../../database.js');
|
||||||
|
|
||||||
await editObject({
|
await editObject({
|
||||||
@ -61,7 +54,7 @@ userSchema.statics.recalculate = async function (user, actingUser) {
|
|||||||
id: userId,
|
id: userId,
|
||||||
updateData: { permissions },
|
updateData: { permissions },
|
||||||
user: actingUser,
|
user: actingUser,
|
||||||
populate: ['profileImage', 'permissionSettings'],
|
populate: ['profileImage', 'permissionSettings', 'groups'],
|
||||||
recalculate: false,
|
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 { stockTransferModel } from './inventory/stocktransfer.schema.js';
|
||||||
import { auditLogModel } from './management/auditlog.schema.js';
|
import { auditLogModel } from './management/auditlog.schema.js';
|
||||||
import { userModel } from './management/user.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 { appPasswordModel } from './management/apppassword.schema.js';
|
||||||
import { noteTypeModel } from './management/notetype.schema.js';
|
import { noteTypeModel } from './management/notetype.schema.js';
|
||||||
import { noteModel } from './misc/note.schema.js';
|
import { noteModel } from './misc/note.schema.js';
|
||||||
@ -213,10 +214,17 @@ export const models = {
|
|||||||
referenceField: '_reference',
|
referenceField: '_reference',
|
||||||
label: 'User',
|
label: 'User',
|
||||||
},
|
},
|
||||||
PMS: {
|
UGP: {
|
||||||
model: permissionSettingsModel,
|
model: userGroupModel,
|
||||||
idField: '_id',
|
idField: '_id',
|
||||||
type: 'permissionSettings',
|
type: 'userGroup',
|
||||||
|
referenceField: '_reference',
|
||||||
|
label: 'User Group',
|
||||||
|
},
|
||||||
|
PMS: {
|
||||||
|
model: permissionSettingModel,
|
||||||
|
idField: '_id',
|
||||||
|
type: 'permissionSetting',
|
||||||
referenceField: '_reference',
|
referenceField: '_reference',
|
||||||
label: 'Permission Settings',
|
label: 'Permission Settings',
|
||||||
},
|
},
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user