Skip to content

Commit

Permalink
Merge d57ac5e into afc0f38
Browse files Browse the repository at this point in the history
  • Loading branch information
deewhyweb committed Apr 11, 2019
2 parents afc0f38 + d57ac5e commit 3aa71c3
Show file tree
Hide file tree
Showing 5 changed files with 267 additions and 10 deletions.
69 changes: 60 additions & 9 deletions app.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,14 @@
*
* Copyright 2016-2017 Red Hat, Inc, and individual contributors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* Licensed under the Apache License, Version 2.0 (the 'License');
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* distributed under the License is distributed on an 'AS IS' BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
Expand All @@ -21,17 +21,57 @@
const path = require('path');
const express = require('express');
const bodyParser = require('body-parser');
const swaggerUi = require('swagger-ui-express');
const swaggerJSDoc = require('swagger-jsdoc');

const app = express();

const probe = require('kube-probe');
const db = require('./lib/db');

const livenessCallback = (req, res) => {
db.query('select now()', err => {
if (err) {
console.log('liveness not ok');
res.writeHead(500);
res.end('not ok');
} else {
res.writeHead(200);
res.end('OK');
}
});
};

const probeOptions = {
livenessCallback
};
const swaggerDefinition = {
info: {
// API informations
title: 'Fruits',
version: '2.1.1',
description: 'A sample RESTful API'
},
basePath: '/'
};

// Options for the swagger docs
const options = {
// Import swaggerDefinitions
swaggerDefinition,
// Path to the API docs
apis: ['./lib/routes/fruits.js']
};
const swaggerSpec = swaggerJSDoc(options);
app.use('/api-docs', swaggerUi.serve, swaggerUi.setup(swaggerSpec));

const fruits = require('./lib/routes/fruits');

