Example #1
0
module.exports = function (_opts) {
  var api = modeler(_opts);

  if (!api.options.db) throw new Error('must pass a node-mongodb-native db with options.db');
  api.options.sort = api.options.sort || '__idx';

  var db = api.options.db
    , collection = db.collection(api.options.name)
    , counter = db.collection('_counters')
    , initialized = false;

  // Make sure we have the indexes we need
  function initialize (cb) {
    collection.ensureIndex([{ "__idx": -1 }], { unique: true }, function (err) {
      if (err) return cb(err);
      counter.ensureIndex([{ "_id": 1, "seq": -1 }], { unique: true }, cb);
    });
  }

  function continuable (skip, limit, reverse, cb) {
    (function next () {
      var sort = {};
      sort[api.options.sort] = reverse ? -1 : 1;
      var cur = collection.find({}, { _id: 0, __idx: 0 }).skip( skip ? skip : 0);
      limit && cur.limit(limit);
      cur.sort(sort);
      cur.toArray(function (err, results) {
        if (err) return cb(err);
        skip += results.length;
        cb(null, results, next);
      });
    })();
  }

  api._head = function (skip, limit, cb) {
    continuable(skip, limit, false, cb);
  };
  api._tail = function (skip, limit, cb) {
    continuable(skip, limit, true, cb);
  };
  api._save = function (entity, cb) {

    if (!initialized) {
      return initialize(function(err) {
        if (err) return cb(err);
        initialized = true;
        api._save(entity, cb);
      });
    }

    function save () {
      var saveEntity = api.copy(entity);
      // We need `_id` to be the same as `id` because MongoDB and Modeler
      // each have their own unique, completely inflexible requirements
      saveEntity._id = entity.id;
      // @todo consider performing a findAndModify using both
      // id and rev in the query part where rev > 1
      collection.save(saveEntity, function (err) {
        cb(err);
      });
    }

    if (entity.rev > 1 || typeof entity.__idx !== 'undefined') save();
    else {
      // http://docs.mongodb.org/manual/tutorial/create-an-auto-incrementing-field/
      // compound unique index is ensured in the initialize function, above
      counter.findAndModify({ _id: api.options.name }, null, { "$inc": { seq: 1 } }, { new: true, upsert: true }, function (err, ct) {
        if (err) return cb(err);
        entity.__idx = ct.seq;
        save();
      });
    }
  };
  api._load = function (id, cb) {
    collection.findOne({ _id: id }, { _id: 0 }, function (err, entity) {
      if (err) return cb(err);
      cb(err, entity);
    });
  };
  api._destroy = function (id, cb) {
    collection.remove({ _id: id }, cb);
  };

  return api;
};
module.exports = function (_opts) {
  if (!_opts.client) {
    throw new Error('You must provide an elasticsearch client.');
  }

  var api = modeler(_opts);

  api.client = api.options.client;
  api.options.sort = api.options.sort || '_timestamp';

  // Refresh the index after all saves, really should only be used in tests.
  api.options.refresh = api.options.refresh || false;

  function continuable (offset, limit, dir, cb) {
    var sort = {};
    sort[api.options.sort] = dir;
    (function next () {
      api.client.search({_type: api.options.name}, {
        from: offset,
        size: limit,
        fields: ['_id', api.options.sort],
        sort: [sort]
      }, function (err, results) {
        if (err) return cb(err);
        if (!results.hits || !results.hits.hits.length) return cb(null, [], next);
        cb(null, results.hits.hits.map(function (hit) {
          return hit._id;
        }), next);
      });
    })();
  }

  api._head = function (offset, limit, cb) {
    continuable(offset, limit, 'asc', cb);
  };

  api._tail = function (offset, limit, cb) {
    continuable(offset, limit, 'desc', cb);
  };

  api._save = function (entity, cb) {
    var doc = hydration.dehydrate(entity);
    api.client.index({_type: api.options.name, _id: ('' + entity.id)}, doc, function (err, result) {
      if (err) return cb(err);
      api._finish(cb);
    });
  };

  api._load = function (id, cb) {
    api.client.get({_type: api.options.name, _id: ('' + id)}, function (err, result) {
      if (err) {
        if (err.statusCode && err.statusCode === 404) {
          return cb(null, null);
        }
        return cb(err);
      }
      cb(null, hydration.hydrate(result._source));
    });
  };

  api._destroy = function (id, cb) {
    api.client.delete({_type: api.options.name, _id: ('' + id)}, function (err) {
      if (err) return cb(err);
      api._finish(cb);
    });
  };

  api._finish = function (cb) {
    if (api.options.refresh) {
      api.client.indices.refresh(function (e) {
        cb(e);
      });
    }
    else {
      cb();
    }
  };

  return api;
};
Example #3
0
module.exports = function (_opts) {
  var api = modeler(_opts)
    , internalProps = [ 'rev', 'created', 'updated' ]
    , stripe
    , collection;

  if (!api.options.secret_key) throw new Error('secret key required');
  if (!api.options.name) throw new Error('name required');
  if (!api.options.name.match(/^(?:customers)/)) throw new Error('collection not supported: ' + api.options.name);

  stripe = Stripe(api.options.secret_key);
  if (api.options.apiVersion) stripe.setApiVersion(api.options.apiVersion);
  collection = stripe[api.options.name];

  api._beforeSave = function (entity) {
    var e = api.copy(entity)
      , metadata = {};

    e.metadata || (e.metadata = {});
    if (e.rev > 1) {
      delete e.id;
    }
    // stash modeler's internal props in metadata
    // or stripe will barf
    Object.keys(e).forEach(function (prop) {
      var idx;
      if (prop === 'metadata') return;
      if (~internalProps.indexOf(prop)) {
        metadata[prop] = e[prop];
        delete e[prop];
      }
      // restore Stripe properties with namespace collisions
      else if ((idx = prop.indexOf('_stripe')) > -1) {
        e[prop.substr(0, idx)] = e[prop];
        delete e[prop];
      }
    });
    try {
      e.metadata.modeler = JSON.stringify(hydration.dehydrate(metadata));
    }
    catch (e) {} // shouldn't happen, but don't throw
    return e;
  };

  api._afterSave = function (entity) {
    var e = _.omit(entity, 'id', internalProps);
    // move Stripe properties with namespace collisions
    Object.keys(entity).forEach(function (prop) {
      if (~internalProps.indexOf(prop)) {
        e[prop + '_stripe'] = e[prop];
      }
    });
    return e;
  };

  api._afterLoad = function (entity) {
    var e = api._afterSave(entity)
      , metadata;
    e.id = entity.id;
    // restore modeler's internal props from metadata
    if (entity.metadata && entity.metadata.modeler) {
      try {
        metadata = JSON.parse(entity.metadata.modeler);
        metadata = hydration.hydrate(metadata);
      }
      catch (e) {} // shouldn't happen, but don't throw
      internalProps.forEach(function (prop) {
        if (prop in metadata) {
          e[prop] = metadata[prop];
        }
      });
    }
    return e;
  };

  api._head = function (offset, count, cb) {
    return cb(new Error('head() is not supported for Stripe lists - please use tail() instead'));
  };

  api._tail = function (offset, count, cb) {
    (function fetchNext () {
      var params = {
        offset: offset ? offset : 0
      };
      // Stripe API requires count to be from 1 to 100
      if (count && count >= 1 && count <= 100) params.count = count;
      collection.list(params, function (err, entities) {
        if (err) return cb(err);
        offset += entities.data.length;
        cb(null, entities.data.map(api._afterLoad), fetchNext);
      });
    })();
  };

  api._save = function (entity, cb) {
    if (entity.rev > 1) {
      collection.update(entity.id, api._beforeSave(entity), function (err, savedEntity) {
        if (err) return cb(err);
        cb(null, api._afterSave(savedEntity));
      });
    }
    else {
      collection.create(api._beforeSave(entity), function (err, savedEntity) {
        if (err) return cb(err);
        cb(null, api._afterSave(savedEntity));
      });
    }
  };

  api._load = function (id, cb) {
    collection.retrieve(id, function (err, savedEntity) {
      if (err && !err.message.match(/No such (?:customer)/)) return cb(err);
      cb(null, savedEntity ? api._afterLoad(savedEntity) : null);
    });
  };

  api._destroy = function (id, cb) {
    collection.del(id, function (err, confirmation) {
      if (err) return cb(err);
      if (!confirmation || !confirmation.deleted) {
        cb(new Error('Delete failed'));
      }
      else cb();
    });
  };

  return api;
};