Skip to content

Commit ec38fe3

Browse files
committed
2 parents 0fbd9d6 + 6cd8c6e commit ec38fe3

File tree

1 file changed

+133
-29
lines changed

1 file changed

+133
-29
lines changed

index.js

Lines changed: 133 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,47 @@
11
'use strict';
22
const JSONAPISerializer = require('jsonapi-serializer').Serializer;
3+
const crypto = require('crypto');
4+
5+
/**
6+
* Converts a string to the `dasherized-key` format.
7+
*
8+
* @private
9+
* @function dasherize
10+
* @param {String} str - String to convert.
11+
* @return {String}
12+
*/
13+
function dasherize(str) {
14+
let newStr = str.substr(0, 1).toLowerCase() + str.substr(1);
15+
newStr = newStr.replace(/([A-Z])/g, '-$1').toLowerCase();
16+
return newStr;
17+
}
18+
19+
/**
20+
* Creates a unique ID for a given object, using a SHA-256 hash.
21+
*
22+
* @private
23+
* @function generateFauxId
24+
* @param {Object} data
25+
* @return {String}
26+
*/
27+
function generateFauxId(data) {
28+
const hash = crypto.createHash('sha256');
29+
hash.update(JSON.stringify(data));
30+
return hash.digest('hex');
31+
}
32+
33+
/**
34+
* Checks if Sequelize objects are available. If they're not, `serializePlainObject`
35+
* will be used to serialize the result. No relationships will be available.
36+
*
37+
* @private
38+
* @function mustParseAsSequelize
39+
* @param {Hook} hook
40+
* @return {Boolean}
41+
*/
42+
function mustParseAsSequelize(hook) {
43+
return hook.service.Model && hook.result.$options;
44+
}
345

