Skip to content

Instantly share code, notes, and snippets.

@marshallswain
Last active January 1, 2016 03:49
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save marshallswain/8087915 to your computer and use it in GitHub Desktop.
Save marshallswain/8087915 to your computer and use it in GitHub Desktop.
A custom collection module that allows you to expose request properties to dashboard scripts by modifying the domain. In this example, I've added a headers property to the domain.

Installation

  1. Drop the dpd-exposed.js file into your node_modules directory. (You might have to create one if you installed Deployd using the dpd command line utility.)
  2. Open your Deployd dashboard and click the big green add button. You'll see the Exposed Collection type. Use it to create a collection like you would any other.

Usage

  1. Add some properties to your collection.

  2. Add some data to your collection. Remember that these scripts are run per instance on records from the database. It won't even run unless you add some data to the collection.

  3. In a dashboard script (ie. ON GET), use the headers variable directly.

    console.log(headers);

// Exposed Controller
// Lines 2-8 are the basic necessities for extending a collection.
var Collection = require('deployd/lib/resources/collection');
var util = require('util');
function ExposedCollection(name, options) {
Collection.apply(this, arguments);
var config = this.config;
if(!this.properties) {
this.properties = {};
}
}
ExposedCollection.external = Collection.external;
util.inherits(ExposedCollection, Collection);
// Additional modules used below.
var path = require('path');
// Label for the type in Deployd's dashboard add button.
ExposedCollection.label = "Exposed Collection";
// Use the same events as Collection
ExposedCollection.events = Collection.events;
// Make sure we show up in Deployd's dashboard.
ExposedCollection.prototype.clientGeneration = true;
ExposedCollection.dashboard = Collection.dashboard;
// I've not put any ExposedColleciton.prototype.handle so it will use
// the one from Collection.prototype.
/**
* Find all the objects in a collection that match the given
* query. Then execute its get script using each object.
*
* @param {Context} ctx
* @param {Function} fn(err, result)
*/
ExposedCollection.prototype.find = function (ctx, fn) {
var collection = this
, store = this.store
, query = ctx.query || {}
, session = ctx.session
, client = ctx.dpd
, errors
, data
, sanitizedQuery = this.sanitizeQuery(query);
function done(err, result) {
// debug("Get listener called back with", err || result);
if(typeof query.id === 'string' && (result && result.length === 0) || !result) {
err = err || {
message: 'not found',
statusCode: 404
};
// debug('could not find object by id %s', query.id);
}
if(err) {
return fn(err);
}
if(typeof query.id === 'string' && Array.isArray(result)) {
return fn(null, result[0]);
}
fn(null, result);
}
// debug('finding %j; sanitized %j', query, sanitizedQuery);
store.find(sanitizedQuery, function (err, result) {
// debug("Find Callback");
if(err) return done(err);
// debug('found %j', err || result || 'none');
if(!collection.shouldRunEvent(collection.events.Get, ctx)) {
return done(err, result);
}
var errors = {};
if(Array.isArray(result)) {
var remaining = result && result.length;
if(!remaining) return done(err, result);
result.forEach(function (data) {
// domain for onGet event scripts
var domain = createDomain(data, errors, ctx);
// console.log(ctx);
collection.events.Get.run(ctx, domain, function (err) {
if (err) {
if (err instanceof Error) {
return done(err);
} else {
errors[data.id] = err;
}
}
remaining--;
if(!remaining) {
done(null, result.filter(function(r) {
return !errors[r.id];
}));
}
});
});
} else {
// domain for onGet event scripts
data = result;
var domain = createDomain(data, errors, ctx);
collection.events.Get.run(ctx, domain, function (err) {
if(err) return done(err);
done(null, data);
});
}
});
};
/**
* Execute a `delete` event script, if one exists, using each object found.
* Then remove a single object that matches the `ctx.query.id`. Finally call
* `fn(err)` passing an `error` if one occurred.
*
* @param {Context} ctx
* @param {Function} fn(err)
*/
Collection.prototype.remove = function (ctx, fn) {
var collection = this
, store = this.store
, session = ctx.session
, query = ctx.query
, sanitizedQuery = this.sanitizeQuery(query)
, errors;
if(!(query && query.id)) return fn('You must include a query with an id when deleting an object from a collection.');
store.find(sanitizedQuery, function (err, result) {
if(err) {
return fn(err);
}
function done(err) {
if(err) return fn(err);
store.remove(sanitizedQuery, fn);
if(session.emitToAll) session.emitToAll(collection.name + ':changed');
}
if(collection.shouldRunEvent(collection.events.Delete, ctx)) {
var domain = createDomain(result, errors, ctx);
domain['this'] = domain.data = result;
collection.events.Delete.run(ctx, domain, done);
} else {
done();
}
});
};
/**
* Execute the onPost or onPut listener. If it succeeds,
* save the given item in the collection.
*
* @param {Context} ctx
* @param {Function} fn(err, result)
*/
Collection.prototype.save = function (ctx, fn) {
var collection = this
, store = this.store
, session = ctx.session
, item = ctx.body
, query = ctx.query || {}
, client = ctx.dpd
, errors = {};
if(!item) return done('You must include an object when saving or updating.');
// build command object
var commands = {};
Object.keys(item).forEach(function (key) {
if(item[key] && typeof item[key] === 'object' && !Array.isArray(item[key])) {
Object.keys(item[key]).forEach(function (k) {
if(k[0] == '$') {
commands[key] = item[key];
}
});
}
});
item = this.sanitize(item);
// handle id on either body or query
if(item.id) {
query.id = item.id;
}
// debug('saving %j with id %s', item, query.id);
function done(err, item) {
errors = domain && domain.hasErrors() && {errors: errors};
// debug('errors: %j', err);
fn(errors || err, item);
}
var domain = createDomain(item, errors, ctx);
domain.protect = function(property) {
delete domain.data[property];
};
domain.changed = function (property) {
if(domain.data.hasOwnProperty(property)) {
if(domain.previous && domain.previous[property] === domain.data[property]) {
return false;
}
return true;
}
return false;
};
domain.previous = {};
function put() {
var id = query.id
, sanitizedQuery = collection.sanitizeQuery(query)
, prev = {};
store.first(sanitizedQuery, function(err, obj) {
if(!obj) {
if (Object.keys(sanitizedQuery) === 1) {
return done(new Error("No object exists with that id"));
} else {
return done(new Error("No object exists that matches that query"));
}
}
if(err) return done(err);
// copy previous obj
Object.keys(obj).forEach(function (key) {
prev[key] = obj[key];
});
// merge changes
Object.keys(item).forEach(function (key) {
obj[key] = item[key];
});
prev.id = id;
item = obj;
domain['this'] = item;
domain.data = item;
domain.previous = prev;
collection.execCommands('update', item, commands);
var errs = collection.validate(item);
if(errs) return done({errors: errs});
function runPutEvent(err) {
if(err) {
return done(err);
}
if(collection.shouldRunEvent(collection.events.Put, ctx)) {
collection.events.Put.run(ctx, domain, commit);
} else {
commit();
}
}
function commit(err) {
if(err || domain.hasErrors()) {
return done(err || errors);
}
delete item.id;
store.update({id: query.id}, item, function (err) {
if(err) return done(err);
item.id = id;
done(null, item);
if(session && session.emitToAll) session.emitToAll(collection.name + ':changed');
});
}
if (collection.shouldRunEvent(collection.events.Validate, ctx)) {
collection.events.Validate.run(ctx, domain, function (err) {
if(err || domain.hasErrors()) return done(err || errors);
runPutEvent(err);
});
} else {
runPutEvent();
}
});
}
function post() {
var errs = collection.validate(item, true);
if(errs) return done({errors: errs});
// generate id before event listener
item.id = store.createUniqueIdentifier();
if(collection.shouldRunEvent(collection.events.Post, ctx)) {
collection.events.Post.run(ctx, domain, function (err) {
if(err) {
// debug('onPost script error %j', err);
return done(err);
}
if(err || domain.hasErrors()) return done(err || errors);
// debug('inserting item', item);
store.insert(item, done);
if(session && session.emitToAll) session.emitToAll(collection.name + ':changed');
});
} else {
store.insert(item, done);
if(session && session.emitToAll) session.emitToAll(collection.name + ':changed');
}
}
if (query.id) {
put();
} else if (collection.shouldRunEvent(collection.events.Validate, ctx)) {
collection.events.Validate.run(ctx, domain, function (err) {
if(err || domain.hasErrors()) return done(err || errors);
post();
});
} else {
post();
}
};
// Modify this function to expand the scripts domain to include
// any context (ctx) variables you would like.
var createDomain = function (data, errors, ctx) {
console.log("I'm new here!");
var hasErrors = false;
var domain = {
error: function(key, val) {
// debug('error %s %s', key, val);
errors[key] = val || true;
hasErrors = true;
},
errorIf: function(condition, key, value) {
if (condition) {
domain.error(key, value);
}
},
errorUnless: function(condition, key, value) {
domain.errorIf(!condition, key, value);
},
hasErrors: function() {
return hasErrors;
},
hide: function(property) {
delete domain.data[property];
},
'this': data,
data: data,
// Added headers to the domain.
headers:ctx.req.headers
};
return domain;
}
// Expose custom methods in dpd.js on the client.
ExposedCollection.prototype.clientGenerationExec = ['find', 'create', 'cancel', 'refund', 'capture', 'modify'];
module.exports = ExposedCollection;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment