Add permission settings model and related functionality
All checks were successful
farmcontrol/farmcontrol-ws/pipeline/head This commit looks good

- Introduced a new permission settings schema and model to manage user permissions effectively.
- Updated user schema to include a reference to permission settings, allowing for dynamic permission management.
- Enhanced the editObject function to conditionally recalculate permissions based on the new recalculate parameter.
- Added utility functions for applying and retrieving permission settings, improving permission handling across the application.
This commit is contained in:
Tom Butcher 2026-08-20 12:27:54 +01:00
parent 7e8879164a
commit 3742839737
5 changed files with 151 additions and 2 deletions

View File

@ -425,7 +425,8 @@ export const editObject = async ({
ownerType = undefined,
populate = [],
auditLog = true,
notify = true
notify = true,
recalculate = true
}) => {
try {
// Determine parentType from model name
@ -494,7 +495,7 @@ export const editObject = async ({
populate
});
if (model.recalculate) {
if (model.recalculate && recalculate == true) {
logger.debug(`Recalculating ${model.modelName}`);
await model.recalculate(newExpandedObject, owner, ownerType);
}

View File

@ -0,0 +1,38 @@
export const applyPermissionSettingsList = (settingsList = []) => {
const permissions = {};
for (const setting of settingsList) {
const matrix = setting?.permissions;
if (!matrix || typeof matrix !== 'object' || Array.isArray(matrix)) {
continue;
}
for (const [modelName, actions] of Object.entries(matrix)) {
if (!actions || typeof actions !== 'object' || Array.isArray(actions)) {
continue;
}
if (!permissions[modelName]) {
permissions[modelName] = {};
}
for (const [actionName, value] of Object.entries(actions)) {
if (value === true || value === false) {
permissions[modelName][actionName] = value;
}
}
if (Object.keys(permissions[modelName]).length === 0) {
delete permissions[modelName];
}
}
}
return permissions;
};
export const getPermissionSettingsId = (value) => {
if (!value) return null;
if (typeof value === 'string') return value;
if (value._id) {
return value._id._id || value._id;
}
return value;
};

View File

@ -0,0 +1,59 @@
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
);

View File

@ -1,5 +1,8 @@
import mongoose from 'mongoose';
import { generateId } from '../../utils.js';
import { applyPermissionSettingsList, getPermissionSettingsId } from '../../permissions.js';
const { Schema } = mongoose;
const userSchema = new mongoose.Schema(
{
@ -11,6 +14,10 @@ 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 },
permissionSettings: [
{ type: Schema.Types.ObjectId, ref: 'permissionSettings', required: false },
],
permissions: { type: Schema.Types.Mixed, required: false, default: () => ({}) },
},
{ timestamps: true }
);
@ -23,4 +30,40 @@ 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 settings = await resolvePermissionSettings(user);
const permissions = applyPermissionSettingsList(settings);
const { editObject } = await import('../../database.js');
await editObject({
model: this,
id: userId,
updateData: { permissions },
user: actingUser,
populate: ['profileImage', 'permissionSettings'],
recalculate: false,
});
};
export const userModel = mongoose.model('user', userSchema);

View File

@ -24,6 +24,7 @@ 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 { appPasswordModel } from './management/apppassword.schema.js';
import { noteTypeModel } from './management/notetype.schema.js';
import { noteModel } from './misc/note.schema.js';
@ -212,6 +213,13 @@ export const models = {
referenceField: '_reference',
label: 'User',
},
PMS: {
model: permissionSettingsModel,
idField: '_id',
type: 'permissionSettings',
referenceField: '_reference',
label: 'Permission Settings',
},
APP: {
model: appPasswordModel,
idField: '_id',