446
/**
547
* Creates a filter helper to check if a given attribute name must be excluded or not.
@@ -203,34 +245,86 @@ function createPagination(hook) {
203245
}
204246
}
205247

248+
/**
249+
* Converts a plain Object instance into a JSON-API-compliant object.
250+
*
251+
* @private
252+
* @function serializePlainObject
253+
* @param {Object} item
254+
* @param {Object} options
255+
* @param {Hook} hook
256+
* @return {Object}
257+
*/
258+
function serializePlainObject(item, options, hook) {
259+
const newItem = {};
260+
261+
if (options.identifierKey) {
262+
newItem.id = item[options.identifierKey];
263+
} else {
264+
newItem.id = generateFauxId(item);
265+
}
266+
if (options.typeKey) {
267+
newItem.type = item[options.typeKey];
268+
} else {
269+
newItem.type = hook.service.options.name;
270+
}
271+
272+
newItem.attributes = {};
273+
Object.keys(item).filter(function(key) {
274+
return key !== options.identifierKey && key !== options.typeKey;
275+
}).forEach(function(key) {
276+
newItem.attributes[dasherize(key)] = item[key];
277+
});
278+
279+
newItem.links = {
280+
self: '/' + hook.path + '/' + newItem.id
281+
};
282+
283+
return newItem;
284+
}
285+
206286
/**
207287
* Creates a JSON API document with multiple records.
208288
*
209289
* @private
210290
* @method jsonapifyViaFind
211291
* @param {Hook} hook
212292
*/
213-
function jsonapifyViaFind(hook) {
293+
function jsonapifyViaFind(hook, options) {
214294
let serialized = {};
215-
hook.result.included = [];
216-
hook.result.data.forEach(function(data, index) {
217-
const jsonItem = data.toJSON();
218-
serialized = jsonapify(jsonItem, hook.service.Model, hook.path + '/' + jsonItem.id, data.$options);
219-
hook.result.data[index] = serialized.document;
220-
if (serialized.related) {
221-
hook.result.included = hook.result.included.concat(serialized.related);
222-
}
223-
if (serialized.links) {
224-
hook.result.data[index].links = serialized.links;
225-
createPagination(hook);
295+
if (mustParseAsSequelize(hook)) {
296+
hook.result.included = [];
297+
hook.result.data.forEach(function(data, index) {
298+
const jsonItem = data.toJSON();
299+
serialized = jsonapify(jsonItem, hook.service.Model, hook.path + '/' + jsonItem.id, data.$options);
300+
hook.result.data[index] = serialized.document;
301+
if (serialized.related) {
302+
hook.result.included = hook.result.included.concat(serialized.related);
303+
}
304+
if (serialized.links) {
305+
hook.result.data[index].links = serialized.links;
306+
createPagination(hook);
307+
}
308+
});
309+
if (!hook.result.included.length) {
310+
delete hook.result.included;
311+
} else {
312+
hook.result.included = removeDuplicateRecords(hook.result.included);
226313
}
227-
});
228-
if (!hook.result.included.length) {
229-
delete hook.result.included;
314+
createMetadata(hook);
230315
} else {
231-
hook.result.included = removeDuplicateRecords(hook.result.included);
316+
const newResult = {};
317+
if (Array.isArray(hook.result) && hook.result.length > 1) {
318+
newResult.data = hook.result.map(function(item) {
319+
return serializePlainObject(item, options, hook);
320+
});
321+
} else if (!Array.isArray(hook.result) && Object.keys(hook.result).length) {
322+
newResult.data = [serializePlainObject(hook.result, options, hook)];
323+
} else {
324+
newResult.data = [];
325+
}
326+
hook.result = newResult;
232327
}
233-
createMetadata(hook);
234328
}
235329

236330
/**
@@ -240,19 +334,23 @@ function jsonapifyViaFind(hook) {
240334
* @method jsonapifyViaGet
241335
* @param {Hook} hook
242336
*/
243-
function jsonapifyViaGet(hook) {
337+
function jsonapifyViaGet(hook, options) {
244338
let serialized = {};
245-
const jsonItem = hook.result.toJSON();
246-
serialized = jsonapify(jsonItem, hook.service.Model, hook.path + '/' + jsonItem.id, hook.result.$options);
247-
hook.result = { data: serialized.document, included: serialized.related };
248-
if (hook.result.included && !hook.result.included.length) {
249-
delete hook.result.included;
250-
}
251-
if (serialized.links) {
252-
hook.result.data.links = serialized.links;
253-
hook.result.data.links.parent = '/' + hook.service.Model.name;
339+
if (mustParseAsSequelize(hook)) {
340+
const jsonItem = hook.result.toJSON();
341+
serialized = jsonapify(jsonItem, hook.service.Model, hook.path + '/' + jsonItem.id, hook.result.$options);
342+
hook.result = { data: serialized.document, included: serialized.related };
343+
if (hook.result.included && !hook.result.included.length) {
344+
delete hook.result.included;
345+
}
346+
if (serialized.links) {
347+
hook.result.data.links = serialized.links;
348+
hook.result.data.links.parent = '/' + hook.service.Model.name;
349+
}
350+
createMetadata(hook);
351+
} else {
352+
hook.result.data = serializePlainObject(hook.result, options, hook);
254353
}
255-
createMetadata(hook);
256354
}
257355

258356
/**
@@ -268,11 +366,17 @@ const entrypoints = { find: jsonapifyViaFind, get: jsonapifyViaGet };
268366
* It is used as an `after` hook. Bindable with `find` and `get` hooks.
269367
*
270368
* @function jsonapify
369+
* @param {Object} options - Define settings for the JSONAPIficiation process.
370+
* Available options:
371+
* - identifierKey: (String) Used by `serializePlainObject` to determine
372+
* which key will be used as `id`.
373+
* - typeKey: (String) Used by `serializePlainObject` to determine
374+
* which key will be used as `type`.
271375
*/
272376
module.exports = function (options = {}) { // eslint-disable-line no-unused-vars
273377
return function (hook) {
274378
if (hook.method === 'find' || hook.method === 'get') {
275-
entrypoints[hook.method](hook);
379+
entrypoints[hook.method](hook, options);
276380
}
277381
return Promise.resolve(hook);
278382
};

0 commit comments

Comments
 (0)