diff --git a/.gitignore b/.gitignore index 7a328cb301..d96e8607dd 100644 --- a/.gitignore +++ b/.gitignore @@ -32,3 +32,8 @@ unleash-server.tar.gz .idea/* .vagrant/ + +# Visual Studio Code +jsconfig.json +typings +.vscode \ No newline at end of file diff --git a/README.md b/README.md index 66d60576ec..aa215a4335 100644 --- a/README.md +++ b/README.md @@ -71,6 +71,6 @@ npm run docker-test 1. Create `migrations/sql/NNN-your-migration-name.up.sql` with your change in SQL. 2. Create `migrations/sql/NNN-your-migration-name.down.sql` with the rollback of your change in SQL. -3. Run `db-migrate create your-migration-name` and edit the generated file to have this line: `module.exports = require('../lib/migrationRunner').create('NNN-your-migration-name');` +3. Run `db-migrate create your-migration-name` and edit the generated file to have this line: `module.exports = require('../scripts/migration-runner').create('NNN-your-migration-name');` 4. Run `db-migrate up`. 5. Generate LB artifact using `scripts/generate-liquibase-artifact` (TODO: make this internal) diff --git a/app.js b/app.js index a67fc63655..feaa0ab007 100644 --- a/app.js +++ b/app.js @@ -1,55 +1,34 @@ -var express = require('express'), - bodyParser = require('body-parser'), - cookieParser = require('cookie-parser'), - log4js = require('log4js'), - logger = require('./lib/logger'), - routes = require('./lib/routes'), - eventApi = require('./lib/eventApi'), - featureApi = require('./lib/featureApi'), - featureArchiveApi = require('./lib/featureArchiveApi'), - strategyApi = require('./lib/strategyApi'), - validator = require('express-validator'), - app = express(), - router = express.Router(), // eslint-disable-line - baseUriPath = process.env.BASE_URI_PATH || ''; +var express = require('express'); +var favicon = require('serve-favicon'); +var bodyParser = require('body-parser'); +var cookieParser = require('cookie-parser'); +var validator = require('express-validator'); +var log4js = require('log4js'); +var logger = require('./lib/logger'); +var routes = require('./lib/routes'); -if (app.get('env') === 'development') { - app.use(require('errorhandler')()); +module.exports = function(config) { + var app = express(); + var router = express.Router(); // eslint-disable-line + var baseUriPath = config.baseUriPath || ''; - var webpack = require('webpack'), - webpackDevMiddleware = require('webpack-dev-middleware'), - webpackConfig = require('./webpack.config'), - compiler = webpack(webpackConfig), - config = { - publicPath: '/js', - noInfo: true - }; + app.set('trust proxy'); + app.set('port', config.port); + app.locals.baseUriPath = baseUriPath; + app.use(cookieParser()); - app.use(baseUriPath, webpackDevMiddleware(compiler, config)); -} + app.use(favicon(__dirname + '/public/favicon.ico')); + app.use(validator([])); + app.use(baseUriPath, express.static(__dirname + '/public')); + app.use(bodyParser.json({ strict: false })); + app.use(log4js.connectLogger(logger, { + format: ':remote-addr :status :method :url :response-timems', + level: 'auto' // 3XX=WARN, 4xx/5xx=ERROR + })); -app.use(validator([])); + // Setup API routes + routes.create(router, config); + app.use(baseUriPath, router); -app.set('trust proxy'); -app.locals.baseUriPath = baseUriPath; - -app.use(log4js.connectLogger(logger, { - format: ':remote-addr :status :method :url :response-timems', - level: 'auto' // 3XX=WARN, 4xx/5xx=ERROR -})); - -app.set('port', process.env.HTTP_PORT || process.env.PORT || 4242); - -app.use(baseUriPath, express.static(__dirname + '/public')); -app.use(bodyParser.json({ strict: false })); - -app.use(cookieParser()); - -eventApi(router); -featureApi(router); -featureArchiveApi(router); -strategyApi(router); -routes(router); -app.use(baseUriPath, router); - -module.exports = app; + return app; +}; diff --git a/lib/dbPool.js b/lib/databaseConfig.js similarity index 55% rename from lib/dbPool.js rename to lib/databaseConfig.js index 5c7cb0ff29..70cb78e3c7 100644 --- a/lib/dbPool.js +++ b/lib/databaseConfig.js @@ -1,12 +1,7 @@ -var logger = require('./logger'); var nconf = require('nconf'); var fs = require('fs'); var ini = require('ini'); -var knex = require('knex'); - -function isTestEnv() { - return process.env.NODE_ENV === 'test'; -} +var logger = require('./logger'); function getDatabaseIniUrl() { // Finn specific way of delivering env variables @@ -18,15 +13,6 @@ function getDatabaseIniUrl() { 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'); @@ -38,15 +24,6 @@ function getDatabaseUrl() { throw new Error('please set DATABASE_URL or pass --databaseini'); } -function createDbPool() { - return knex({ - client: 'pg', - connection: isTestEnv() ? getTestDatabaseUrl() : getDatabaseUrl(), - pool: { - min: 2, - max: 20 - } - }); -} - -module.exports = createDbPool(); +module.exports = { + getDatabaseUrl: getDatabaseUrl +}; diff --git a/lib/db/dbPool.js b/lib/db/dbPool.js new file mode 100644 index 0000000000..33a863e674 --- /dev/null +++ b/lib/db/dbPool.js @@ -0,0 +1,9 @@ +var knex = require('knex'); + +module.exports = function(databaseConnection) { + return knex({ + client: 'pg', + connection: databaseConnection, + pool: { min: 2, max: 20 } + }); +}; diff --git a/lib/db/event.js b/lib/db/event.js new file mode 100644 index 0000000000..8d4d1f24f5 --- /dev/null +++ b/lib/db/event.js @@ -0,0 +1,45 @@ +var EVENT_COLUMNS = ['id', 'type', 'created_by', 'created_at', 'data']; + +module.exports = function(db) { + function storeEvent(event) { + return db('events').insert({ + type: event.type, + created_by: event.createdBy, // eslint-disable-line + data: event.data + }); + } + + function getEvents() { + return db + .select(EVENT_COLUMNS) + .from('events') + .orderBy('created_at', 'desc') + .map(rowToEvent); + } + + function getEventsFilterByName(name) { + return db + .select(EVENT_COLUMNS) + .from('events') + .whereRaw("data ->> 'name' = ?", [name]) + .orderBy('created_at', 'desc') + .map(rowToEvent); + } + + function rowToEvent(row) { + return { + id: row.id, + type: row.type, + createdBy: row.created_by, + createdAt: row.created_at, + data: row.data + }; + } + + return { + store: storeEvent, + getEvents: getEvents, + getEventsFilterByName: getEventsFilterByName + }; +}; + diff --git a/lib/db/feature.js b/lib/db/feature.js new file mode 100644 index 0000000000..fe54a553c9 --- /dev/null +++ b/lib/db/feature.js @@ -0,0 +1,118 @@ +var eventType = require('../eventType'); +var logger = require('../logger'); +var NotFoundError = require('../error/NotFoundError'); +var FEATURE_COLUMNS = ['name', 'description', 'enabled', 'strategy_name', 'parameters']; + +module.exports = function(db, eventStore) { + eventStore.on(eventType.featureCreated, function (event) { + return createFeature(event.data); + }); + + eventStore.on(eventType.featureUpdated, function (event) { + return updateFeature(event.data); + }); + + eventStore.on(eventType.featureArchived, function (event) { + return archiveFeature(event.data); + }); + + eventStore.on(eventType.featureRevived, function (event) { + return reviveFeature(event.data); + }); + + function getFeatures() { + return db + .select(FEATURE_COLUMNS) + .from('features') + .where({ archived: 0 }) + .orderBy('name', 'asc') + .map(rowToFeature); + } + + function getFeature(name) { + return db + .first(FEATURE_COLUMNS) + .from('features') + .where({ name: name }) + .then(rowToFeature); + } + + function getArchivedFeatures() { + return db + .select(FEATURE_COLUMNS) + .from('features') + .where({ archived: 1 }) + .orderBy('name', 'asc') + .map(rowToFeature); + } + + + function rowToFeature(row) { + if (!row) { + throw new NotFoundError('No feature toggle found'); + } + + return { + name: row.name, + description: row.description, + enabled: row.enabled > 0, + strategy: row.strategy_name, // eslint-disable-line + parameters: row.parameters + }; + } + + function eventDataToRow(data) { + return { + name: data.name, + description: data.description, + enabled: data.enabled ? 1 : 0, + archived: data.archived ? 1 :0, + strategy_name: data.strategy, // eslint-disable-line + parameters: data.parameters + }; + } + + function createFeature(data) { + return db('features') + .insert(eventDataToRow(data)) + .catch(function (err) { + logger.error('Could not insert feature, error was: ', err); + }); + } + + function updateFeature(data) { + return db('features') + .where({ name: data.name }) + .update(eventDataToRow(data)) + .catch(function (err) { + logger.error('Could not update feature, error was: ', err); + }); + } + + function archiveFeature(data) { + return db('features') + .where({ name: data.name }) + .update({ archived: 1 }) + .catch(function (err) { + logger.error('Could not archive feature, error was: ', err); + }); + } + + function reviveFeature(data) { + return db('features') + .where({ name: data.name }) + .update({ archived: 0, enabled: 0 }) + .catch(function (err) { + logger.error('Could not archive feature, error was: ', err); + }); + } + + + return { + getFeatures: getFeatures, + getFeature: getFeature, + getArchivedFeatures: getArchivedFeatures, + _createFeature: createFeature, // visible for testing + _updateFeature: updateFeature // visible for testing + }; +}; diff --git a/lib/db/strategy.js b/lib/db/strategy.js new file mode 100644 index 0000000000..9a19a9c503 --- /dev/null +++ b/lib/db/strategy.js @@ -0,0 +1,70 @@ +var eventType = require('../eventType'); +var logger = require('../logger'); +var NotFoundError = require('../error/NotFoundError'); +var STRATEGY_COLUMNS = ['name', 'description', 'parameters_template']; + +module.exports = function(db, eventStore) { + eventStore.on(eventType.strategyCreated, function (event) { + return createStrategy(event.data); + }); + + eventStore.on(eventType.strategyDeleted, function (event) { + db('strategies') + .where('name', event.data.name) + .del() + .catch(function (err) { + logger.error('Could not delete strategy, error was: ', err); + }); + }); + + function getStrategies() { + return db + .select(STRATEGY_COLUMNS) + .from('strategies') + .orderBy('created_at', 'asc') + .map(rowToStrategy); + } + + function getStrategy(name) { + return db + .first(STRATEGY_COLUMNS) + .from('strategies') + .where({ name: name }) + .then(rowToStrategy); + } + + function rowToStrategy(row) { + if (!row) { + throw new NotFoundError('No strategy found'); + } + + return { + name: row.name, + description: row.description, + parametersTemplate: row.parameters_template + }; + } + + function eventDataToRow(data) { + return { + name: data.name, + description: data.description, + parameters_template: data.parametersTemplate // eslint-disable-line + }; + } + + function createStrategy(data) { + db('strategies') + .insert(eventDataToRow(data)) + .catch(function (err) { + logger.error('Could not insert strategy, error was: ', err); + }); + } + + return { + getStrategies: getStrategies, + getStrategy: getStrategy, + _createStrategy: createStrategy // visible for testing + }; +}; + diff --git a/lib/eventDb.js b/lib/eventDb.js deleted file mode 100644 index 5dea161375..0000000000 --- a/lib/eventDb.js +++ /dev/null @@ -1,43 +0,0 @@ -var knex = require('./dbPool'); -var EVENT_COLUMNS = ['id', 'type', 'created_by', 'created_at', 'data']; - -function storeEvent(event) { - return knex('events').insert({ - type: event.type, - created_by: event.createdBy, // eslint-disable-line - data: event.data - }); -} - -function getEvents() { - return knex - .select(EVENT_COLUMNS) - .from('events') - .orderBy('created_at', 'desc') - .map(rowToEvent); -} - -function getEventsFilterByName(name) { - return knex - .select(EVENT_COLUMNS) - .from('events') - .whereRaw("data ->> 'name' = ?", [name]) - .orderBy('created_at', 'desc') - .map(rowToEvent); -} - -function rowToEvent(row) { - return { - id: row.id, - type: row.type, - createdBy: row.created_by, - createdAt: row.created_at, - data: row.data - }; -} - -module.exports = { - store: storeEvent, - getEvents: getEvents, - getEventsFilterByName: getEventsFilterByName -}; diff --git a/lib/eventStore.js b/lib/eventStore.js index 67802c3cfa..1f39dbdf2c 100644 --- a/lib/eventStore.js +++ b/lib/eventStore.js @@ -1,17 +1,17 @@ -var util = require('util'), - eventDb = require('./eventDb'), - EventEmitter = require('events').EventEmitter; +var util = require('util'); +var EventEmitter = require('events').EventEmitter; -function EventStore() { +function EventStore(eventDb) { + this.eventDb = eventDb; EventEmitter.call(this); } util.inherits(EventStore, EventEmitter); EventStore.prototype.create = function (event) { var that = this; - return eventDb.store(event).then(function() { - return that.emit(event.type, event); + return this.eventDb.store(event).then(function() { + that.emit(event.type, event); }); }; -module.exports = new EventStore(); +module.exports = EventStore; diff --git a/lib/featureDb.js b/lib/featureDb.js deleted file mode 100644 index 28f51bd173..0000000000 --- a/lib/featureDb.js +++ /dev/null @@ -1,118 +0,0 @@ -var eventStore = require('./eventStore'); -var eventType = require('./eventType'); -var logger = require('./logger'); -var knex = require('./dbPool'); -var NotFoundError = require('./error/NotFoundError'); -var FEATURE_COLUMNS = ['name', 'description', 'enabled', 'strategy_name', 'parameters']; - -eventStore.on(eventType.featureCreated, function (event) { - return createFeature(event.data); -}); - -eventStore.on(eventType.featureUpdated, function (event) { - return updateFeature(event.data); -}); - -eventStore.on(eventType.featureArchived, function (event) { - return archiveFeature(event.data); -}); - -eventStore.on(eventType.featureRevived, function (event) { - return reviveFeature(event.data); -}); - -function getFeatures() { - return knex - .select(FEATURE_COLUMNS) - .from('features') - .where({ archived: 0 }) - .orderBy('name', 'asc') - .map(rowToFeature); -} - -function getFeature(name) { - return knex - .first(FEATURE_COLUMNS) - .from('features') - .where({ name: name }) - .then(rowToFeature); -} - -function getArchivedFeatures() { - return knex - .select(FEATURE_COLUMNS) - .from('features') - .where({ archived: 1 }) - .orderBy('name', 'asc') - .map(rowToFeature); -} - - -function rowToFeature(row) { - if (!row) { - throw new NotFoundError('No feature toggle found'); - } - - return { - name: row.name, - description: row.description, - enabled: row.enabled > 0, - strategy: row.strategy_name, // eslint-disable-line - parameters: row.parameters - }; -} - -function eventDataToRow(data) { - return { - name: data.name, - description: data.description || '', - enabled: data.enabled ? 1 : 0, - archived: data.archived ? 1 :0, - strategy_name: data.strategy || 'default', // eslint-disable-line - parameters: data.parameters || {} - }; -} - -function createFeature(data) { - return knex('features') - .insert(eventDataToRow(data)) - .catch(function (err) { - logger.error('Could not insert feature, error was: ', err); - }); -} - -function updateFeature(data) { - return knex('features') - .where({ name: data.name }) - .update(eventDataToRow(data)) - .catch(function (err) { - logger.error('Could not update feature, error was: ', err); - }); -} - -function archiveFeature(data) { - return knex('features') - .where({ name: data.name }) - .update({ archived: 1 }) - .catch(function (err) { - logger.error('Could not archive feature, error was: ', err); - }); -} - -function reviveFeature(data) { - return knex('features') - .where({ name: data.name }) - .update({ archived: 0, enabled: 0 }) - .catch(function (err) { - logger.error('Could not archive feature, error was: ', err); - }); -} - - -module.exports = { - getFeatures: getFeatures, - getFeature: getFeature, - getArchivedFeatures: getArchivedFeatures, - _createFeature: createFeature, // visible for testing - _updateFeature: updateFeature // visible for testing -}; diff --git a/lib/eventApi.js b/lib/routes/event.js similarity index 82% rename from lib/eventApi.js rename to lib/routes/event.js index 6c8e2b33e6..b69c7f7f09 100644 --- a/lib/eventApi.js +++ b/lib/routes/event.js @@ -1,7 +1,8 @@ -var eventDb = require('./eventDb'); -var eventDiffer = require('./eventDiffer'); +var eventDiffer = require('../eventDiffer'); + +module.exports = function (app, config) { + var eventDb = config.eventDb; -module.exports = function (app) { app.get('/events', function (req, res) { eventDb.getEvents().then(function (events) { eventDiffer.addDiffs(events); diff --git a/lib/featureArchiveApi.js b/lib/routes/feature-archive.js similarity index 73% rename from lib/featureArchiveApi.js rename to lib/routes/feature-archive.js index 430b0c82a7..29fc82448a 100644 --- a/lib/featureArchiveApi.js +++ b/lib/routes/feature-archive.js @@ -1,11 +1,12 @@ -var logger = require('./logger'); -var eventStore = require('./eventStore'); -var eventType = require('./eventType'); -var featureDb = require('./featureDb'); -var ValidationError = require('./error/ValidationError'); -var validateRequest = require('./error/validateRequest'); +var logger = require('../logger'); +var eventType = require('../eventType'); +var ValidationError = require('../error/ValidationError'); +var validateRequest = require('../error/validateRequest'); + +module.exports = function (app, config) { + var featureDb = config.featureDb; + var eventStore = config.eventStore; -module.exports = function (app) { app.get('/archive/features', function (req, res) { featureDb.getArchivedFeatures().then(function (archivedFeatures) { res.json({ 'features': archivedFeatures }); diff --git a/lib/featureApi.js b/lib/routes/feature.js similarity index 87% rename from lib/featureApi.js rename to lib/routes/feature.js index 5a4ba8a817..1f3c726eaf 100644 --- a/lib/featureApi.js +++ b/lib/routes/feature.js @@ -1,15 +1,16 @@ var Promise = require("bluebird"); -var logger = require('./logger'); -var eventStore = require('./eventStore'); -var eventType = require('./eventType'); -var featureDb = require('./featureDb'); -var NameExistsError = require('./error/NameExistsError'); -var NotFoundError = require('./error/NotFoundError'); -var ValidationError = require('./error/ValidationError'); -var validateRequest = require('./error/validateRequest'); -var extractUser = require('./extractUser'); +var logger = require('../logger'); +var eventType = require('../eventType'); +var NameExistsError = require('../error/NameExistsError'); +var NotFoundError = require('../error/NotFoundError'); +var ValidationError = require('../error/ValidationError'); +var validateRequest = require('../error/validateRequest'); +var extractUser = require('../extractUser'); + +module.exports = function (app, config) { + var featureDb = config.featureDb; + var eventStore = config.eventStore; -module.exports = function (app) { app.get('/features', function (req, res) { featureDb.getFeatures().then(function (features) { res.json({ features: features }); diff --git a/lib/routes.js b/lib/routes/health-check.js similarity index 75% rename from lib/routes.js rename to lib/routes/health-check.js index f354178c10..1343e81686 100644 --- a/lib/routes.js +++ b/lib/routes/health-check.js @@ -1,9 +1,8 @@ -var knex = require('./dbPool'); -var logger = require('./logger'); +var logger = require('../logger'); -module.exports = function (app) { +module.exports = function (app, config) { app.get('/health', function (req, res) { - knex.select(1) + config.db.select(1) .from('features') .then(function() { res.json({ health: 'GOOD' }); diff --git a/lib/routes/index.js b/lib/routes/index.js new file mode 100644 index 0000000000..6e3515fa64 --- /dev/null +++ b/lib/routes/index.js @@ -0,0 +1,11 @@ +/** + * TODO: we should also inject config and + * services to the routes to ease testing. +**/ +exports.create = function (app, config) { + require('./event')(app, config); + require('./feature')(app, config); + require('./feature-archive')(app, config); + require('./strategy')(app, config); + require('./health-check')(app, config); +}; diff --git a/lib/strategyApi.js b/lib/routes/strategy.js similarity index 84% rename from lib/strategyApi.js rename to lib/routes/strategy.js index 2837f3ff89..4176d215c4 100644 --- a/lib/strategyApi.js +++ b/lib/routes/strategy.js @@ -1,15 +1,16 @@ var Promise = require("bluebird"); -var eventStore = require('./eventStore'); -var eventType = require('./eventType'); -var strategyDb = require('./strategyDb'); -var logger = require('./logger'); -var NameExistsError = require('./error/NameExistsError'); -var ValidationError = require('./error/ValidationError'); -var NotFoundError = require('./error/NotFoundError'); -var validateRequest = require('./error/validateRequest'); -var extractUser = require('./extractUser'); +var eventType = require('../eventType'); +var logger = require('../logger'); +var NameExistsError = require('../error/NameExistsError'); +var ValidationError = require('../error/ValidationError'); +var NotFoundError = require('../error/NotFoundError'); +var validateRequest = require('../error/validateRequest'); +var extractUser = require('../extractUser'); + +module.exports = function (app, config) { + var strategyDb = config.strategyDb; + var eventStore = config.eventStore; -module.exports = function (app) { app.get('/strategies', function (req, res) { strategyDb.getStrategies().then(function (strategies) { res.json({ strategies: strategies }); diff --git a/lib/strategyDb.js b/lib/strategyDb.js deleted file mode 100644 index 2972bdfae1..0000000000 --- a/lib/strategyDb.js +++ /dev/null @@ -1,70 +0,0 @@ -var eventStore = require('./eventStore'); -var eventType = require('./eventType'); -var logger = require('./logger'); -var knex = require('./dbPool'); -var NotFoundError = require('./error/NotFoundError'); -var STRATEGY_COLUMNS = ['name', 'description', 'parameters_template']; - -eventStore.on(eventType.strategyCreated, function (event) { - return createStrategy(event.data); -}); - -eventStore.on(eventType.strategyDeleted, function (event) { - knex('strategies') - .where('name', event.data.name) - .del() - .catch(function (err) { - logger.error('Could not delete strategy, error was: ', err); - }); -}); - -function getStrategies() { - return knex - .select(STRATEGY_COLUMNS) - .from('strategies') - .orderBy('created_at', 'asc') - .map(rowToStrategy); -} - -function getStrategy(name) { - return knex - .first(STRATEGY_COLUMNS) - .from('strategies') - .where({ name: name }) - .then(rowToStrategy); -} - -function rowToStrategy(row) { - if (!row) { - throw new NotFoundError('No strategy found'); - } - - return { - name: row.name, - description: row.description, - parametersTemplate: row.parameters_template - }; -} - -function eventDataToRow(data) { - return { - name: data.name, - description: data.description, - parameters_template: data.parametersTemplate || {} // eslint-disable-line - }; -} - -function createStrategy(data) { - knex('strategies') - .insert(eventDataToRow(data)) - .catch(function (err) { - logger.error('Could not insert strategy, error was: ', err); - }); -} - -module.exports = { - getStrategies: getStrategies, - getStrategy: getStrategy, - _createStrategy: createStrategy // visible for testing -}; - diff --git a/migrations/20141020151056-initial-schema.js b/migrations/20141020151056-initial-schema.js index b1d5b3f5f0..0a41df16f3 100644 --- a/migrations/20141020151056-initial-schema.js +++ b/migrations/20141020151056-initial-schema.js @@ -1 +1 @@ -module.exports = require('../lib/migrationRunner').create('001-initial-schema'); +module.exports = require('../scripts/migration-runner').create('001-initial-schema'); diff --git a/migrations/20141110144153-add-description-to-features.js b/migrations/20141110144153-add-description-to-features.js index fd90440779..248e5aa474 100644 --- a/migrations/20141110144153-add-description-to-features.js +++ b/migrations/20141110144153-add-description-to-features.js @@ -1 +1 @@ -module.exports = require('../lib/migrationRunner').create('002-add-description-to-features'); +module.exports = require('../scripts/migration-runner').create('002-add-description-to-features'); diff --git a/migrations/20141117200435-add-parameters-template-to-strategies.js b/migrations/20141117200435-add-parameters-template-to-strategies.js index e8068b7a53..0c6ef1b1a2 100644 --- a/migrations/20141117200435-add-parameters-template-to-strategies.js +++ b/migrations/20141117200435-add-parameters-template-to-strategies.js @@ -1,2 +1,2 @@ -module.exports = require('../lib/migrationRunner').create('003-add-parameters-template-to-strategies'); +module.exports = require('../scripts/migration-runner').create('003-add-parameters-template-to-strategies'); diff --git a/migrations/20141117202209-insert-default-strategy.js b/migrations/20141117202209-insert-default-strategy.js index 2023e55488..1bc6251635 100644 --- a/migrations/20141117202209-insert-default-strategy.js +++ b/migrations/20141117202209-insert-default-strategy.js @@ -1 +1 @@ -module.exports = require('../lib/migrationRunner').create('004-insert-default-strategy'); +module.exports = require('../scripts/migration-runner').create('004-insert-default-strategy'); diff --git a/migrations/20141118071458-default-strategy-event.js b/migrations/20141118071458-default-strategy-event.js index 8157598e8c..488b3ac594 100644 --- a/migrations/20141118071458-default-strategy-event.js +++ b/migrations/20141118071458-default-strategy-event.js @@ -1 +1 @@ -module.exports = require('../lib/migrationRunner').create('004-insert-default-strategy-event'); +module.exports = require('../scripts/migration-runner').create('004-insert-default-strategy-event'); diff --git a/migrations/20141215210141-005-archived-flag-to-features.js b/migrations/20141215210141-005-archived-flag-to-features.js index 12a3995472..0bd29e3271 100644 --- a/migrations/20141215210141-005-archived-flag-to-features.js +++ b/migrations/20141215210141-005-archived-flag-to-features.js @@ -1 +1 @@ -module.exports = require('../lib/migrationRunner').create('005-archived-flag-to-features'); +module.exports = require('../scripts/migration-runner').create('005-archived-flag-to-features'); diff --git a/migrations/20150210152531-006-rename-eventtype.js b/migrations/20150210152531-006-rename-eventtype.js index d184ba32f7..0de099bdbd 100644 --- a/migrations/20150210152531-006-rename-eventtype.js +++ b/migrations/20150210152531-006-rename-eventtype.js @@ -1 +1 @@ -module.exports = require('../lib/migrationRunner').create('006-rename-eventtype'); +module.exports = require('../scripts/migration-runner').create('006-rename-eventtype'); diff --git a/package.json b/package.json index af8e718853..de9bc6abe4 100644 --- a/package.json +++ b/package.json @@ -1,96 +1,105 @@ { - "name": "unleash-server", - "description": "unleash your features", - "version": "0.1.0", - "keywords": [ - "unleash", - "feature toggle", - "feature", - "toggle" + "name": "unleash-server", + "description": "unleash your features", + "version": "0.1.0", + "keywords": [ + "unleash", + "feature toggle", + "feature", + "toggle" + ], + "repository": { + "type": "git", + "url": "ssh://git@github.com:finn-no/unleash.git" + }, + "bugs": { + "url": "https://github.com/finn-no/unleash/issues" + }, + "engines": { + "node": ">=4.4.3" + }, + "private": true, + "scripts": { + "db-migrate-and-start": "npm run db-migrate && npm run start", + "start": "NODE_ENV=production node server.js", + "build": "./node_modules/.bin/webpack -p", + "dev": "NODE_ENV=development supervisor --ignore ./node_modules/,./public/js server.js", + "start-pg": "pg_virtualenv npm run start-pg-chain", + "start-pg-chain": "export DATABASE_URL=postgres://$PGUSER:$PGPASSWORD@localhost:$PGPORT/postgres ; db-migrate up && npm run dev", + "test": "export PORT=4243 ; jest && npm run lint && mocha test test/*.js && npm run coverage", + "docker-test": "export PORT=4243 ; ./scripts/docker-postgres.sh", + "pg-virtualenv-test": "pg_virtualenv npm run pg-virtualenv-chain", + "pg-virtualenv-chain": "export TEST_DATABASE_URL=postgres://$PGUSER:$PGPASSWORD@localhost:$PGPORT/postgres ; npm run db-migrate-testdb && npm test", + "db-migrate": "node_modules/.bin/db-migrate up", + "db-migrate-testdb": "DATABASE_URL=$TEST_DATABASE_URL ./node_modules/.bin/db-migrate up", + "tdd": "mocha --watch test test/*", + "test-bamboo-ci": "mocha test test/*", + "coverage": "istanbul cover ./node_modules/mocha/bin/_mocha --report lcovonly -- -R spec", + "coverage-report": "cat ./coverage/lcov.info | ./node_modules/coveralls/bin/coveralls.js && rm -rf ./coverage", + "postinstall": "npm run build", + "jest": "jest", + "lint": "eslint . --ignore-path .gitignore" + }, + "dependencies": { + "bluebird": "2.9.14", + "body-parser": "1.15.0", + "cookie-parser": "^1.4.1", + "db-migrate": "0.9.23", + "deep-diff": "^0.3.3", + "errorhandler": "1.3.5", + "express": "4.13.4", + "express-validator": "2.20.3", + "ini": "1.3.4", + "install": "^0.6.1", + "jsx-loader": "0.12.2", + "jsxhint": "0.13.2", + "knex": "^0.10.0", + "lodash": "^3.5.0", + "log4js": "0.6.35", + "moment": "^2.13.0", + "nconf": "0.8.4", + "npm": "^3.8.8", + "pg": "^4.5.5", + "react": "^0.13.1", + "react-router": "^0.13.2", + "reflux": "^0.2.10", + "reqwest": "^2.0.5", + "serve-favicon": "^2.3.0", + "webpack": "^1.13.0", + "webpack-dev-middleware": "^1.6.1" + }, + "devDependencies": { + "chai": "3.5.0", + "coveralls": "^2.11.9", + "eslint": "^2.9.0", + "eslint-config-spt": "^2.0.0", + "eslint-plugin-react": "^4.3.0", + "istanbul": "^0.4.3", + "jest-cli": "0.5.8", + "mocha": "^2.4.5", + "mocha-lcov-reporter": "1.2.0", + "nsp": "^2.3.2", + "pre-commit": "^1.0.2", + "react-tools": "^0.13.1", + "supertest": "^1.2.0", + "supervisor": "^0.10.0", + "xmlbuilder": "^8.2.2" + }, + "jest": { + "scriptPreprocessor": "/scripts/jest-preprocessor.js", + "modulePathIgnorePatterns": [ + "/node_modules/npm" ], - "repository": { - "type": "git", - "url": "ssh://git@github.com:finn-no/unleash.git" - }, - "bugs": { - "url": "https://github.com/finn-no/unleash/issues" - }, - "private": true, - "scripts": { - "db-migrate-and-start": "npm run db-migrate && npm run start", - "start": "NODE_ENV=production node server.js", - "build": "./node_modules/.bin/webpack -p", - "dev": "NODE_ENV=development supervisor --ignore ./node_modules/,./public/js server.js", - "start-pg": "pg_virtualenv npm run start-pg-chain", - "start-pg-chain": "export DATABASE_URL=postgres://$PGUSER:$PGPASSWORD@localhost:$PGPORT/postgres ; db-migrate up && npm run dev", - "test": "export PORT=4243 ; jest && npm run lint && mocha test test/*.js && npm run coverage", - "docker-test": "export PORT=4243 ; ./scripts/docker-postgres.sh", - "pg-virtualenv-test": "pg_virtualenv npm run pg-virtualenv-chain", - "pg-virtualenv-chain": "export TEST_DATABASE_URL=postgres://$PGUSER:$PGPASSWORD@localhost:$PGPORT/postgres ; npm run db-migrate-testdb && npm test", - "db-migrate": "node_modules/.bin/db-migrate up", - "db-migrate-testdb": "DATABASE_URL=$TEST_DATABASE_URL ./node_modules/.bin/db-migrate up", - "tdd": "mocha --watch test test/*", - "test-bamboo-ci": "mocha test test/*", - "coverage": "istanbul cover ./node_modules/mocha/bin/_mocha --report lcovonly -- -R spec", - "coverage-report": "cat ./coverage/lcov.info | ./node_modules/coveralls/bin/coveralls.js && rm -rf ./coverage", - "postinstall": "npm run build", - "jest": "jest", - "lint": "eslint . --ignore-path .gitignore" - }, - "dependencies": { - "bluebird": "2.9.14", - "body-parser": "1.12.2", - "cookie-parser": "^1.3.3", - "db-migrate": "0.9.23", - "deep-diff": "^0.3.0", - "errorhandler": "1.3.5", - "express": "4.12.3", - "express-validator": "2.9.0", - "ini": "1.3.3", - "jsx-loader": "0.12.2", - "jsxhint": "0.13.2", - "knex": "^0.11.0", - "lodash": "^3.5.0", - "log4js": "0.6.22", - "moment": "^2.11.2", - "nconf": "0.7.1", - "pg": "^4.5.5", - "react": "^0.13.1", - "react-router": "^0.13.2", - "reflux": "^0.2.10", - "reqwest": "^1.1.4", - "webpack": "1.7.3", - "webpack-dev-middleware": "^1.0.11" - }, - "devDependencies": { - "chai": "2.1.2", - "coveralls": "^2.11.2", - "eslint": "^2.7.0", - "eslint-config-spt": "^2.0.0", - "eslint-plugin-react": "^4.3.0", - "istanbul": "^0.3.5", - "jest-cli": "0.5.4", - "mocha": "^2.1.0", - "mocha-lcov-reporter": "0.0.2", - "nsp": "^1.0.0", - "pre-commit": "^1.0.2", - "react-tools": "^0.13.1", - "supertest": "^0.15.0", - "supervisor": "^0.6.0", - "xmlbuilder": "^2.5.1" - }, - "jest": { - "scriptPreprocessor": "/jest-preprocessor.js", - "unmockedModulePathPatterns": [ - "/node_modules/react", - "/node_modules/reflux" - ], - "moduleFileExtensions": [ - "jsx", - "js" - ] - }, - "pre-commit": [ - "lint" + "unmockedModulePathPatterns": [ + "/node_modules/react", + "/node_modules/reflux" + ], + "moduleFileExtensions": [ + "jsx", + "js" ] + }, + "pre-commit": [ + "lint" + ] } diff --git a/public/favicon.ico b/public/favicon.ico new file mode 100644 index 0000000000..a2bc43c660 Binary files /dev/null and b/public/favicon.ico differ diff --git a/jest-preprocessor.js b/scripts/jest-preprocessor.js similarity index 100% rename from jest-preprocessor.js rename to scripts/jest-preprocessor.js diff --git a/lib/migrationRunner.js b/scripts/migration-runner.js similarity index 100% rename from lib/migrationRunner.js rename to scripts/migration-runner.js diff --git a/server.js b/server.js index 238ac698f7..e02a37d17a 100644 --- a/server.js +++ b/server.js @@ -1,10 +1,44 @@ -var app = require('./app'); 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 = { + baseUriPath: process.env.BASE_URI_PATH || '', + 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 server = app.listen(app.get('port'), function() { logger.info('unleash started on ' + app.get('port')); }); +if (app.get('env') === 'development') { + app.use(require('errorhandler')()); + + var webpack = require('webpack'); + var webpackDevMiddleware = require('webpack-dev-middleware'); + var webpackConfig = require('./webpack.config'); + var compiler = webpack(webpackConfig); + + app.use(config.baseUriPath, webpackDevMiddleware(compiler, { + publicPath: '/js', + noInfo: true + })); +} + process.on('uncaughtException', function(err) { logger.error('Uncaught Exception:', err.message); logger.error(err.stack); diff --git a/test/databaseConfig.js b/test/databaseConfig.js new file mode 100644 index 0000000000..5f80b7f2c5 --- /dev/null +++ b/test/databaseConfig.js @@ -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 +}; diff --git a/test/featureApiSpec.js b/test/featureApiSpec.js index 39d473429d..0ba1b70310 100644 --- a/test/featureApiSpec.js +++ b/test/featureApiSpec.js @@ -1,4 +1,5 @@ 'use strict'; +var logger = require('../lib/logger'); var assert = require('assert'); var specHelper = require('./specHelper'); var request = specHelper.request; @@ -32,6 +33,7 @@ describe('The features api', function () { }); it('cant get feature that dose not exist', function (done) { + logger.setLevel('FATAL'); request .get('/features/myfeature') .expect('Content-Type', /json/) @@ -47,6 +49,7 @@ describe('The features api', function () { }); it('creates new feature toggle with createdBy', function (done) { + logger.setLevel('FATAL'); request .post('/features') .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) { + logger.setLevel('FATAL'); request .post('/features') .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) { + logger.setLevel('FATAL'); request .put('/features/should-not-exist') .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) { + logger.setLevel('FATAL'); request .put('/features/featureY') .send({ name: 'featureY', enabled: true }) diff --git a/test/specHelper.js b/test/specHelper.js index 3f8f7552ca..7ad8c98790 100644 --- a/test/specHelper.js +++ b/test/specHelper.js @@ -1,12 +1,24 @@ 'use strict'; process.env.NODE_ENV = 'test'; -var Promise = require('bluebird'); -var request = require('supertest'); -var app = require('../app'); -var knex = require('../lib/dbPool'); -var featureDb = require('../lib/featureDb'); -var strategyDb = require('../lib/strategyDb'); +var Promise = require('bluebird'); +var request = require('supertest'); +var databaseUri = require('./databaseConfig').getDatabaseUri(); +var knex = require('../lib/db/dbPool')(databaseUri); +var eventDb = require('../lib/db/event')(knex); +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); request = request(app);