const Organisation = require('./Organisation'); class Organisations { #values = {} #overlord static entries (orgs) { return orgs.names().map(name => [name, orgs.get(name)]); } constructor (data, overlord) { if (data instanceof Organisations) { for (const [name, value] of Organisations.entries(data)) { this.set(name, new Organisation(value)); } } else if (data instanceof Object) { for (const [name, value] of Object.entries(data)) { this.set(name, new Organisation(value)); } } else if (data instanceof String) { this.set(data, new Organisation()); } else if (typeof data !== "undefined") { throw new Error("Invalid constructor argument"); } if (overlord) { this.#overlord = overlord; } } get values () { return this.#values; } get length () { return this.names().length; } get overlord () { return this.#overlord; } set overlord (v) { this.#overlord = new Organisations(v); } /** Get the operations for `name` */ get (name) { const key = Object.keys(this.values).find( k => k.toLowerCase() == name.toLowerCase() ) ?? name; return this.values[key]; } /** Set the operations for `name` to `value` * * If we have an overlord, ensure we cannot: * * 1. Add new organisations which the overlord * is not a member of * 2. Access operations that the overlord is not * allowed to access */ set (name, value) { name = String(name).trim(); const key = Object.keys(this.values).find( k => k.toLowerCase() == name.toLowerCase() ) ?? name; const org = new Organisation(value); if (this.overlord) { const parent = this.overlord.get(key) ?? this.overlord.get("*"); if (parent) { this.values[key] = parent.filter(org); } } else { this.values[key] = new Organisation(value); } return this; } /** Enable the operation `op` in all organisations */ enableOperation (op) { if (this.overlord) { Object.keys(this.#values) .filter( key => (this.overlord.get(key) ?? this.overlord.get("*"))?.[op] ) .forEach( key => this.#values[key][op] = true ); } else { Object.values(this.#values).forEach( org => org[op] = true ); } return this; } /** Disable the operation `op` in all organisations */ disableOperation (op) { Object.values(this.#values).forEach( org => org[op] = false ); return this; } /** Create a new organisation object limited by the caller's rights * * The spawned Organisations instance will have the same organisations * and rights as the caller minus the applied `mask`. With the default * mask, the spawned object will inherit all rights except for `edit` * rights. * * The "*" organisation must be explicitly assigned. It is not inherited. */ spawn (mask = {read: true, write: true, edit: false}) { const parent = new Organisations(); const wildcard = this.get("*").edit; // If true, we can spawn everywhere this.entries().forEach( ([k, v]) => { // if (k != "*") { // This organisation is not inherited if (v.edit || wildcard) { // We have the right to spawn in this organisation const o = new Organisation({ read: v.read && mask.read, write: v.write && mask.write, edit: v.edit && mask.edit }); parent.set(k, o); } // } }); return new Organisations({}, parent); } remove (name) { const key = Object.keys(this.values).find( k => k.toLowerCase() == name.toLowerCase() ) ?? name; delete this.values[key]; } /** Return the list of organisation names */ names () { return Object.keys(this.values); } /** Same as this.get(name) */ value (name) { return this.values[name]; } /** Same as Object.prototype.entries */ entries () { return this.names().map( name => [ name, this.value(name) ] ); } /** Return true if the named organisation is present */ has (name) { return Boolean(this.value(name)); } /** Return only those of our organisations * and operations present in `other` */ filter (other) { const filteredOrganisations = new Organisations(); const wildcard = other.value("*"); for (const [name, org] of this.entries()) { const ownOrg = other.value(name) ?? wildcard; if (ownOrg) { filteredOrganisations.set(name, org.filter(ownOrg)) } } return filteredOrganisations; } /** Return only those organisations * that have access to the required * operation */ accessToOperation (op) { const filteredOrganisations = new Organisations(); for (const [name, org] of this.entries()) { if (org[op]) { filteredOrganisations.set(name, org); } } return filteredOrganisations; } toJSON () { const obj = {}; for (const key in this.values) { obj[key] = this.values[key].toJSON(); } return obj; } toString (replacer, space) { return JSON.stringify(this.toJSON(), replacer, space); } *[Symbol.iterator] () { for (const [name, operations] of this.entries()) { yield {name, operations}; } } } if (typeof module !== 'undefined' && module.exports) { module.exports = Organisations; // CJS export } // ESM export if (typeof exports !== 'undefined' && !exports.default) { exports.default = Organisations; // ESM export }