diff --git a/portal-api/lib/data.js b/portal-api/lib/data.js deleted file mode 100644 index c3998f7e..00000000 --- a/portal-api/lib/data.js +++ /dev/null @@ -1,86 +0,0 @@ -'use strict'; - -const Examples = require('./models/examples'); - - -module.exports = class Data { - constructor (options) { - this._options = options; - } - - createDeployment (deployment) { - return new Promise((resolve, reject) => { - resolve(Examples.deployment); - }); - } - getDeployment (id) { - return new Promise((resolve, reject) => { - resolve(Examples.deployment); - }); - } - updateDeployment (deployment) { - return new Promise((resolve, reject) => { - resolve(Examples.deployment); - }); - } - deleteDeployment (id) { - return new Promise((resolve, reject) => { - resolve(); - }); - } - getDeployments () { - return new Promise((resolve, reject) => { - resolve(Examples.deployments); - }); - } - - getDatacenters () { - return new Promise((resolve, reject) => { - resolve(Examples.datacenters); - }); - } - - createManifest (deploymentId, manifest) { - return new Promise((resolve, reject) => { - resolve(Examples.manifest); - }); - } - getManifest (deploymentId, revision) { - return new Promise((resolve, reject) => { - resolve(Examples.manifest); - }); - } - - getActivities (deploymentId) { - return new Promise((resolve, reject) => { - resolve(Examples.activities); - }); - } - getMetrics (deploymentId) { - return new Promise((resolve, reject) => { - resolve(Examples.metrics); - }); - } - - getState (deploymentId) { - return new Promise((resolve, reject) => { - resolve(Examples.state); - }); - } - updateState (deploymentId, action) { - return new Promise((resolve, reject) => { - resolve(Examples.state); - }); - } - - getServices (deploymentId) { - return new Promise((resolve, reject) => { - resolve(Examples.services); - }); - } - updateService (deploymentId, service) { - return new Promise((resolve, reject) => { - resolve(Examples.service); - }); - } -}; diff --git a/portal-api/lib/data/index.js b/portal-api/lib/data/index.js new file mode 100644 index 00000000..aae0330e --- /dev/null +++ b/portal-api/lib/data/index.js @@ -0,0 +1,156 @@ +'use strict'; + +const Hoek = require('hoek'); +const Penseur = require('penseur'); + + +const internals = { + defaults: { + name: 'portal' + } +}; + + +module.exports = class Data { + constructor (options) { + const settings = Hoek.applyToDefaults(options || {}, internals.defaults); + const name = settings.name; + delete settings.name; + + // Penseur will assert that the options are correct + this._db = new Penseur.Db(name, settings); + } + + connect (cb) { + this._db.establish(['activities', 'datacenters', 'deployments', 'metrics'], cb); + } + + createDeployment (deployment) { + return new Promise((resolve, reject) => { + this._db.deployments.insert(deployment, (err, key) => { + if (err) { + return reject(err); + } + + deployment.id = key; + + resolve(deployment); + }); + }); + } + + getDeployment (id) { + return new Promise((resolve, reject) => { + this._db.deployments.get(id, (err, deployment) => { + return err ? reject(err) : resolve(deployment); + }); + }); + } + + updateDeployment (deployment) { + return new Promise((resolve, reject) => { + this._db.deployments.update(deployment.id, deployment, (err) => { + return err ? reject(err) : resolve(deployment); + }); + }); + } + + deleteDeployment (id) { + return new Promise((resolve, reject) => { + this._db.deployments.remove(id, (err) => { + return err ? reject(err) : resolve(); + }); + }); + } + + getDeployments () { + return new Promise((resolve, reject) => { + this._db.deployments.all((err, deployments) => { + return err ? reject(err) : resolve(deployments); + }); + }); + } + + getDatacenters () { + return new Promise((resolve, reject) => { + this._db.datacenters.all((err, datacenters) => { + return err ? reject(err) : resolve(datacenters); + }); + }); + } + + createManifest (deploymentId, manifest) { + return new Promise((resolve, reject) => { + this._db.deployments.update(deploymentId, { manifests: this._db.append(manifest) }, (err, deployment) => { + return err ? reject(err) : resolve(deployment); + }); + }); + } + getManifest (deploymentId, revision) { + return new Promise((resolve, reject) => { + this._db.deployments.get(deploymentId, { filter: 'manifests', from: revision, count: 1 }, (err, manifest) => { + return err ? reject(err) : resolve(manifest); + }); + }); + } + + getActivities (deploymentId) { + return new Promise((resolve, reject) => { + this._db.activities.query({ deploymentId }, (err, activities) => { + return err ? reject(err) : resolve(activities); + }); + }); + } + + getMetrics (deploymentId) { + return new Promise((resolve, reject) => { + this._db.metrics.query({ deploymentId }, (err, activities) => { + return err ? reject(err) : resolve(activities); + }); + }); + } + + getState (deploymentId) { + return new Promise((resolve, reject) => { + this._db.deployment.query({ id: deploymentId }, { filter: 'state' }, (err, state) => { + return err ? reject(err) : resolve(state); + }); + }); + } + updateState (deploymentId, action) { + return new Promise((resolve, reject) => { + const changes = { state: { action } }; + this._db.deployment.update(deploymentId, changes, (err, keys) => { + if (err) { + return reject(err); + } + + this._db.deployment.get(deploymentId, { filter: 'state' }, (err, state) => { + return err ? reject(err) : resolve(state); + }); + }); + }); + } + + getServices (deploymentId) { + return new Promise((resolve, reject) => { + this._db.deployment.get(deploymentId, { filter: 'services' }, (err, services) => { + return err ? reject(err) : resolve(services); + }); + }); + } + updateService (deploymentId, service) { + return new Promise((resolve, reject) => { + const changes = { services: service }; + this._db.deployment.update(deploymentId, changes, (err, keys) => { + if (err) { + return reject(err); + } + + this._db.deployment.get(deploymentId, { filter: 'services' }, (err, services) => { + return err ? reject(err) : resolve(services); + }); + }); + }); + } +}; diff --git a/portal-api/lib/handlers.js b/portal-api/lib/handlers.js index 4bb84669..bb25bbae 100644 --- a/portal-api/lib/handlers.js +++ b/portal-api/lib/handlers.js @@ -18,7 +18,7 @@ exports.deploymentGet = function (request, reply) { exports.deploymentUpdate = function (request, reply) { const payload = request.payload; - payload.id = request.deploymentId; + payload.id = request.params.deploymentId; reply(this.updateDeployment(payload)); }; diff --git a/portal-api/lib/index.js b/portal-api/lib/index.js index f5eac30b..4948a393 100644 --- a/portal-api/lib/index.js +++ b/portal-api/lib/index.js @@ -9,38 +9,43 @@ const Routes = require('./routes'); module.exports = function (server, options, next) { const data = new Data(options.data); - server.bind(data); - - - server.register([ - { - register: GraphqlHapi.graphqlHapi, - options: { - path: '/graphql', - graphqlOptions: Graphql.options(data), - route: { - cors: true - } - } + data.connect((err) => { + if (err) { + return next(err); } - ]); - if (process.env.NODE_ENV === 'dev') { - server.register({ - register: GraphqlHapi.graphiqlHapi, - options: { - path: '/graphiql', - graphiqlOptions: Graphql.options(data), - route: { - cors: true + server.bind(data); + + server.register([ + { + register: GraphqlHapi.graphqlHapi, + options: { + path: '/graphql', + graphqlOptions: Graphql.options(data), + route: { + cors: true + } } } - }); - } + ]); - server.route(Routes); + if (process.env.NODE_ENV === 'dev') { + server.register({ + register: GraphqlHapi.graphiqlHapi, + options: { + path: '/graphiql', + graphiqlOptions: Graphql.options(data), + route: { + cors: true + } + } + }); + } - next(); + server.route(Routes); + + next(); + }); }; module.exports.attributes = { diff --git a/portal-api/lib/models/examples.js b/portal-api/lib/models/examples.js index 437a7c46..c41db949 100644 --- a/portal-api/lib/models/examples.js +++ b/portal-api/lib/models/examples.js @@ -26,7 +26,7 @@ exports.datacenters = [ exports.deployments = [{ - id: 42, + id: 'd1f6c3af-1180-46cc-8d3f-1e7e90e5795d', name: 'User Services', datacenter: 'us-sw-1' }]; diff --git a/portal-api/lib/models/index.js b/portal-api/lib/models/index.js index cb957dd4..a5bd1128 100644 --- a/portal-api/lib/models/index.js +++ b/portal-api/lib/models/index.js @@ -34,7 +34,7 @@ exports.datacenters = Joi.array().items(exports.datacenter).example(Examples.dat // Deployments -exports.deploymentId = Joi.number().required().description('ID of deployment group'); +exports.deploymentId = Joi.string().required().description('ID of deployment group'); exports.deploymentCreate = Joi.object({ name: Joi.string().required().description('Name of deployment group'), diff --git a/portal-api/package.json b/portal-api/package.json index 4c9000e3..9c3cfde1 100644 --- a/portal-api/package.json +++ b/portal-api/package.json @@ -5,6 +5,8 @@ "main": "./lib/index.js", "scripts": { "lint": "belly-button", + "rethinkdb-up": "docker run -d -p 8080:8080 -p 28015:28015 -p 29015:29015 --name rethinkdb rethinkdb", + "rethinkdb-down": "docker rm -f rethinkdb", "test": "npm run lint && lab -t 97" }, "keywords": [], @@ -23,6 +25,8 @@ "boom": "^4.3.1", "graphql": "^0.9.3", "graphql-server-hapi": "^0.7.2", - "joi": "^10.4.1" + "hoek": "^4.1.1", + "joi": "^10.4.1", + "penseur": "^7.8.1" } } diff --git a/portal-api/test/data.js b/portal-api/test/data.js new file mode 100644 index 00000000..e69de29b diff --git a/portal-api/test/index.js b/portal-api/test/index.js index 8a3c37e6..b28fdf97 100644 --- a/portal-api/test/index.js +++ b/portal-api/test/index.js @@ -14,11 +14,17 @@ const it = lab.it; const expect = Code.expect; +const internals = { + options: { data: { test: true, name: 'test' } } +}; + +internals.register = { register: PortalApi, options: internals.options }; + describe('portal-api plugin', () => { it('can be registered with hapi', (done) => { const server = new Hapi.Server(); server.connection(); - server.register(PortalApi, (err) => { + server.register(internals.register, (err) => { expect(err).to.not.exist(); done(); }); @@ -30,7 +36,7 @@ describe('deployments', () => { it('can be created', (done) => { const server = new Hapi.Server(); server.connection(); - server.register(PortalApi, (err) => { + server.register(internals.register, (err) => { expect(err).to.not.exist(); const payload = { name: 'User Services', @@ -48,16 +54,24 @@ describe('deployments', () => { it('can be updated', (done) => { const server = new Hapi.Server(); server.connection(); - server.register(PortalApi, (err) => { + server.register(internals.register, (err) => { expect(err).to.not.exist(); const payload = { name: 'User Services', datacenter: 'us-sw-1' }; - server.inject({ method: 'PUT', url: '/deployment/42', payload }, (res) => { - expect(res.statusCode).to.equal(200); - done(); + server.inject({ method: 'POST', url: '/deployment', payload }, (res) => { + expect(res.statusCode).to.equal(201); + expect(res.result.name).to.equal('User Services'); + res.result.name = 'Customer Services'; + const id = res.result.id; + delete res.result.id; + server.inject({ method: 'PUT', url: `/deployment/${id}`, payload: res.result }, (res) => { + expect(res.statusCode).to.equal(200); + expect(res.result.name).to.equal('Customer Services'); + done(); + }); }); }); }); @@ -65,7 +79,7 @@ describe('deployments', () => { it('can be retrieved', (done) => { const server = new Hapi.Server(); server.connection(); - server.register(PortalApi, (err) => { + server.register(internals.register, (err) => { expect(err).to.not.exist(); server.inject({ method: 'GET', url: '/deployment/42' }, (res) => { @@ -79,7 +93,7 @@ describe('deployments', () => { it('can be deleted', (done) => { const server = new Hapi.Server(); server.connection(); - server.register(PortalApi, (err) => { + server.register(internals.register, (err) => { expect(err).to.not.exist(); server.inject({ method: 'DELETE', url: '/deployment/42' }, (res) => { @@ -92,7 +106,7 @@ describe('deployments', () => { it('can all be retrieved', (done) => { const server = new Hapi.Server(); server.connection(); - server.register(PortalApi, (err) => { + server.register(internals.register, (err) => { expect(err).to.not.exist(); server.inject({ method: 'GET', url: '/deployments' }, (res) => { @@ -109,7 +123,7 @@ describe('datacenters', () => { it('can be retrieved', (done) => { const server = new Hapi.Server(); server.connection(); - server.register(PortalApi, (err) => { + server.register(internals.register, (err) => { expect(err).to.not.exist(); server.inject({ method: 'GET', url: '/datacenters' }, (res) => { @@ -126,7 +140,7 @@ describe('manifests', () => { it('can be created', (done) => { const server = new Hapi.Server(); server.connection(); - server.register(PortalApi, (err) => { + server.register(internals.register, (err) => { expect(err).to.not.exist(); const payload = { file: {} @@ -143,7 +157,7 @@ describe('manifests', () => { it('can be retrieved', (done) => { const server = new Hapi.Server(); server.connection(); - server.register(PortalApi, (err) => { + server.register(internals.register, (err) => { expect(err).to.not.exist(); server.inject({ method: 'GET', url: '/deployment/42/manifest/5' }, (res) => { @@ -160,7 +174,7 @@ describe('activities', () => { it('can be retrieved', (done) => { const server = new Hapi.Server(); server.connection(); - server.register(PortalApi, (err) => { + server.register(internals.register, (err) => { expect(err).to.not.exist(); server.inject({ method: 'GET', url: '/deployment/42/activities' }, (res) => { @@ -177,7 +191,7 @@ describe('metrics', () => { it('can be retrieved', (done) => { const server = new Hapi.Server(); server.connection(); - server.register(PortalApi, (err) => { + server.register(internals.register, (err) => { expect(err).to.not.exist(); server.inject({ method: 'GET', url: '/deployment/42/metrics' }, (res) => { @@ -194,7 +208,7 @@ describe('deployment state', () => { it('can be retrieved', (done) => { const server = new Hapi.Server(); server.connection(); - server.register(PortalApi, (err) => { + server.register(internals.register, (err) => { expect(err).to.not.exist(); server.inject({ method: 'GET', url: '/deployment/42/state' }, (res) => { @@ -207,7 +221,7 @@ describe('deployment state', () => { it('can be updated', (done) => { const server = new Hapi.Server(); server.connection(); - server.register(PortalApi, (err) => { + server.register(internals.register, (err) => { expect(err).to.not.exist(); const payload = { action: 'restart' @@ -226,7 +240,7 @@ describe('services', () => { it('can be retrieved', (done) => { const server = new Hapi.Server(); server.connection(); - server.register(PortalApi, (err) => { + server.register(internals.register, (err) => { expect(err).to.not.exist(); server.inject({ method: 'GET', url: '/deployment/42/services' }, (res) => { @@ -240,7 +254,7 @@ describe('services', () => { it('can be updated', (done) => { const server = new Hapi.Server(); server.connection(); - server.register(PortalApi, (err) => { + server.register(internals.register, (err) => { expect(err).to.not.exist(); const payload = { count: 3 @@ -260,7 +274,7 @@ describe('graphql', () => { it('route exists', (done) => { const server = new Hapi.Server(); server.connection(); - server.register(PortalApi, (err) => { + server.register(internals.register, (err) => { expect(err).to.not.exist(); const url = '/graphql?query=%7B%0A%20%20getDeployment(id%3A%201)%20%7B%0A%20%20%20%20id%0A%20%20%7D%0A%7D';