Skip to content

Commit

Permalink
#171: Return record count from storage
Browse files Browse the repository at this point in the history
  • Loading branch information
groenroos committed Feb 20, 2022
1 parent 6ceef8e commit f211a50
Show file tree
Hide file tree
Showing 11 changed files with 118 additions and 80 deletions.
2 changes: 1 addition & 1 deletion core/loadCustomTags.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ export default async function loadCustomTags(next) {

/* Not allowed so give an empty array */
if (!allowed) {
return [];
return this.storage.formatResponse([]);
}

/* Request the data */
Expand Down
5 changes: 4 additions & 1 deletion drivers/db/Memory.js
Original file line number Diff line number Diff line change
Expand Up @@ -140,18 +140,21 @@ export default class Memory extends Interface {
*/
async remove(collection, conditions) {
const records = this.memory[collection] || [];
let count = 0;

if (Object.keys(conditions).length > 0) {
for (const [index, record] of records.entries()) {
if (this.isMatch(record, conditions) && this.memory[collection]) {
this.memory[collection].splice(index, 1);
count++;
}
}
} else {
count = collection in this.memory ? this.memory[collection].length : 0;
this.memory[collection] = [];
}

return [{ success: true }];
return { data: true, count };
}


Expand Down
4 changes: 2 additions & 2 deletions hooks/sapling/user/recover.js
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ export default async function recover(app, request, response) {
});

/* If there is no such user */
if (user.length === 0) {
if (!user) {
return new Response(app, request, response, new SaplingError({
status: '401',
code: '4004',
Expand All @@ -98,7 +98,7 @@ export default async function recover(app, request, response) {
delete request.body.new_password;

/* Update the new password and clear the key */
const userData = await app.storage.post({
const { data: userData } = await app.storage.post({
url: `/data/users/_id/${user._id}`,
body: { password: hash[1], _salt: hash[0], _authkey: '' },
session: app.adminSession,
Expand Down
2 changes: 1 addition & 1 deletion hooks/sapling/user/register.js
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ export default async function register(app, request, response) {
delete request.body.password_confirm;

/* Save to the database */
const userData = await app.storage.post({
const { data: userData } = await app.storage.post({
url: '/data/users',
session: request.session,
permission: request.permission,
Expand Down
2 changes: 1 addition & 1 deletion hooks/sapling/user/update.js
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ export default async function update(app, request, response) {
}

/* Send to the database */
const userData = await app.storage.post({
const { data: userData } = await app.storage.post({
url: `/data/users/_id/${user._id}`,
body: request.body,
session: request.session,
Expand Down
18 changes: 5 additions & 13 deletions lib/Response.js
Original file line number Diff line number Diff line change
Expand Up @@ -122,19 +122,11 @@ export default class Response {
/**
* Return a string for number of records found/affected in an array.
*
* @param {any} data Data to be analysed
* @param {any} response Response to be analysed
*/
getRecordsFound(data) {
/* Coerce an object into an array */
if (isobject(data)) {
data = [data];
}

if (Array.isArray(data)) {
return data.length + (data.length === 1 ? ' record ' : ' records ') + (this.request.method === 'GET' ? 'found' : 'affected');
}

return '';
getRecordsFound(response) {
const count = (this.request.query.single || !('count' in response)) ? 1 : response.count;
return count + (count === 1 ? ' record ' : ' records ') + (this.request.method === 'GET' ? 'found' : 'affected');
}


Expand Down Expand Up @@ -193,7 +185,7 @@ export default class Response {
{
request: this.request.method + ' ' + this.request.originalUrl,
status: this.getRecordsFound(this.content),
data: this.convertArrayToTables(this.content),
data: this.convertArrayToTables(this.content.data),
date: new Date(),
},
));
Expand Down
57 changes: 46 additions & 11 deletions lib/Storage.js
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,41 @@ export default class Storage {
}


/**
* Format the response from the database driver to be uniform
*
* @param {any} response Response from DB driver
* @returns {object} Formatted response
*/
formatResponse(response) {
const formattedResponse = {
data: [],
count: 0,
};

if (typeof response === 'boolean' || typeof response.data === 'boolean') {
/* If it's a data-less boolean response (i.e. deleting a record) */
delete formattedResponse.data;
formattedResponse.count = 'count' in response ? response.count : 1;
formattedResponse.success = true;
} else if (isobject(response)) {
/* Format the object with some guesswork */
formattedResponse.data = 'data' in response ? response.data : response;
formattedResponse.count = 'count' in response ? response.count : formattedResponse.data.length;
} else if (Array.isArray(response)) {
/* Assume the array is array of records */
formattedResponse.data = response;
formattedResponse.count = response.length;
} else {
/* Fallback */
formattedResponse.data = response;
formattedResponse.count = 1;
}

return formattedResponse;
}


/**
* Serve an incoming GET request from the database
*
Expand Down Expand Up @@ -282,7 +317,7 @@ export default class Storage {

/* Get it from the database */
try {
const array = await this.db.read(request.collection, conditions, options, references);
const array = this.formatResponse(await this.db.read(request.collection, conditions, options, references));
const rules = this.getRules(request.collection);

/* Get the list of fields we should not be able to see */
Expand All @@ -292,24 +327,24 @@ export default class Storage {
const ownerFields = this.app.user.ownerFields(rules);

/* Process fields against both lists */
for (let i = 0; i < array.length; ++i) {
for (let i = 0; i < array.data.length; ++i) {
/* Omit fields from the disallowedFields */
array[i] = _.omit(array[i], omit);
array.data[i] = _.omit(array.data[i], omit);

/* Check for ownership */
const owner = array[i]._creator || array[i]._id;
const owner = array.data[i]._creator || array.data[i]._id;

if (role !== 'admin' && (!request.isLogged || owner !== request.session.user._id)) {
for (const ownerField of ownerFields) {
delete array[i][ownerField];
delete array.data[i][ownerField];
}
}
}

/* If we only have a single result, return it bare */
/* If we only want a single result, return it bare */
/* Otherwise, an array */
if (request.query.single && array.length > 0) {
return array[0];
if (request.query.single) {
return array.data.length > 0 ? array.data[0] : false;
}

return array;
Expand Down Expand Up @@ -406,7 +441,7 @@ export default class Storage {

/* Send to the database */
try {
return await this.db.modify(request.collection, conditions, data);
return this.formatResponse(await this.db.modify(request.collection, conditions, data));
} catch (error) {
return new Response(this.app, request, response, new SaplingError(error));
}
Expand All @@ -423,7 +458,7 @@ export default class Storage {

/* Send to the database */
try {
return await this.db.write(request.collection, data);
return this.formatResponse(await this.db.write(request.collection, data));
} catch (error) {
return new Response(this.app, request, response, new SaplingError(error));
}
Expand Down Expand Up @@ -451,6 +486,6 @@ export default class Storage {
conditions = _.extend(conditions, this.app.request.getConstraints(request), this.app.request.getCreatorConstraint(request, role));

/* Send it to the database */
return await this.db.remove(request.collection, conditions);
return this.formatResponse(await this.db.remove(request.collection, conditions));
}
}
15 changes: 10 additions & 5 deletions test/core/loadCustomTags.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,8 @@ test('get tag fetches data', async t => {

t.context.app.templating.renderer.registerTags = async (tags) => {
const response = await tags.get.call(t.context.app, '/data/posts');
t.is(response.length, 2);
t.is(response.count, 2);
t.is(response.data.length, 2);
};

await loadCustomTags.call(t.context.app);
Expand All @@ -67,7 +68,8 @@ test('get tag fetches data with given role', async t => {

t.context.app.templating.renderer.registerTags = async (tags) => {
const response = await tags.get.call(t.context.app, '/data/posts', 'admin');
t.is(response.length, 2);
t.is(response.count, 2);
t.is(response.data.length, 2);
};

await loadCustomTags.call(t.context.app);
Expand All @@ -92,7 +94,8 @@ test('get tag fetches data with session role', async t => {

t.context.app.templating.renderer.registerTags = async (tags) => {
const response = await tags.get.call(t.context.app, '/data/posts');
t.is(response.length, 2);
t.is(response.count, 2);
t.is(response.data.length, 2);
};

await loadCustomTags.call(t.context.app);
Expand All @@ -109,7 +112,8 @@ test('get tag returns empty data with insufficient given role', async t => {

t.context.app.templating.renderer.registerTags = async (tags) => {
const response = await tags.get.call(t.context.app, '/data/posts', 'member');
t.is(response.length, 0);
t.is(response.count, 0);
t.is(response.data.length, 0);
};

await loadCustomTags.call(t.context.app);
Expand All @@ -134,7 +138,8 @@ test('get tag returns empty data with insufficient session role', async t => {

t.context.app.templating.renderer.registerTags = async (tags) => {
const response = await tags.get.call(t.context.app, '/data/posts');
t.is(response.length, 0);
t.is(response.count, 0);
t.is(response.data.length, 0);
};

await loadCustomTags.call(t.context.app);
Expand Down
2 changes: 1 addition & 1 deletion test/drivers/db/Memory.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -286,5 +286,5 @@ test.serial('deletes all records', async t => {
test.serial('deletes nothing in a non-existent collection', async t => {
const result = await t.context.memory.remove('fourth', {});

t.true(result[0].success);
t.true(result.data);
});
16 changes: 6 additions & 10 deletions test/lib/Response.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -81,40 +81,36 @@ test('returns the proper HTML string for a number value', t => {
/* getRecordsFound */

test('returns appropriate label for 1 record found', t => {
t.is((new Response(t.context.app, t.context.request)).getRecordsFound([{foo:'bar'}]), '1 record found');
t.is((new Response(t.context.app, t.context.request)).getRecordsFound({data:[{foo:'bar'}],count:1}), '1 record found');
});

test('returns appropriate label for multiple records found', t => {
t.is((new Response(t.context.app, t.context.request)).getRecordsFound([{foo:'bar'},{foo:'bar'}]), '2 records found');
t.is((new Response(t.context.app, t.context.request)).getRecordsFound({data:[{foo:'bar'},{foo:'bar'}],count:2}), '2 records found');
});

test('returns appropriate label for no records found', t => {
t.is((new Response(t.context.app, t.context.request)).getRecordsFound([]), '0 records found');
t.is((new Response(t.context.app, t.context.request)).getRecordsFound({data:[],count:0}), '0 records found');
});

test('returns appropriate label for 1 record affected', t => {
t.context.request.method = 'POST';
t.is((new Response(t.context.app, t.context.request)).getRecordsFound([{foo:'bar'}]), '1 record affected');
t.is((new Response(t.context.app, t.context.request)).getRecordsFound({data:[{foo:'bar'}],count:1}), '1 record affected');
});

test('returns appropriate label for multiple records affected', t => {
t.context.request.method = 'POST';
t.is((new Response(t.context.app, t.context.request)).getRecordsFound([{foo:'bar'},{foo:'bar'}]), '2 records affected');
t.is((new Response(t.context.app, t.context.request)).getRecordsFound({data:[{foo:'bar'},{foo:'bar'}],count:2}), '2 records affected');
});

test('returns appropriate label for no records affected', t => {
t.context.request.method = 'POST';
t.is((new Response(t.context.app, t.context.request)).getRecordsFound([]), '0 records affected');
t.is((new Response(t.context.app, t.context.request)).getRecordsFound({data:[],count:0}), '0 records affected');
});

test('returns appropriate label for a single record', t => {
t.is((new Response(t.context.app, t.context.request)).getRecordsFound({foo:'bar'}), '1 record found');
});

test('returns empty string for bad data', t => {
t.is((new Response(t.context.app, t.context.request)).getRecordsFound('bar'), '');
});


/* viewResponse */

Expand Down
Loading

0 comments on commit f211a50

Please sign in to comment.