diff --git a/app/imports/api/creature/creatureComputation.js b/app/imports/api/creature/creatureComputation.js index 72334fbd..7029174d 100644 --- a/app/imports/api/creature/creatureComputation.js +++ b/app/imports/api/creature/creatureComputation.js @@ -8,6 +8,7 @@ import Creatures from "/imports/api/creature/Creatures.js"; import Attributes from "/imports/api/creature/properties/Attributes.js"; import Skills from "/imports/api/creature/properties/Skills.js"; import Effects from "/imports/api/creature/properties/Effects.js"; +import Proficiencies from "/imports/api/creature/properties/Proficiencies.js"; import DamageMultipliers from "/imports/api/creature/properties/DamageMultipliers.js"; import Classes from "/imports/api/creature/properties/Classes.js"; import * as math from 'mathjs'; diff --git a/app/imports/api/creature/creaturePermissions.js b/app/imports/api/creature/creaturePermissions.js index 66412755..5b1d67e0 100644 --- a/app/imports/api/creature/creaturePermissions.js +++ b/app/imports/api/creature/creaturePermissions.js @@ -6,7 +6,7 @@ import { function getCreature(creature, fields){ if (typeof creature === 'string'){ - return Creatures.findOne(id, {fields}); + return Creatures.findOne(creature, {fields}); } else { return creature; } @@ -20,52 +20,9 @@ export function assertOwnership(creature, userId){ export function assertEditPermission(creature, userId) { creature = getCreature(creature, {owner: 1, writers: 1}); editPermission(creature, userId); -}; +} export function assertViewPermission(creature, userId) { creature = getCreature(creature, {owner: 1, writers: 1, public: 1}); viewPermission(creature, userId); -}; - -// Checks if the method has permission to run on the document. If the document -// has a charId, that creature is checked, otherwise if it has an _id and the -// collection is defined in the method options, that document is fetched to -// determine its charId, otherwise a getCharId method can be defined to perform -// a special search for the required creature -export function creaturePermissionMixin(methodOptions){ - let assertPermission; - if (methodOptions.permission === 'owner'){ - assertPermission = assertOwnership; - } else if (methodOptions.permission === 'edit'){ - assertPermission = assertEditPermission; - } else if (methodOptions.permission === 'view'){ - assertPermission = assertViewPermission; - } else { - throw "`permission` missing in method options"; - } - - let getCharId; - if (methodOptions.getCharId){ - getCharId = methodOptions.getCharId - } else if (methodOptions.collection) { - getCharId = function({_id}){ - methodOptions.collection.findOne(_id, { - fields: {charId: 1} - }).charId; - }; - } else { - getCharId = function(){ - throw "`getCharId` or `collection` missing in method options," + - " or {charId} missing in call"; - } - } - - let runFunc = methodOptions.run; - methodOptions.run = function(doc, ...rest){ - // Store the charId on the doc for other mixins if it had to be fetched - doc.charId = doc.charId || getCharId.apply(this, arguments); - assertPermission(charId, this.userId) - return runFunc.call(this, doc, ...rest); - }; - return methodOptions; -}; +} diff --git a/app/imports/api/creature/properties/Actions.js b/app/imports/api/creature/properties/Actions.js index 767c53ef..2bc8b12a 100644 --- a/app/imports/api/creature/properties/Actions.js +++ b/app/imports/api/creature/properties/Actions.js @@ -7,10 +7,10 @@ import ChildSchema from '/imports/api/parenting/ChildSchema.js'; import ColorSchema from '/imports/api/creature/subSchemas/ColorSchema.js'; // Mixins -import { creaturePermissionMixin } from '/imports/api/creature/creaturePermissions.js'; -import { setDocToLastMixin } from '/imports/api/order.js'; +import creaturePermissionMixin from '/imports/api/mixins/creaturePermissionMixin.js'; +import { setDocToLastMixin } from '/imports/api/mixins/setDocToLastMixin.js'; import { setDocAncestryMixin, ensureAncestryContainsCharIdMixin } from '/imports/api/parenting/parenting.js'; -import simpleSchemaMixin from '/imports/api/simpleSchemaMixin.js'; +import simpleSchemaMixin from '/imports/api/mixins/simpleSchemaMixin.js'; let Actions = new Mongo.Collection('actions'); @@ -88,9 +88,9 @@ const insertAction = new ValidatedMethod({ name: 'Actions.methods.insert', mixins: [ creaturePermissionMixin, - setDocToLastMixin, setDocAncestryMixin, ensureAncestryContainsCharIdMixin, + setDocToLastMixin, simpleSchemaMixin, ], collection: Actions, diff --git a/app/imports/api/creature/properties/Attributes.js b/app/imports/api/creature/properties/Attributes.js index 4bfbcc8e..cff80927 100644 --- a/app/imports/api/creature/properties/Attributes.js +++ b/app/imports/api/creature/properties/Attributes.js @@ -4,13 +4,19 @@ import ColorSchema from '/imports/api/creature/subSchemas/ColorSchema.js'; import SimpleSchema from 'simpl-schema'; import schema from '/imports/api/schema.js'; import VARIABLE_NAME_REGEX from '/imports/constants/VARIABLE_NAME_REGEX.js'; +import getModifierFields from '/imports/api/getModifierFields.js'; // Mixins -import recomputeCreatureMixin from '/imports/api/creature/recomputeCreatureMixin.js'; -import { creaturePermissionMixin } from '/imports/api/creature/creaturePermissions.js'; -import { setDocToLastMixin } from '/imports/api/order.js'; -import { setDocAncestryMixin, ensureAncestryContainsCharIdMixin } from '/imports/api/parenting/parenting.js'; -import simpleSchemaMixin from '/imports/api/simpleSchemaMixin.js'; +import recomputeCreatureMixin from '/imports/api/mixins/recomputeCreatureMixin.js'; +import creaturePermissionMixin from '/imports/api/mixins/creaturePermissionMixin.js'; +import { setDocToLastMixin } from '/imports/api/mixins/setDocToLastMixin.js'; +import { + setDocAncestryMixin, + ensureAncestryContainsCharIdMixin +} from '/imports/api/parenting/parenting.js'; +import simpleSchemaMixin from '/imports/api/mixins/simpleSchemaMixin.js'; +import updateSchemaMixin from '/imports/api/mixins/updateSchemaMixin.js'; +import propagateInheritanceUpdateMixin from '/imports/api/mixins/propagateInheritanceUpdateMixin.js'; let Attributes = new Mongo.Collection('attributes'); @@ -73,6 +79,7 @@ let AttributeSchema = schema({ }); AttributeSchema.extend(ColorSchema); +AttributeSchema.extend(PropertySchema); const ComputedAttributeSchema = schema({ // The computed value of the attribute @@ -88,17 +95,16 @@ const ComputedAttributeSchema = schema({ }).extend(AttributeSchema); Attributes.attachSchema(ComputedAttributeSchema); -Attributes.attachSchema(PropertySchema); Attributes.attachSchema(ChildSchema); const insertAttribute = new ValidatedMethod({ name: 'Attributes.methods.insert', mixins: [ - creaturePermissionMixin, - setDocToLastMixin, setDocAncestryMixin, ensureAncestryContainsCharIdMixin, recomputeCreatureMixin, + creaturePermissionMixin, + setDocToLastMixin, simpleSchemaMixin, ], collection: Attributes, @@ -112,31 +118,32 @@ const insertAttribute = new ValidatedMethod({ const updateAttribute = new ValidatedMethod({ name: 'Attributes.methods.update', mixins: [ - creaturePermissionMixin, recomputeCreatureMixin, - simpleSchemaMixin, + propagateInheritanceUpdateMixin, + updateSchemaMixin, + creaturePermissionMixin, ], collection: Attributes, permission: 'edit', - schema: new SimpleSchema({ - _id: SimpleSchema.RegEx.Id, - update: AttributeSchema.omit('adjustment', 'name'), - }), + updateSchema: AttributeSchema, skipRecompute({update}){ - return !('variableName' in update) && - !('type' in update) && - !('baseValue' in update) + let fields = getModifierFields(update); + return !fields.hasAny([ + 'variableName', + 'type', + 'baseValue', + ]); }, run({_id, update}) { - return Attributes.update(_id, {$set: update}); + return Attributes.update(_id, update); }, }); const adjustAttribute = new ValidatedMethod({ name: 'Attributes.methods.adjust', mixins: [ - creaturePermissionMixin, simpleSchemaMixin, + creaturePermissionMixin, ], collection: Attributes, permission: 'edit', diff --git a/app/imports/api/creature/properties/Buffs.js b/app/imports/api/creature/properties/Buffs.js index e47a5517..54a8be9b 100644 --- a/app/imports/api/creature/properties/Buffs.js +++ b/app/imports/api/creature/properties/Buffs.js @@ -5,10 +5,10 @@ import ChildSchema from '/imports/api/parenting/ChildSchema.js'; import { EffectSchema } from '/imports/api/creature/properties/Effects.js'; // Mixins -import { creaturePermissionMixin } from '/imports/api/creature/creaturePermissions.js'; -import { setDocToLastMixin } from '/imports/api/order.js'; +import creaturePermissionMixin from '/imports/api/mixins/creaturePermissionMixin.js'; +import { setDocToLastMixin } from '/imports/api/mixins/setDocToLastMixin.js'; import { setDocAncestryMixin, ensureAncestryContainsCharIdMixin } from '/imports/api/parenting/parenting.js'; -import simpleSchemaMixin from '/imports/api/simpleSchemaMixin.js'; +import simpleSchemaMixin from '/imports/api/mixins/simpleSchemaMixin.js'; let Buffs = new Mongo.Collection('buffs'); @@ -83,9 +83,9 @@ const insertBuff = new ValidatedMethod({ name: 'Buffs.methods.insert', mixins: [ creaturePermissionMixin, - setDocToLastMixin, setDocAncestryMixin, ensureAncestryContainsCharIdMixin, + setDocToLastMixin, simpleSchemaMixin, ], collection: Buffs, diff --git a/app/imports/api/creature/properties/ClassLevels.js b/app/imports/api/creature/properties/ClassLevels.js index 4138952c..111d2aee 100644 --- a/app/imports/api/creature/properties/ClassLevels.js +++ b/app/imports/api/creature/properties/ClassLevels.js @@ -5,10 +5,10 @@ import ChildSchema from '/imports/api/parenting/ChildSchema.js'; import VARIABLE_NAME_REGEX from '/imports/constants/VARIABLE_NAME_REGEX.js'; // Mixins -import { creaturePermissionMixin } from '/imports/api/creature/creaturePermissions.js'; -import { setDocToLastMixin } from '/imports/api/order.js'; +import creaturePermissionMixin from '/imports/api/mixins/creaturePermissionMixin.js'; +import { setDocToLastMixin } from '/imports/api/mixins/setDocToLastMixin.js'; import { setDocAncestryMixin, ensureAncestryContainsCharIdMixin } from '/imports/api/parenting/parenting.js'; -import simpleSchemaMixin from '/imports/api/simpleSchemaMixin.js'; +import simpleSchemaMixin from '/imports/api/mixins/simpleSchemaMixin.js'; let ClassLevels = new Mongo.Collection("classLevels"); @@ -50,9 +50,9 @@ const insertClassLevel = new ValidatedMethod({ name: 'ClassLevels.methods.insert', mixins: [ creaturePermissionMixin, - setDocToLastMixin, setDocAncestryMixin, ensureAncestryContainsCharIdMixin, + setDocToLastMixin, simpleSchemaMixin, ], collection: ClassLevels, diff --git a/app/imports/api/creature/properties/Classes.js b/app/imports/api/creature/properties/Classes.js index 56e80f18..d38dcb89 100644 --- a/app/imports/api/creature/properties/Classes.js +++ b/app/imports/api/creature/properties/Classes.js @@ -6,10 +6,10 @@ import ColorSchema from "/imports/api/creature/subSchemas/ColorSchema.js"; import VARIABLE_NAME_REGEX from '/imports/constants/VARIABLE_NAME_REGEX.js'; // Mixins -import { creaturePermissionMixin } from '/imports/api/creature/creaturePermissions.js'; -import { setDocToLastMixin } from '/imports/api/order.js'; +import creaturePermissionMixin from '/imports/api/mixins/creaturePermissionMixin.js'; +import { setDocToLastMixin } from '/imports/api/mixins/setDocToLastMixin.js'; import { setDocAncestryMixin, ensureAncestryContainsCharIdMixin } from '/imports/api/parenting/parenting.js'; -import simpleSchemaMixin from '/imports/api/simpleSchemaMixin.js'; +import simpleSchemaMixin from '/imports/api/mixins/simpleSchemaMixin.js'; let Classes = new Mongo.Collection("classes"); @@ -35,9 +35,9 @@ const insertClass = new ValidatedMethod({ name: 'Classes.methods.insert', mixins: [ creaturePermissionMixin, - setDocToLastMixin, setDocAncestryMixin, ensureAncestryContainsCharIdMixin, + setDocToLastMixin, simpleSchemaMixin, ], collection: Classes, diff --git a/app/imports/api/creature/properties/DamageMultipliers.js b/app/imports/api/creature/properties/DamageMultipliers.js index a2d7c3e8..74d939b9 100644 --- a/app/imports/api/creature/properties/DamageMultipliers.js +++ b/app/imports/api/creature/properties/DamageMultipliers.js @@ -5,11 +5,11 @@ import ChildSchema from '/imports/api/parenting/ChildSchema.js'; import DAMAGE_TYPES from '/imports/constants/DAMAGE_TYPES.js'; // Mixins -import recomputeCreatureMixin from '/imports/api/creature/recomputeCreatureMixin.js'; -import { creaturePermissionMixin } from '/imports/api/creature/creaturePermissions.js'; -import { setDocToLastMixin } from '/imports/api/order.js'; +import recomputeCreatureMixin from '/imports/api/mixins/recomputeCreatureMixin.js'; +import creaturePermissionMixin from '/imports/api/mixins/creaturePermissionMixin.js'; +import { setDocToLastMixin } from '/imports/api/mixins/setDocToLastMixin.js'; import { setDocAncestryMixin, ensureAncestryContainsCharIdMixin } from '/imports/api/parenting/parenting.js'; -import simpleSchemaMixin from '/imports/api/simpleSchemaMixin.js'; +import simpleSchemaMixin from '/imports/api/mixins/simpleSchemaMixin.js'; let DamageMultipliers = new Mongo.Collection("damageMultipliers"); @@ -43,10 +43,10 @@ const insertDamageMultiplier = new ValidatedMethod({ name: 'DamageMultipliers.methods.insert', mixins: [ creaturePermissionMixin, - setDocToLastMixin, setDocAncestryMixin, ensureAncestryContainsCharIdMixin, recomputeCreatureMixin, + setDocToLastMixin, simpleSchemaMixin, ], collection: DamageMultipliers, diff --git a/app/imports/api/creature/properties/Effects.js b/app/imports/api/creature/properties/Effects.js index 98d54227..d833bc38 100644 --- a/app/imports/api/creature/properties/Effects.js +++ b/app/imports/api/creature/properties/Effects.js @@ -4,11 +4,11 @@ import PropertySchema from '/imports/api/creature/subSchemas/PropertySchema.js'; import ChildSchema from '/imports/api/parenting/ChildSchema.js'; // Mixins -import recomputeCreatureMixin from '/imports/api/creature/recomputeCreatureMixin.js'; -import { creaturePermissionMixin } from '/imports/api/creature/creaturePermissions.js'; -import { setDocToLastMixin } from '/imports/api/order.js'; +import recomputeCreatureMixin from '/imports/api/mixins/recomputeCreatureMixin.js'; +import creaturePermissionMixin from '/imports/api/mixins/creaturePermissionMixin.js'; +import { setDocToLastMixin } from '/imports/api/mixins/setDocToLastMixin.js'; import { setDocAncestryMixin, ensureAncestryContainsCharIdMixin } from '/imports/api/parenting/parenting.js'; -import simpleSchemaMixin from '/imports/api/simpleSchemaMixin.js'; +import simpleSchemaMixin from '/imports/api/mixins/simpleSchemaMixin.js'; let Effects = new Mongo.Collection('effects'); @@ -64,10 +64,10 @@ const insertEffect = new ValidatedMethod({ name: 'Effects.methods.insert', mixins: [ creaturePermissionMixin, - setDocToLastMixin, setDocAncestryMixin, ensureAncestryContainsCharIdMixin, recomputeCreatureMixin, + setDocToLastMixin, simpleSchemaMixin, ], collection: Effects, diff --git a/app/imports/api/creature/properties/Experiences.js b/app/imports/api/creature/properties/Experiences.js index 19843292..8ff256f0 100644 --- a/app/imports/api/creature/properties/Experiences.js +++ b/app/imports/api/creature/properties/Experiences.js @@ -4,11 +4,11 @@ import PropertySchema from '/imports/api/creature/subSchemas/PropertySchema.js'; import recomputeCreatureXP from '/imports/api/creature/creatureComputation.js'; // Mixins -import recomputeCreatureMixin from '/imports/api/creature/recomputeCreatureMixin.js'; -import { creaturePermissionMixin } from '/imports/api/creature/creaturePermissions.js'; -import { setDocToLastMixin } from '/imports/api/order.js'; +import recomputeCreatureMixin from '/imports/api/mixins/recomputeCreatureMixin.js'; +import creaturePermissionMixin from '/imports/api/mixins/creaturePermissionMixin.js'; +import { setDocToLastMixin } from '/imports/api/mixins/setDocToLastMixin.js'; import { setDocAncestryMixin, ensureAncestryContainsCharIdMixin } from '/imports/api/parenting/parenting.js'; -import simpleSchemaMixin from '/imports/api/simpleSchemaMixin.js'; +import simpleSchemaMixin from '/imports/api/mixins/simpleSchemaMixin.js'; let Experiences = new Mongo.Collection("experience"); @@ -53,10 +53,10 @@ const insertExperience = new ValidatedMethod({ name: 'Experiences.methods.insert', mixins: [ creaturePermissionMixin, - setDocToLastMixin, setDocAncestryMixin, ensureAncestryContainsCharIdMixin, recomputeCreatureMixin, + setDocToLastMixin, simpleSchemaMixin, ], collection: Experiences, diff --git a/app/imports/api/creature/properties/Features.js b/app/imports/api/creature/properties/Features.js index 2e43fecb..d08591a7 100644 --- a/app/imports/api/creature/properties/Features.js +++ b/app/imports/api/creature/properties/Features.js @@ -2,17 +2,17 @@ import SimpleSchema from 'simpl-schema'; import schema from '/imports/api/schema.js'; import { assertEditPermission } from '/imports/api/creature/creaturePermissions.js'; import { recomputeCreatureById } from '/imports/api/creature/creatureComputation.js' -import { getHighestOrder } from '/imports/api/order.js'; +import { getHighestOrder } from '/imports/api/order/order.js'; import PropertySchema from '/imports/api/creature/subSchemas/PropertySchema.js'; import ChildSchema from '/imports/api/parenting/ChildSchema.js'; import ColorSchema from '/imports/api/creature/subSchemas/ColorSchema.js'; // Mixins -import recomputeCreatureMixin from '/imports/api/creature/recomputeCreatureMixin.js'; -import { creaturePermissionMixin } from '/imports/api/creature/creaturePermissions.js'; -import { setDocToLastMixin } from '/imports/api/order.js'; +import recomputeCreatureMixin from '/imports/api/mixins/recomputeCreatureMixin.js'; +import creaturePermissionMixin from '/imports/api/mixins/creaturePermissionMixin.js'; +import { setDocToLastMixin } from '/imports/api/mixins/setDocToLastMixin.js'; import { setDocAncestryMixin, ensureAncestryContainsCharIdMixin } from '/imports/api/parenting/parenting.js'; -import simpleSchemaMixin from '/imports/api/simpleSchemaMixin.js'; +import simpleSchemaMixin from '/imports/api/mixins/simpleSchemaMixin.js'; let Features = new Mongo.Collection('features'); @@ -41,9 +41,9 @@ const insertFeature = new ValidatedMethod({ name: 'Features.methods.insert', mixins: [ creaturePermissionMixin, - setDocToLastMixin, setDocAncestryMixin, ensureAncestryContainsCharIdMixin, + setDocToLastMixin, simpleSchemaMixin, ], collection: Features, diff --git a/app/imports/api/creature/properties/Folders.js b/app/imports/api/creature/properties/Folders.js index 8d2e6d40..53fdb86b 100644 --- a/app/imports/api/creature/properties/Folders.js +++ b/app/imports/api/creature/properties/Folders.js @@ -4,10 +4,10 @@ import PropertySchema from '/imports/api/creature/subSchemas/PropertySchema.js'; import ChildSchema from '/imports/api/parenting/ChildSchema.js'; // Mixins -import { creaturePermissionMixin } from '/imports/api/creature/creaturePermissions.js'; -import { setDocToLastMixin } from '/imports/api/order.js'; +import creaturePermissionMixin from '/imports/api/mixins/creaturePermissionMixin.js'; +import { setDocToLastMixin } from '/imports/api/mixins/setDocToLastMixin.js'; import { setDocAncestryMixin, ensureAncestryContainsCharIdMixin } from '/imports/api/parenting/parenting.js'; -import simpleSchemaMixin from '/imports/api/simpleSchemaMixin.js'; +import simpleSchemaMixin from '/imports/api/mixins/simpleSchemaMixin.js'; let Folders = new Mongo.Collection('folders'); @@ -26,9 +26,9 @@ const insertFolder = new ValidatedMethod({ name: 'Folders.methods.insert', mixins: [ creaturePermissionMixin, - setDocToLastMixin, setDocAncestryMixin, ensureAncestryContainsCharIdMixin, + setDocToLastMixin, simpleSchemaMixin, ], collection: Folders, diff --git a/app/imports/api/creature/properties/Notes.js b/app/imports/api/creature/properties/Notes.js index b662de9a..af74d733 100644 --- a/app/imports/api/creature/properties/Notes.js +++ b/app/imports/api/creature/properties/Notes.js @@ -4,10 +4,10 @@ import ColorSchema from "/imports/api/creature/subSchemas/ColorSchema.js"; import PropertySchema from '/imports/api/creature/subSchemas/PropertySchema.js'; // Mixins -import { creaturePermissionMixin } from '/imports/api/creature/creaturePermissions.js'; -import { setDocToLastMixin } from '/imports/api/order.js'; +import creaturePermissionMixin from '/imports/api/mixins/creaturePermissionMixin.js'; +import { setDocToLastMixin } from '/imports/api/mixins/setDocToLastMixin.js'; import { setDocAncestryMixin, ensureAncestryContainsCharIdMixin } from '/imports/api/parenting/parenting.js'; -import simpleSchemaMixin from '/imports/api/simpleSchemaMixin.js'; +import simpleSchemaMixin from '/imports/api/mixins/simpleSchemaMixin.js'; let Notes = new Mongo.Collection("notes"); @@ -31,9 +31,9 @@ const insertNote = new ValidatedMethod({ name: 'Notes.methods.insert', mixins: [ creaturePermissionMixin, - setDocToLastMixin, setDocAncestryMixin, ensureAncestryContainsCharIdMixin, + setDocToLastMixin, simpleSchemaMixin, ], collection: Notes, diff --git a/app/imports/api/creature/properties/Proficiencies.js b/app/imports/api/creature/properties/Proficiencies.js index 8090bebf..3df0227d 100644 --- a/app/imports/api/creature/properties/Proficiencies.js +++ b/app/imports/api/creature/properties/Proficiencies.js @@ -4,11 +4,11 @@ import PropertySchema from '/imports/api/creature/subSchemas/PropertySchema.js'; import ChildSchema from '/imports/api/parenting/ChildSchema.js'; // Mixins -import recomputeCreatureMixin from '/imports/api/creature/recomputeCreatureMixin.js'; -import { creaturePermissionMixin } from '/imports/api/creature/creaturePermissions.js'; -import { setDocToLastMixin } from '/imports/api/order.js'; +import recomputeCreatureMixin from '/imports/api/mixins/recomputeCreatureMixin.js'; +import creaturePermissionMixin from '/imports/api/mixins/creaturePermissionMixin.js'; +import { setDocToLastMixin } from '/imports/api/mixins/setDocToLastMixin.js'; import { setDocAncestryMixin, ensureAncestryContainsCharIdMixin } from '/imports/api/parenting/parenting.js'; -import simpleSchemaMixin from '/imports/api/simpleSchemaMixin.js'; +import simpleSchemaMixin from '/imports/api/mixins/simpleSchemaMixin.js'; let Proficiencies = new Mongo.Collection("proficiencies"); diff --git a/app/imports/api/creature/properties/Rolls.js b/app/imports/api/creature/properties/Rolls.js index b248b798..892c0285 100644 --- a/app/imports/api/creature/properties/Rolls.js +++ b/app/imports/api/creature/properties/Rolls.js @@ -5,10 +5,10 @@ import AdjustmentSchema from '/imports/api/creature/subSchemas/AdjustmentSchema. import StoredBuffSchema from '/imports/api/creature/properties/Buffs.js'; // Mixins -import { creaturePermissionMixin } from '/imports/api/creature/creaturePermissions.js'; -import { setDocToLastMixin } from '/imports/api/order.js'; +import creaturePermissionMixin from '/imports/api/mixins/creaturePermissionMixin.js'; +import { setDocToLastMixin } from '/imports/api/mixins/setDocToLastMixin.js'; import { setDocAncestryMixin, ensureAncestryContainsCharIdMixin } from '/imports/api/parenting/parenting.js'; -import simpleSchemaMixin from '/imports/api/simpleSchemaMixin.js'; +import simpleSchemaMixin from '/imports/api/mixins/simpleSchemaMixin.js'; let Rolls = new Mongo.Collection('rolls'); diff --git a/app/imports/api/creature/properties/Skills.js b/app/imports/api/creature/properties/Skills.js index d4d03dc2..1b6267d9 100644 --- a/app/imports/api/creature/properties/Skills.js +++ b/app/imports/api/creature/properties/Skills.js @@ -5,11 +5,11 @@ import ChildSchema from '/imports/api/parenting/ChildSchema.js'; import ColorSchema from '/imports/api/creature/subSchemas/ColorSchema.js'; // Mixins -import recomputeCreatureMixin from '/imports/api/creature/recomputeCreatureMixin.js'; -import { creaturePermissionMixin } from '/imports/api/creature/creaturePermissions.js'; -import { setDocToLastMixin } from '/imports/api/order.js'; +import recomputeCreatureMixin from '/imports/api/mixins/recomputeCreatureMixin.js'; +import creaturePermissionMixin from '/imports/api/mixins/creaturePermissionMixin.js'; +import { setDocToLastMixin } from '/imports/api/mixins/setDocToLastMixin.js'; import { setDocAncestryMixin, ensureAncestryContainsCharIdMixin } from '/imports/api/parenting/parenting.js'; -import simpleSchemaMixin from '/imports/api/simpleSchemaMixin.js'; +import simpleSchemaMixin from '/imports/api/mixins/simpleSchemaMixin.js'; let Skills = new Mongo.Collection("skills"); diff --git a/app/imports/api/creature/properties/SpellLists.js b/app/imports/api/creature/properties/SpellLists.js index d6bdaefc..14ff7532 100644 --- a/app/imports/api/creature/properties/SpellLists.js +++ b/app/imports/api/creature/properties/SpellLists.js @@ -5,10 +5,10 @@ import PropertySchema from '/imports/api/creature/subSchemas/PropertySchema.js'; import ChildSchema from '/imports/api/parenting/ChildSchema.js'; // Mixins -import { creaturePermissionMixin } from '/imports/api/creature/creaturePermissions.js'; -import { setDocToLastMixin } from '/imports/api/order.js'; +import creaturePermissionMixin from '/imports/api/mixins/creaturePermissionMixin.js'; +import { setDocToLastMixin } from '/imports/api/mixins/setDocToLastMixin.js'; import { setDocAncestryMixin, ensureAncestryContainsCharIdMixin } from '/imports/api/parenting/parenting.js'; -import simpleSchemaMixin from '/imports/api/simpleSchemaMixin.js'; +import simpleSchemaMixin from '/imports/api/mixins/simpleSchemaMixin.js'; let SpellLists = new Mongo.Collection("spellLists"); diff --git a/app/imports/api/creature/properties/Spells.js b/app/imports/api/creature/properties/Spells.js index ea155237..0ee95348 100644 --- a/app/imports/api/creature/properties/Spells.js +++ b/app/imports/api/creature/properties/Spells.js @@ -5,10 +5,10 @@ import PropertySchema from '/imports/api/creature/subSchemas/PropertySchema.js'; import ChildSchema from '/imports/api/parenting/ChildSchema.js'; // Mixins -import { creaturePermissionMixin } from '/imports/api/creature/creaturePermissions.js'; -import { setDocToLastMixin } from '/imports/api/order.js'; +import creaturePermissionMixin from '/imports/api/mixins/creaturePermissionMixin.js'; +import { setDocToLastMixin } from '/imports/api/mixins/setDocToLastMixin.js'; import { setDocAncestryMixin, ensureAncestryContainsCharIdMixin } from '/imports/api/parenting/parenting.js'; -import simpleSchemaMixin from '/imports/api/simpleSchemaMixin.js'; +import simpleSchemaMixin from '/imports/api/mixins/simpleSchemaMixin.js'; const magicSchools = [ 'Abjuration', diff --git a/app/imports/api/getModifierFields.js b/app/imports/api/getModifierFields.js new file mode 100644 index 00000000..f26e42af --- /dev/null +++ b/app/imports/api/getModifierFields.js @@ -0,0 +1,28 @@ +import MONGO_OPERATORS from '/imports/constants/MONGO_OPERATORS.js'; + +const hasAny = function(values){ + for (let value of values){ + if (this.has(value)){ + return true; + } + } + return false; +}; + +// Returns a Set of fields the modifier changes +// The set has been extended with the "hasAny" function +export default function getModifierFields (modifier) { + let fields = new Set(); + + for (let operator of MONGO_OPERATORS){ + if (modifier[operator]) for (let field in modifier[operator]){ + const indexOfDot = field.indexOf('.'); + if (indexOfDot !== -1) { + field = field.substring(0, indexOfDot); + } + fields.add(field); + } + } + fields.hasAny = hasAny; + return fields; +} diff --git a/app/imports/api/mixins/creaturePermissionMixin.js b/app/imports/api/mixins/creaturePermissionMixin.js new file mode 100644 index 00000000..887879b2 --- /dev/null +++ b/app/imports/api/mixins/creaturePermissionMixin.js @@ -0,0 +1,52 @@ +import { + assertEditPermission, + assertViewPermission, + assertOwnership, +} from '/imports/api/creature/creaturePermissions.js'; + +// Checks if the method has permission to run on the document. If the document +// has a charId, that creature is checked, otherwise if it has an _id and the +// collection is defined in the method options, that document is fetched to +// determine its charId, otherwise a getCharId method can be defined to perform +// a special search for the required creature +// +// Because this mixin injects the charId into argument objects that don't +// already contain it, it should always come last in the mixin list, so that it +// the outermost wrapper of the run function +export default function creaturePermissionMixin(methodOptions){ + let assertPermission; + if (methodOptions.permission === 'owner'){ + assertPermission = assertOwnership; + } else if (methodOptions.permission === 'edit'){ + assertPermission = assertEditPermission; + } else if (methodOptions.permission === 'view'){ + assertPermission = assertViewPermission; + } else { + throw "`permission` missing in method options"; + } + + let getCharId; + if (methodOptions.getCharId){ + getCharId = methodOptions.getCharId; + } else if (methodOptions.collection) { + getCharId = function({_id}){ + return methodOptions.collection.findOne(_id, { + fields: {charId: 1} + }).charId; + }; + } else { + getCharId = function(){ + throw "`getCharId` or `collection` missing in method options," + + " or {charId} missing in call"; + }; + } + + let runFunc = methodOptions.run; + methodOptions.run = function(doc, ...rest){ + // Store the charId on the doc for other mixins if it had to be fetched + doc.charId = doc.charId || getCharId.apply(this, arguments); + assertPermission(doc.charId, this.userId); + return runFunc.call(this, doc, ...rest); + }; + return methodOptions; +} diff --git a/app/imports/api/mixins/propagateInheritanceUpdateMixin.js b/app/imports/api/mixins/propagateInheritanceUpdateMixin.js new file mode 100644 index 00000000..fb0ef8c4 --- /dev/null +++ b/app/imports/api/mixins/propagateInheritanceUpdateMixin.js @@ -0,0 +1,59 @@ +import { + updateChildren, + updateDecendents, +} from '/imports/api/parenting/parenting.js'; +import { inheritedFields } from '/imports/api/parenting/ChildSchema.js'; +import MONGO_OPERATORS from '/imports/constants/MONGO_OPERATORS.js'; + +// This mixin can be safely applied to all update methods which are validated +// with the updateSchemaMixin. It will propagate updates to fields which +// are inherited and normalised on the parent or ancestor docs +// It should have neglible performance impact for updates that aren't inherited +function propagateInheritanceUpdate({_id, update}){ + let childModifier = {}; + let decendentModifier = {}; + // For each operator + for (let operator of MONGO_OPERATORS){ + // If the operator is in the update, for each field + if (update[operator]) for (let field in update[operator]){ + // Get the top level field that was actually modified + const indexOfDot = field.indexOf('.'); + let modifiedField; + if (indexOfDot !== -1) { + modifiedField = field.substring(0, indexOfDot); + } else { + modifiedField = field; + } + // If that field is updated and inherited + if (inheritedFields.has(modifiedField)){ + // Perform the same update on the decendents + if (!childModifier[operator]) childModifier[operator] = {}; + if (!decendentModifier[operator]) decendentModifier[operator] = {}; + childModifier[operator][`parent.${field}`] = update[operator][field]; + decendentModifier[operator][`ancestors.$.${field}`] = update[operator][field]; + } + } + } + + // Update the parent object of its children + updateChildren({ + parentId: _id, + modifier: childModifier, + }); + + // Update the ancestors object of its decendents + updateDecendents({ + ancestorId: _id, + modifier: decendentModifier, + }); +} + +export default function propagateInheritanceUpdateMixin(methodOptions){ + let runFunc = methodOptions.run; + methodOptions.run = function({_id, update}){ + const result = runFunc.apply(this, arguments); + propagateInheritanceUpdate({_id, update}); + return result; + }; + return methodOptions; +} diff --git a/app/imports/api/creature/recomputeCreatureMixin.js b/app/imports/api/mixins/recomputeCreatureMixin.js similarity index 75% rename from app/imports/api/creature/recomputeCreatureMixin.js rename to app/imports/api/mixins/recomputeCreatureMixin.js index 564e2ae3..4878ce09 100644 --- a/app/imports/api/creature/recomputeCreatureMixin.js +++ b/app/imports/api/mixins/recomputeCreatureMixin.js @@ -1,3 +1,5 @@ +import { recomputeCreatureById } from '/imports/api/creature/creatureComputation.js'; + export default function recomputeCreatureMixin(methodOptions){ let runFunc = methodOptions.run; methodOptions.run = function({charId}){ @@ -5,9 +7,11 @@ export default function recomputeCreatureMixin(methodOptions){ if ( methodOptions.skipRecompute && methodOptions.skipRecompute.apply(this, arguments) - ) return result; + ) { + return result; + } recomputeCreatureById(charId); return result; }; return methodOptions; -}; +} diff --git a/app/imports/api/mixins/setDocToLastMixin.js b/app/imports/api/mixins/setDocToLastMixin.js new file mode 100644 index 00000000..1b31b90d --- /dev/null +++ b/app/imports/api/mixins/setDocToLastMixin.js @@ -0,0 +1,27 @@ +import SimpleSchema from 'simpl-schema'; +import { setDocToLastOrder } from '/imports/api/order/order.js'; + +export function setDocToLastMixin(methodOptions){ + // Make sure the doc has a charId + // This mixin should come before simpleSchemaMixin so that it can extend the + // schema before it is turned into a validate function + if (methodOptions.validate){ + throw new Meteor.Error(`setDocToLastMixin should come before simpleSchemaMixin`); + } + methodOptions.schema.extend({ + charId: { + type: String, + regEx: SimpleSchema.RegEx.Id, + }, + }); + let collection = methodOptions.collection; + if (!collection){ + throw new Meteor.Error("`collection` required in method options for setDocToLastMixin"); + } + let runFunc = methodOptions.run; + methodOptions.run = function(doc){ + setDocToLastOrder({collection, doc}); + return runFunc.apply(this, arguments); + }; + return methodOptions; +} diff --git a/app/imports/api/simpleSchemaMixin.js b/app/imports/api/mixins/simpleSchemaMixin.js similarity index 100% rename from app/imports/api/simpleSchemaMixin.js rename to app/imports/api/mixins/simpleSchemaMixin.js diff --git a/app/imports/api/mixins/updateSchemaMixin.js b/app/imports/api/mixins/updateSchemaMixin.js new file mode 100644 index 00000000..2e840492 --- /dev/null +++ b/app/imports/api/mixins/updateSchemaMixin.js @@ -0,0 +1,61 @@ +const argumentSchema = new SimpleSchema({ + _id: SimpleSchema.RegEx.Id, + update: { + type: Object, + blackbox: true, + }, +}); + +// Modified simpleSchemaMixin +import SimpleSchema from 'simpl-schema'; + +export default function updateSchemaMixin(methodOptions) { + // If the user didn't give us a schema and they did give us a validate, assume + // that they are choosing to use the validate way of doing things in this call. + // If they've built a wrapper around ValidateMethod that includes this mixin + // all the time, this could happen semi-"intentionally". There may be times they + // just don't want to use a schema and have specified a "validate" option. So + // returning the unchanged options instead of an error seems proper. + if (( + typeof methodOptions.updateSchema === 'undefined' && + typeof methodOptions.validate !== 'undefined' + ) || ( + typeof methodOptions.updateSchema !== 'undefined' && + methodOptions.updateSchema === null && + typeof methodOptions.validate !== 'undefined' && + methodOptions.validate !== null + )) { + return methodOptions; + } + + // If they truly gave us both... that just doesn't seem proper. + if (methodOptions.validate && methodOptions.validate !== null) { + throw new Meteor.Error( + 'simpleSchemaMixin.options', + '"schema" and "validate" options cannot be used together'); + } + + // Note that setting them both null will make it through, defaulting to the + // schema = null behavior (enforce no args) instead of the validate = null + // behavior (do no validation). + + // Apply default validator options if none are provided + methodOptions.schemaValidatorOptions = + methodOptions.schemaValidatorOptions || + { clean: true, modifier: true }; + + // Make the update schema a SimpleSchema, if it isn't already + let updateSchema; + if (methodOptions.updateSchema instanceof SimpleSchema) { + updateSchema = methodOptions.updateSchema; + } else { + updateSchema = new SimpleSchema(methodOptions.updateSchema); + } + + // Set up the new validation + methodOptions.validate = function(args){ + argumentSchema.validate(args); + updateSchema.validate(args.update, methodOptions.schemaValidatorOptions); + }; + return methodOptions; +} diff --git a/app/imports/api/order.js b/app/imports/api/order/order.js similarity index 74% rename from app/imports/api/order.js rename to app/imports/api/order/order.js index 2eced3d7..834f69af 100644 --- a/app/imports/api/order.js +++ b/app/imports/api/order/order.js @@ -8,34 +8,13 @@ export function getHighestOrder({collection, charId}){ sort: {order: -1}, }); return (highestOrderedDoc && highestOrderedDoc.order) || 0; -}; +} export function setDocToLastOrder({collection, doc}){ doc.order = getHighestOrder({ collection, charId: doc.charId, }) + 1; -}; - -export function setDocToLastMixin(methodOptions){ - // Make sure the doc has a charId - // This mixin should come before simpleSchemaMixin - methodOptions.schema.extend({ - charId: { - type: String, - regEx: SimpleSchema.RegEx.Id, - }, - }); - let collection = methodOptions.collection - if (!collection){ - throw "`collection` required in method options for setDocToLastMixin" - } - let runFunc = methodOptions.run; - methodOptions.run = function(doc){ - setDocToLastOrder({collection, doc}); - return runFunc.apply(this, arguments); - }; - return methodOptions; } export function setDocOrder({collection, doc, order}){ @@ -70,7 +49,7 @@ export function setDocOrder({collection, doc, order}){ multi: true, }); } -}; +} export function reorderDocs({collection, charId}){ let bulkWrite = []; @@ -96,4 +75,4 @@ export function reorderDocs({collection, charId}){ collection.update(op.filter, op.update); }); } -}; +} diff --git a/app/imports/api/parenting/ChildSchema.js b/app/imports/api/parenting/ChildSchema.js index 33a84928..10500921 100644 --- a/app/imports/api/parenting/ChildSchema.js +++ b/app/imports/api/parenting/ChildSchema.js @@ -1,7 +1,7 @@ import SimpleSchema from 'simpl-schema'; import schema from '/imports/api/schema.js'; -const refSchema = new SimpleSchema({ +const RefSchema = new SimpleSchema({ id: { type: String, regEx: SimpleSchema.RegEx.Id, @@ -21,9 +21,9 @@ const refSchema = new SimpleSchema({ }, }); -let childSchema = schema({ +let ChildSchema = schema({ parent: { - type: refSchema, + type: RefSchema, optional: true, }, ancestors: { @@ -31,8 +31,13 @@ let childSchema = schema({ defaultValue: [], }, 'ancestors.$': { - type: refSchema, + type: RefSchema, }, }); -export default childSchema; +const inheritedFields = new Set(RefSchema.objectKeys()); +inheritedFields.delete('id'); +inheritedFields.delete('collection'); + +export default ChildSchema; +export { inheritedFields }; diff --git a/app/imports/api/parenting/parenting.js b/app/imports/api/parenting/parenting.js index 12ec8bad..88bba7f0 100644 --- a/app/imports/api/parenting/parenting.js +++ b/app/imports/api/parenting/parenting.js @@ -7,12 +7,12 @@ let collections = []; export function registerCollection(collectionName){ collections.push(collectionName); -}; +} // 1 database hit to get the parent by reference export function fetchParent({id, collection}){ return fetchDocByRef({id, collection}); -}; +} // n database hits to get the children by parent id export function fetchChildren({parentId, filter = {}, options}){ @@ -35,7 +35,7 @@ export function updateChildren({parentId, filter = {}, modifier, options={}}){ collections.forEach(collection => { collection.update(filter, modifier, options); }); -}; +} // n database hits to fetch the decendents by ancestor id, in no particular order export function fetchDecendents({ancestorId, filter = {}, options}){ @@ -45,7 +45,7 @@ export function fetchDecendents({ancestorId, filter = {}, options}){ decendents.push(...collection.find(filter, options).fetch()); }); return decendents; -}; +} // n database hits to update the decendents export function updateDecendents({ancestorId, filter = {}, modifier, options={}}){ @@ -54,7 +54,7 @@ export function updateDecendents({ancestorId, filter = {}, modifier, options={}} collections.forEach(collection => { collection.update(filter, modifier, options); }); -}; +} // n database hits to get decendents to act on export function forEachDecendent({ancestorId, filter = {}, options}, callback){ @@ -62,9 +62,10 @@ export function forEachDecendent({ancestorId, filter = {}, options}, callback){ collections.forEach(collection => { collection.find(filter, options).forEach(callback); }); -}; +} // 1 database read +// TODO generalise for all inheritedFields export function getAncestry({id, collection}){ // Get the parent ref let parentDoc = fetchDocByRef({id, collection}, {fields: { @@ -110,7 +111,7 @@ export function setDocAncestryMixin(methodOptions){ return runFunc.call(this, doc, ...rest); }; return methodOptions; -}; +} function ensureAncestryContainsId(ancestors, id){ if (!id){ @@ -148,7 +149,7 @@ export function ensureAncestryContainsCharIdMixin(methodOptions){ return runFunc.apply(this, arguments); }; return methodOptions; -}; +} export function updateParent(docRef, parentRef){ @@ -183,57 +184,16 @@ export function updateParent(docRef, parentRef){ }, }}, }); -}; - -export function setInheritedField({id, collection, fieldName, fieldValue}){ - - // Update the doc - collection = getCollectionByName(collection); - collection.update(id, {$set: { - [`${fieldName}`]: fieldValue, - }}); - - // Update the parent object of its children - updateChildren({ - parentId: id, - modifier: {$set: { - [`parent.${fieldName}`]: fieldValue, - }}, - }); - - // Update the ancestors object of its decendents - updateDecendents({ - ancestorId: id, - modifier: {$set: { - [`ancestors.$.${fieldName}`]: fieldValue, - }}, - }); - -}; - -export function setEnabled({id, collection, enabled}){ - setInheritedField({ - id, - collection, - fieldName: 'enabled', - fieldValue: enabled, - }); -}; - -export function setName({id, collection, name}){ - setInheritedField({ - id, - collection, - fieldName: 'name', - fieldValue: name, - }); -}; +} +// TODO these rely on hard coding inherited fields +// the inherited fields should only appear on the childChema, nowhere else +// Move these somewhere appropriate export function findEnabled(collection, query, options){ - query['enabled'] = true; + query.enabled = true; query['ancestors.$.enabled'] = {$not: false}; return collection.find(query, options); -}; +} export function getName(doc){ if (doc.name) return name; diff --git a/app/imports/api/pickKeysAsOptional.js b/app/imports/api/pickKeysAsOptional.js deleted file mode 100644 index 3de0ba30..00000000 --- a/app/imports/api/pickKeysAsOptional.js +++ /dev/null @@ -1,9 +0,0 @@ -export default function pickKeysAsOptional(schema, keys){ - let newSchema = schema.pick(...keys); - let optionalSchema = {}; - for (let i of keys){ - optionalSchema[i] = {optional: true} - }; - newSchema.extend(optionalSchema); - return newSchema; -}; diff --git a/app/imports/constants/MONGO_OPERATORS.js b/app/imports/constants/MONGO_OPERATORS.js new file mode 100644 index 00000000..e81f4cd8 --- /dev/null +++ b/app/imports/constants/MONGO_OPERATORS.js @@ -0,0 +1,17 @@ +const MONGO_OPERATORS = Object.freeze([ + '$addToSet', + '$bit', + '$currentDate', + '$inc', + '$max', + '$min', + '$pop', + '$pull', + '$pullAll', + '$push', + '$rename', + '$set', + '$unset', +]); + +export default MONGO_OPERATORS; diff --git a/app/imports/ui/components/attributes/AttributeDialogContainer.vue b/app/imports/ui/components/attributes/AttributeDialogContainer.vue index 12e3ac9b..f0be0a83 100644 --- a/app/imports/ui/components/attributes/AttributeDialogContainer.vue +++ b/app/imports/ui/components/attributes/AttributeDialogContainer.vue @@ -9,8 +9,12 @@