diff --git a/src/domains.js b/src/domains.js new file mode 100644 index 000000000..5b97511a3 --- /dev/null +++ b/src/domains.js @@ -0,0 +1,111 @@ +'use strict'; + +module.exports = exports = { + add: add, + get: get, + getAll: getAll, + update: update, + del: del, + + DomainError: DomainError +}; + +var assert = require('assert'), + DatabaseError = require('./databaseerror.js'), + domaindb = require('./domaindb.js'), + util = require('util'); + +function DomainError(reason, errorOrMessage) { + assert.strictEqual(typeof reason, 'string'); + assert(errorOrMessage instanceof Error || typeof errorOrMessage === 'string' || typeof errorOrMessage === 'undefined'); + + Error.call(this); + Error.captureStackTrace(this, this.constructor); + + this.name = this.constructor.name; + this.reason = reason; + if (typeof errorOrMessage === 'undefined') { + this.message = reason; + } else if (typeof errorOrMessage === 'string') { + this.message = errorOrMessage; + } else { + this.message = 'Internal error'; + this.nestedError = errorOrMessage; + } +} +util.inherits(DomainError, Error); + +DomainError.NOT_FOUND = 'No such domain'; +DomainError.ALREADY_EXISTS = 'Domain already exists'; +DomainError.EXTERNAL_ERROR = 'External error'; +DomainError.BAD_FIELD = 'Bad Field'; +DomainError.STILL_BUSY = 'Still busy'; +DomainError.INTERNAL_ERROR = 'Internal error'; +DomainError.ACCESS_DENIED = 'Access denied'; +DomainError.INVALID_PROVIDER = 'provider must be route53, gcdns, digitalocean, cloudflare, noop, manual or caas'; + +function add(domain, config, callback) { + assert.strictEqual(typeof domain, 'string'); + assert.strictEqual(typeof config, 'object'); + assert.strictEqual(typeof callback, 'function'); + + // TODO validate domain and config + + domaindb.add(domain, config, function (error) { + if (error && error.reason === DatabaseError.ALREADY_EXISTS) return callback(new DomainError(DomainError.ALREADY_EXISTS)); + if (error) return callback(new DomainError(DomainError.INTERNAL_ERROR, error)); + + return callback(null); + }); +} + +function get(domain, callback) { + assert.strictEqual(typeof domain, 'string'); + assert.strictEqual(typeof callback, 'function'); + + domaindb.get(domain, function (error, result) { + if (error && error.reason === DatabaseError.NOT_FOUND) return callback(new DomainError(DomainError.NOT_FOUND)); + if (error) return callback(new DomainError(DomainError.INTERNAL_ERROR, error)); + + return callback(null, result); + }); +} + +function getAll(callback) { + assert.strictEqual(typeof callback, 'function'); + + domaindb.getAll(function (error, result) { + if (error) return callback(new DomainError(DomainError.INTERNAL_ERROR, error)); + + return callback(null, result); + }); +} + +function update(domain, config, callback) { + assert.strictEqual(typeof domain, 'string'); + assert.strictEqual(typeof config, 'object'); + assert.strictEqual(typeof callback, 'function'); + + // TODO validate config + + domaindb.update(domain, config, function (error) { + if (error && error.reason === DatabaseError.NOT_FOUND) return callback(new DomainError(DomainError.NOT_FOUND)); + if (error) return callback(new DomainError(DomainError.INTERNAL_ERROR, error)); + + return callback(null); + }); +} + +function del(domain, callback) { + assert.strictEqual(typeof domain, 'string'); + assert.strictEqual(typeof callback, 'function'); + + // TODO check if domain is still used by an app + + domaindb.del(domain, function (error) { + if (error && error.reason === DatabaseError.NOT_FOUND) return callback(new DomainError(DomainError.NOT_FOUND)); + if (error) return callback(new DomainError(DomainError.INTERNAL_ERROR, error)); + + return callback(null); + }); +} diff --git a/src/routes/domains.js b/src/routes/domains.js new file mode 100644 index 000000000..10c81f7ba --- /dev/null +++ b/src/routes/domains.js @@ -0,0 +1,75 @@ +'use strict'; + +exports = module.exports = { + add: add, + get: get, + getAll: getAll, + update: update, + del: del +}; + +var assert = require('assert'), + domains = require('../domains.js'), + DomainError = domains.DomainError, + HttpError = require('connect-lastmile').HttpError, + HttpSuccess = require('connect-lastmile').HttpSuccess; + +function add(req, res, next) { + assert.strictEqual(typeof req.body, 'object'); + + if (typeof req.body.domain !== 'string') return next(new HttpError(400, 'domain must be a string')); + if (typeof req.body.config !== 'object') return next(new HttpError(400, 'config must be an object')); + + domains.add(req.body.domain, req.body.config, function (error) { + if (error && error.reason === DomainError.ALREADY_EXISTS) return next(new HttpError(400, error.message)); + if (error && error.reason === DomainError.BAD_FIELD) return next(new HttpError(400, error.message)); + if (error && error.reason === DomainError.INVALID_PROVIDER) return next(new HttpError(400, error.message)); + if (error) return next(new HttpError(500, error)); + + next(new HttpSuccess(201, { domain: req.body.domain, config: req.body.config })); + }); +} + +function get(req, res, next) { + assert.strictEqual(typeof req.params.domain, 'string'); + + domains.get(req.params.domain, function (error, result) { + if (error && error.reason === DomainError.NOT_FOUND) return next(new HttpError(404, error.message)); + if (error) return next(new HttpError(500, error)); + + next(new HttpSuccess(200, result)); + }); +} + +function getAll(req, res, next) { + domains.getAll(function (error, result) { + if (error) return next(new HttpError(500, error)); + + next(new HttpSuccess(200, { domains: result })); + }); +} + +function update(req, res, next) { + assert.strictEqual(typeof req.params.domain, 'string'); + assert.strictEqual(typeof req.body, 'object'); + + if (typeof req.body.config !== 'object') return next(new HttpError(400, 'config must be an object')); + + domains.update(req.params.domain, req.body.config, function (error) { + if (error && error.reason === DomainError.NOT_FOUND) return next(new HttpError(404, error.message)); + if (error) return next(new HttpError(500, error)); + + next(new HttpSuccess(204, {})); + }); +} + +function del(req, res, next) { + assert.strictEqual(typeof req.params.domain, 'string'); + + domains.del(req.params.domain, function (error) { + if (error && error.reason === DomainError.NOT_FOUND) return next(new HttpError(404, error.message)); + if (error) return next(new HttpError(500, error)); + + next(new HttpSuccess(204, {})); + }); +} diff --git a/src/routes/index.js b/src/routes/index.js index 58c1b64d0..5b86cf128 100644 --- a/src/routes/index.js +++ b/src/routes/index.js @@ -6,6 +6,7 @@ exports = module.exports = { clients: require('./clients.js'), cloudron: require('./cloudron.js'), developer: require('./developer.js'), + domains: require('./domains.js'), eventlog: require('./eventlog.js'), graphs: require('./graphs.js'), groups: require('./groups.js'), diff --git a/src/server.js b/src/server.js index be2fcb53f..10eb7ebd2 100644 --- a/src/server.js +++ b/src/server.js @@ -235,6 +235,13 @@ function initializeExpressSync() { router.get ('/api/v1/backups', settingsScope, routes.user.requireAdmin, routes.backups.get); router.post('/api/v1/backups', settingsScope, routes.user.requireAdmin, routes.backups.create); + // domain routes + router.post('/api/v1/domains', settingsScope, routes.user.requireAdmin, routes.domains.add); + router.get ('/api/v1/domains', settingsScope, routes.user.requireAdmin, routes.domains.getAll); + router.get ('/api/v1/domains/:domain', settingsScope, routes.user.requireAdmin, routes.domains.get); + router.put ('/api/v1/domains/:domain', settingsScope, routes.user.requireAdmin, routes.domains.update); + router.del ('/api/v1/domains/:domain', settingsScope, routes.user.requireAdmin, routes.domains.del); + // disable server socket "idle" timeout. we use the timeout middleware to handle timeouts on a route level // we rely on nginx for timeouts on the TCP level (see client_header_timeout) httpServer.setTimeout(0); diff --git a/src/subdomains.js b/src/subdomains.js index 83b78a1df..25a3ca34d 100644 --- a/src/subdomains.js +++ b/src/subdomains.js @@ -37,6 +37,7 @@ function SubdomainError(reason, errorOrMessage) { util.inherits(SubdomainError, Error); SubdomainError.NOT_FOUND = 'No such domain'; +SubdomainError.ALREADY_EXISTS = 'Domain already exists'; SubdomainError.EXTERNAL_ERROR = 'External error'; SubdomainError.BAD_FIELD = 'Bad Field'; SubdomainError.STILL_BUSY = 'Still busy';