Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 10 additions & 1 deletion lib/action-util.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ const internals = {};
*
* @type {Object}
*/
module.exports = (request, options) => {
module.exports = (request, options, h) => {

return {

Expand Down Expand Up @@ -194,6 +194,15 @@ module.exports = (request, options) => {
const userId = Hoek.reach(request.auth.credentials, options.userIdProperty);

return userId;
},

replyWithRange: (model, rangeStart, rangeEnd, total, results) => {
if (options.totalCountHeader === 'content-range') {
return h.response(results).header('content-range', `${model} ${rangeStart}-${rangeStart + results.length}/${total.total}`);
}

return h.response(results).header(options.totalCountHeader, String(total.total));

}
};
};
4 changes: 2 additions & 2 deletions lib/actions/add.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ module.exports = function addToCollection(route, options) {

return async (request, h) => {

const actionUtil = Actions(request, options);
const actionUtil = Actions(request, options, h);

// Ensure a model and alias can be deduced from the request.
const Model = actionUtil.modelFromParam(options.model);
Expand All @@ -32,7 +32,7 @@ module.exports = function addToCollection(route, options) {
return Boom.notFound('No record found with the specified `id`.');
}

//if a key was specified for the relation, we're linking an exsiting.
//if a key was specified for the relation, we're linking an existing.
if (keys.child.value) {

const updatedTokenId = await foundModel.$relatedQuery(options.associationAttr)
Expand Down
2 changes: 1 addition & 1 deletion lib/actions/create.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ module.exports = function createRecord(route, options) {

return async (request, h) => {

const actionUtil = Actions(request, options);
const actionUtil = Actions(request, options, h);
const Model = actionUtil.parseModel();

return h.response(await Model.query().insertAndFetch(request.payload)).code(201);
Expand Down
2 changes: 1 addition & 1 deletion lib/actions/destroy.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ module.exports = function destroyOneRecord(route, options) {

return async (request, h) => {

const actionUtil = Actions(request, options);
const actionUtil = Actions(request, options, h);

const Model = actionUtil.parseModel();
const keys = actionUtil.getKeys();
Expand Down
4 changes: 2 additions & 2 deletions lib/actions/find-by-id.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,9 @@ const Actions = require('../action-util');

module.exports = function findOneRecord(route, options) {

return async (request) => {
return async (request, h) => {

const actionUtil = Actions(request, options);
const actionUtil = Actions(request, options, h);

const Model = actionUtil.modelFromParam(options.model);
const keys = actionUtil.getKeys();
Expand Down
22 changes: 17 additions & 5 deletions lib/actions/find.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,16 +21,16 @@ const Actions = require('../action-util');

module.exports = function findRecords(route, options) {

return async (request) => {
return async (request, h) => {

const actionUtil = Actions(request, options);
const actionUtil = Actions(request, options, h);
// Look up the model
const Model = actionUtil.parseModel();
// Lookup for records that match the specified criteria. Are we just counting?
if (options._private.count) {

const modelCount = await Model.query().count().first();
return modelCount[Object.keys(modelCount)[0]];
const modelCount = await Model.query().count({ total: '*' }).first();
return modelCount.total;
}

const limit = actionUtil.parseLimit();
Expand All @@ -39,18 +39,30 @@ module.exports = function findRecords(route, options) {
const rangeEnd = rangeStart ? rangeStart + limit : limit;
const where = actionUtil.parseWhere(Model);

const foundModelsQuery = Model.query().range(rangeStart, rangeEnd).limit(limit);
const foundModelsQuery = Model.query();

if (where) {
foundModelsQuery.where(where);
}

let total;

if (options.includeTotalCount) {
total = await foundModelsQuery.clone().count({ total: '*' }).first();
}

if (sort) {
foundModelsQuery.orderByRaw(sort);
}

foundModelsQuery.range(rangeStart, rangeEnd).limit(limit);

const foundModels = await foundModelsQuery;

if (total) {
return actionUtil.replyWithRange(options.model, rangeStart, rangeEnd, total, foundModels.results);
}

return foundModels.results;

};
Expand Down
22 changes: 17 additions & 5 deletions lib/actions/populate.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@ const Actions = require('../action-util');
/**
* Populate an association
*
* get /model/:parentid/relation
* get /model/:parentid/relation/count
* get /model/:parentid/relation/:id
* get /model/:parentid/:relation
* get /model/:parentid/:relation/count
* get /model/:parentid/:relation/:id
*
* @param {Object} where - the find criteria (passed directly to the ORM)
* @param {Integer} limit - the maximum number of records to send back (useful for pagination)
Expand All @@ -21,7 +21,7 @@ module.exports = function expand(route, options) {

return async (request, h) => {

const actionUtil = Actions(request, options);
const actionUtil = Actions(request, options, h);

const Model = actionUtil.modelFromParam(options.model);
const relation = options.associationAttr;
Expand All @@ -37,6 +37,8 @@ module.exports = function expand(route, options) {
const rangeStart = actionUtil.parseSkip();
const rangeEnd = rangeStart + limit;

let total;

const modelFullQuery = Model.query().findById(keys.parent.value)
.withGraphFetched(relation)
.modifyGraph(relation, (builder) => {
Expand All @@ -45,6 +47,12 @@ module.exports = function expand(route, options) {
builder.where(Ref(RelationModel.tableName + '.' + keys.child.key), '=', keys.child.value);
}

if (options.includeTotalCount) {
// we need to wrap this in an async iffe to trigger the query,
// as otherwise the main query gets borked
total = (async () => await builder.clone().count({ total: Ref(RelationModel.tableName + '.' + keys.child.key) }).first())();
}

builder.range(rangeStart, rangeEnd).limit(limit);

if (sort) {
Expand All @@ -66,10 +74,14 @@ module.exports = function expand(route, options) {
return Boom.notFound();
}

if (total) {
return actionUtil.replyWithRange(options.associationAttr, rangeStart, rangeEnd, await total, modelFull[options.associationAttr]);
}

if (options._private.count) {
return modelFull[options.associationAttr].length;
}

return modelFull;
return modelFull[options.associationAttr];
};
};
2 changes: 1 addition & 1 deletion lib/actions/remove.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ module.exports = function remove(route, options) {

return async (request, h) => {

const actionUtil = Actions(request, options);
const actionUtil = Actions(request, options, h);

const Model = actionUtil.modelFromParam(options.model);
const keys = actionUtil.getKeys();
Expand Down
4 changes: 2 additions & 2 deletions lib/actions/update.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,9 @@ const Actions = require('../action-util');

module.exports = function updateOneRecord(route, options) {

return async (request) => {
return async (request, h) => {

const actionUtil = Actions(request, options);
const actionUtil = Actions(request, options, h);

// Look up the model
const Model = actionUtil.parseModel();
Expand Down
2 changes: 2 additions & 0 deletions lib/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,8 @@ internals.defaults = {
userModel: 'Users', // since it's not in the url
userIdProperty: 'id', // on auth credentials
prefix: '',
includeTotalCount: false,
totalCountHeader: 'content-range',
_private: {
actAsUserModifiedPath: false,
count: false
Expand Down
73 changes: 73 additions & 0 deletions test/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -955,6 +955,79 @@ describe('Tandy', () => {
expect(result).to.be.an.array();
expect(result.length).to.equal(4);
});
it('Fetches all users and includes total count', async () => {

const server = await getServer(getOptions({
tandy: {
includeTotalCount: true
}
}));
server.registerModel([
TestModels.Users,
TestModels.Tokens
]);

await server.initialize();
const knex = server.knex();
// const data = await knex.seed.run({ directory: 'test/seeds' });
await knex.seed.run({ directory: 'test/seeds' });

server.route({
method: 'GET',
path: '/users',
handler: { tandy: {} }
});

const options = {
method: 'GET',
url: '/users'
};

const response = await server.inject(options);
const result = response.result;

expect(response.statusCode).to.equal(200);
expect(result).to.be.an.array();
expect(result.length).to.equal(4);
expect(response.headers['content-range']).to.equal('users 0-4/4');
});
it('Fetches all users and includes total count, custom header name', async () => {

const server = await getServer(getOptions({
tandy: {
includeTotalCount: true,
totalCountHeader: 'x-count'
}
}));
server.registerModel([
TestModels.Users,
TestModels.Tokens
]);

await server.initialize();
const knex = server.knex();
// const data = await knex.seed.run({ directory: 'test/seeds' });
await knex.seed.run({ directory: 'test/seeds' });

server.route({
method: 'GET',
path: '/users',
handler: { tandy: {} }
});

const options = {
method: 'GET',
url: '/users'
};

const response = await server.inject(options);
const result = response.result;

expect(response.statusCode).to.equal(200);
expect(result).to.be.an.array();
expect(result.length).to.equal(4);
expect(response.headers['x-count']).to.equal('4');
});
it('Fetches all users without actAsUser', async () => {

const config = getOptions({
Expand Down