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

View File

@ -25,16 +25,18 @@ describe('/', () => {
it('returns the first page', done => {
TestHelper.request(server, done, {
method: 'get',
url: '/changelog/1979-07-28T06:04:51.000Z/0/2',
url: '/changelog/120000030000000000000000/0/2',
auth: {basic: 'admin'},
httpStatus: 200
}).end((err, res) => {
if (err) return done(err);
console.log(res.body);
should(res.body).have.lengthOf(2);
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).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('collection', 'samples');
should(log).have.property('conditions', {_id: '400000000000000000000001'});
@ -46,7 +48,7 @@ describe('/', () => {
it('returns another page', done => {
TestHelper.request(server, done, {
method: 'get',
url: '/changelog/1979-07-28T06:04:51.000Z/1/2',
url: '/changelog/120000030000000000000000/1/2',
auth: {basic: 'admin'},
httpStatus: 200
}).end((err, res) => {
@ -54,7 +56,8 @@ describe('/', () => {
should(res.body).have.lengthOf(1);
should(res.body[0].date).be.eql('1979-07-28T06:04:49.000Z');
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('collection', 'samples');
should(log).have.property('conditions', {_id: '400000000000000000000001'});
@ -66,7 +69,7 @@ describe('/', () => {
it('returns an empty array for a page with no results', done => {
TestHelper.request(server, done, {
method: 'get',
url: '/changelog/1979-07-28T06:04:51.000Z/10/2',
url: '/changelog/120000030000000000000000/10/2',
auth: {basic: 'admin'},
httpStatus: 200
}).end((err, res) => {
@ -75,28 +78,19 @@ describe('/', () => {
done();
});
});
it('rejects timestamps pre unix epoch', done => {
it('rejects invalid ids', done => {
TestHelper.request(server, done, {
method: 'get',
url: '/changelog/1879-07-28T06:04:51.000Z/10/2',
url: '/changelog/12000003000000h000000000/10/2',
auth: {basic: 'admin'},
httpStatus: 400,
res: {status: 'Invalid body format', details: '"timestamp" must be greater than or equal to "1970-01-01T00:00:00.000Z"'}
});
});
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'}
res: {status: 'Invalid body format', details: 'Invalid object id'}
});
});
it('rejects negative page numbers', done => {
TestHelper.request(server, done, {
method: 'get',
url: '/changelog/1979-07-28T06:04:51.000Z/-10/2',
url: '/changelog/120000030000000000000000/-10/2',
auth: {basic: 'admin'},
httpStatus: 400,
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 => {
TestHelper.request(server, done, {
method: 'get',
url: '/changelog/1979-07-28T06:04:51.000Z/10/-2',
url: '/changelog/120000030000000000000000/10/-2',
auth: {basic: 'admin'},
httpStatus: 400,
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 => {
TestHelper.request(server, done, {
method: 'get',
url: '/changelog/1979-07-28T06:04:51.000Z/10/2',
url: '/changelog/120000030000000000000000/10/2',
auth: {basic: 'janedoe'},
httpStatus: 403
});
@ -122,7 +116,7 @@ describe('/', () => {
it('rejects requests from an API key', done => {
TestHelper.request(server, done, {
method: 'get',
url: '/changelog/1979-07-28T06:04:51.000Z/10/2',
url: '/changelog/120000030000000000000000/10/2',
auth: {key: 'admin'},
httpStatus: 401
});
@ -130,7 +124,7 @@ describe('/', () => {
it('rejects unauthorized requests', done => {
TestHelper.request(server, done, {
method: 'get',
url: '/changelog/1979-07-28T06:04:51.000Z/10/2',
url: '/changelog/120000030000000000000000/10/2',
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;
const {error, value: options} = RootValidate.changelogParams({
timestamp: req.params.timestamp,
id: req.params.id,
page: req.params.page,
pagesize: req.params.pagesize
});
if (error) return res400(error, res);
const id = new mongoose.Types
.ObjectId(Math.floor(new Date(options.timestamp).getTime() / 1000).toString(16) + '0000000000000000');
ChangelogModel.find({_id: {$lte: id}}).sort({_id: -1}).skip(options.page * options.pagesize).limit(options.pagesize)
ChangelogModel.find({_id: {$lte: mongoose.Types.ObjectId(options.id)}})
.sort({_id: -1}).skip(options.page * options.pagesize).limit(options.pagesize)
.lean().exec((err, data) => {
if (err) return next(err);

View File

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