From 735aa6fbd35cdd8528c89db85f4495411da4a569 Mon Sep 17 00:00:00 2001 From: Wyatt Johnson Date: Fri, 7 Apr 2017 16:40:30 -0600 Subject: [PATCH 1/2] Added new Metadata Service and models --- models/asset.js | 6 ++++ models/comment.js | 8 ++++- services/metadata.js | 80 ++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 93 insertions(+), 1 deletion(-) create mode 100644 services/metadata.js diff --git a/models/asset.js b/models/asset.js index f0046aef0..51ff42705 100644 --- a/models/asset.js +++ b/models/asset.js @@ -46,6 +46,12 @@ const AssetSchema = new Schema({ type: Schema.Types.Mixed, default: null }, + + // Additional metadata stored on the field. + metadata: { + default: {}, + type: Object + } }, { versionKey: false, timestamps: { diff --git a/models/comment.js b/models/comment.js index e6523e45f..0984a0832 100644 --- a/models/comment.js +++ b/models/comment.js @@ -75,7 +75,13 @@ const CommentSchema = new Schema({ default: 'NONE' }, tags: [TagSchema], - parent_id: String + parent_id: String, + + // Additional metadata stored on the field. + metadata: { + default: {}, + type: Object + } }, { timestamps: { createdAt: 'created_at', diff --git a/services/metadata.js b/services/metadata.js new file mode 100644 index 000000000..a0c433f8b --- /dev/null +++ b/services/metadata.js @@ -0,0 +1,80 @@ +/** + * The key must be composed of alpha characters with periods seperating them. + */ +const KEY_REGEX = /^(?:[A-Za-z][A-Za-z\.]*[A-Za-z])?(?:[A-Za-z]*)$/; + +/** + * Allows metadata properties to be set/unset from specific models. It is the + * expecatation of this API that the metadata field is either accessed later + * directly, or accessed as a result of another database load rather than + * this service providing an interface to do so. + * + * @class MetadataService + */ +class MetadataService { + + /** + * Parses a key by ensuring that if it is either a string, or an array with + * only characters defined in the `KEY_REGEX` + * + * @static + * @param {String|Array} key + * @returns {String} string form of the key + * + * @memberOf Metadata + */ + static parseKey(key) { + if (Array.isArray(key)) { + key = key.join('.'); + } + + if ((typeof key !== 'string') || !KEY_REGEX.test(key) || key.length === 0) { + throw new Error(`${key} is not valid, only a-zA-Z. allowed`); + } + + return ['metadata', key].join('.'); + } + + /** + * Sets an object on the metadata field of an object. + * + * @static + * @param {mongoose.Model} model the mongoose model for the object + * @param {String} id the value for the field `id` of the model + * @param {String|Array} key key for the metadata field + * @param {any} value javascript object to set the value of the metadata to + * @returns {Promise} resolves when the update is complete + * + * @memberOf Metadata + */ + static async set(model, id, key, value) { + key = MetadataService.parseKey(key); + + return model.update({id}, { + $set: { + [key]: value + } + }); + } + + /** + * Removes the value for the metadata field as the specific key. + * + * @static + * @param {mongoose.Model} model the mongoose model for the object + * @param {String} id the value for the field `id` of the model + * @param {String|Array} key key for the metadata field + * @returns + * + * @memberOf Metadata + */ + static async unset(model, id, key) { + key = MetadataService.parseKey(key); + + return model.update({id}, { + $unset: key + }); + } +} + +module.exports = MetadataService; From 8e33d25a2801c9c0816ccaaa398f9ff4a1261f86 Mon Sep 17 00:00:00 2001 From: Wyatt Johnson Date: Mon, 10 Apr 2017 12:12:07 -0600 Subject: [PATCH 2/2] Some bug fixes + validation updates --- graph/hooks.js | 8 ++++++++ services/metadata.js | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/graph/hooks.js b/graph/hooks.js index 499eb60b1..c5b7541b3 100644 --- a/graph/hooks.js +++ b/graph/hooks.js @@ -3,6 +3,7 @@ const { GraphQLInterfaceType } = require('graphql'); const debug = require('debug')('talk:graph:schema'); +const Joi = require('joi'); /** * XXX taken from graphql-js: src/execution/execute.js, because that function @@ -82,6 +83,8 @@ const decorateWithHooks = (schema, hooks) => forEachField(schema, (field, typeNa Object.keys(hooks).forEach((hook) => { switch (hook) { case 'pre': + Joi.assert(hooks.pre, Joi.func().maxArity(4)); + debug(`adding pre hook to resolver ${typeName}.${fieldName} from plugin '${plugin.name}'`); if (typeof hooks.pre !== 'function') { @@ -91,6 +94,8 @@ const decorateWithHooks = (schema, hooks) => forEachField(schema, (field, typeNa acc.pre.push(hooks.pre); break; case 'post': + Joi.assert(hooks.pre, Joi.func().maxArity(5)); + debug(`adding post hook to resolver ${typeName}.${fieldName} from plugin '${plugin.name}'`); if (typeof hooks.post !== 'function') { @@ -129,6 +134,9 @@ const decorateWithHooks = (schema, hooks) => forEachField(schema, (field, typeNa return; } + // Ensure it matches the format we expect. + Joi.assert(post, Joi.array().items(Joi.func().maxArity(3)), `invalid post hooks were found for ${typeName}.${fieldName}`); + // Cache the original resolverType function. let resolveType = field.resolveType; diff --git a/services/metadata.js b/services/metadata.js index a0c433f8b..2cabed8b8 100644 --- a/services/metadata.js +++ b/services/metadata.js @@ -72,7 +72,7 @@ class MetadataService { key = MetadataService.parseKey(key); return model.update({id}, { - $unset: key + $unset: {[key]: ''} }); } }