Add programmable error rate to catalogue and user
This commit is contained in:
@@ -11,10 +11,11 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"body-parser": "^1.18.1",
|
"body-parser": "^1.18.1",
|
||||||
"express": "^4.15.4",
|
"express": "^4.15.4",
|
||||||
"mongodb": "^2.2.33",
|
"mongodb": "2.2.33",
|
||||||
"pino": "^5.10.8",
|
"pino": "^5.10.8",
|
||||||
"express-pino-logger": "^4.0.0",
|
"express-pino-logger": "^4.0.0",
|
||||||
"pino-pretty": "^2.5.0",
|
"pino-pretty": "^2.5.0",
|
||||||
"@instana/collector": "^1.28.0"
|
"@instana/collector": "^1.28.0",
|
||||||
|
"mathjs": "^6.6.4"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -26,7 +26,80 @@ const expLogger = expPino({
|
|||||||
|
|
||||||
// MongoDB
|
// MongoDB
|
||||||
var productsCollection;
|
var productsCollection;
|
||||||
var mongoConnected = false;
|
|
||||||
|
const port = process.env.CATALOGUE_SERVER_PORT || '8080';
|
||||||
|
|
||||||
|
// set up Mongo
|
||||||
|
function mongoConnectionPromise() {
|
||||||
|
return new Promise(function() {
|
||||||
|
var mongoURL;
|
||||||
|
|
||||||
|
if (process.env.VCAP_SERVICES) {
|
||||||
|
const bindingName = 'catalogue_database';
|
||||||
|
|
||||||
|
connectionDetails = null;
|
||||||
|
|
||||||
|
console.log('Env var \'VCAP_SERVICES\' found, scanning for \'catalogue_database\' service binding');
|
||||||
|
|
||||||
|
for (let [key, value] of Object.entries(JSON.parse(process.env.VCAP_SERVICES))) {
|
||||||
|
try {
|
||||||
|
connectionDetails = value.find(function(binding) {
|
||||||
|
return bindingName == binding.binding_name && binding.credentials;
|
||||||
|
}).credentials;
|
||||||
|
|
||||||
|
if (!connectionDetails) {
|
||||||
|
throw new Error(`Cannot find a service binding with name '${bindingName}'`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const username = connectionDetails.username;
|
||||||
|
const password = connectionDetails.password;
|
||||||
|
|
||||||
|
const normalizedAuthDetails = `${encodeURIComponent(username)}:${encodeURIComponent(password)}`;
|
||||||
|
|
||||||
|
mongoURL = connectionDetails.uri.replace(`${username}:${password}`, normalizedAuthDetails);
|
||||||
|
} catch (err) {
|
||||||
|
console.log('Cannot process key \'' + key + '\' of \'VCAP_SERVICES\'', err);
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (process.env.MONGO_URL) {
|
||||||
|
mongoURL = process.env.MONGO_URL;
|
||||||
|
|
||||||
|
console.log('MongoDB URI found in \'MONGO_URL\': ' + mongoURL);
|
||||||
|
} else {
|
||||||
|
mongoURL = 'mongodb://mongodb:27017/catalogue';
|
||||||
|
|
||||||
|
console.log('Using default MongoDB URI');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!mongoURL) {
|
||||||
|
throw new Error('MongoDB connection data missing');
|
||||||
|
}
|
||||||
|
|
||||||
|
return mongoClient.connect(mongoURL);
|
||||||
|
})
|
||||||
|
.then(function(db) {
|
||||||
|
productsCollection = db.collection('products');
|
||||||
|
|
||||||
|
db.on('close', function() {
|
||||||
|
logger.error('Disconnected from the MongoDB database, reconnecting...', e);
|
||||||
|
|
||||||
|
mongoConnect();
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.then(function() {
|
||||||
|
logger.info('MongoDB connected');
|
||||||
|
})
|
||||||
|
.catch(function(err) {
|
||||||
|
console.log(`Cannot connect to MongoDB: ${err}`);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
async function mongoConnect() {
|
||||||
|
await mongoConnectionPromise();
|
||||||
|
}
|
||||||
|
|
||||||
|
mongoConnect();
|
||||||
|
|
||||||
const app = express();
|
const app = express();
|
||||||
|
|
||||||
@@ -51,22 +124,16 @@ app.get('/health', (req, res) => {
|
|||||||
|
|
||||||
// all products
|
// all products
|
||||||
app.get('/products', (req, res) => {
|
app.get('/products', (req, res) => {
|
||||||
if(mongoConnected) {
|
|
||||||
productsCollection.find({}).toArray().then((products) => {
|
productsCollection.find({}).toArray().then((products) => {
|
||||||
res.json(products);
|
res.json(products);
|
||||||
}).catch((e) => {
|
}).catch((e) => {
|
||||||
req.log.error('ERROR', e);
|
req.log.error('ERROR', e);
|
||||||
res.status(500).send(e);
|
res.status(500).send(e);
|
||||||
});
|
});
|
||||||
} else {
|
|
||||||
req.log.error('database not available');
|
|
||||||
res.status(500).send('database not avaiable');
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// product by SKU
|
// product by SKU
|
||||||
app.get('/product/:sku', (req, res) => {
|
app.get('/product/:sku', (req, res) => {
|
||||||
if(mongoConnected) {
|
|
||||||
productsCollection.findOne({sku: req.params.sku}).then((product) => {
|
productsCollection.findOne({sku: req.params.sku}).then((product) => {
|
||||||
req.log.info('product', product);
|
req.log.info('product', product);
|
||||||
if(product) {
|
if(product) {
|
||||||
@@ -78,15 +145,10 @@ app.get('/product/:sku', (req, res) => {
|
|||||||
req.log.error('ERROR', e);
|
req.log.error('ERROR', e);
|
||||||
res.status(500).send(e);
|
res.status(500).send(e);
|
||||||
});
|
});
|
||||||
} else {
|
|
||||||
req.log.error('database not available');
|
|
||||||
res.status(500).send('database not available');
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// products in a category
|
// products in a category
|
||||||
app.get('/products/:cat', (req, res) => {
|
app.get('/products/:cat', (req, res) => {
|
||||||
if(mongoConnected) {
|
|
||||||
productsCollection.find({ categories: req.params.cat }).sort({ name: 1 }).toArray().then((products) => {
|
productsCollection.find({ categories: req.params.cat }).sort({ name: 1 }).toArray().then((products) => {
|
||||||
if(products) {
|
if(products) {
|
||||||
res.json(products);
|
res.json(products);
|
||||||
@@ -97,117 +159,29 @@ app.get('/products/:cat', (req, res) => {
|
|||||||
req.log.error('ERROR', e);
|
req.log.error('ERROR', e);
|
||||||
res.status(500).send(e);
|
res.status(500).send(e);
|
||||||
});
|
});
|
||||||
} else {
|
|
||||||
req.log.error('database not available');
|
|
||||||
res.status(500).send('database not avaiable');
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// all categories
|
// all categories
|
||||||
app.get('/categories', (req, res) => {
|
app.get('/categories', (req, res) => {
|
||||||
if(mongoConnected) {
|
|
||||||
productsCollection.distinct('categories').then((categories) => {
|
productsCollection.distinct('categories').then((categories) => {
|
||||||
res.json(categories);
|
res.json(categories);
|
||||||
}).catch((e) => {
|
}).catch((e) => {
|
||||||
req.log.error('ERROR', e);
|
req.log.error('ERROR', e);
|
||||||
res.status(500).send(e);
|
res.status(500).send(e);
|
||||||
});
|
});
|
||||||
} else {
|
|
||||||
req.log.error('database not available');
|
|
||||||
res.status(500).send('database not available');
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// search name and description
|
// search name and description
|
||||||
app.get('/search/:text', (req, res) => {
|
app.get('/search/:text', (req, res) => {
|
||||||
if(mongoConnected) {
|
|
||||||
productsCollection.find({ '$text': { '$search': req.params.text }}).toArray().then((hits) => {
|
productsCollection.find({ '$text': { '$search': req.params.text }}).toArray().then((hits) => {
|
||||||
res.json(hits);
|
res.json(hits);
|
||||||
}).catch((e) => {
|
}).catch((e) => {
|
||||||
req.log.error('ERROR', e);
|
req.log.error('ERROR', e);
|
||||||
res.status(500).send(e);
|
res.status(500).send(e);
|
||||||
});
|
});
|
||||||
} else {
|
|
||||||
req.log.error('database not available');
|
|
||||||
res.status(500).send('database not available');
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// set up Mongo
|
// Fire up
|
||||||
function mongoConnect() {
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
var mongoURL;
|
|
||||||
|
|
||||||
if (process.env.VCAP_SERVICES) {
|
|
||||||
connectionDetails = null;
|
|
||||||
|
|
||||||
console.log('Env var \'VCAP_SERVICES\' found, scanning for \'catalogue_database\' service binding');
|
|
||||||
|
|
||||||
for (let [key, value] of Object.entries(JSON.parse(process.env.VCAP_SERVICES))) {
|
|
||||||
try {
|
|
||||||
binding = value.find(function(binding) {
|
|
||||||
return 'catalogue_database' == binding.binding_name && binding.credentials;
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!binding) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
connectionDetails = binding.credentials;
|
|
||||||
|
|
||||||
if (connectionDetails) {
|
|
||||||
mongoURL = connectionDetails.uri;
|
|
||||||
|
|
||||||
console.log('MongoDB URI for \'catalogue_database\' service binding found in \'VCAP_SERVICES\'');
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
console.log('Cannot process key \'' + key + '\' of \'VCAP_SERVICES\'', err);
|
|
||||||
throw err;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if (process.env.MONGO_URL) {
|
|
||||||
mongoURL = process.env.MONGO_URL;
|
|
||||||
|
|
||||||
console.log('MongoDB URI found in \'MONGO_URL\': ' + mongoURL);
|
|
||||||
} else {
|
|
||||||
mongoURL = 'mongodb://mongodb:27017/catalogue';
|
|
||||||
|
|
||||||
console.log('Using default MongoDB URI');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!mongoURL) {
|
|
||||||
throw new Error('MongoDB connection data missing');
|
|
||||||
}
|
|
||||||
|
|
||||||
mongoClient.connect(mongoURL, (error, db) => {
|
|
||||||
if(error) {
|
|
||||||
reject(error);
|
|
||||||
} else {
|
|
||||||
productsCollection = db.collection('products');
|
|
||||||
resolve('connected');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// mongodb connection retry loop
|
|
||||||
function mongoLoop() {
|
|
||||||
mongoConnect().then((r) => {
|
|
||||||
mongoConnected = true;
|
|
||||||
logger.info('MongoDB connected');
|
|
||||||
}).catch((e) => {
|
|
||||||
logger.error('An error occurred', e);
|
|
||||||
setTimeout(mongoLoop, 2000);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
mongoLoop();
|
|
||||||
|
|
||||||
// fire it up!
|
|
||||||
const port = process.env.CATALOGUE_SERVER_PORT || '8080';
|
|
||||||
app.listen(port, () => {
|
app.listen(port, () => {
|
||||||
logger.info('Started on port', port);
|
logger.info('Started on port', port);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
63
user/package-lock.json
generated
Normal file
63
user/package-lock.json
generated
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
{
|
||||||
|
"name": "user",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"lockfileVersion": 1,
|
||||||
|
"requires": true,
|
||||||
|
"dependencies": {
|
||||||
|
"complex.js": {
|
||||||
|
"version": "2.0.11",
|
||||||
|
"resolved": "https://registry.npmjs.org/complex.js/-/complex.js-2.0.11.tgz",
|
||||||
|
"integrity": "sha512-6IArJLApNtdg1P1dFtn3dnyzoZBEF0MwMnrfF1exSBRpZYoy4yieMkpZhQDC0uwctw48vii0CFVyHfpgZ/DfGw=="
|
||||||
|
},
|
||||||
|
"decimal.js": {
|
||||||
|
"version": "10.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.2.0.tgz",
|
||||||
|
"integrity": "sha512-vDPw+rDgn3bZe1+F/pyEwb1oMG2XTlRVgAa6B4KccTEpYgF8w6eQllVbQcfIJnZyvzFtFpxnpGtx8dd7DJp/Rw=="
|
||||||
|
},
|
||||||
|
"escape-latex": {
|
||||||
|
"version": "1.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/escape-latex/-/escape-latex-1.2.0.tgz",
|
||||||
|
"integrity": "sha512-nV5aVWW1K0wEiUIEdZ4erkGGH8mDxGyxSeqPzRNtWP7ataw+/olFObw7hujFWlVjNsaDFw5VZ5NzVSIqRgfTiw=="
|
||||||
|
},
|
||||||
|
"fraction.js": {
|
||||||
|
"version": "4.0.12",
|
||||||
|
"resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.0.12.tgz",
|
||||||
|
"integrity": "sha512-8Z1K0VTG4hzYY7kA/1sj4/r1/RWLBD3xwReT/RCrUCbzPszjNQCCsy3ktkU/eaEqX3MYa4pY37a52eiBlPMlhA=="
|
||||||
|
},
|
||||||
|
"javascript-natural-sort": {
|
||||||
|
"version": "0.7.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/javascript-natural-sort/-/javascript-natural-sort-0.7.1.tgz",
|
||||||
|
"integrity": "sha1-+eIwPUUH9tdDVac2ZNFED7Wg71k="
|
||||||
|
},
|
||||||
|
"mathjs": {
|
||||||
|
"version": "6.6.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/mathjs/-/mathjs-6.6.4.tgz",
|
||||||
|
"integrity": "sha512-fvmP89ujJbDAC8ths7FZh7PWdA71dfA5WJVAzJbQhSDCHK1aBk8WRf1XcTw51ERs+sKx9nYBGsRshqmb/oe8Ag==",
|
||||||
|
"requires": {
|
||||||
|
"complex.js": "^2.0.11",
|
||||||
|
"decimal.js": "^10.2.0",
|
||||||
|
"escape-latex": "^1.2.0",
|
||||||
|
"fraction.js": "^4.0.12",
|
||||||
|
"javascript-natural-sort": "^0.7.1",
|
||||||
|
"seed-random": "^2.2.0",
|
||||||
|
"tiny-emitter": "^2.1.0",
|
||||||
|
"typed-function": "^1.1.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"seed-random": {
|
||||||
|
"version": "2.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/seed-random/-/seed-random-2.2.0.tgz",
|
||||||
|
"integrity": "sha1-KpsZ4lCoFwmSMaW5mk2vgLf77VQ="
|
||||||
|
},
|
||||||
|
"tiny-emitter": {
|
||||||
|
"version": "2.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/tiny-emitter/-/tiny-emitter-2.1.0.tgz",
|
||||||
|
"integrity": "sha512-NB6Dk1A9xgQPMoGqC5CVXn123gWyte215ONT5Pp5a0yt4nlEoO1ZWeCwpncaekPHXO60i47ihFnZPiRPjRMq4Q=="
|
||||||
|
},
|
||||||
|
"typed-function": {
|
||||||
|
"version": "1.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/typed-function/-/typed-function-1.1.1.tgz",
|
||||||
|
"integrity": "sha512-RbN7MaTQBZLJYzDENHPA0nUmWT0Ex80KHItprrgbTPufYhIlTePvCXZxyQK7wgn19FW5bnuaBIKcBb5mRWjB1Q=="
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -11,11 +11,12 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"body-parser": "^1.18.1",
|
"body-parser": "^1.18.1",
|
||||||
"express": "^4.15.4",
|
"express": "^4.15.4",
|
||||||
"mongodb": "^2.2.33",
|
|
||||||
"redis": "^2.8.0",
|
|
||||||
"pino": "^5.10.8",
|
|
||||||
"express-pino-logger": "^4.0.0",
|
"express-pino-logger": "^4.0.0",
|
||||||
|
"instana-nodejs-sensor": "^1.28.0",
|
||||||
|
"mathjs": "^6.6.4",
|
||||||
|
"mongodb": "^2.2.33",
|
||||||
|
"pino": "^5.10.8",
|
||||||
"pino-pretty": "^2.5.0",
|
"pino-pretty": "^2.5.0",
|
||||||
"instana-nodejs-sensor": "^1.28.0"
|
"redis": "^2.8.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -15,6 +15,10 @@ const bodyParser = require('body-parser');
|
|||||||
const express = require('express');
|
const express = require('express');
|
||||||
const pino = require('pino');
|
const pino = require('pino');
|
||||||
const expPino = require('express-pino-logger');
|
const expPino = require('express-pino-logger');
|
||||||
|
const { randomInt } = require('mathjs');
|
||||||
|
|
||||||
|
const errorRate = parseInt(process.env.ERROR_RATE) || 0
|
||||||
|
|
||||||
|
|
||||||
// MongoDB
|
// MongoDB
|
||||||
var db;
|
var db;
|
||||||
@@ -55,6 +59,11 @@ app.get('/health', (req, res) => {
|
|||||||
|
|
||||||
// use REDIS INCR to track anonymous users
|
// use REDIS INCR to track anonymous users
|
||||||
app.get('/uniqueid', (req, res) => {
|
app.get('/uniqueid', (req, res) => {
|
||||||
|
if (errorRate > randomInt(1, 100)) {
|
||||||
|
res.status(500).send('Cannot process the uniqueid request');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
req.log.error('Unique ID test');
|
req.log.error('Unique ID test');
|
||||||
// get number from Redis
|
// get number from Redis
|
||||||
redisClient.incr('anonymous-counter', (err, r) => {
|
redisClient.incr('anonymous-counter', (err, r) => {
|
||||||
@@ -71,6 +80,11 @@ app.get('/uniqueid', (req, res) => {
|
|||||||
|
|
||||||
// check user exists
|
// check user exists
|
||||||
app.get('/check/:id', (req, res) => {
|
app.get('/check/:id', (req, res) => {
|
||||||
|
if (errorRate > randomInt(1, 100)) {
|
||||||
|
res.status(500).send('Cannot process the check user request');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if(mongoConnected) {
|
if(mongoConnected) {
|
||||||
usersCollection.findOne({name: req.params.id}).then((user) => {
|
usersCollection.findOne({name: req.params.id}).then((user) => {
|
||||||
if(user) {
|
if(user) {
|
||||||
@@ -104,6 +118,11 @@ app.get('/users', (req, res) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
app.post('/login', (req, res) => {
|
app.post('/login', (req, res) => {
|
||||||
|
if (errorRate > randomInt(1, 100)) {
|
||||||
|
res.status(500).send('Cannot process the login request');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
req.log.info('login', req.body);
|
req.log.info('login', req.body);
|
||||||
if(req.body.name === undefined || req.body.password === undefined) {
|
if(req.body.name === undefined || req.body.password === undefined) {
|
||||||
req.log.warn('credentails not complete');
|
req.log.warn('credentails not complete');
|
||||||
@@ -134,6 +153,11 @@ app.post('/login', (req, res) => {
|
|||||||
|
|
||||||
// TODO - validate email address format
|
// TODO - validate email address format
|
||||||
app.post('/register', (req, res) => {
|
app.post('/register', (req, res) => {
|
||||||
|
if (errorRate > randomInt(1, 100)) {
|
||||||
|
res.status(500).send('Cannot process the registration request');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
req.log.info('register', req.body);
|
req.log.info('register', req.body);
|
||||||
if(req.body.name === undefined || req.body.password === undefined || req.body.email === undefined) {
|
if(req.body.name === undefined || req.body.password === undefined || req.body.email === undefined) {
|
||||||
req.log.warn('insufficient data');
|
req.log.warn('insufficient data');
|
||||||
@@ -169,6 +193,11 @@ app.post('/register', (req, res) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
app.post('/order/:id', (req, res) => {
|
app.post('/order/:id', (req, res) => {
|
||||||
|
if (errorRate > randomInt(1, 100)) {
|
||||||
|
res.status(500).send('Cannot process the order request');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
req.log.info('order', req.body);
|
req.log.info('order', req.body);
|
||||||
// only for registered users
|
// only for registered users
|
||||||
if(mongoConnected) {
|
if(mongoConnected) {
|
||||||
@@ -223,6 +252,11 @@ app.post('/order/:id', (req, res) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
app.get('/history/:id', (req, res) => {
|
app.get('/history/:id', (req, res) => {
|
||||||
|
if (errorRate > randomInt(1, 100)) {
|
||||||
|
res.status(500).send('Cannot process the history request');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if(mongoConnected) {
|
if(mongoConnected) {
|
||||||
ordersCollection.findOne({
|
ordersCollection.findOne({
|
||||||
name: req.params.id
|
name: req.params.id
|
||||||
|
Reference in New Issue
Block a user