Merge pull request #42 in ~VLE2FE/definma-api from develop to master
* commit '156b65d03308d077ecf9ff507fc817bbe52de9ea': adapted changelog
This commit is contained in:
commit
f004a4a84c
26
README.md
26
README.md
@ -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.
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -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);
|
||||||
|
|
||||||
|
@ -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,
|
||||||
|
Reference in New Issue
Block a user