Archived
2

adapted changelog

This commit is contained in:
VLE2FE 2020-08-28 16:50:33 +02:00
parent c891933d11
commit 156b65d033
5 changed files with 53 additions and 40 deletions

View File

@ -1,11 +1,33 @@
# DeFinMa - API # DeFinMa - API
This is the API to access the database of the digital fingerprint of plastics project. This is the API to access the database of the digital fingerprint of plastics project. A deep insight into the project
structure can be gained in the
[Bachelor Thesis](https://definma.apps.de1.bosch-iot-cloud.com/assets/docs/Veit-Lukas_Bachelor-Thesis.pdf).
## API documentation ## API documentation
The API is documented using the Open API Specification 3.0.2 and hosted together with the API under the `/api` path The API is documented using the Open API Specification 3.0.2 in the [api](./api) and hosted together with the API under
the `/api-doc` path. The online version can be found [here](https://definma-api.apps.de1.bosch-iot-cloud.com/api-doc).
The files are automatically bundled and validated when starting the server.
## Testing ## Testing
Testing is done with mocha and can be executed using `npm test`. Testing is done with mocha and can be executed using `npm test`.
## General structure
[index.ts](./src/index.ts) is exectued when starting the server. It includes all setup tasks, registers middleware,
routes and error handlers. Setting the `NODE_ENV` environment variable allows starting the server either in
`production`, `development` or `test` mode.
All route files including the corresponding test files are in the [routes](./src/routes) folder. The
[validate](./src/routes/validate) folder stores Joi validations for data input and output.
[models](./src/models) keeps all Mongoose database collection models.
[helpers](./src/helpers) includes functions needed in multiple places like the authorization done for every route,
csv conversion and object flattening as well as a mail service, registering an email address using the mail service on
the BIC and replacing it with a logging statement in development.
[test](./src/test) holds files for testing like the test database `db.json` loaded freshly for every test case and
a helper utilized for easier testing. `loadDev` loads the `db.json` into the `development` database for a fresh start.

View File

@ -47,14 +47,9 @@
500: 500:
$ref: 'api.yaml#/components/responses/500' $ref: 'api.yaml#/components/responses/500'
/changelog/{timestamp}/{page}/{pagesize}: /changelog/{id}/{page}/{pagesize}:
parameters: parameters:
- name: timestamp - $ref: 'api.yaml#/components/parameters/Id'
in: path
required: true
schema:
type: string
example: 1970-01-01T00:00:00.000Z
- name: page - name: page
in: path in: path
required: true required: true
@ -69,9 +64,9 @@
example: 30 example: 30
get: get:
summary: get changelog summary: get changelog
description: 'Auth: basic, levels: dev, admin<br>Displays all logs older than timestamp, sorted by date descending, description: 'Auth: basic, levels: dev, admin<br>Displays all logs older than timestamp of the given id, sorted by
page defaults to 0, pagesize defaults to 25<br>Avoid using high page numbers for older logs, better use an older date descending, page defaults to 0, pagesize defaults to 25
timestamp' <br>Avoid using high page numbers for older logs, better use an older timestamp'
tags: tags:
- / - /
responses: responses:
@ -80,6 +75,8 @@
content: content:
application/json: application/json:
schema: schema:
allOf:
- $ref: 'api.yaml#/components/schemas/_Id'
properties: properties:
date: date:
type: string type: string

View File

@ -25,16 +25,18 @@ describe('/', () => {
it('returns the first page', done => { it('returns the first page', done => {
TestHelper.request(server, done, { TestHelper.request(server, done, {
method: 'get', method: 'get',
url: '/changelog/1979-07-28T06:04:51.000Z/0/2', url: '/changelog/120000030000000000000000/0/2',
auth: {basic: 'admin'}, auth: {basic: 'admin'},
httpStatus: 200 httpStatus: 200
}).end((err, res) => { }).end((err, res) => {
if (err) return done(err); if (err) return done(err);
console.log(res.body);
should(res.body).have.lengthOf(2); should(res.body).have.lengthOf(2);
should(res.body[0].date).be.eql('1979-07-28T06:04:51.000Z'); should(res.body[0].date).be.eql('1979-07-28T06:04:51.000Z');
should(res.body[1].date).be.eql('1979-07-28T06:04:50.000Z'); should(res.body[1].date).be.eql('1979-07-28T06:04:50.000Z');
should(res.body).matchEach(log => { should(res.body).matchEach(log => {
should(log).have.only.keys('date', 'action', 'collection', 'conditions', 'data'); should(log).have.only.keys('_id', 'date', 'action', 'collection', 'conditions', 'data');
should(log).have.property('_id').be.type('string');
should(log).have.property('action', 'PUT /sample/400000000000000000000001'); should(log).have.property('action', 'PUT /sample/400000000000000000000001');
should(log).have.property('collection', 'samples'); should(log).have.property('collection', 'samples');
should(log).have.property('conditions', {_id: '400000000000000000000001'}); should(log).have.property('conditions', {_id: '400000000000000000000001'});
@ -46,7 +48,7 @@ describe('/', () => {
it('returns another page', done => { it('returns another page', done => {
TestHelper.request(server, done, { TestHelper.request(server, done, {
method: 'get', method: 'get',
url: '/changelog/1979-07-28T06:04:51.000Z/1/2', url: '/changelog/120000030000000000000000/1/2',
auth: {basic: 'admin'}, auth: {basic: 'admin'},
httpStatus: 200 httpStatus: 200
}).end((err, res) => { }).end((err, res) => {
@ -54,7 +56,8 @@ describe('/', () => {
should(res.body).have.lengthOf(1); should(res.body).have.lengthOf(1);
should(res.body[0].date).be.eql('1979-07-28T06:04:49.000Z'); should(res.body[0].date).be.eql('1979-07-28T06:04:49.000Z');
should(res.body).matchEach(log => { should(res.body).matchEach(log => {
should(log).have.only.keys('date', 'action', 'collection', 'conditions', 'data'); should(log).have.only.keys('_id', 'date', 'action', 'collection', 'conditions', 'data');
should(log).have.property('_id').be.type('string');
should(log).have.property('action', 'PUT /sample/400000000000000000000001'); should(log).have.property('action', 'PUT /sample/400000000000000000000001');
should(log).have.property('collection', 'samples'); should(log).have.property('collection', 'samples');
should(log).have.property('conditions', {_id: '400000000000000000000001'}); should(log).have.property('conditions', {_id: '400000000000000000000001'});
@ -66,7 +69,7 @@ describe('/', () => {
it('returns an empty array for a page with no results', done => { it('returns an empty array for a page with no results', done => {
TestHelper.request(server, done, { TestHelper.request(server, done, {
method: 'get', method: 'get',
url: '/changelog/1979-07-28T06:04:51.000Z/10/2', url: '/changelog/120000030000000000000000/10/2',
auth: {basic: 'admin'}, auth: {basic: 'admin'},
httpStatus: 200 httpStatus: 200
}).end((err, res) => { }).end((err, res) => {
@ -75,28 +78,19 @@ describe('/', () => {
done(); done();
}); });
}); });
it('rejects timestamps pre unix epoch', done => { it('rejects invalid ids', done => {
TestHelper.request(server, done, { TestHelper.request(server, done, {
method: 'get', method: 'get',
url: '/changelog/1879-07-28T06:04:51.000Z/10/2', url: '/changelog/12000003000000h000000000/10/2',
auth: {basic: 'admin'}, auth: {basic: 'admin'},
httpStatus: 400, httpStatus: 400,
res: {status: 'Invalid body format', details: '"timestamp" must be greater than or equal to "1970-01-01T00:00:00.000Z"'} res: {status: 'Invalid body format', details: 'Invalid object id'}
});
});
it('rejects invalid timestamps', done => {
TestHelper.request(server, done, {
method: 'get',
url: '/changelog/1979-14-28T06:04:51.000Z/10/2',
auth: {basic: 'admin'},
httpStatus: 400,
res: {status: 'Invalid body format', details: '"timestamp" must be in ISO 8601 date format'}
}); });
}); });
it('rejects negative page numbers', done => { it('rejects negative page numbers', done => {
TestHelper.request(server, done, { TestHelper.request(server, done, {
method: 'get', method: 'get',
url: '/changelog/1979-07-28T06:04:51.000Z/-10/2', url: '/changelog/120000030000000000000000/-10/2',
auth: {basic: 'admin'}, auth: {basic: 'admin'},
httpStatus: 400, httpStatus: 400,
res: {status: 'Invalid body format', details: '"page" must be greater than or equal to 0'} res: {status: 'Invalid body format', details: '"page" must be greater than or equal to 0'}
@ -105,7 +99,7 @@ describe('/', () => {
it('rejects negative pagesizes', done => { it('rejects negative pagesizes', done => {
TestHelper.request(server, done, { TestHelper.request(server, done, {
method: 'get', method: 'get',
url: '/changelog/1979-07-28T06:04:51.000Z/10/-2', url: '/changelog/120000030000000000000000/10/-2',
auth: {basic: 'admin'}, auth: {basic: 'admin'},
httpStatus: 400, httpStatus: 400,
res: {status: 'Invalid body format', details: '"pagesize" must be greater than or equal to 0'} res: {status: 'Invalid body format', details: '"pagesize" must be greater than or equal to 0'}
@ -114,7 +108,7 @@ describe('/', () => {
it('rejects request from a write user', done => { it('rejects request from a write user', done => {
TestHelper.request(server, done, { TestHelper.request(server, done, {
method: 'get', method: 'get',
url: '/changelog/1979-07-28T06:04:51.000Z/10/2', url: '/changelog/120000030000000000000000/10/2',
auth: {basic: 'janedoe'}, auth: {basic: 'janedoe'},
httpStatus: 403 httpStatus: 403
}); });
@ -122,7 +116,7 @@ describe('/', () => {
it('rejects requests from an API key', done => { it('rejects requests from an API key', done => {
TestHelper.request(server, done, { TestHelper.request(server, done, {
method: 'get', method: 'get',
url: '/changelog/1979-07-28T06:04:51.000Z/10/2', url: '/changelog/120000030000000000000000/10/2',
auth: {key: 'admin'}, auth: {key: 'admin'},
httpStatus: 401 httpStatus: 401
}); });
@ -130,7 +124,7 @@ describe('/', () => {
it('rejects unauthorized requests', done => { it('rejects unauthorized requests', done => {
TestHelper.request(server, done, { TestHelper.request(server, done, {
method: 'get', method: 'get',
url: '/changelog/1979-07-28T06:04:51.000Z/10/2', url: '/changelog/120000030000000000000000/10/2',
httpStatus: 401 httpStatus: 401
}); });
}); });

View File

@ -22,19 +22,18 @@ router.get('/authorized', (req, res) => {
}); });
}); });
router.get('/changelog/:timestamp/:page?/:pagesize?', (req, res, next) => { router.get('/changelog/:id/:page?/:pagesize?', (req, res, next) => {
if (!req.auth(res, ['dev', 'admin'], 'basic')) return; if (!req.auth(res, ['dev', 'admin'], 'basic')) return;
const {error, value: options} = RootValidate.changelogParams({ const {error, value: options} = RootValidate.changelogParams({
timestamp: req.params.timestamp, id: req.params.id,
page: req.params.page, page: req.params.page,
pagesize: req.params.pagesize pagesize: req.params.pagesize
}); });
if (error) return res400(error, res); if (error) return res400(error, res);
const id = new mongoose.Types ChangelogModel.find({_id: {$lte: mongoose.Types.ObjectId(options.id)}})
.ObjectId(Math.floor(new Date(options.timestamp).getTime() / 1000).toString(16) + '0000000000000000'); .sort({_id: -1}).skip(options.page * options.pagesize).limit(options.pagesize)
ChangelogModel.find({_id: {$lte: id}}).sort({_id: -1}).skip(options.page * options.pagesize).limit(options.pagesize)
.lean().exec((err, data) => { .lean().exec((err, data) => {
if (err) return next(err); if (err) return next(err);

View File

@ -28,7 +28,7 @@ export default class RootValidate { // validate input for root methods
static changelogParams (data) { static changelogParams (data) {
return Joi.object({ return Joi.object({
timestamp: this.changelog.timestamp.required(), id: IdValidate.get(),
page: this.changelog.page, page: this.changelog.page,
pagesize: this.changelog.pagesize pagesize: this.changelog.pagesize
}).validate(data); }).validate(data);
@ -39,6 +39,7 @@ export default class RootValidate { // validate input for root methods
data.collection = data.collection_name; data.collection = data.collection_name;
data = IdValidate.stringify(data); data = IdValidate.stringify(data);
const {value, error} = Joi.object({ const {value, error} = Joi.object({
_id: IdValidate.get(),
date: this.changelog.timestamp, date: this.changelog.timestamp,
action: this.changelog.action, action: this.changelog.action,
collection: this.changelog.collection, collection: this.changelog.collection,