app.use(bodyParser.json());
app.use((error, req, res, next) => {
if (req.body === '' || (error instanceof SyntaxError && error.type === 'entity.parse.failed')) {
if (
req.body === '' ||
(error instanceof SyntaxError && error.type === 'entity.parse.failed')
) {
res.status(415);
return res.send('Invalid payload!');
}
Expand All @@ -46,12 +86,23 @@ app.use('/licenses', express.static(path.join(__dirname, 'licenses')));
app.use('/api', fruits);

// Add a health check
probe(app);

db.init().then(() => {
console.log('Database init\'d');
}).catch(error => {
console.log(error);
db.init()
.then(() => {
console.log('Database init\'d, starting probe');
probe(app, probeOptions);
})
.catch(error => {
console.log(error);
});

process.on('SIGTERM', () => {
console.info(
'Got SIGTERM. Graceful shutdown start now',
new Date().toISOString()
);
db.end();
console.info('DB Shutdown');
});

module.exports = app;
3 changes: 3 additions & 0 deletions lib/db/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,5 +29,8 @@ module.exports = {
},
init: () => {
return pool.query(initScript);
},
end: () => {
return pool.end();
}
};
109 changes: 109 additions & 0 deletions lib/routes/fruits.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,26 @@ const router = express.Router();
const validations = require('../validations');
const fruits = require('../api/fruits');

/**
* @swagger
* /api/fruits/{id}:
* get:
* summary: Get a fruit by id
* parameters:
* - in: path
* name: id
* schema:
* type: integer
* required: true
* description: Numeric id of the fruit to get
* tags:
* - Fruits
* produces:
* - application/json
* responses:
* 200:
* description: fruit
*/
router.get('/fruits/:id', (request, response) => {
const {id} = request.params;
fruits.find(id).then(result => {
Expand All @@ -21,6 +41,19 @@ router.get('/fruits/:id', (request, response) => {
});
});

/**
* @swagger
* /api/fruits/:
* get:
* summary: Get all fruits
* tags:
* - Fruits
* produces:
* - application/json
* responses:
* 200:
* description: array of fruits
*/
router.get('/fruits', (request, response) => {
fruits.findAll().then(results => {
response.send(results.rows);
Expand All @@ -29,6 +62,31 @@ router.get('/fruits', (request, response) => {
});
});

/**
* @swagger
* /api/fruits:
* post:
* summary: Add a new fruit
* tags:
* - Fruits
* consumes:
* - application/json
* parameters:
* - in: body
* name: fruit
* description: The fruit to add
* required: true
* schema:
* type: object
* properties:
* name:
* type: string
* stock:
* type: integer
* responses:
* '201':
* description: Created
*/
router.post('/fruits', validations.validateCreateUpdateRequest, (request, response) => {
const {name, stock} = request.body;
return fruits.create(name, stock).then(result => {
Expand All @@ -40,6 +98,37 @@ router.post('/fruits', validations.validateCreateUpdateRequest, (request, respon
});
});

/**
* @swagger
* /api/fruits/{id}:
* put:
* summary: Update a fruit
* tags:
* - Fruits
* consumes:
* - application/json
* parameters:
* - in: path
* name: id
* schema:
* type: integer
* required: true
* description: Numeric id of the fruit to update
* - in: body
* name: fruit
* description: The fruit to update
* required: true
* schema:
* type: object
* properties:
* name:
* type: string
* stock:
* type: integer
* responses:
* '204':
* description: Updated
*/
router.put('/fruits/:id', validations.validateCreateUpdateRequest, (request, response) => {
const {name, stock} = request.body;
const {id} = request.params;
Expand All @@ -56,6 +145,26 @@ router.put('/fruits/:id', validations.validateCreateUpdateRequest, (request, res
});
});

/**
* @swagger
* /api/fruits/{id}:
* delete:
* summary: Delete a fruit by id
* parameters:
* - in: path
* name: id
* schema:
* type: integer
* required: true
* description: Numeric id of the fruit to delete
* tags:
* - Fruits
* produces:
* - application/json
* responses:
* 204:
* description: Deleted
*/
router.delete('/fruits/:id', (request, response) => {
const {id} = request.params;
fruits.remove(id).then(result => {
Expand Down
4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,8 @@
"express": "4.16.4",
"kube-probe": "^0.3.1",
"license-reporter": "^1.2.0",
"pg": "^7.8.2"
"pg": "^7.8.2",
"swagger-jsdoc": "^3.2.8",
"swagger-ui-express": "^4.0.2"
}
}
92 changes: 92 additions & 0 deletions test/app-test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
'use strict';

const test = require('tape');
const supertest = require('supertest');
const proxyquire = require('proxyquire');

test('test liveness ok', t => {
const mockDb = {
init: () => {
return Promise.resolve();
},
query: (query, cb) => {
cb(null, true);
}
};

const app = proxyquire('../app', {
'./lib/db': mockDb
});

supertest(app)
.get('/api/health/liveness')
.expect(200)
.then(response => {
t.equal(response.text, 'OK', 'should return OK');
t.end();
});
});

test('test liveness not ok', t => {
const mockDb = {
init: () => {
return Promise.resolve();
},
query: (query, cb) => {
cb('error');
}
};

const app = proxyquire('../app', {
'./lib/db': mockDb
});

supertest(app)
.get('/api/health/liveness')
.expect(500)
.then(response => {
t.equal(response.text, 'not ok', 'should return not ok');
t.end();
});
});

test('SIGTERM', t => {
const mockDb = {
init: () => {
return Promise.resolve();
},
end: () => {
t.end();
}
};

process.on = (signal, cb) => {
if (signal === 'SIGTERM') {
cb();
}
};

proxyquire('../app', {
'./lib/db': mockDb
});
});

test('test swagger ui', t => {
const mockDb = {
init: () => {
return Promise.resolve();
}
};

const app = proxyquire('../app', {
'./lib/db': mockDb
});

supertest(app)
.get('/api-docs/')
.expect('Content-Type', /text\/html/)
.expect(200)
.then(() => {
t.end();
});
});

0 comments on commit 3aa71c3

Please sign in to comment.