diff --git a/catalogue/package.json b/catalogue/package.json index 04e2a35..2d48701 100644 --- a/catalogue/package.json +++ b/catalogue/package.json @@ -11,10 +11,11 @@ "dependencies": { "body-parser": "^1.18.1", "express": "^4.15.4", - "mongodb": "^2.2.33", + "mongodb": "2.2.33", "pino": "^5.10.8", "express-pino-logger": "^4.0.0", "pino-pretty": "^2.5.0", - "@instana/collector": "^1.28.0" + "@instana/collector": "^1.28.0", + "mathjs": "^6.6.4" } } diff --git a/catalogue/server.js b/catalogue/server.js index 9d4b773..79a8c6b 100644 --- a/catalogue/server.js +++ b/catalogue/server.js @@ -26,7 +26,80 @@ const expLogger = expPino({ // MongoDB 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(); @@ -51,163 +124,64 @@ app.get('/health', (req, res) => { // all products app.get('/products', (req, res) => { - if(mongoConnected) { - productsCollection.find({}).toArray().then((products) => { - res.json(products); - }).catch((e) => { - req.log.error('ERROR', e); - res.status(500).send(e); - }); - } else { - req.log.error('database not available'); - res.status(500).send('database not avaiable'); - } + productsCollection.find({}).toArray().then((products) => { + res.json(products); + }).catch((e) => { + req.log.error('ERROR', e); + res.status(500).send(e); + }); }); // product by SKU app.get('/product/:sku', (req, res) => { - if(mongoConnected) { - productsCollection.findOne({sku: req.params.sku}).then((product) => { - req.log.info('product', product); - if(product) { - res.json(product); - } else { - res.status(404).send('SKU not found'); - } - }).catch((e) => { - req.log.error('ERROR', e); - res.status(500).send(e); - }); - } else { - req.log.error('database not available'); - res.status(500).send('database not available'); - } + productsCollection.findOne({sku: req.params.sku}).then((product) => { + req.log.info('product', product); + if(product) { + res.json(product); + } else { + res.status(404).send('SKU not found'); + } + }).catch((e) => { + req.log.error('ERROR', e); + res.status(500).send(e); + }); }); // products in a category app.get('/products/:cat', (req, res) => { - if(mongoConnected) { - productsCollection.find({ categories: req.params.cat }).sort({ name: 1 }).toArray().then((products) => { - if(products) { - res.json(products); - } else { - res.status(404).send('No products for ' + req.params.cat); - } - }).catch((e) => { - req.log.error('ERROR', e); - res.status(500).send(e); - }); - } else { - req.log.error('database not available'); - res.status(500).send('database not avaiable'); - } + productsCollection.find({ categories: req.params.cat }).sort({ name: 1 }).toArray().then((products) => { + if(products) { + res.json(products); + } else { + res.status(404).send('No products for ' + req.params.cat); + } + }).catch((e) => { + req.log.error('ERROR', e); + res.status(500).send(e); + }); }); // all categories app.get('/categories', (req, res) => { - if(mongoConnected) { - productsCollection.distinct('categories').then((categories) => { - res.json(categories); - }).catch((e) => { - req.log.error('ERROR', e); - res.status(500).send(e); - }); - } else { - req.log.error('database not available'); - res.status(500).send('database not available'); - } + productsCollection.distinct('categories').then((categories) => { + res.json(categories); + }).catch((e) => { + req.log.error('ERROR', e); + res.status(500).send(e); + }); }); // search name and description app.get('/search/:text', (req, res) => { - if(mongoConnected) { - productsCollection.find({ '$text': { '$search': req.params.text }}).toArray().then((hits) => { - res.json(hits); - }).catch((e) => { - req.log.error('ERROR', e); - res.status(500).send(e); - }); - } else { - req.log.error('database not available'); - res.status(500).send('database not available'); - } + productsCollection.find({ '$text': { '$search': req.params.text }}).toArray().then((hits) => { + res.json(hits); + }).catch((e) => { + req.log.error('ERROR', e); + res.status(500).send(e); + }); }); -// set up Mongo -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'; +// Fire up app.listen(port, () => { logger.info('Started on port', port); }); - diff --git a/user/package-lock.json b/user/package-lock.json new file mode 100644 index 0000000..4f79255 --- /dev/null +++ b/user/package-lock.json @@ -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==" + } + } +} diff --git a/user/package.json b/user/package.json index 577c8f2..0ba98d5 100644 --- a/user/package.json +++ b/user/package.json @@ -9,13 +9,14 @@ "author": "SteveW", "license": "Apache-2.0", "dependencies": { - "body-parser": "^1.18.1", - "express": "^4.15.4", - "mongodb": "^2.2.33", - "redis": "^2.8.0", - "pino": "^5.10.8", - "express-pino-logger": "^4.0.0", - "pino-pretty": "^2.5.0", - "instana-nodejs-sensor": "^1.28.0" + "body-parser": "^1.18.1", + "express": "^4.15.4", + "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", + "redis": "^2.8.0" } } diff --git a/user/server.js b/user/server.js index 1fbd340..1ce3456 100644 --- a/user/server.js +++ b/user/server.js @@ -15,6 +15,10 @@ const bodyParser = require('body-parser'); const express = require('express'); const pino = require('pino'); const expPino = require('express-pino-logger'); +const { randomInt } = require('mathjs'); + +const errorRate = parseInt(process.env.ERROR_RATE) || 0 + // MongoDB var db; @@ -55,6 +59,11 @@ app.get('/health', (req, res) => { // use REDIS INCR to track anonymous users 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'); // get number from Redis redisClient.incr('anonymous-counter', (err, r) => { @@ -71,6 +80,11 @@ app.get('/uniqueid', (req, res) => { // check user exists app.get('/check/:id', (req, res) => { + if (errorRate > randomInt(1, 100)) { + res.status(500).send('Cannot process the check user request'); + return; + } + if(mongoConnected) { usersCollection.findOne({name: req.params.id}).then((user) => { if(user) { @@ -104,6 +118,11 @@ app.get('/users', (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); if(req.body.name === undefined || req.body.password === undefined) { req.log.warn('credentails not complete'); @@ -134,6 +153,11 @@ app.post('/login', (req, res) => { // TODO - validate email address format 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); if(req.body.name === undefined || req.body.password === undefined || req.body.email === undefined) { req.log.warn('insufficient data'); @@ -169,6 +193,11 @@ app.post('/register', (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); // only for registered users if(mongoConnected) { @@ -223,6 +252,11 @@ app.post('/order/: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) { ordersCollection.findOne({ name: req.params.id