Coverage

98%
102
100
2

/home/pmcnr/work/jsonapi-store-mongodb/lib/debugging.js

100%
2
2
0
LineHitsSource
11"use strict";
2
31module.exports = require("debug")("jsonApi:store:mongodb");
4

/home/pmcnr/work/jsonapi-store-mongodb/lib/mongoHandler.js

98%
100
98
2
LineHitsSource
11"use strict";
21var _ = {
3 clone: require("lodash.clone"),
4 omit: require("lodash.omit")
5};
61var async = require("async");
71var debug = require("/home/pmcnr/work/jsonapi-store-mongodb/lib/./debugging");
81var mongodb = require("mongodb");
9
10
111var MongoStore = module.exports = function MongoStore(config) {
125 this._config = config;
13};
14
15
16/**
17 Handlers readiness status. This should be set to `true` once all handlers are ready to process requests.
18 */
191MongoStore.prototype.ready = false;
20
21
221MongoStore._mongoUuid = function(uuid) {
23123 return new mongodb.Binary(uuid, mongodb.Binary.SUBTYPE_UUID);
24};
25
26
271MongoStore._isRelationshipAttribute = function(attribute) {
2824 return attribute._settings && (attribute._settings.__one || attribute._settings.__many);
29};
30
31
321MongoStore._toMongoDocument = function(resource) {
3317 var document = _.clone(resource, true);
3417 document._id = MongoStore._mongoUuid(document.id);
3517 return document;
36};
37
38
391MongoStore._getRelationshipAttributeNames = function(attributes) {
405 var attributeNames = Object.getOwnPropertyNames(attributes);
415 var relationshipAttributeNames = attributeNames.reduce(function(partialAttributeNames, name) {
4224 var attribute = attributes[name];
4324 if (MongoStore._isRelationshipAttribute(attribute)) {
4411 return partialAttributeNames.concat(name);
45 }
4613 return partialAttributeNames;
47 }, []);
485 return relationshipAttributeNames;
49};
50
51
521MongoStore._getSearchCriteria = function(relationships) {
5344 if (!relationships) return {};
5412 var relationshipNames = Object.getOwnPropertyNames(relationships);
5512 var criteria = relationshipNames.reduce(function(partialCriteria, relationshipName) {
5612 var relationshipId = relationships[relationshipName];
5712 partialCriteria[relationshipName + ".id"] = relationshipId;
5812 return partialCriteria;
59 }, {});
6012 return criteria;
61};
62
63
641MongoStore._notFoundError = function(type, id) {
659 return {
66 status: "404",
67 code: "ENOTFOUND",
68 title: "Requested resource does not exist",
69 detail: "There is no " + type + " with id " + id
70 };
71};
72
73
741MongoStore.prototype._createIndexesForRelationships = function(collection, relationshipAttributeNames) {
755 var index = relationshipAttributeNames.reduce(function(partialIndex, name) {
7611 partialIndex[name + ".id"] = 1;
7711 return partialIndex;
78 }, {});
795 collection.createIndex(index);
80};
81
82
83/**
84 Initialise gets invoked once for each resource that uses this handler.
85 */
861MongoStore.prototype.initialise = function(resourceConfig) {
875 var self = this;
885 if (!self._config.url) {
890 return console.error("MongoDB url missing from configuration");
90 }
915 self.resourceConfig = resourceConfig;
925 self.relationshipAttributeNames = MongoStore._getRelationshipAttributeNames(resourceConfig.attributes);
935 mongodb.MongoClient.connect(self._config.url).then(function(db) {
945 self._db = db;
95 }).catch(function(err) {
960 return console.error("error connecting to MongoDB:", err.message);
97 }).then(function() {
985 var resourceName = resourceConfig.resource;
995 debug("initialising resource [" + resourceName + "]");
1005 var collection = self._db.collection(resourceName);
1015 self._createIndexesForRelationships(collection, self.relationshipAttributeNames);
1025 self.ready = true;
103 });
104};
105
106
107/**
108 Drops the database if it already exists and populates it with example documents.
109 */
1101MongoStore.prototype.populate = function(callback) {
1115 var self = this;
1125 self._db.dropDatabase(function(err) {
1135 if (err) return console.error("error dropping database");
1145 async.each(self.resourceConfig.examples, function(document, cb) {
11516 self.create({ params: {} }, document, cb);
116 }, function(error) {
1175 if (error) console.error("error creating example document:", error);
1185 return callback();
119 });
120 });
121};
122
123
124/**
125 Search for a list of resources, give a resource type.
126 */
1271MongoStore.prototype.search = function(request, callback) {
12828 var collection = this._db.collection(request.params.type);
12928 debug("relationships> " + JSON.stringify(request.params.relationships, null, 2));
13028 var criteria = MongoStore._getSearchCriteria(request.params.relationships);
13128 debug("criteria> " + JSON.stringify(criteria, null, 2));
13228 collection.find(criteria, { _id: 0 }).toArray(callback);
133};
134
135
136/**
137 Find a specific resource, given a resource type and and id.
138 */
1391MongoStore.prototype.find = function(request, callback) {
14096 var collection = this._db.collection(request.params.type);
14196 var documentId = MongoStore._mongoUuid(request.params.id);
14296 collection.findOne({ _id: documentId }, { _id: 0 }, function(err, result) {
14396 if (err || !result) {
1446 return callback(MongoStore._notFoundError(request.params.type, request.params.id));
145 }
14690 return callback(null, result);
147 });
148};
149
150
151/**
152 Create (store) a new resource give a resource type and an object.
153 */
1541MongoStore.prototype.create = function(request, newResource, callback) {
15517 var collection = this._db.collection(newResource.type);
15617 var document = MongoStore._toMongoDocument(newResource);
15717 collection.insertOne(document, function(err) {
15817 if (err) return callback(err);
15917 collection.findOne(document, { _id: 0 }, callback);
160 });
161};
162
163
164/**
165 Delete a resource, given a resource type and an id.
166 */
1671MongoStore.prototype.delete = function(request, callback) {
1682 var collection = this._db.collection(request.params.type);
1692 var documentId = MongoStore._mongoUuid(request.params.id);
1702 collection.deleteOne({ _id: documentId }, function(err, result) {
1712 if (err) return callback(err);
1722 if (result.deletedCount === 0) {
1731 return callback(MongoStore._notFoundError(request.params.type, request.params.id));
174 }
1751 return callback(err, result);
176 });
177};
178
179
180/**
181 Update a resource, given a resource type and id, along with a partialResource.
182 partialResource contains a subset of changes that need to be merged over the original.
183 */
1841MongoStore.prototype.update = function(request, partialResource, callback) {
1858 var collection = this._db.collection(request.params.type);
1868 var documentId = MongoStore._mongoUuid(request.params.id);
18748 var partialDocument = _.omit(partialResource, function(value) { return value === undefined; });
1888 debug("partialDocument> " + JSON.stringify(partialDocument, null, 2));
1898 collection.findOneAndUpdate({
190 _id: documentId
191 }, {
192 $set: partialDocument
193 }, {
194 returnOriginal: false,
195 projection: { _id: 0 }
196 }, function(err, result) {
1978 debug("err>", JSON.stringify(err, null, 2));
1988 debug("result>", JSON.stringify(result, null, 2));
1998 if (err) return callback(err);
2008 if (!result || !result.value) {
2012 return callback(MongoStore._notFoundError(request.params.type, request.params.id));
202 }
2036 return callback(null, result.value);
204 });
205};
206