Rename "roles" to "units". Put units in their own folders.

This commit is contained in:
Simo Kinnunen
2014-08-26 14:34:34 +09:00
parent 7d9d64ddcb
commit 3a9b193f68
63 changed files with 105 additions and 105 deletions

215
lib/units/app/index.js Normal file
View File

@@ -0,0 +1,215 @@
var http = require('http')
var express = require('express')
var validator = require('express-validator')
var cookieSession = require('cookie-session')
var bodyParser = require('body-parser')
var serveFavicon = require('serve-favicon')
var serveStatic = require('serve-static')
var csrf = require('csurf')
var Promise = require('bluebird')
var httpProxy = require('http-proxy')
var compression = require('compression')
var logger = require('../../util/logger')
var pathutil = require('../../util/pathutil')
var dbapi = require('../../db/api')
var datautil = require('../../util/datautil')
var auth = require('./middleware/auth')
var deviceIconMiddleware = require('./middleware/device-icons')
var browserIconMiddleware = require('./middleware/browser-icons')
var appstoreIconMiddleware = require('./middleware/appstore-icons')
var webpackServerConfig = require('./../../../webpack.config').webpackServer
module.exports = function(options) {
var log = logger.createLogger('app')
, app = express()
, server = http.createServer(app)
, proxy = httpProxy.createProxyServer()
proxy.on('error', function(err) {
log.error('Proxy had an error', err.stack)
})
app.set('view engine', 'jade')
app.set('views', pathutil.resource('app/views'))
app.set('strict routing', true)
app.set('case sensitive routing', true)
if (options.disableWatch) {
app.use(compression())
app.use('/static/app/build/entry',
serveStatic(pathutil.resource('build/entry')))
app.use('/static/app/build', serveStatic(pathutil.resource('build'), {
maxAge: '10d'
}))
}
else {
app.use('/static/app/build',
require('./middleware/webpack')(webpackServerConfig))
}
app.use('/static/bower_components',
serveStatic(pathutil.resource('bower_components')))
app.use('/intro',
serveStatic(pathutil.resource('bower_components/stf-site/intro')))
app.use('/manual-basic',
serveStatic(pathutil.resource('bower_components/stf-site/manual/basic')))
app.use('/manual-advanced',
serveStatic(pathutil.resource('bower_components/stf-site/manual/advanced')))
app.use('/v2-features',
serveStatic(pathutil.resource('bower_components/stf-site/v2-features')))
app.use('/static/app/data', serveStatic(pathutil.resource('data')))
app.use('/static/app/status', serveStatic(pathutil.resource('common/status')))
app.use('/static/app/browsers', browserIconMiddleware())
app.use('/static/app/appstores', appstoreIconMiddleware())
app.use('/static/app/devices', deviceIconMiddleware())
app.use('/static/app', serveStatic(pathutil.resource('app')))
app.use(serveFavicon(pathutil.resource(
'bower_components/stf-graphics/logo/exports/STF-128.png')))
app.use(cookieSession({
name: options.ssid
, keys: [options.secret]
}))
app.use(auth({
secret: options.secret
, authUrl: options.authUrl
}))
// Proxied requests must come before any body parsers. These proxies are
// here mainly for convenience, they should be replaced with proper reverse
// proxies in production.
app.all('/api/v1/s/image/*', function(req, res) {
proxy.web(req, res, {
target: options.storagePluginImageUrl
})
})
app.all('/api/v1/s/apk/*', function(req, res) {
proxy.web(req, res, {
target: options.storagePluginApkUrl
})
})
app.all('/api/v1/s/*', function(req, res) {
proxy.web(req, res, {
target: options.storageUrl
})
})
app.use(bodyParser.json())
app.use(csrf())
app.use(validator())
app.get('/', function(req, res) {
res.render('index')
})
app.get('/api/v1/appstate.js', function(req, res) {
res.type('application/javascript')
res.send('var GLOBAL_APPSTATE = ' + JSON.stringify({
config: {
websocketUrl: options.websocketUrl
}
, user: req.user
})
)
})
app.get('/api/v1/angular-appstate.js', function(req, res) {
res.type('application/javascript')
res.send('angular.module("stf.app-state")' +
'.config(function(AppState){AppState.set(' +
JSON.stringify({
config: {
websocketUrl: options.websocketUrl
}
, user: req.user
}) +
')})')
})
app.get('/api/v1/app/user', function(req, res) {
res.json({
success: true
, user: req.user
})
})
app.get('/api/v1/app/group', function(req, res) {
dbapi.loadGroup(req.user.email)
.then(function(cursor) {
return Promise.promisify(cursor.toArray, cursor)()
.then(function(list) {
list.forEach(function(device) {
datautil.normalize(device, req.user)
})
res.json({
success: true
, devices: list
})
})
})
.catch(function(err) {
log.error('Failed to load group: ', err.stack)
res.json(500, {
success: false
})
})
})
app.get('/api/v1/app/devices', function(req, res) {
dbapi.loadDevices()
.then(function(cursor) {
return Promise.promisify(cursor.toArray, cursor)()
.then(function(list) {
list.forEach(function(device) {
datautil.normalize(device, req.user)
})
res.json({
success: true
, devices: list
})
})
})
.catch(function(err) {
log.error('Failed to load device list: ', err.stack)
res.json(500, {
success: false
})
})
})
app.get('/api/v1/app/devices/:serial', function(req, res) {
dbapi.loadDevice(req.params.serial)
.then(function(device) {
if (device) {
datautil.normalize(device, req.user)
res.json({
success: true
, device: device
})
}
else {
res.json(404, {
success: false
})
}
})
.catch(function(err) {
log.error('Failed to load device "%s": ', req.params.serial, err.stack)
res.json(500, {
success: false
})
})
})
server.listen(options.port)
log.info('Listening on port %d', options.port)
}

