Skip to content

Commit

Permalink
Filter by ids (#283)
Browse files Browse the repository at this point in the history
* feat: add the possibility to filter by array of ids
feat: add custom verbs permissions

* filter out bad ids
  • Loading branch information
ChristopheBraud committed Mar 12, 2024
1 parent 0372465 commit e53554e
Show file tree
Hide file tree
Showing 8 changed files with 101 additions and 24 deletions.
12 changes: 7 additions & 5 deletions services/docs/lib/handlers.js
Original file line number Diff line number Diff line change
Expand Up @@ -51,14 +51,16 @@ module.exports.deleteLock = async function (req, res) {
};

module.exports.getDocuments = function (req, res) {
const fullQuery = { ...req.query, ...req.body };

const pagination = {};
const perPage = req.query.perPage || req.resource.perPage;
const perPage = fullQuery.perPage || req.resource.perPage;
if (perPage) pagination.perPage = perPage;
if (req.query.page) pagination.page = req.query.page;
pagination.infinite = req.query.pagination && `${req.query.pagination}`.toLowerCase() === 'false';
if (fullQuery.page) pagination.page = fullQuery.page;
pagination.infinite = fullQuery.pagination && `${fullQuery.pagination}`.toLowerCase() === 'false';

documentService
.getDocuments(req.resource, req.filter, req.user, req.query, req.state, req.query.sort, pagination, req.options.resources)
.getDocuments(req.resource, req.filter, req.user, fullQuery, req.state, fullQuery.sort, pagination, req.options.resources)
.then(data => {
const links = [];
Object.entries(data.nav).forEach(([rel, page]) => {
Expand All @@ -83,7 +85,7 @@ module.exports.getDocuments = function (req, res) {

return helpers.json(res, data.docs, headers);
})
.catch(err => {
.catch(() => {
helpers.notFound(res);
});
};
Expand Down
6 changes: 4 additions & 2 deletions services/docs/lib/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,10 +32,11 @@ module.exports = class DocsService extends CampsiService {
};

const additionalMiddlewares = (req, res, next) => {
if (typeof req.resource?.additionalMiddlewares?.[req.method] !== 'function') {
const method = req.customMethod || req.method;
if (typeof req.resource?.additionalMiddlewares?.[method] !== 'function') {
return next();
}
return req.resource?.additionalMiddlewares[req.method](req, res, next);
return req.resource?.additionalMiddlewares[method](req, res, next);
};

this.router.use('/', (req, res, next) => {
Expand All @@ -49,6 +50,7 @@ module.exports = class DocsService extends CampsiService {
this.router.param('resource', param.attachResource(service.options));
this.router.get('/', handlers.getResources);
this.router.get('/:resource', additionalMiddlewares, handlers.getDocuments);
this.router.post('/:resource/documents[:]get', additionalMiddlewares, handlers.getDocuments);
this.router.post('/:resource/:id/locks', additionalMiddlewares, handlers.lockDocument);
this.router.get('/:resource/:id/locks', additionalMiddlewares, handlers.getLocks);
this.router.get('/:resource/:id/users', additionalMiddlewares, handlers.getDocUsers);
Expand Down
21 changes: 19 additions & 2 deletions services/docs/lib/param.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,26 @@ module.exports.attach = (req, res, next, options) => {
}
}

// Custom Method
const customMethods = {
':get': 'GET',
':post': 'POST',
':put': 'PUT',
':patch': 'PATCH',
':delete': 'DELETE'
};
Object.entries(customMethods).some(([fragment, method]) => {
const customMethodFound = req.path.includes(fragment);
if (customMethodFound) {
req.customMethod = method;
}
return customMethodFound;
});

// check if the document is locked by someone else if we are trying to modify it
const lockChek = new Promise((resolve, reject) => {
if (['PUT', 'POST', 'PATCH', 'DELETE'].some(method => req.method.includes(method))) {
const requestMethod = req.customMethod || req.method;
if (['PUT', 'POST', 'PATCH', 'DELETE'].some(method => requestMethod.includes(method))) {
documentService
.isDocumentLockedByOtherUser(req.state, req.filter, req.user, getDocumentLockServiceOptions(req), req.db)
.then(lock => {
Expand All @@ -56,7 +73,7 @@ module.exports.attach = (req, res, next, options) => {
lockChek
.then(() => {
try {
const filter = can(req.user, req.resource, req.method, req.state);
const filter = can(req.user, req.resource, req.customMethod || req.method, req.state);
req.filter = { ...req.filter, ...filter };

next();
Expand Down
7 changes: 6 additions & 1 deletion services/docs/lib/services/document.js
Original file line number Diff line number Diff line change
Expand Up @@ -178,7 +178,12 @@ module.exports.getDocuments = async function (resource, filter, user, query, sta
const queryBuilderOptions = { resource, user, query, state };
const filterState = {};
filterState[`states.${state}`] = { $exists: true };
const dbQuery = Object.assign(filterState, filter, builder.find(queryBuilderOptions));
const filterIds = {};
if (query.ids) {
// TODO: $in operator should not have more than 200 elements
filterIds._id = { $in: query.ids.map(id => createObjectId(id)).filter(Boolean) };
}
const dbQuery = Object.assign(filterState, filterIds, filter, builder.find(queryBuilderOptions));

const dbFields = { _id: 1, states: 1, users: 1, groups: 1 };
if (query?.with?.includes('metadata')) {
Expand Down
22 changes: 20 additions & 2 deletions test/docs/crud.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,6 @@ process.env.NODE_CONFIG_DIR = './test/docs/config';
process.env.NODE_ENV = 'test';

// Require the dev-dependencies
const { MongoClient } = require('mongodb');
const mongoUriBuilder = require('mongo-uri-builder');
const debug = require('debug')('campsi:test');
const chai = require('chai');
const chaiHttp = require('chai-http');
Expand Down Expand Up @@ -71,6 +69,26 @@ describe('CRUD', () => {
});
});
});
/*
* Test the /GET docs/pizzas route
*/
describe('/POST docs/pizzas/:getDocuments', () => {
it('it should GET all the documents', done => {
chai
.request(campsi.app)
.get('/docs/pizzas')
.end((err, res) => {
if (err) debug(`received an error from chai: ${err.message}`);
res.should.have.status(200);
res.should.have.header('x-total-count', '0');
res.should.not.have.header('link');
res.should.be.json;
res.body.should.be.a('array');
res.body.length.should.be.eq(0);
done();
});
});
});
/*
* Test the /GET docs/pizzas route
*/
Expand Down
2 changes: 0 additions & 2 deletions test/docs/document-links.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@
process.env.NODE_CONFIG_DIR = './test/docs/config';
process.env.NODE_ENV = 'test';
// Require the dev-dependencies
const { MongoClient } = require('mongodb');
const mongoUriBuilder = require('mongo-uri-builder');
const debug = require('debug')('campsi:test');
const chai = require('chai');
const expect = chai.expect;
Expand Down
3 changes: 0 additions & 3 deletions test/docs/embed.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,13 @@ process.env.NODE_CONFIG_DIR = './test/docs/config';
process.env.NODE_ENV = 'test';

// Require the dev-dependencies
const { MongoClient } = require('mongodb');
const mongoUriBuilder = require('mongo-uri-builder');
const debug = require('debug')('campsi:test');
const chai = require('chai');
const chaiHttp = require('chai-http');
const format = require('string-format');
const CampsiServer = require('campsi');
const config = require('config');
const builder = require('../../services/docs/lib/modules/queryBuilder');
const async = require('async');
const fakeId = require('fake-object-id');
const { emptyDatabase } = require('../helpers/emptyDatabase');

Expand Down
52 changes: 45 additions & 7 deletions test/docs/filters.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,10 +36,12 @@ async function createData() {
{ label: 'third label', price: 30, start: new Date('2022-05-11'), visible: true }
];

const createdRecords = [];
for await (const data of entries) {
const record = await builder.create({ user: owner, data, resource: category, state: 'published' });
await category.collection.insertOne(record);
createdRecords.push(await category.collection.insertOne(record));
}
return createdRecords;
}

function testResponse(response, length) {
Expand Down Expand Up @@ -186,7 +188,7 @@ describe('Filter Documents', () => {
});

describe('Number lt', () => {
it('it should return first & second document', async () => {
it('it should return first & second documents', async () => {
await createData();
return new Promise(resolve => {
chai
Expand All @@ -204,7 +206,7 @@ describe('Filter Documents', () => {
});

describe('Number gt', () => {
it('it should return second and third document', async () => {
it('it should return second and third documents', async () => {
await createData();
return new Promise(resolve => {
chai
Expand All @@ -222,7 +224,7 @@ describe('Filter Documents', () => {
});

describe('Number lte', () => {
it('it should return first & second document', async () => {
it('it should return first & second documents', async () => {
await createData();
return new Promise(resolve => {
chai
Expand All @@ -240,7 +242,7 @@ describe('Filter Documents', () => {
});

describe('Number gte', () => {
it('it should return second & third document', async () => {
it('it should return second & third documents', async () => {
await createData();
return new Promise(resolve => {
chai
Expand All @@ -258,7 +260,7 @@ describe('Filter Documents', () => {
});

describe('Number in', () => {
it('it should return first & third document', async () => {
it('it should return first & third documents', async () => {
await createData();
return new Promise(resolve => {
chai
Expand Down Expand Up @@ -341,7 +343,7 @@ describe('Filter Documents', () => {
});

describe('Exists', () => {
it('it should return second & third document', async () => {
it('it should return second & third documents', async () => {
await createData();
return new Promise(resolve => {
chai
Expand Down Expand Up @@ -370,4 +372,40 @@ describe('Filter Documents', () => {
});
});
});

describe('Filter by Ids', () => {
it('it should return first document and filter out bad ids', async () => {
const createdRecords = await createData();
const ids = [createdRecords[0].insertedId, '007'];
return new Promise(resolve => {
chai
.request(campsi.app)
.post('/docs/categories/documents:get')
.send({ ids })
.end((err, res) => {
if (err) debug(`received an error from chai: ${err.message}`);
testResponse(res, 1);
testDocument(res.body[0], 0);
resolve();
});
});
});
it('it should return first & third documents', async () => {
const createdRecords = await createData();
const ids = [createdRecords[0].insertedId, createdRecords[2].insertedId];
return new Promise(resolve => {
chai
.request(campsi.app)
.post('/docs/categories/documents:get')
.send({ ids })
.end((err, res) => {
if (err) debug(`received an error from chai: ${err.message}`);
testResponse(res, 2);
testDocument(res.body[0], 0);
testDocument(res.body[1], 2);
resolve();
});
});
});
});
});

0 comments on commit e53554e

Please sign in to comment.