From 18e3f653f309711176bbca5dc87d683ac60f2315 Mon Sep 17 00:00:00 2001 From: Stefan Zermatten Date: Wed, 13 Mar 2019 14:03:03 +0200 Subject: [PATCH] Added insert and update methods for all properties --- .../api/creature/creaturePermissions.js | 43 +++++ .../api/creature/properties/Actions.js | 47 ++++- .../api/creature/properties/Attributes.js | 160 ++++++++---------- app/imports/api/creature/properties/Buffs.js | 50 +++++- .../api/creature/properties/ClassLevels.js | 67 +++++++- .../api/creature/properties/Classes.js | 56 +++++- .../creature/properties/DamageMultipliers.js | 52 +++++- .../api/creature/properties/Effects.js | 47 +++++ .../api/creature/properties/Experiences.js | 64 ++++++- .../api/creature/properties/Features.js | 64 ++++--- .../api/creature/properties/Folders.js | 50 +++++- app/imports/api/creature/properties/Notes.js | 46 ++++- .../api/creature/properties/Proficiencies.js | 50 +++++- app/imports/api/creature/properties/Rolls.js | 46 ++++- app/imports/api/creature/properties/Skills.js | 47 +++++ .../api/creature/properties/SpellLists.js | 45 +++++ app/imports/api/creature/properties/Spells.js | 46 ++++- .../api/creature/recomputeCreatureMixin.js | 13 ++ app/imports/api/library/LibraryNodes.js | 30 ++-- app/imports/api/order.js | 38 ++++- app/imports/api/parenting/parenting.js | 87 +++++++++- app/imports/api/simpleSchemaMixin.js | 51 ++++++ app/imports/constants/VARIABLE_NAME_REGEX.js | 4 + 23 files changed, 1049 insertions(+), 154 deletions(-) create mode 100644 app/imports/api/creature/recomputeCreatureMixin.js create mode 100644 app/imports/api/simpleSchemaMixin.js create mode 100644 app/imports/constants/VARIABLE_NAME_REGEX.js diff --git a/app/imports/api/creature/creaturePermissions.js b/app/imports/api/creature/creaturePermissions.js index b67c03d6..66412755 100644 --- a/app/imports/api/creature/creaturePermissions.js +++ b/app/imports/api/creature/creaturePermissions.js @@ -26,3 +26,46 @@ 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 24355ea1..767c53ef 100644 --- a/app/imports/api/creature/properties/Actions.js +++ b/app/imports/api/creature/properties/Actions.js @@ -6,12 +6,23 @@ 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 { 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'; + + let Actions = new Mongo.Collection('actions'); /* * Actions are things a character can do */ let ActionSchema = schema({ + name: { + type: String, + optional: true, + }, description: { type: String, optional: true, @@ -73,5 +84,39 @@ Actions.attachSchema(ActionSchema); Actions.attachSchema(PropertySchema); Actions.attachSchema(ChildSchema); +const insertAction = new ValidatedMethod({ + name: 'Actions.methods.insert', + mixins: [ + creaturePermissionMixin, + setDocToLastMixin, + setDocAncestryMixin, + ensureAncestryContainsCharIdMixin, + simpleSchemaMixin, + ], + collection: Actions, + permission: 'edit', + schema: ActionSchema, + run(action) { + return Actions.insert(action); + }, +}); + +const updateAction = new ValidatedMethod({ + name: 'Actions.methods.update', + mixins: [ + creaturePermissionMixin, + simpleSchemaMixin, + ], + collection: Actions, + permission: 'edit', + schema: new SimpleSchema({ + _id: SimpleSchema.RegEx.Id, + update: ActionSchema.omit('name'), + }), + run({_id, update}) { + return Actions.update(_id, {$set: update}); + }, +}); + export default Actions; -export { ActionSchema }; +export { ActionSchema, insertAction, updateAction }; diff --git a/app/imports/api/creature/properties/Attributes.js b/app/imports/api/creature/properties/Attributes.js index d03da5fb..4bfbcc8e 100644 --- a/app/imports/api/creature/properties/Attributes.js +++ b/app/imports/api/creature/properties/Attributes.js @@ -3,10 +3,14 @@ import ChildSchema from '/imports/api/parenting/ChildSchema.js'; import ColorSchema from '/imports/api/creature/subSchemas/ColorSchema.js'; 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 pickKeysAsOptional from '/imports/api/pickKeysAsOptional.js'; +import VARIABLE_NAME_REGEX from '/imports/constants/VARIABLE_NAME_REGEX.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'; let Attributes = new Mongo.Collection('attributes'); @@ -14,11 +18,14 @@ let Attributes = new Mongo.Collection('attributes'); * Attributes are numbered stats of a character */ let AttributeSchema = schema({ + name: { + type: String, + optional: true, + }, // The technical, lowercase, single-word name used in formulae variableName: { type: String, - // Must contain a letter, and be made of word characters only - regEx: /^\w*[a-z]\w*$/i, + regEx: VARIABLE_NAME_REGEX, }, // How it is displayed and computed is determined by type type: { @@ -85,119 +92,88 @@ Attributes.attachSchema(PropertySchema); Attributes.attachSchema(ChildSchema); const insertAttribute = new ValidatedMethod({ - name: 'Attributes.methods.insert', - - validate: AttributeSchema.validator({ clean: true }), - - run({attribute}) { - const charId = attribute.charId; - assertEditPermission(charId, this.userId); - attribute.order = getHighestOrder({ - collection: Attributes, - charId, - }) + 1; - attribute.parent = { - id: charId, - collection: 'Creatures', - }; - let attId = Attributes.insert(attribute); - recomputeCreatureById(charId); - return attId; + mixins: [ + creaturePermissionMixin, + setDocToLastMixin, + setDocAncestryMixin, + ensureAncestryContainsCharIdMixin, + recomputeCreatureMixin, + simpleSchemaMixin, + ], + collection: Attributes, + permission: 'edit', + schema: AttributeSchema, + run(attribute) { + return Attributes.insert(attribute); }, }); const updateAttribute = new ValidatedMethod({ - name: 'Attributes.methods.update', - - validate: schema({ - _id: { - type: String, - regEx: SimpleSchema.RegEx.Id, - }, - update: AttributeSchema, - }).validator(), - - run({_id, update}) { - let currentAttribute = Attributes.findOne(_id, {fields: {value: 1, charId: 1}}); - if (!currentAttribute){ - throw new Meteor.Error('Attributes.methods.update.denied', - `No attributes exist with the id: ${_id}`); - } - let charId = currentAttribute.charId; - assertEditPermission(charId, this.userId) - if (typeof update.adjustment === 'number'){ - let val = currentAttribute.value; - if (update.adjustment < -val) update.adjustment = -val; - if (update.adjustment > 0) update.adjustment = 0; - } - Attributes.update(_id, {$set: update}); - recomputeCreatureById(charId); + mixins: [ + creaturePermissionMixin, + recomputeCreatureMixin, + simpleSchemaMixin, + ], + collection: Attributes, + permission: 'edit', + schema: new SimpleSchema({ + _id: SimpleSchema.RegEx.Id, + update: AttributeSchema.omit('adjustment', 'name'), + }), + skipRecompute({update}){ + return !('variableName' in update) && + !('type' in update) && + !('baseValue' in update) + }, + run({_id, update}) { + return Attributes.update(_id, {$set: update}); }, - }); const adjustAttribute = new ValidatedMethod({ - name: 'Attributes.methods.adjust', - - validate: new SimpleSchema({ - _id: { - type: String, - regEx: SimpleSchema.RegEx.Id, - }, - increment: { - type: Number, - optional: true - }, - set: { - type: Number, - optional: true, - custom() { - if (!this.isSet && !this.field('increment').isSet) { - // either set or increment must exist - return SimpleSchema.ErrorTypes.REQUIRED; - } else if (this.isSet && this.field('increment').isSet){ - return 'Can\'t increment and set an attritbute adjustment in one operation'; - } - }, - }, - }).validator(), - - run({_id, increment, set}) { - let currentAttribute = Attributes.findOne(_id); - if (!currentAttribute){ - throw new Meteor.Error('Attributes.methods.update.denied', - `No attributes exist with the id: ${_id}`); - } - let charId = currentAttribute.charId; - assertEditPermission(charId, this.userId) - if (typeof set === 'number'){ - let val = currentAttribute.value; + mixins: [ + creaturePermissionMixin, + simpleSchemaMixin, + ], + collection: Attributes, + permission: 'edit', + schema: new SimpleSchema({ + _id: SimpleSchema.RegEx.Id, + type: { + type: String, + allowedValues: ['set', 'increment'] + }, + value: Number, + }), + run({_id, type, value}) { + if (type === 'set'){ + let currentValue = currentAttribute.value; // Set represents what we want the value to be after adjustment // So we need the actual adjustment to get to that value - let adjustment = set - val; + let adjustment = value - currentValue; // Ajustment can't exceed total value - if (-adjustment > val) adjustment = -val; + if (-adjustment > currentValue) adjustment = -currentValue; // Adjustment must be negative if (adjustment > 0) adjustment = 0; - Attributes.update(_id, {$set: {adjustment}}); - } else if (typeof increment === 'number'){ + return Attributes.update(_id, {$set: {adjustment}}); + } else if (type === 'increment'){ let remaining = currentAttribute.value + (currentAttribute.adjustment || 0); let adj = currentAttribute.adjustment; // Can't decrease adjustment below remaining value + let increment = value; if (-increment > remaining) increment = -remaining; // Can't increase adjustment above zero if (increment > -adj) increment = -adj; if (typeof currentAttribute.adjustment === 'number'){ - Attributes.update(_id, {$inc: {adjustment: increment}}); + return Attributes.update(_id, {$inc: {adjustment: increment}}); } else { - Attributes.update(_id, {$set: {adjustment: increment}}); + return Attributes.update(_id, {$set: {adjustment: increment}}); } } }, - }); export default Attributes; diff --git a/app/imports/api/creature/properties/Buffs.js b/app/imports/api/creature/properties/Buffs.js index e9ec82ff..e47a5517 100644 --- a/app/imports/api/creature/properties/Buffs.js +++ b/app/imports/api/creature/properties/Buffs.js @@ -4,9 +4,19 @@ import PropertySchema from '/imports/api/creature/subSchemas/PropertySchema.js'; 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 { setDocAncestryMixin, ensureAncestryContainsCharIdMixin } from '/imports/api/parenting/parenting.js'; +import simpleSchemaMixin from '/imports/api/simpleSchemaMixin.js'; + let Buffs = new Mongo.Collection('buffs'); let BuffSchema = new SimpleSchema({ + name: { + type: String, + optional: true, + }, description: { type: String, optional: true, @@ -32,9 +42,9 @@ let StoredBuffSchema = new SimpleSchema({ target: { type: String, allowedValues: [ - // the character who took the action + // the character who took the buff 'self', - // the singular `target` of the action + // the singular `target` of the buff 'target', // rolled once for `each` target 'each', @@ -69,5 +79,39 @@ Buffs.attachSchema(AppliedBuffSchema); Buffs.attachSchema(PropertySchema); Buffs.attachSchema(ChildSchema); +const insertBuff = new ValidatedMethod({ + name: 'Buffs.methods.insert', + mixins: [ + creaturePermissionMixin, + setDocToLastMixin, + setDocAncestryMixin, + ensureAncestryContainsCharIdMixin, + simpleSchemaMixin, + ], + collection: Buffs, + permission: 'edit', + schema: BuffSchema, + run(buff) { + return Buffs.insert(buff); + }, +}); + +const updateBuff = new ValidatedMethod({ + name: 'Buffs.methods.update', + mixins: [ + creaturePermissionMixin, + simpleSchemaMixin, + ], + collection: Buffs, + permission: 'edit', + schema: new SimpleSchema({ + _id: SimpleSchema.RegEx.Id, + update: BuffSchema.omit('name'), + }), + run({_id, update}) { + return Buffs.update(_id, {$set: update}); + }, +}); + export default Buffs; -export { AppliedBuffSchema, StoredBuffSchema }; +export { AppliedBuffSchema, StoredBuffSchema, insertBuff, updateBuff }; diff --git a/app/imports/api/creature/properties/ClassLevels.js b/app/imports/api/creature/properties/ClassLevels.js index 885d08df..4138952c 100644 --- a/app/imports/api/creature/properties/ClassLevels.js +++ b/app/imports/api/creature/properties/ClassLevels.js @@ -2,10 +2,39 @@ import SimpleSchema from 'simpl-schema'; import schema from '/imports/api/schema.js'; import PropertySchema from '/imports/api/creature/subSchemas/PropertySchema.js'; 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 { setDocAncestryMixin, ensureAncestryContainsCharIdMixin } from '/imports/api/parenting/parenting.js'; +import simpleSchemaMixin from '/imports/api/simpleSchemaMixin.js'; let ClassLevels = new Mongo.Collection("classLevels"); let ClassLevelSchema = schema({ + name: { + type: String, + optional: true, + }, + // The name of this class level's variable + variableName: { + type: String, + regEx: VARIABLE_NAME_REGEX, + }, + // The variable name of the base class this level is attached to + baseClass: { + type: String, + regEx: VARIABLE_NAME_REGEX, + }, + // The name of the class level that needs to preceed this class level + // So a totemWarrior level 5 must be preceded by a totemWarrior level 4 + // If it's not set, any level below with the same baseClass is matched + previousClassLevel: { + type: String, + regEx: VARIABLE_NAME_REGEX, + optional: true, + }, level: { type: SimpleSchema.Integer, }, @@ -15,5 +44,41 @@ ClassLevels.attachSchema(ClassLevelSchema); ClassLevels.attachSchema(PropertySchema); ClassLevels.attachSchema(ChildSchema); +// Todo ensure the class level is being parented to a compatible class, and that +// previous level requirements are met +const insertClassLevel = new ValidatedMethod({ + name: 'ClassLevels.methods.insert', + mixins: [ + creaturePermissionMixin, + setDocToLastMixin, + setDocAncestryMixin, + ensureAncestryContainsCharIdMixin, + simpleSchemaMixin, + ], + collection: ClassLevels, + permission: 'edit', + schema: ClassLevelSchema, + run(classLevel) { + return ClassLevels.insert(classLevel); + }, +}); + +const updateClassLevel = new ValidatedMethod({ + name: 'ClassLevels.methods.update', + mixins: [ + creaturePermissionMixin, + simpleSchemaMixin, + ], + collection: ClassLevels, + permission: 'edit', + schema: new SimpleSchema({ + _id: SimpleSchema.RegEx.Id, + update: ClassLevelSchema.omit('name'), + }), + run({_id, update}) { + return ClassLevels.update(_id, {$set: update}); + }, +}); + export default ClassLevels; -export { ClassLevelSchema }; +export { ClassLevelSchema, insertClassLevel, updateClassLevel }; diff --git a/app/imports/api/creature/properties/Classes.js b/app/imports/api/creature/properties/Classes.js index 4f3718ad..56e80f18 100644 --- a/app/imports/api/creature/properties/Classes.js +++ b/app/imports/api/creature/properties/Classes.js @@ -1,11 +1,29 @@ +import SimpleSchema from 'simpl-schema'; import schema from '/imports/api/schema.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"; +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 { setDocAncestryMixin, ensureAncestryContainsCharIdMixin } from '/imports/api/parenting/parenting.js'; +import simpleSchemaMixin from '/imports/api/simpleSchemaMixin.js'; let Classes = new Mongo.Collection("classes"); -let ClassSchema = schema({}); +// TODO use variable name in computation engine, rather than a generated one +let ClassSchema = schema({ + name: { + type: String, + optional: true, + }, + variableName: { + type: String, + regEx: VARIABLE_NAME_REGEX, + }, +}); ClassSchema.extend(ColorSchema); @@ -13,5 +31,39 @@ Classes.attachSchema(ClassSchema); Classes.attachSchema(PropertySchema); Classes.attachSchema(ChildSchema); +const insertClass = new ValidatedMethod({ + name: 'Classes.methods.insert', + mixins: [ + creaturePermissionMixin, + setDocToLastMixin, + setDocAncestryMixin, + ensureAncestryContainsCharIdMixin, + simpleSchemaMixin, + ], + collection: Classes, + permission: 'edit', + schema: ClassSchema, + run(cls) { + return Classes.insert(cls); + }, +}); + +const updateClass = new ValidatedMethod({ + name: 'Classes.methods.update', + mixins: [ + creaturePermissionMixin, + simpleSchemaMixin, + ], + collection: Classes, + permission: 'edit', + schema: new SimpleSchema({ + _id: SimpleSchema.RegEx.Id, + update: ClassSchema.omit('name'), + }), + run({_id, update}) { + return Classes.update(_id, {$set: update}); + }, +}); + export default Classes; -export { ClassSchema }; +export { ClassSchema, insertClass, updateClass }; diff --git a/app/imports/api/creature/properties/DamageMultipliers.js b/app/imports/api/creature/properties/DamageMultipliers.js index 9789726f..a2d7c3e8 100644 --- a/app/imports/api/creature/properties/DamageMultipliers.js +++ b/app/imports/api/creature/properties/DamageMultipliers.js @@ -4,12 +4,24 @@ import PropertySchema from '/imports/api/creature/subSchemas/PropertySchema.js'; 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 { setDocAncestryMixin, ensureAncestryContainsCharIdMixin } from '/imports/api/parenting/parenting.js'; +import simpleSchemaMixin from '/imports/api/simpleSchemaMixin.js'; + let DamageMultipliers = new Mongo.Collection("damageMultipliers"); /* - * DamageMultipliers are multipliers that affect + * DamageMultipliers are multipliers that affect how much damage is taken from + * a given damage type */ let DamageMultiplierSchema = schema({ + name: { + type: String, + optional: true, + }, // The technical, lowercase, single-word name used in formulae damageType: { type: String, @@ -27,5 +39,41 @@ DamageMultipliers.attachSchema(DamageMultiplierSchema); DamageMultipliers.attachSchema(PropertySchema); DamageMultipliers.attachSchema(ChildSchema); +const insertDamageMultiplier = new ValidatedMethod({ + name: 'DamageMultipliers.methods.insert', + mixins: [ + creaturePermissionMixin, + setDocToLastMixin, + setDocAncestryMixin, + ensureAncestryContainsCharIdMixin, + recomputeCreatureMixin, + simpleSchemaMixin, + ], + collection: DamageMultipliers, + permission: 'edit', + schema: DamageMultiplierSchema, + run(dm) { + return DamageMultipliers.insert(dm); + }, +}); + +const updateDamageMultiplier = new ValidatedMethod({ + name: 'DamageMultipliers.methods.update', + mixins: [ + creaturePermissionMixin, + recomputeCreatureMixin, + simpleSchemaMixin, + ], + collection: DamageMultipliers, + permission: 'edit', + schema: new SimpleSchema({ + _id: SimpleSchema.RegEx.Id, + update: DamageMultiplierSchema.omit('name'), + }), + run({_id, update}) { + return DamageMultipliers.update(_id, {$set: update}); + }, +}); + export default DamageMultipliers; -export { DamageMultiplierSchema }; +export { DamageMultiplierSchema, insertDamageMultiplier, updateDamageMultiplier }; diff --git a/app/imports/api/creature/properties/Effects.js b/app/imports/api/creature/properties/Effects.js index 97349017..98d54227 100644 --- a/app/imports/api/creature/properties/Effects.js +++ b/app/imports/api/creature/properties/Effects.js @@ -3,6 +3,13 @@ import schema from '/imports/api/schema.js'; 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 { setDocAncestryMixin, ensureAncestryContainsCharIdMixin } from '/imports/api/parenting/parenting.js'; +import simpleSchemaMixin from '/imports/api/simpleSchemaMixin.js'; + let Effects = new Mongo.Collection('effects'); /* @@ -10,6 +17,10 @@ let Effects = new Mongo.Collection('effects'); * that modify their final value or presentation in some way */ let EffectSchema = schema({ + name: { + type: String, + optional: true, + }, operation: { type: String, defaultValue: 'add', @@ -49,5 +60,41 @@ Effects.attachSchema(PropertySchema); Effects.attachSchema(ChildSchema); Effects.attachSchema(EffectComputedSchema); +const insertEffect = new ValidatedMethod({ + name: 'Effects.methods.insert', + mixins: [ + creaturePermissionMixin, + setDocToLastMixin, + setDocAncestryMixin, + ensureAncestryContainsCharIdMixin, + recomputeCreatureMixin, + simpleSchemaMixin, + ], + collection: Effects, + permission: 'edit', + schema: EffectSchema, + run(effect) { + return Effects.insert(effect); + }, +}); + +const updateEffect = new ValidatedMethod({ + name: 'Effects.methods.update', + mixins: [ + creaturePermissionMixin, + recomputeCreatureMixin, + simpleSchemaMixin, + ], + collection: Effects, + permission: 'edit', + schema: new SimpleSchema({ + _id: SimpleSchema.RegEx.Id, + update: EffectSchema.omit('name'), + }), + run({_id, update}) { + return Effects.update(_id, {$set: update}); + }, +}); + export default Effects; export { EffectSchema }; diff --git a/app/imports/api/creature/properties/Experiences.js b/app/imports/api/creature/properties/Experiences.js index 9e9a1ab5..19843292 100644 --- a/app/imports/api/creature/properties/Experiences.js +++ b/app/imports/api/creature/properties/Experiences.js @@ -1,10 +1,22 @@ import SimpleSchema from 'simpl-schema'; import schema from '/imports/api/schema.js'; 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 { setDocAncestryMixin, ensureAncestryContainsCharIdMixin } from '/imports/api/parenting/parenting.js'; +import simpleSchemaMixin from '/imports/api/simpleSchemaMixin.js'; let Experiences = new Mongo.Collection("experience"); let ExperienceSchema = schema({ + name: { + type: String, + optional: true, + }, // Potentially long description of the event description: { type: String, @@ -37,5 +49,55 @@ let ExperienceSchema = schema({ Experiences.attachSchema(PropertySchema); Experiences.attachSchema(ExperienceSchema); +const insertExperience = new ValidatedMethod({ + name: 'Experiences.methods.insert', + mixins: [ + creaturePermissionMixin, + setDocToLastMixin, + setDocAncestryMixin, + ensureAncestryContainsCharIdMixin, + recomputeCreatureMixin, + simpleSchemaMixin, + ], + collection: Experiences, + permission: 'edit', + schema: ExperienceSchema, + skipRecompute(experience){ + return !experience.value; + }, + run(experience) { + let result = Experiences.insert(experience); + if (experience.value){ + recomputeCreatureXP(charId); + } + return result; + }, +}); + +const updateExperience = new ValidatedMethod({ + name: 'Experiences.methods.update', + mixins: [ + creaturePermissionMixin, + recomputeCreatureMixin, + simpleSchemaMixin, + ], + collection: Experiences, + permission: 'edit', + schema: new SimpleSchema({ + _id: SimpleSchema.RegEx.Id, + update: ExperienceSchema.omit('name'), + }), + skipRecompute({update}){ + return !('value' in update); + }, + run({_id, update, charId}) { + let result = Experiences.update(_id, {$set: update}); + if ('value' in update){ + recomputeCreatureXP(charId); + } + return result; + }, +}); + export default Experiences; -export { ExperienceSchema }; +export { ExperienceSchema, insertExperience, updateExperience }; diff --git a/app/imports/api/creature/properties/Features.js b/app/imports/api/creature/properties/Features.js index 6565b5db..2e43fecb 100644 --- a/app/imports/api/creature/properties/Features.js +++ b/app/imports/api/creature/properties/Features.js @@ -7,9 +7,20 @@ 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 { setDocAncestryMixin, ensureAncestryContainsCharIdMixin } from '/imports/api/parenting/parenting.js'; +import simpleSchemaMixin from '/imports/api/simpleSchemaMixin.js'; + let Features = new Mongo.Collection('features'); let FeatureSchema = schema({ + name: { + type: String, + optional: true, + }, description: { type: String, optional: true, @@ -27,33 +38,38 @@ Features.attachSchema(PropertySchema); Features.attachSchema(ChildSchema); const insertFeature = new ValidatedMethod({ - name: 'Features.methods.insert', + mixins: [ + creaturePermissionMixin, + setDocToLastMixin, + setDocAncestryMixin, + ensureAncestryContainsCharIdMixin, + simpleSchemaMixin, + ], + collection: Features, + permission: 'edit', + schema: FeatureSchema, + run(feature) { + return Features.insert(feature); + }, +}); - validate: FeatureSchema.validator({clean: true}), - - run({feature}) { - const charId = feature.charId; - assertEditPermission(charId, this.userId); - - // Set order - feature.order = getHighestOrder({ - collection: Features, - charId, - }) + 1; - - // Set parent - feature.parent = { - id: charId, - collection: 'Creatures', - }; - - // Insert - let featureId = Features.insert(feature); - recomputeCreatureById(charId); - return featureId; +const updateFeature = new ValidatedMethod({ + name: 'Features.methods.update', + mixins: [ + creaturePermissionMixin, + simpleSchemaMixin, + ], + collection: Features, + permission: 'edit', + schema: new SimpleSchema({ + _id: SimpleSchema.RegEx.Id, + update: FeatureSchema.omit('name'), + }), + run({_id, update}) { + return Features.update(_id, {$set: update}); }, }); export default Features; -export { FeatureSchema, insertFeature } +export { FeatureSchema, insertFeature, updateFeature } diff --git a/app/imports/api/creature/properties/Folders.js b/app/imports/api/creature/properties/Folders.js index fb2c96ba..8d2e6d40 100644 --- a/app/imports/api/creature/properties/Folders.js +++ b/app/imports/api/creature/properties/Folders.js @@ -1,14 +1,60 @@ +import SimpleSchema from 'simpl-schema'; import schema from '/imports/api/schema.js'; 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 { setDocAncestryMixin, ensureAncestryContainsCharIdMixin } from '/imports/api/parenting/parenting.js'; +import simpleSchemaMixin from '/imports/api/simpleSchemaMixin.js'; + let Folders = new Mongo.Collection('folders'); -let FolderSchema = schema({}); +let FolderSchema = schema({ + name: { + type: String, + optional: true, + }, +}); Folders.attachSchema(FolderSchema); Folders.attachSchema(PropertySchema); Folders.attachSchema(ChildSchema); +const insertFolder = new ValidatedMethod({ + name: 'Folders.methods.insert', + mixins: [ + creaturePermissionMixin, + setDocToLastMixin, + setDocAncestryMixin, + ensureAncestryContainsCharIdMixin, + simpleSchemaMixin, + ], + collection: Folders, + permission: 'edit', + schema: FolderSchema, + run(folder) { + return Folders.insert(folder); + }, +}); + +const updateFolder = new ValidatedMethod({ + name: 'Folders.methods.update', + mixins: [ + creaturePermissionMixin, + simpleSchemaMixin, + ], + collection: Folders, + permission: 'edit', + schema: new SimpleSchema({ + _id: SimpleSchema.RegEx.Id, + update: FolderSchema.omit('name'), + }), + run({_id, update}) { + return Folders.update(_id, {$set: update}); + }, +}); + export default Folders; -export { FolderSchema }; +export { FolderSchema, insertFolder, updateFolder }; diff --git a/app/imports/api/creature/properties/Notes.js b/app/imports/api/creature/properties/Notes.js index 313a222c..b662de9a 100644 --- a/app/imports/api/creature/properties/Notes.js +++ b/app/imports/api/creature/properties/Notes.js @@ -3,9 +3,19 @@ import schema from '/imports/api/schema.js'; 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 { setDocAncestryMixin, ensureAncestryContainsCharIdMixin } from '/imports/api/parenting/parenting.js'; +import simpleSchemaMixin from '/imports/api/simpleSchemaMixin.js'; + let Notes = new Mongo.Collection("notes"); let NoteSchema = schema({ + name: { + type: String, + optional: true, + }, description: { type: String, optional: true, @@ -17,5 +27,39 @@ NoteSchema.extend(ColorSchema); Notes.attachSchema(NoteSchema); Notes.attachSchema(PropertySchema); +const insertNote = new ValidatedMethod({ + name: 'Notes.methods.insert', + mixins: [ + creaturePermissionMixin, + setDocToLastMixin, + setDocAncestryMixin, + ensureAncestryContainsCharIdMixin, + simpleSchemaMixin, + ], + collection: Notes, + permission: 'edit', + schema: NoteSchema, + run(note) { + return Notes.insert(note); + }, +}); + +const updateNote = new ValidatedMethod({ + name: 'Notes.methods.update', + mixins: [ + creaturePermissionMixin, + simpleSchemaMixin, + ], + collection: Notes, + permission: 'edit', + schema: new SimpleSchema({ + _id: SimpleSchema.RegEx.Id, + update: NoteSchema.omit('name'), + }), + run({_id, update}) { + return Notes.update(_id, {$set: update}); + }, +}); + export default Notes; -export { NoteSchema }; +export { NoteSchema, insertNote, updateNote }; diff --git a/app/imports/api/creature/properties/Proficiencies.js b/app/imports/api/creature/properties/Proficiencies.js index a4cd577a..8090bebf 100644 --- a/app/imports/api/creature/properties/Proficiencies.js +++ b/app/imports/api/creature/properties/Proficiencies.js @@ -1,10 +1,22 @@ +import SimpleSchema from 'simpl-schema'; import schema from '/imports/api/schema.js'; 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 { setDocAncestryMixin, ensureAncestryContainsCharIdMixin } from '/imports/api/parenting/parenting.js'; +import simpleSchemaMixin from '/imports/api/simpleSchemaMixin.js'; + let Proficiencies = new Mongo.Collection("proficiencies"); let ProficiencySchema = schema({ + name: { + type: String, + optional: true, + }, // A number representing how proficient the character is value: { type: Number, @@ -22,5 +34,41 @@ Proficiencies.attachSchema(ProficiencySchema); Proficiencies.attachSchema(PropertySchema); Proficiencies.attachSchema(ChildSchema); +const insertProficiency = new ValidatedMethod({ + name: 'Proficiencies.methods.insert', + mixins: [ + creaturePermissionMixin, + setDocToLastMixin, + setDocAncestryMixin, + ensureAncestryContainsCharIdMixin, + recomputeCreatureMixin, + simpleSchemaMixin, + ], + collection: Proficiencies, + permission: 'edit', + schema: ProficiencySchema, + run(prof) { + return Proficiencies.insert(prof); + }, +}); + +const updateProficiency = new ValidatedMethod({ + name: 'Proficiencies.methods.update', + mixins: [ + creaturePermissionMixin, + recomputeCreatureMixin, + simpleSchemaMixin, + ], + collection: Proficiencies, + permission: 'edit', + schema: new SimpleSchema({ + _id: SimpleSchema.RegEx.Id, + update: ProficiencySchema.omit('name'), + }), + run({_id, update}) { + return Proficiencies.update(_id, {$set: update}); + }, +}); + export default Proficiencies; -export { ProficiencySchema }; +export { ProficiencySchema, insertProficiency, updateProficiency }; diff --git a/app/imports/api/creature/properties/Rolls.js b/app/imports/api/creature/properties/Rolls.js index a04af17e..b248b798 100644 --- a/app/imports/api/creature/properties/Rolls.js +++ b/app/imports/api/creature/properties/Rolls.js @@ -4,9 +4,19 @@ import ChildSchema from '/imports/api/parenting/ChildSchema.js'; import AdjustmentSchema from '/imports/api/creature/subSchemas/AdjustmentSchema.js'; 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 { setDocAncestryMixin, ensureAncestryContainsCharIdMixin } from '/imports/api/parenting/parenting.js'; +import simpleSchemaMixin from '/imports/api/simpleSchemaMixin.js'; + let Rolls = new Mongo.Collection('rolls'); let RollChildrenSchema = new SimpleSchema({ + name: { + type: String, + optional: true, + }, // The adjustments to be applied adjustments: { type: Array, @@ -93,5 +103,39 @@ Rolls.attachSchema(RollSchema); Rolls.attachSchema(PropertySchema); Rolls.attachSchema(ChildSchema); +const insertRoll = new ValidatedMethod({ + name: 'Rolls.methods.insert', + mixins: [ + creaturePermissionMixin, + setDocToLastMixin, + setDocAncestryMixin, + ensureAncestryContainsCharIdMixin, + simpleSchemaMixin, + ], + collection: Rolls, + permission: 'edit', + schema: RollSchema, + run(roll) { + return Rolls.insert(roll); + }, +}); + +const updateRoll = new ValidatedMethod({ + name: 'Rolls.methods.update', + mixins: [ + creaturePermissionMixin, + simpleSchemaMixin, + ], + collection: Rolls, + permission: 'edit', + schema: new SimpleSchema({ + _id: SimpleSchema.RegEx.Id, + update: RollSchema.omit('name'), + }), + run({_id, update}) { + return Rolls.update(_id, {$set: update}); + }, +}); + export default Rolls; -export { RollSchema }; +export { RollSchema, insertRoll, updateRoll }; diff --git a/app/imports/api/creature/properties/Skills.js b/app/imports/api/creature/properties/Skills.js index c47a7d8a..d4d03dc2 100644 --- a/app/imports/api/creature/properties/Skills.js +++ b/app/imports/api/creature/properties/Skills.js @@ -4,6 +4,13 @@ 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 { setDocAncestryMixin, ensureAncestryContainsCharIdMixin } from '/imports/api/parenting/parenting.js'; +import simpleSchemaMixin from '/imports/api/simpleSchemaMixin.js'; + let Skills = new Mongo.Collection("skills"); /* @@ -11,6 +18,10 @@ let Skills = new Mongo.Collection("skills"); * Skills have an ability score modifier that they use as their basis */ let SkillSchema = schema({ + name: { + type: String, + optional: true, + }, // The technical, lowercase, single-word name used in formulae variableName: { type: String, @@ -92,5 +103,41 @@ Skills.attachSchema(ComputedSkillSchema); Skills.attachSchema(PropertySchema); Skills.attachSchema(ChildSchema); +const insertSkill = new ValidatedMethod({ + name: 'Skills.methods.insert', + mixins: [ + creaturePermissionMixin, + setDocToLastMixin, + setDocAncestryMixin, + ensureAncestryContainsCharIdMixin, + recomputeCreatureMixin, + simpleSchemaMixin, + ], + collection: Skills, + permission: 'edit', + schema: SkillSchema, + run(skill) { + return Skills.insert(skill); + }, +}); + +const updateSkill = new ValidatedMethod({ + name: 'Skills.methods.update', + mixins: [ + creaturePermissionMixin, + recomputeCreatureMixin, + simpleSchemaMixin, + ], + collection: Skills, + permission: 'edit', + schema: new SimpleSchema({ + _id: SimpleSchema.RegEx.Id, + update: SkillSchema.omit('name'), + }), + run({_id, update}) { + return Skills.update(_id, {$set: update}); + }, +}); + export default Skills; export { SkillSchema }; diff --git a/app/imports/api/creature/properties/SpellLists.js b/app/imports/api/creature/properties/SpellLists.js index a7e2c354..d6bdaefc 100644 --- a/app/imports/api/creature/properties/SpellLists.js +++ b/app/imports/api/creature/properties/SpellLists.js @@ -1,11 +1,22 @@ +import SimpleSchema from 'simpl-schema'; import schema from '/imports/api/schema.js'; import ColorSchema from "/imports/api/creature/subSchemas/ColorSchema.js"; 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 { setDocAncestryMixin, ensureAncestryContainsCharIdMixin } from '/imports/api/parenting/parenting.js'; +import simpleSchemaMixin from '/imports/api/simpleSchemaMixin.js'; + let SpellLists = new Mongo.Collection("spellLists"); let SpellListSchema = schema({ + name: { + type: String, + optional: true, + }, description: { type: String, optional: true, @@ -33,4 +44,38 @@ SpellLists.attachSchema(SpellListSchema); SpellLists.attachSchema(PropertySchema); SpellLists.attachSchema(ChildSchema); +const insertSpellList = new ValidatedMethod({ + name: 'SpellLists.methods.insert', + mixins: [ + creaturePermissionMixin, + setDocToLastMixin, + setDocAncestryMixin, + ensureAncestryContainsCharIdMixin, + simpleSchemaMixin, + ], + collection: SpellLists, + permission: 'edit', + schema: SpellListSchema, + run(spellList) { + return SpellLists.insert(spellList); + }, +}); + +const updateSpellList = new ValidatedMethod({ + name: 'SpellLists.methods.update', + mixins: [ + creaturePermissionMixin, + simpleSchemaMixin, + ], + collection: SpellLists, + permission: 'edit', + schema: new SimpleSchema({ + _id: SimpleSchema.RegEx.Id, + update: SpellListSchema.omit('name'), + }), + run({_id, update}) { + return SpellLists.update(_id, {$set: update}); + }, +}); + export default SpellLists; diff --git a/app/imports/api/creature/properties/Spells.js b/app/imports/api/creature/properties/Spells.js index 17987278..ea155237 100644 --- a/app/imports/api/creature/properties/Spells.js +++ b/app/imports/api/creature/properties/Spells.js @@ -4,6 +4,12 @@ import schema from '/imports/api/schema.js'; 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 { setDocAncestryMixin, ensureAncestryContainsCharIdMixin } from '/imports/api/parenting/parenting.js'; +import simpleSchemaMixin from '/imports/api/simpleSchemaMixin.js'; + const magicSchools = [ 'Abjuration', 'Conjuration', @@ -18,6 +24,10 @@ const magicSchools = [ let Spells = new Mongo.Collection('spells'); let SpellSchema = schema({ + name: { + type: String, + optional: true, + }, prepared: { type: String, defaultValue: 'prepared', @@ -78,5 +88,39 @@ Spells.attachSchema(SpellSchema); Spells.attachSchema(PropertySchema); Spells.attachSchema(ChildSchema); +const insertSpell = new ValidatedMethod({ + name: 'Spells.methods.insert', + mixins: [ + creaturePermissionMixin, + setDocToLastMixin, + setDocAncestryMixin, + ensureAncestryContainsCharIdMixin, + simpleSchemaMixin, + ], + collection: Spells, + permission: 'edit', + schema: SpellSchema, + run(spell) { + return Spells.insert(spell); + }, +}); + +const updateSpell = new ValidatedMethod({ + name: 'Spells.methods.update', + mixins: [ + creaturePermissionMixin, + simpleSchemaMixin, + ], + collection: Spells, + permission: 'edit', + schema: new SimpleSchema({ + _id: SimpleSchema.RegEx.Id, + update: SpellSchema.omit('name'), + }), + run({_id, update}) { + return Spells.update(_id, {$set: update}); + }, +}); + export default Spells; -export { SpellSchema }; +export { SpellSchema, insertSpell, updateSpell }; diff --git a/app/imports/api/creature/recomputeCreatureMixin.js b/app/imports/api/creature/recomputeCreatureMixin.js new file mode 100644 index 00000000..564e2ae3 --- /dev/null +++ b/app/imports/api/creature/recomputeCreatureMixin.js @@ -0,0 +1,13 @@ +export default function recomputeCreatureMixin(methodOptions){ + let runFunc = methodOptions.run; + methodOptions.run = function({charId}){ + const result = runFunc.apply(this, arguments); + if ( + methodOptions.skipRecompute && + methodOptions.skipRecompute.apply(this, arguments) + ) return result; + recomputeCreatureById(charId); + return result; + }; + return methodOptions; +}; diff --git a/app/imports/api/library/LibraryNodes.js b/app/imports/api/library/LibraryNodes.js index e052059a..f9e72782 100644 --- a/app/imports/api/library/LibraryNodes.js +++ b/app/imports/api/library/LibraryNodes.js @@ -6,24 +6,28 @@ import librarySchemas from '/imports/api/library/librarySchemas.js'; let LibraryNodes = new Mongo.Collection('libraryNodes'); let LibraryNodeSchema = schema({ - type: { + name: { + type: String, + optional: true, + }, + libraryNodeType: { type: String, allowedValues: Object.keys(librarySchemas), }, - data: { - type: Object, - custom(){ - let type = this.field('type'); - let schema = librarySchemas[type]; - schema.validate(this.value) - }, - }, }); -LibraryNodeSchema.extend(SharingSchema); - -LibraryNodes.attachSchema(LibraryNodeSchema); -LibraryNodes.attachSchema(ChildSchema); +for (key in librarySchemas){ + let schema = new SimpleSchema({}); + schema.extend(librarySchemas[key]); + schema.extend(LibraryNodeSchema); + schema.extend(ChildSchema); + if (key === 'folder'){ + schema.extend(SharingSchema); + } + LibraryNodes.attachSchema(schema, { + selector: {libraryNodeType: key} + }); +} export default LibraryNodes; export { LibraryNodeSchema }; diff --git a/app/imports/api/order.js b/app/imports/api/order.js index 6b566bd5..2eced3d7 100644 --- a/app/imports/api/order.js +++ b/app/imports/api/order.js @@ -1,4 +1,6 @@ -const getHighestOrder = function({collection, charId}){ +import SimpleSchema from 'simpl-schema'; + +export function getHighestOrder({collection, charId}){ const highestOrderedDoc = collection.findOne({ charId }, { @@ -6,9 +8,37 @@ const getHighestOrder = function({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; } -const moveDocToOrder = function({collection, doc, order}){ +export function setDocOrder({collection, doc, order}){ const currentOrder = doc.order; if (currentOrder === order){ return; @@ -42,7 +72,7 @@ const moveDocToOrder = function({collection, doc, order}){ } }; -const reorderDocs = function({collection, charId}){ +export function reorderDocs({collection, charId}){ let bulkWrite = []; collection.find({ charId @@ -67,5 +97,3 @@ const reorderDocs = function({collection, charId}){ }); } }; - -export { getHighestOrder, moveDocToOrder, reorderDocs }; diff --git a/app/imports/api/parenting/parenting.js b/app/imports/api/parenting/parenting.js index df99244b..12ec8bad 100644 --- a/app/imports/api/parenting/parenting.js +++ b/app/imports/api/parenting/parenting.js @@ -1,5 +1,6 @@ import fetchDocByRef from '/imports/api/parenting/fetchDocByRef.js'; import getCollectionByName from '/imports/api/parenting/getCollectionByName.js'; +import SimpleSchema from 'simpl-schema'; // n = collections.length let collections = []; @@ -64,8 +65,7 @@ export function forEachDecendent({ancestorId, filter = {}, options}, callback){ }; // 1 database read -export function getParenting({id, collection}){ - +export function getAncestry({id, collection}){ // Get the parent ref let parentDoc = fetchDocByRef({id, collection}, {fields: { name: 1, @@ -86,6 +86,71 @@ export function getParenting({id, collection}){ return {parent, ancestors}; } +export function setDocAncestryMixin(methodOptions){ + // Extend the method's schema to require the needed properties + // This mixin should come before simpleschema mixin + methodOptions.schema.extend({ + parent: { + type: Object, + }, + 'parent.id': { + type: String, + regEx: SimpleSchema.RegEx.Id, + }, + 'parent.collection': { + type: String, + }, + }); + // Change the doc's ancestry before running + let runFunc = methodOptions.run; + methodOptions.run = function(doc, ...rest){ + let {parent, ancestors} = getAncestry(doc.parent); + doc.parent = parent; + doc.ancestors = ancestors; + return runFunc.call(this, doc, ...rest); + }; + return methodOptions; +}; + +function ensureAncestryContainsId(ancestors, id){ + if (!id){ + throw new Meteor.Error('ancestor-check-failed', + `Expected charId, got ${id}` + ); + } + if (!ancestors){ + throw new Meteor.Error('ancestor-check-failed', + `Expected ancestors array, got ${ancestors}` + ); + } + for (let ancestor of ancestors){ + if (ancestor.id === id){ + return; + } + } + throw new Meteor.Error('ancestor-check-failed', + `Ancestors did not contain id: ${id}` + ); +} + +export function ensureAncestryContainsCharIdMixin(methodOptions){ + // Extend the method's schema to require the needed properties + // This mixin should come before simpleSchemaMixin + methodOptions.schema.extend({ + charId: { + type: String, + regEx: SimpleSchema.RegEx.Id, + }, + }); + let runFunc = methodOptions.run; + methodOptions.run = function({charId, ancestors}){ + ensureAncestryContainsId(ancestors, charId); + return runFunc.apply(this, arguments); + }; + return methodOptions; +}; + + export function updateParent(docRef, parentRef){ let collection = getCollectionByName(docRef.collection); let oldDoc = fetchDocByRef(docRef, {fields: { @@ -97,7 +162,7 @@ export function updateParent(docRef, parentRef){ if (oldDoc.parent.id === parentRef.id) return; // update the document's parenting - let {parent, ancestors} = getParenting(parentRef); + let {parent, ancestors} = getAncestry(parentRef); collection.update(docRef.id, {$set: {parent, ancestors}}); // Remove the old ancestors from the decendents @@ -123,7 +188,7 @@ export function updateParent(docRef, parentRef){ export function setInheritedField({id, collection, fieldName, fieldValue}){ // Update the doc - let collection = getCollectionByName(collection); + collection = getCollectionByName(collection); collection.update(id, {$set: { [`${fieldName}`]: fieldValue, }}); @@ -163,3 +228,17 @@ export function setName({id, collection, name}){ fieldValue: name, }); }; + +export function findEnabled(collection, query, options){ + query['enabled'] = true; + query['ancestors.$.enabled'] = {$not: false}; + return collection.find(query, options); +}; + +export function getName(doc){ + if (doc.name) return name; + var i = doc.ancestors.length; + while(i--) { + if (ancestors[i].name) return ancestors[i].name; + } +} diff --git a/app/imports/api/simpleSchemaMixin.js b/app/imports/api/simpleSchemaMixin.js new file mode 100644 index 00000000..e0d12299 --- /dev/null +++ b/app/imports/api/simpleSchemaMixin.js @@ -0,0 +1,51 @@ +// Copied from https://github.com/sethjgore/meteor-simple-schema-mixin +// and updated to simpl-schema npm package +import SimpleSchema from 'simpl-schema'; + +export default function simpleSchemaMixin(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.schema === 'undefined' + && typeof methodOptions.validate !== 'undefined') + || (typeof methodOptions.schema !== 'undefined' && methodOptions.schema === 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). + + const newOptions = methodOptions; + newOptions.schemaValidatorOptions = + newOptions.schemaValidatorOptions || + { clean: true, filter: false }; + let simpleSchema; + if (!newOptions.schema || newOptions.schema === null) { + // Allow simply leaving off both the schema and validate specifications + // or setting them to "null" as a shorthand. In this case, unlike + // the straight default validate or typical coder's call to validator, + // we will ENFORCE the Method be called without parameters because of + // the "filter: false" above. + simpleSchema = new SimpleSchema({}); + } else if (newOptions.schema instanceof SimpleSchema) { + // In this one case, we can save ourselves the time to make a schema out + // of the schema. + simpleSchema = newOptions.schema; + } else { + simpleSchema = new SimpleSchema(newOptions.schema); + } + newOptions.validate = simpleSchema.validator(newOptions.schemaValidatorOptions); + return newOptions; +}; diff --git a/app/imports/constants/VARIABLE_NAME_REGEX.js b/app/imports/constants/VARIABLE_NAME_REGEX.js new file mode 100644 index 00000000..7a9e1ee0 --- /dev/null +++ b/app/imports/constants/VARIABLE_NAME_REGEX.js @@ -0,0 +1,4 @@ +// Must contain a letter, and be made of word characters only +const VARIABLE_NAME_REGEX = /^\w*[a-z]\w*$/i; + +export default VARIABLE_NAME_REGEX;