View File

@@ -0,0 +1,12 @@
var serveStatic = require('serve-static')
var pathutil = require('../../../util/pathutil')
module.exports = function() {
return serveStatic(
pathutil.root('node_modules/stf-appstore-db/dist')
, {
maxAge: '30d'
}
)
}

View File

@@ -0,0 +1,50 @@
var jwtutil = require('../../../util/jwtutil')
var urlutil = require('../../../util/urlutil')
var dbapi = require('../../../db/api')
module.exports = function(options) {
return function(req, res, next) {
if (req.query.jwt) {
// Coming from auth client
var data = jwtutil.decode(req.query.jwt, options.secret)
, redir = urlutil.removeParam(req.url, 'jwt')
if (data) {
// Redirect once to get rid of the token
dbapi.saveUserAfterLogin({
name: data.name
, email: data.email
, ip: req.ip
})
.then(function() {
req.session.jwt = data
res.redirect(redir)
})
.catch(next)
}
else {
// Invalid token, forward to auth client
res.redirect(options.authUrl)
}
}
else if (req.session && req.session.jwt) {
dbapi.loadUser(req.session.jwt.email)
.then(function(user) {
if (user) {
// Continue existing session
req.user = user
next()
}
else {
// We no longer have the user in the database
res.redirect(options.authUrl)
}
})
.catch(next)
}
else {
// No session, forward to auth client
res.redirect(options.authUrl)
}
}
}

View File

@@ -0,0 +1,12 @@
var serveStatic = require('serve-static')
var pathutil = require('../../../util/pathutil')
module.exports = function() {
return serveStatic(
pathutil.root('node_modules/stf-browser-db/dist')
, {
maxAge: '30d'
}
)
}

View File

@@ -0,0 +1,12 @@
var serveStatic = require('serve-static')
var pathutil = require('../../../util/pathutil')
module.exports = function() {
return serveStatic(
pathutil.root('node_modules/stf-device-db/dist')
, {
maxAge: '30d'
}
)
}

View File

@@ -0,0 +1,109 @@
var path = require('path')
var url = require('url')
var webpack = require('webpack')
var mime = require('mime')
var Promise = require('bluebird')
var _ = require('lodash')
var MemoryFileSystem = require('webpack/node_modules/memory-fs')
var logger = require('../../../util/logger')
var lifecycle = require('../../../util/lifecycle')
var globalOptions = require('../../../../webpack.config').webpack
// Similar to webpack-dev-middleware, but integrates with our custom
// lifecycle, behaves more like normal express middleware, and removes
// all unnecessary features.
module.exports = function(options) {
var log = logger.createLogger('middleware:webpack')
options = _.defaults(options || {}, globalOptions)
var compiler = webpack(options)
var fs = compiler.outputFileSystem = new MemoryFileSystem()
var valid = false
var queue = []
log.info('Creating bundle')
var watching = compiler.watch(options.watchDelay, function(err) {
if (err) {
log.fatal('Webpack had an error', err.stack)
lifecycle.fatal()
}
})
lifecycle.observe(function() {
if (watching.watcher) {
watching.watcher.close()
}
})
function doneListener(stats) {
process.nextTick(function() {
if (valid) {
log.info(stats.toString(options.stats))
if (stats.hasErrors()) {
log.error('Bundle has errors')
}
else if (stats.hasWarnings()) {
log.warn('Bundle has warnings')
}
else {
log.info('Bundle is now valid')
}
queue.forEach(function(resolver) {
resolver.resolve()
})
}
})
valid = true
}
function invalidate() {
if (valid) {
log.info('Bundle is now invalid')
valid = false
}
}
compiler.plugin('done', doneListener)
compiler.plugin('invalid', invalidate)
compiler.plugin('compile', invalidate)
function bundle() {
if (valid) {
return Promise.resolve()
}
else {
log.info('Waiting for bundle to finish')
var resolver = Promise.defer()
queue.push(resolver)
return resolver.promise
}
}
return function(req, res, next) {
var parsedUrl = url.parse(req.url)
var target = path.join(
compiler.outputPath
, parsedUrl.pathname
)
bundle()
.then(function() {
try {
var body = fs.readFileSync(target)
res.set('Content-Type', mime.lookup(target))
res.end(body)
}
catch (err) {
return next()
}
})
.catch(next)
}
}