1
0
mirror of https://github.com/Unleash/unleash.git synced 2025-01-01 00:08:27 +01:00

Statefull modules should be injected from top

This commit is contained in:
Ivar 2016-05-01 22:53:09 +02:00
parent b85d9eb6d6
commit 2d8fa7ee6b
17 changed files with 305 additions and 271 deletions

3
app.js
View File

@ -31,7 +31,8 @@ module.exports = function(config) {
app.use(cookieParser()); app.use(cookieParser());
routes.create(router); // Setup API routes
routes.create(router, config);
app.use(baseUriPath, router); app.use(baseUriPath, router);

29
lib/databaseConfig.js Normal file
View File

@ -0,0 +1,29 @@
var nconf = require('nconf');
var fs = require('fs');
var ini = require('ini');
var logger = require('./logger');
function getDatabaseIniUrl() {
// Finn specific way of delivering env variables
var databaseini = nconf.argv().get('databaseini');
var config = ini.parse(fs.readFileSync(databaseini, 'utf-8'));
logger.info('unleash started with databaseini: ' + databaseini);
return config.DATABASE_URL;
}
function getDatabaseUrl() {
if (process.env.DATABASE_URL) {
logger.info('unleash started with DATABASE_URL');
return process.env.DATABASE_URL;
} else if (nconf.argv().get('databaseini') !== undefined) {
return getDatabaseIniUrl();
}
throw new Error('please set DATABASE_URL or pass --databaseini');
}
module.exports = {
getDatabaseUrl: getDatabaseUrl
};

View File

@ -1,52 +1,9 @@
var logger = require('../logger');
var nconf = require('nconf');
var fs = require('fs');
var ini = require('ini');
var knex = require('knex'); var knex = require('knex');
function isTestEnv() { module.exports = function(databaseConnection) {
return process.env.NODE_ENV === 'test';
}
function getDatabaseIniUrl() {
// Finn specific way of delivering env variables
var databaseini = nconf.argv().get('databaseini');
var config = ini.parse(fs.readFileSync(databaseini, 'utf-8'));
logger.info('unleash started with databaseini: ' + databaseini);
return config.DATABASE_URL;
}
function getTestDatabaseUrl() {
if (process.env.TEST_DATABASE_URL) {
logger.info('unleash started with TEST_DATABASE_URL');
return process.env.TEST_DATABASE_URL;
} else {
throw new Error('please set TEST_DATABASE_URL');
}
}
function getDatabaseUrl() {
if (process.env.DATABASE_URL) {
logger.info('unleash started with DATABASE_URL');
return process.env.DATABASE_URL;
} else if (nconf.argv().get('databaseini') !== undefined) {
return getDatabaseIniUrl();
}
throw new Error('please set DATABASE_URL or pass --databaseini');
}
function createDbPool() {
return knex({ return knex({
client: 'pg', client: 'pg',
connection: isTestEnv() ? getTestDatabaseUrl() : getDatabaseUrl(), connection: databaseConnection,
pool: { pool: { min: 2, max: 20 }
min: 2,
max: 20
}
}); });
} };
module.exports = createDbPool();

View File

@ -1,8 +1,8 @@
var knex = require('./dbPool');
var EVENT_COLUMNS = ['id', 'type', 'created_by', 'created_at', 'data']; var EVENT_COLUMNS = ['id', 'type', 'created_by', 'created_at', 'data'];
module.exports = function(db) {
function storeEvent(event) { function storeEvent(event) {
return knex('events').insert({ return db('events').insert({
type: event.type, type: event.type,
created_by: event.createdBy, // eslint-disable-line created_by: event.createdBy, // eslint-disable-line
data: event.data data: event.data
@ -10,7 +10,7 @@ function storeEvent(event) {
} }
function getEvents() { function getEvents() {
return knex return db
.select(EVENT_COLUMNS) .select(EVENT_COLUMNS)
.from('events') .from('events')
.orderBy('created_at', 'desc') .orderBy('created_at', 'desc')
@ -18,7 +18,7 @@ function getEvents() {
} }
function getEventsFilterByName(name) { function getEventsFilterByName(name) {
return knex return db
.select(EVENT_COLUMNS) .select(EVENT_COLUMNS)
.from('events') .from('events')
.whereRaw("data ->> 'name' = ?", [name]) .whereRaw("data ->> 'name' = ?", [name])
@ -36,8 +36,10 @@ function rowToEvent(row) {
}; };
} }
module.exports = { return {
store: storeEvent, store: storeEvent,
getEvents: getEvents, getEvents: getEvents,
getEventsFilterByName: getEventsFilterByName getEventsFilterByName: getEventsFilterByName
}; };
};

View File

@ -1,10 +1,9 @@
var eventStore = require('../eventStore');
var eventType = require('../eventType'); var eventType = require('../eventType');
var logger = require('../logger'); var logger = require('../logger');
var knex = require('./dbPool');
var NotFoundError = require('../error/NotFoundError'); var NotFoundError = require('../error/NotFoundError');
var FEATURE_COLUMNS = ['name', 'description', 'enabled', 'strategy_name', 'parameters']; var FEATURE_COLUMNS = ['name', 'description', 'enabled', 'strategy_name', 'parameters'];
module.exports = function(db, eventStore) {
eventStore.on(eventType.featureCreated, function (event) { eventStore.on(eventType.featureCreated, function (event) {
return createFeature(event.data); return createFeature(event.data);
}); });
@ -22,7 +21,7 @@ eventStore.on(eventType.featureRevived, function (event) {
}); });
function getFeatures() { function getFeatures() {
return knex return db
.select(FEATURE_COLUMNS) .select(FEATURE_COLUMNS)
.from('features') .from('features')
.where({ archived: 0 }) .where({ archived: 0 })
@ -31,7 +30,7 @@ function getFeatures() {
} }
function getFeature(name) { function getFeature(name) {
return knex return db
.first(FEATURE_COLUMNS) .first(FEATURE_COLUMNS)
.from('features') .from('features')
.where({ name: name }) .where({ name: name })
@ -39,7 +38,7 @@ function getFeature(name) {
} }
function getArchivedFeatures() { function getArchivedFeatures() {
return knex return db
.select(FEATURE_COLUMNS) .select(FEATURE_COLUMNS)
.from('features') .from('features')
.where({ archived: 1 }) .where({ archived: 1 })
@ -65,16 +64,16 @@ function rowToFeature(row) {
function eventDataToRow(data) { function eventDataToRow(data) {
return { return {
name: data.name, name: data.name,
description: data.description || '', description: data.description,
enabled: data.enabled ? 1 : 0, enabled: data.enabled ? 1 : 0,
archived: data.archived ? 1 :0, archived: data.archived ? 1 :0,
strategy_name: data.strategy || 'default', // eslint-disable-line strategy_name: data.strategy, // eslint-disable-line
parameters: data.parameters || {} parameters: data.parameters
}; };
} }
function createFeature(data) { function createFeature(data) {
return knex('features') return db('features')
.insert(eventDataToRow(data)) .insert(eventDataToRow(data))
.catch(function (err) { .catch(function (err) {
logger.error('Could not insert feature, error was: ', err); logger.error('Could not insert feature, error was: ', err);
@ -82,7 +81,7 @@ function createFeature(data) {
} }
function updateFeature(data) { function updateFeature(data) {
return knex('features') return db('features')
.where({ name: data.name }) .where({ name: data.name })
.update(eventDataToRow(data)) .update(eventDataToRow(data))
.catch(function (err) { .catch(function (err) {
@ -91,7 +90,7 @@ function updateFeature(data) {
} }
function archiveFeature(data) { function archiveFeature(data) {
return knex('features') return db('features')
.where({ name: data.name }) .where({ name: data.name })
.update({ archived: 1 }) .update({ archived: 1 })
.catch(function (err) { .catch(function (err) {
@ -100,7 +99,7 @@ function archiveFeature(data) {
} }
function reviveFeature(data) { function reviveFeature(data) {
return knex('features') return db('features')
.where({ name: data.name }) .where({ name: data.name })
.update({ archived: 0, enabled: 0 }) .update({ archived: 0, enabled: 0 })
.catch(function (err) { .catch(function (err) {
@ -109,10 +108,11 @@ function reviveFeature(data) {
} }
module.exports = { return {
getFeatures: getFeatures, getFeatures: getFeatures,
getFeature: getFeature, getFeature: getFeature,
getArchivedFeatures: getArchivedFeatures, getArchivedFeatures: getArchivedFeatures,
_createFeature: createFeature, // visible for testing _createFeature: createFeature, // visible for testing
_updateFeature: updateFeature // visible for testing _updateFeature: updateFeature // visible for testing
}; };
};

View File

@ -1,16 +1,15 @@
var eventStore = require('../eventStore');
var eventType = require('../eventType'); var eventType = require('../eventType');
var logger = require('../logger'); var logger = require('../logger');
var knex = require('./dbPool');
var NotFoundError = require('../error/NotFoundError'); var NotFoundError = require('../error/NotFoundError');
var STRATEGY_COLUMNS = ['name', 'description', 'parameters_template']; var STRATEGY_COLUMNS = ['name', 'description', 'parameters_template'];
module.exports = function(db, eventStore) {
eventStore.on(eventType.strategyCreated, function (event) { eventStore.on(eventType.strategyCreated, function (event) {
return createStrategy(event.data); return createStrategy(event.data);
}); });
eventStore.on(eventType.strategyDeleted, function (event) { eventStore.on(eventType.strategyDeleted, function (event) {
knex('strategies') db('strategies')
.where('name', event.data.name) .where('name', event.data.name)
.del() .del()
.catch(function (err) { .catch(function (err) {
@ -19,7 +18,7 @@ eventStore.on(eventType.strategyDeleted, function (event) {
}); });
function getStrategies() { function getStrategies() {
return knex return db
.select(STRATEGY_COLUMNS) .select(STRATEGY_COLUMNS)
.from('strategies') .from('strategies')
.orderBy('created_at', 'asc') .orderBy('created_at', 'asc')
@ -27,7 +26,7 @@ function getStrategies() {
} }
function getStrategy(name) { function getStrategy(name) {
return knex return db
.first(STRATEGY_COLUMNS) .first(STRATEGY_COLUMNS)
.from('strategies') .from('strategies')
.where({ name: name }) .where({ name: name })
@ -50,21 +49,22 @@ function eventDataToRow(data) {
return { return {
name: data.name, name: data.name,
description: data.description, description: data.description,
parameters_template: data.parametersTemplate || {} // eslint-disable-line parameters_template: data.parametersTemplate // eslint-disable-line
}; };
} }
function createStrategy(data) { function createStrategy(data) {
knex('strategies') db('strategies')
.insert(eventDataToRow(data)) .insert(eventDataToRow(data))
.catch(function (err) { .catch(function (err) {
logger.error('Could not insert strategy, error was: ', err); logger.error('Could not insert strategy, error was: ', err);
}); });
} }
module.exports = { return {
getStrategies: getStrategies, getStrategies: getStrategies,
getStrategy: getStrategy, getStrategy: getStrategy,
_createStrategy: createStrategy // visible for testing _createStrategy: createStrategy // visible for testing
}; };
};

View File

@ -1,17 +1,17 @@
var util = require('util'), var util = require('util');
eventDb = require('./db/event'), var EventEmitter = require('events').EventEmitter;
EventEmitter = require('events').EventEmitter;
function EventStore() { function EventStore(eventDb) {
this.eventDb = eventDb;
EventEmitter.call(this); EventEmitter.call(this);
} }
util.inherits(EventStore, EventEmitter); util.inherits(EventStore, EventEmitter);
EventStore.prototype.create = function (event) { EventStore.prototype.create = function (event) {
var that = this; var that = this;
return eventDb.store(event).then(function() { return this.eventDb.store(event).then(function() {
return that.emit(event.type, event); that.emit(event.type, event);
}); });
}; };
module.exports = new EventStore(); module.exports = EventStore;

View File

@ -1,7 +1,8 @@
var eventDb = require('../db/event');
var eventDiffer = require('../eventDiffer'); var eventDiffer = require('../eventDiffer');
module.exports = function (app) { module.exports = function (app, config) {
var eventDb = config.eventDb;
app.get('/events', function (req, res) { app.get('/events', function (req, res) {
eventDb.getEvents().then(function (events) { eventDb.getEvents().then(function (events) {
eventDiffer.addDiffs(events); eventDiffer.addDiffs(events);

View File

@ -1,11 +1,12 @@
var logger = require('../logger'); var logger = require('../logger');
var eventStore = require('../eventStore');
var eventType = require('../eventType'); var eventType = require('../eventType');
var featureDb = require('../db/feature');
var ValidationError = require('../error/ValidationError'); var ValidationError = require('../error/ValidationError');
var validateRequest = require('../error/validateRequest'); var validateRequest = require('../error/validateRequest');
module.exports = function (app) { module.exports = function (app, config) {
var featureDb = config.featureDb;
var eventStore = config.eventStore;
app.get('/archive/features', function (req, res) { app.get('/archive/features', function (req, res) {
featureDb.getArchivedFeatures().then(function (archivedFeatures) { featureDb.getArchivedFeatures().then(function (archivedFeatures) {
res.json({ 'features': archivedFeatures }); res.json({ 'features': archivedFeatures });

View File

@ -1,15 +1,16 @@
var Promise = require("bluebird"); var Promise = require("bluebird");
var logger = require('../logger'); var logger = require('../logger');
var eventStore = require('../eventStore');
var eventType = require('../eventType'); var eventType = require('../eventType');
var featureDb = require('../db/feature');
var NameExistsError = require('../error/NameExistsError'); var NameExistsError = require('../error/NameExistsError');
var NotFoundError = require('../error/NotFoundError'); var NotFoundError = require('../error/NotFoundError');
var ValidationError = require('../error/ValidationError'); var ValidationError = require('../error/ValidationError');
var validateRequest = require('../error/validateRequest'); var validateRequest = require('../error/validateRequest');
var extractUser = require('../extractUser'); var extractUser = require('../extractUser');
module.exports = function (app) { module.exports = function (app, config) {
var featureDb = config.featureDb;
var eventStore = config.eventStore;
app.get('/features', function (req, res) { app.get('/features', function (req, res) {
featureDb.getFeatures().then(function (features) { featureDb.getFeatures().then(function (features) {
res.json({ features: features }); res.json({ features: features });

View File

@ -1,9 +1,8 @@
var knex = require('../db/dbPool');
var logger = require('../logger'); var logger = require('../logger');
module.exports = function (app) { module.exports = function (app, config) {
app.get('/health', function (req, res) { app.get('/health', function (req, res) {
knex.select(1) config.db.select(1)
.from('features') .from('features')
.then(function() { .then(function() {
res.json({ health: 'GOOD' }); res.json({ health: 'GOOD' });

View File

@ -2,10 +2,10 @@
* TODO: we should also inject config and * TODO: we should also inject config and
* services to the routes to ease testing. * services to the routes to ease testing.
**/ **/
exports.create = function (app) { exports.create = function (app, config) {
require('./event')(app); require('./event')(app, config);
require('./feature')(app); require('./feature')(app, config);
require('./feature-archive')(app); require('./feature-archive')(app, config);
require('./strategy')(app); require('./strategy')(app, config);
require('./health-check')(app); require('./health-check')(app, config);
}; };

View File

@ -1,7 +1,5 @@
var Promise = require("bluebird"); var Promise = require("bluebird");
var eventStore = require('../eventStore');
var eventType = require('../eventType'); var eventType = require('../eventType');
var strategyDb = require('../db/strategy');
var logger = require('../logger'); var logger = require('../logger');
var NameExistsError = require('../error/NameExistsError'); var NameExistsError = require('../error/NameExistsError');
var ValidationError = require('../error/ValidationError'); var ValidationError = require('../error/ValidationError');
@ -9,7 +7,10 @@ var NotFoundError = require('../error/NotFoundError');
var validateRequest = require('../error/validateRequest'); var validateRequest = require('../error/validateRequest');
var extractUser = require('../extractUser'); var extractUser = require('../extractUser');
module.exports = function (app) { module.exports = function (app, config) {
var strategyDb = config.strategyDb;
var eventStore = config.eventStore;
app.get('/strategies', function (req, res) { app.get('/strategies', function (req, res) {
strategyDb.getStrategies().then(function (strategies) { strategyDb.getStrategies().then(function (strategies) {
res.json({ strategies: strategies }); res.json({ strategies: strategies });

View File

@ -1,8 +1,22 @@
var logger = require('./lib/logger'); var logger = require('./lib/logger');
var databaseUri = require('./lib/databaseConfig').getDatabaseUrl();
// Database dependecies (statefull)
var db = require('./lib/db/dbPool')(databaseUri);
var eventDb = require('./lib/db/event')(db);
var EventStore = require('./lib/eventStore');
var eventStore = new EventStore(eventDb);
var featureDb = require('./lib/db/feature')(db, eventStore);
var strategyDb = require('./lib/db/strategy')(db, eventStore);
var config = { var config = {
baseUriPath: process.env.BASE_URI_PATH || '', baseUriPath: process.env.BASE_URI_PATH || '',
port: process.env.HTTP_PORT || process.env.PORT || 4242 port: process.env.HTTP_PORT || process.env.PORT || 4242,
db: db,
eventDb: eventDb,
eventStore: eventStore,
featureDb: featureDb,
strategyDb: strategyDb
}; };
var app = require('./app')(config); var app = require('./app')(config);
@ -25,7 +39,6 @@ if (app.get('env') === 'development') {
})); }));
} }
process.on('uncaughtException', function(err) { process.on('uncaughtException', function(err) {
logger.error('Uncaught Exception:', err.message); logger.error('Uncaught Exception:', err.message);
logger.error(err.stack); logger.error(err.stack);

11
test/databaseConfig.js Normal file
View File

@ -0,0 +1,11 @@
'use strict';
function getDatabaseUri() {
if (!process.env.TEST_DATABASE_URL) {
throw new Error('please set TEST_DATABASE_URL');
} else {
return process.env.TEST_DATABASE_URL;
}
}
module.exports = {
getDatabaseUri: getDatabaseUri
};

View File

@ -1,4 +1,5 @@
'use strict'; 'use strict';
var logger = require('../lib/logger');
var assert = require('assert'); var assert = require('assert');
var specHelper = require('./specHelper'); var specHelper = require('./specHelper');
var request = specHelper.request; var request = specHelper.request;
@ -32,6 +33,7 @@ describe('The features api', function () {
}); });
it('cant get feature that dose not exist', function (done) { it('cant get feature that dose not exist', function (done) {
logger.setLevel('FATAL');
request request
.get('/features/myfeature') .get('/features/myfeature')
.expect('Content-Type', /json/) .expect('Content-Type', /json/)
@ -47,6 +49,7 @@ describe('The features api', function () {
}); });
it('creates new feature toggle with createdBy', function (done) { it('creates new feature toggle with createdBy', function (done) {
logger.setLevel('FATAL');
request request
.post('/features') .post('/features')
.send({ name: 'com.test.Username', enabled: false }) .send({ name: 'com.test.Username', enabled: false })
@ -63,6 +66,7 @@ describe('The features api', function () {
}); });
it('require new feature toggle to have a name', function (done) { it('require new feature toggle to have a name', function (done) {
logger.setLevel('FATAL');
request request
.post('/features') .post('/features')
.send({ name: '' }) .send({ name: '' })
@ -71,6 +75,7 @@ describe('The features api', function () {
}); });
it('can not change status of feature toggle that does not exist', function (done) { it('can not change status of feature toggle that does not exist', function (done) {
logger.setLevel('FATAL');
request request
.put('/features/should-not-exist') .put('/features/should-not-exist')
.send({ name: 'should-not-exist', enabled: false }) .send({ name: 'should-not-exist', enabled: false })
@ -79,6 +84,7 @@ describe('The features api', function () {
}); });
it('can change status of feature toggle that does exist', function (done) { it('can change status of feature toggle that does exist', function (done) {
logger.setLevel('FATAL');
request request
.put('/features/featureY') .put('/features/featureY')
.send({ name: 'featureY', enabled: true }) .send({ name: 'featureY', enabled: true })

View File

@ -3,10 +3,22 @@ process.env.NODE_ENV = 'test';
var Promise = require('bluebird'); var Promise = require('bluebird');
var request = require('supertest'); var request = require('supertest');
var app = require('../app')({ baseUriPath: '' }); var databaseUri = require('./databaseConfig').getDatabaseUri();
var knex = require('../lib/db/dbPool'); var knex = require('../lib/db/dbPool')(databaseUri);
var featureDb = require('../lib/db/feature'); var eventDb = require('../lib/db/event')(knex);
var strategyDb = require('../lib/db/strategy'); var EventStore = require('../lib/eventStore');
var eventStore = new EventStore(eventDb);
var featureDb = require('../lib/db/feature')(knex, eventStore);
var strategyDb = require('../lib/db/strategy')(knex, eventStore);
var app = require('../app')({
baseUriPath: '',
db: knex,
eventDb: eventDb,
eventStore: eventStore,
featureDb: featureDb,
strategyDb: strategyDb
});
Promise.promisifyAll(request); Promise.promisifyAll(request);
request = request(app); request = request(app);