diff --git a/src/database/database.js b/src/database/database.js index 4e12704..2d8df55 100644 --- a/src/database/database.js +++ b/src/database/database.js @@ -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); } diff --git a/src/database/permissions.js b/src/database/permissions.js new file mode 100644 index 0000000..73d5b4a --- /dev/null +++ b/src/database/permissions.js @@ -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; +}; diff --git a/src/database/schemas/management/permissionsettings.schema.js b/src/database/schemas/management/permissionsettings.schema.js new file mode 100644 index 0000000..688c38f --- /dev/null +++ b/src/database/schemas/management/permissionsettings.schema.js @@ -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 +); diff --git a/src/database/schemas/management/user.schema.js b/src/database/schemas/management/user.schema.js index 283fecb..1da0e62 100644 --- a/src/database/schemas/management/user.schema.js +++ b/src/database/schemas/management/user.schema.js @@ -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); diff --git a/src/database/schemas/models.js b/src/database/schemas/models.js index b55486a..7a63124 100644 --- a/src/database/schemas/models.js +++ b/src/database/schemas/models.js @@ -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',