var express = require('express'); var bodyParser = require('body-parser'); var _ = require('lodash'); var di = require('di'); /** * @param {DillerConfig} config * @param {DillerMqttClient} mqttClient * @param {function(function(PgTx, DillerDao, Diller))} tx * @constructor */ function DillerWeb(config, mqttClient, tx) { var log = config.log(); var calls = []; var app; mqttClient.run('web'); /** * @param {HttpRes} res */ function genericErrorHandler(res) { return function (data) { if (typeof data === 'string') { data = {message: data}; } log.warn('fail', data); return res.status(500).json(data); }; } function deviceResponse(data) { var device = data[0]; device.properties = data[1]; return {device: device}; } /** * @param {HttpReq} req * @param {HttpRes} res */ function getDevices(req, res) { tx(function (pg, dao) { return dao.devices(); }).then(function (devices) { res.json({devices: devices}); }, genericErrorHandler(res)); } /** * @param {HttpReq} req * @param {HttpRes} res */ function getDevice(req, res) { tx(function (pg, dao) { var deviceId = req.params.deviceId; return pg.batch([ dao.deviceById(deviceId), dao.devicePropertiesByDeviceId(deviceId)] ); }).then(function (data) { res.json(deviceResponse(data)); }, genericErrorHandler(res)); } /** * @param {HttpReq} req * @param {HttpRes} res */ function patchDevice(req, res) { tx(function (pg, dao, diller) { var deviceId = req.params.deviceId; var body = req.body; var p; if (!body.attribute) { res.status(400).json({message: 'Required keys: "attribute" and "value".'}); } else if (body.attribute == 'name' || body.attribute == 'description') { var attributes = {}; attributes[body.attribute] = body.value; p = diller.updateDeviceAttributes(deviceId, attributes); } else { p = Promise.reject('Unsupported attribute: ' + body.attribute); } return p.then(function () { return pg.batch([ dao.deviceById(deviceId), dao.devicePropertiesByDeviceId(deviceId)] ); }); }).then(function (data) { var device = data[0]; device.properties = data[1]; res.json({device: device}); }, genericErrorHandler(res)); } /** * @param {HttpReq} req * @param {HttpRes} res */ function getValues(req, res) { tx(function (tx, dao) { var propertyId = req.params.propertyId; return dao.valuesByPropertyId(propertyId, 10); }).then(function (values) { res.json({values: values}); }, function (err) { log.warn('fail', err); res.status(500).json({message: 'fail'}); }); } /** * @param {HttpReq} req * @param {HttpRes} res */ function patchProperty(req, res) { tx(function (pg, dao, diller) { var propertyId = req.params.propertyId; return dao.devicePropertyById(propertyId) .then(function (property) { return dao.deviceById(property.device) .then(function (device) { return {property: property, device: device}; }); }) .then(function (data) { var property = data.property; var device = data.device; var body = req.body; var p; if (!body.attribute) { res.status(400).json({message: 'Required keys: "attribute" and "value".'}); } else if (body.attribute == 'name' || body.attribute == 'description') { var attributes = {}; attributes[body.attribute] = body.value; p = diller.updatePropertyAttributes(propertyId, attributes); var topic = '/diller/' + device.key + '/property/' + property.key + '/' + body.attribute; var opts = { retain: true }; mqttClient.publish(topic, body.value, opts); } else { p = Promise.reject('Unsupported attribute: ' + body.attribute); } return p.then(function () { return pg.batch([ dao.deviceById(property.device), dao.devicePropertiesByDeviceId(property.device)] ); }); }); }).then(function (data) { var device = data[0]; device.properties = data[1]; res.json({device: device}); }, genericErrorHandler(res)); } /** * @param {HttpReq} req * @param {HttpRes} res */ function getIndex(req, res) { var baseUrl = (req.headers['trygvis-prefix'] || '').replace(/\/$/, ''); res.render('index', {baseUrl: baseUrl + '/'}); } function init() { app = express(); app.use(bodyParser.urlencoded({extended: true})); app.use(bodyParser.json()); var api = express.Router(); /** * @param name * @param method * @param path * @param {function(HttpReq, HttpRes)} callback */ function addApi(name, method, path, callback) { api[method](path, callback); var layer = _.last(api.stack); var methodWithPayloads = { put: true, post: true, patch: true }; var keys = _.map(layer.keys, function (key) { return key.name; }); var hasPayload = methodWithPayloads[method]; if (hasPayload) { keys.push('payload'); } calls.push({ name: name, method: method, path: path, layer: layer, hasPayload: hasPayload, keys: keys }); } addApi('getDevices', 'get', '/device', getDevices); addApi('getDevice', 'get', '/device/:deviceId', getDevice); addApi('patchDevice', 'patch', '/device/:deviceId', patchDevice); addApi('getValues', 'get', '/property/:propertyId/values', getValues); addApi('patchProperty', 'patch', '/property/:propertyId/values', patchProperty); var templates = express.Router(); templates.get('/', getIndex); app.set('views', 'web/templates'); app.set('view engine', 'jade'); app.set('view cache', false); app.use('/api', api); app.use('/', templates); app.use(express.static('web/static')); } function listen() { log.info('Starting HTTP listener on ' + config.httpPort); app.listen(config.httpPort); } function generateRpc() { var s = 'function DillerRpc($http, DillerConfig) {\n' + ' var baseUrl = DillerConfig.baseUrl;\n' + '\n'; s += _.map(calls, function (call) { var s = ' function ' + call.name + '(' + call.keys.join(', ') + ') {\n' + ' var req = {};\n' + ' req.method = \'' + call.method + '\';\n' + ' req.url = baseUrl + \'/api' + call.path + '\';\n'; s += _.map(call.layer.keys, function (key) { return ' req.url = req.url.replace(/:' + key.name + '/, ' + key.name + ');\n' }).join('\n'); if (call.hasPayload) { s += ' req.data = payload;\n'; } s += ' return $http(req);\n' + ' }\n'; return s; }).join('\n'); s += '\n'; s += ' return {\n'; s += _.map(calls, function (call) { return ' ' + call.name + ': ' + call.name }).join(',\n'); s += '\n'; s += ' };\n'; s += '}\n'; s += '\n'; s += 'DillerRpcResolve = {};\n'; s += _.map(calls, function (call) { var s = ''; var args = ['DillerRpc']; if (call.keys.length > 0) { args.push('$route'); } s += 'DillerRpcResolve.' + call.name + ' = function(' + args.join(', ') + ') {\n'; args = _.map(call.keys, function (key) { return '$route.current.params.' + key; }); s += ' return DillerRpc.' + call.name + '(' + args.join(', ') + ');\n'; s += '};\n'; return s; }).join(''); return s; } return { init: init, listen: listen, generateRpc: generateRpc } } di.annotate(DillerWeb, new di.Inject( require('../DillerConfig'), require('../mqtt/DillerMqttClient'), require('../DillerTx'))); module.exports = DillerWeb;