Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Record count #415

Merged
merged 1 commit into from
Feb 20, 2022
Merged
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
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