Implemented basic sup text support
This commit is contained in:
parent
c3e93ac603
commit
0d4fa14fec
35 changed files with 20292 additions and 3 deletions
7
.babelrc
Normal file
7
.babelrc
Normal file
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"presets": ["es2015-node5"],
|
||||
"plugins": [
|
||||
"transform-async-to-generator",
|
||||
"syntax-async-functions"
|
||||
]
|
||||
}
|
33
.eslintrc
Normal file
33
.eslintrc
Normal file
|
@ -0,0 +1,33 @@
|
|||
{
|
||||
"parser": "babel-eslint",
|
||||
"extends": "airbnb/base",
|
||||
"ecmaFeatures": {
|
||||
"modules": false
|
||||
},
|
||||
"plugins": [
|
||||
"mocha"
|
||||
],
|
||||
"rules": {
|
||||
"mocha/no-exclusive-tests": 2,
|
||||
"semi": [2, "never"],
|
||||
"max-len": [1, 120],
|
||||
"prefer-const": 0,
|
||||
"consistent-return": 0,
|
||||
"no-param-reassign": [2, {"props": false}],
|
||||
"no-use-before-define": [2, {"functions": false, "classes": true}],
|
||||
"no-unused-vars": [
|
||||
2,
|
||||
{
|
||||
"args": "none"
|
||||
}
|
||||
]
|
||||
},
|
||||
"globals": {
|
||||
"describe": false,
|
||||
"it": false,
|
||||
"before": false,
|
||||
"beforeEach": false,
|
||||
"after": false,
|
||||
"afterEach": false
|
||||
}
|
||||
}
|
41
app/client.js
Normal file
41
app/client.js
Normal file
|
@ -0,0 +1,41 @@
|
|||
const socket = require('./socket')
|
||||
|
||||
socket.on('client.display', (data) => {
|
||||
let exists = document.getElementById(data.key)
|
||||
|
||||
if (exists) {
|
||||
exists.tag.remove()
|
||||
exists.remove()
|
||||
}
|
||||
|
||||
let element = document.createElement('div')
|
||||
element.innerHTML = data.html
|
||||
element.id = data.key
|
||||
element.classList.add('root-element')
|
||||
|
||||
let styleElement = document.createElement('style')
|
||||
styleElement.setAttribute('type', 'text/css')
|
||||
styleElement.innerHTML = data.css
|
||||
|
||||
element.tag = styleElement
|
||||
|
||||
document.body.appendChild(element)
|
||||
document.head.appendChild(styleElement)
|
||||
|
||||
window.setTimeout(() => {
|
||||
element.classList.add('root-element-display')
|
||||
}, 50)
|
||||
})
|
||||
|
||||
socket.on('client.hide', (data) => {
|
||||
let exists = document.getElementById(data.key)
|
||||
|
||||
if (exists) {
|
||||
exists.classList.remove('root-element-display')
|
||||
|
||||
window.setTimeout(() => {
|
||||
exists.tag.remove()
|
||||
exists.remove()
|
||||
}, 1500)
|
||||
}
|
||||
})
|
98
app/controller/content.js
Normal file
98
app/controller/content.js
Normal file
|
@ -0,0 +1,98 @@
|
|||
const m = require('mithril')
|
||||
const socket = require('../socket')
|
||||
const store = require('./store')
|
||||
|
||||
const Content = { }
|
||||
|
||||
Content.vm = (function() {
|
||||
let vm = {}
|
||||
|
||||
vm.storeUpdated = function() {
|
||||
vm.content = store.get('content') || {}
|
||||
m.redraw()
|
||||
}
|
||||
|
||||
vm.init = function() {
|
||||
vm.content = store.get('content') || {}
|
||||
store.listen('content', vm.storeUpdated)
|
||||
}
|
||||
|
||||
vm.onunload = function() {
|
||||
store.unlisten('content')
|
||||
}
|
||||
|
||||
vm.updated = function(name, control) {
|
||||
vm.content[name] = control.target.value
|
||||
store.set('content', vm.content)
|
||||
}
|
||||
|
||||
vm.display = function() {
|
||||
socket.emit('content.display', vm.content)
|
||||
}
|
||||
|
||||
vm.hide = function() {
|
||||
socket.emit('content.hide')
|
||||
}
|
||||
|
||||
return vm
|
||||
})()
|
||||
|
||||
Content.controller = function() {
|
||||
Content.vm.init()
|
||||
|
||||
this.onunload = Content.vm.onunload
|
||||
}
|
||||
|
||||
Content.view = function() {
|
||||
return m('div', [
|
||||
m('h3', 'Content'),
|
||||
m('div', { class: 'row' }, [
|
||||
m('div', { class: 'small-12 columns' }, [
|
||||
m('label', [
|
||||
'HTML (use <%- name %> and <%- title %> for values)',
|
||||
m('textarea', {
|
||||
rows: '4',
|
||||
oninput: Content.vm.updated.bind(null, 'html'),
|
||||
value: Content.vm.content.html || '',
|
||||
})
|
||||
]),
|
||||
]),
|
||||
m('div', { class: 'small-12 columns' }, [
|
||||
m('label', [
|
||||
'CSS',
|
||||
m('textarea', {
|
||||
rows: '4',
|
||||
oninput: Content.vm.updated.bind(null, 'css'),
|
||||
value: Content.vm.content.css || '',
|
||||
})
|
||||
]),
|
||||
]),
|
||||
m('div', { class: 'small-12 columns' }, [
|
||||
m('label', [
|
||||
'Name',
|
||||
m('input[type=text]', {
|
||||
oninput: Content.vm.updated.bind(null, 'name'),
|
||||
value: Content.vm.content.name || '',
|
||||
})
|
||||
]),
|
||||
]),
|
||||
m('div', { class: 'small-12 columns' }, [
|
||||
m('label', [
|
||||
'Title',
|
||||
m('input[type=text]', {
|
||||
oninput: Content.vm.updated.bind(null, 'title'),
|
||||
value: Content.vm.content.title || '',
|
||||
})
|
||||
]),
|
||||
]),
|
||||
m('a.button', {
|
||||
onclick: Content.vm.display
|
||||
}, 'Display'),
|
||||
m('a.button.alert', {
|
||||
onclick: Content.vm.hide
|
||||
}, 'Hide'),
|
||||
]),
|
||||
])
|
||||
}
|
||||
|
||||
module.exports = Content
|
23
app/controller/menu.js
Normal file
23
app/controller/menu.js
Normal file
|
@ -0,0 +1,23 @@
|
|||
const m = require('mithril')
|
||||
|
||||
const Menu = {
|
||||
controller: function() {
|
||||
return {}
|
||||
},
|
||||
|
||||
view: function(ctrl) {
|
||||
return m('div', [
|
||||
m('h3', 'Menu'),
|
||||
m('ul', [
|
||||
m('li', [
|
||||
m('a', { href: '/', config: m.route }, 'Home'),
|
||||
]),
|
||||
m('li', [
|
||||
m('a', { href: '/content', config: m.route }, 'Content'),
|
||||
])
|
||||
]),
|
||||
])
|
||||
},
|
||||
}
|
||||
|
||||
module.exports = Menu
|
39
app/controller/store.js
Normal file
39
app/controller/store.js
Normal file
|
@ -0,0 +1,39 @@
|
|||
const socket = require('../socket')
|
||||
const storage = {}
|
||||
const events = {}
|
||||
|
||||
const store = {
|
||||
get: function(name) {
|
||||
return storage[name]
|
||||
},
|
||||
|
||||
set: function(name, value, dontSend) {
|
||||
storage[name] = value
|
||||
|
||||
if (dontSend) {
|
||||
if (events[name]) {
|
||||
events[name]()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
socket.emit('store', {
|
||||
name,
|
||||
value,
|
||||
})
|
||||
},
|
||||
|
||||
listen: function(name, caller) {
|
||||
events[name] = caller
|
||||
},
|
||||
|
||||
unlisten: function(name) {
|
||||
delete events[name]
|
||||
},
|
||||
}
|
||||
|
||||
socket.on('store', (data) => {
|
||||
store.set(data.name, data.value, true)
|
||||
})
|
||||
|
||||
module.exports = store
|
27
app/main.js
Normal file
27
app/main.js
Normal file
|
@ -0,0 +1,27 @@
|
|||
/**
|
||||
* @license
|
||||
* caspar-sup <https://filadelfia.is>
|
||||
* Copyright 2015 Jonatan Nilsson <http://jonatan.nilsson.is/>
|
||||
*
|
||||
* Available under WTFPL License (http://www.wtfpl.net/txt/copying/)
|
||||
*/
|
||||
|
||||
'use strict'
|
||||
|
||||
//Add debug components to window. Allows us to play with controls
|
||||
//in the console.
|
||||
window.components = {}
|
||||
|
||||
require('./socket')
|
||||
require('./controller/store')
|
||||
|
||||
const m = require('mithril')
|
||||
const Menu = require('./controller/menu')
|
||||
const Content = require('./controller/content')
|
||||
|
||||
m.mount(document.getElementById('menu'), Menu)
|
||||
|
||||
m.route(document.getElementById('content'), '/', {
|
||||
'/': {},
|
||||
'/content': Content,
|
||||
});
|
5
app/socket.js
Normal file
5
app/socket.js
Normal file
|
@ -0,0 +1,5 @@
|
|||
const io = require('socket.io-client')
|
||||
|
||||
const socket = io()
|
||||
|
||||
module.exports = socket
|
60
config.js
Normal file
60
config.js
Normal file
|
@ -0,0 +1,60 @@
|
|||
import _ from 'lodash'
|
||||
import nconf from 'nconf'
|
||||
const pckg = require('./package.json')
|
||||
|
||||
// Helper method for global usage.
|
||||
nconf.inTest = () => nconf.get('NODE_ENV') === 'test'
|
||||
|
||||
// Config follow the following priority check order:
|
||||
// 1. Arguments
|
||||
// 2. package.json
|
||||
// 3. Enviroment variables
|
||||
// 4. config/config.json
|
||||
// 5. default settings
|
||||
|
||||
|
||||
// Load arguments as highest priority
|
||||
nconf.argv()
|
||||
|
||||
|
||||
// Load package.json for name and such
|
||||
let project = _.pick(pckg, ['name', 'version', 'description', 'author', 'license', 'homepage'])
|
||||
|
||||
|
||||
// If we have global.it, there's a huge chance
|
||||
// we're in test mode so we force node_env to be test.
|
||||
if (typeof global.it === 'function') {
|
||||
project.NODE_ENV = 'test'
|
||||
}
|
||||
|
||||
|
||||
// Load overrides as second priority
|
||||
nconf.overrides(project)
|
||||
|
||||
|
||||
// Load enviroment variables as third priority
|
||||
nconf.env()
|
||||
|
||||
|
||||
// Load any overrides from the appropriate config file
|
||||
let configFile = 'config/config.json'
|
||||
|
||||
if (nconf.get('NODE_ENV') === 'test') {
|
||||
configFile = 'config/config.test.json'
|
||||
}
|
||||
|
||||
|
||||
nconf.file('main', configFile)
|
||||
|
||||
// Load defaults
|
||||
nconf.file('default', 'config/config.default.json')
|
||||
|
||||
|
||||
// Final sanity checks
|
||||
/* istanbul ignore if */
|
||||
if (typeof global.it === 'function' & !nconf.inTest()) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log('Critical: potentially running test on production enviroment. Shutting down.')
|
||||
process.exit(1)
|
||||
}
|
||||
module.exports = nconf
|
23
config/config.default.json
Normal file
23
config/config.default.json
Normal file
|
@ -0,0 +1,23 @@
|
|||
{
|
||||
"NODE_ENV": "development",
|
||||
"server": {
|
||||
"port": 3000,
|
||||
"host": "0.0.0.0"
|
||||
},
|
||||
"bunyan": {
|
||||
"name": "keywe",
|
||||
"streams": [{
|
||||
"stream": "process.stdout",
|
||||
"level": "debug"
|
||||
}
|
||||
]
|
||||
},
|
||||
"knex": {
|
||||
"client": "sqlite3",
|
||||
"connection": {
|
||||
"filename" : "./db.sqlite"
|
||||
},
|
||||
"migrations": {
|
||||
}
|
||||
}
|
||||
}
|
1
config/config.json
Normal file
1
config/config.json
Normal file
|
@ -0,0 +1 @@
|
|||
{}
|
BIN
db.sqlite
Normal file
BIN
db.sqlite
Normal file
Binary file not shown.
38
index.js
Normal file
38
index.js
Normal file
|
@ -0,0 +1,38 @@
|
|||
'use strict'
|
||||
require('babel-register')
|
||||
|
||||
let log = require('./log').default
|
||||
|
||||
function exitHandler(options, err) {
|
||||
if (options.cleanup) {
|
||||
log.warn('Application is shutting down')
|
||||
}
|
||||
if (err) {
|
||||
log.error('An unhandled error occured')
|
||||
log.error(err)
|
||||
}
|
||||
if (options.exit) {
|
||||
log.warn('Application is exiting')
|
||||
process.exit()
|
||||
}
|
||||
}
|
||||
|
||||
// do something when app is closing
|
||||
process.on('exit', exitHandler.bind(null, { cleanup: true }))
|
||||
|
||||
// catches ctrl+c event
|
||||
process.on('SIGINT', exitHandler.bind(null, { exit: true }))
|
||||
|
||||
// catches uncaught exceptions
|
||||
process.on('uncaughtException', exitHandler.bind(null, { exit: true }))
|
||||
|
||||
// Run the database script automatically.
|
||||
log.info('Running database integrity scan.')
|
||||
let setup = require('./script/setup')
|
||||
|
||||
setup().then(() => {
|
||||
require('./server')
|
||||
}).catch((error) => {
|
||||
log.error(error, 'Error while preparing database')
|
||||
process.exit(1)
|
||||
})
|
15
knexfile.js
Normal file
15
knexfile.js
Normal file
|
@ -0,0 +1,15 @@
|
|||
'use strict'
|
||||
require('babel-register')
|
||||
|
||||
const _ = require('lodash')
|
||||
const config = require('./config')
|
||||
|
||||
let out = {}
|
||||
|
||||
// This is important for setup to run cleanly.
|
||||
let knexConfig = _.cloneDeep(config.get('knex'))
|
||||
knexConfig.pool = { min: 1, max: 1 }
|
||||
|
||||
out[config.get('NODE_ENV')] = knexConfig
|
||||
|
||||
module.exports = out
|
20
log.js
Normal file
20
log.js
Normal file
|
@ -0,0 +1,20 @@
|
|||
import _ from 'lodash'
|
||||
import bunyan from 'bunyan'
|
||||
import config from './config'
|
||||
|
||||
// Clone the settings as we will be touching
|
||||
// on them slightly.
|
||||
let settings = _.cloneDeep(config.get('bunyan'))
|
||||
|
||||
// Replace any instance of 'process.stdout' with the
|
||||
// actual reference to the process.stdout.
|
||||
for (let i = 0; i < settings.streams.length; i++) {
|
||||
if (settings.streams[i].stream === 'process.stdout') {
|
||||
settings.streams[i].stream = process.stdout
|
||||
}
|
||||
}
|
||||
|
||||
// Create our logger.
|
||||
const logger = bunyan.createLogger(settings)
|
||||
|
||||
export default logger
|
23
migrations/20160410033515_base.js
Normal file
23
migrations/20160410033515_base.js
Normal file
|
@ -0,0 +1,23 @@
|
|||
/* eslint-disable */
|
||||
'use strict';
|
||||
|
||||
exports.up = function(knex, Promise) {
|
||||
return Promise.all([
|
||||
knex.schema.createTable('store', function(table) {
|
||||
table.increments()
|
||||
table.text('name')
|
||||
table.text('value')
|
||||
}).then(() => {
|
||||
return knex('store').insert({
|
||||
name: 'content',
|
||||
value: '{}'
|
||||
})
|
||||
}),
|
||||
]);
|
||||
};
|
||||
|
||||
exports.down = function(knex, Promise) {
|
||||
return Promise.all([
|
||||
knex.schema.dropTable('store'),
|
||||
]);
|
||||
};
|
3
nodemon.json
Normal file
3
nodemon.json
Normal file
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"ignore": ["app/*", "public/*"]
|
||||
}
|
47
package.json
47
package.json
|
@ -1,10 +1,18 @@
|
|||
{
|
||||
"name": "caspar-sup",
|
||||
"version": "1.0.0",
|
||||
"description": "",
|
||||
"description": "CasparCG superimposed graphics project",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"test": "npm test"
|
||||
"test": "npm test",
|
||||
"build-main:js": "browserify app/main.js -o public/main.js --debug",
|
||||
"watch-main:js": "watchify app/main.js -o public/main.js --debug",
|
||||
"build-client:js": "browserify app/client.js -o public/client.js --debug",
|
||||
"watch-client:js": "watchify app/client.js -o public/client.js --debug",
|
||||
"build": "npm run build-main:js && npm run build-client:js",
|
||||
"build:watch": "parallelshell \"npm run watch-main:js\" \"npm run watch-client:js\"",
|
||||
"start": "node index.js",
|
||||
"start:dev": "nodemon index.js | bunyan"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
@ -20,5 +28,38 @@
|
|||
"bugs": {
|
||||
"url": "https://github.com/nfp-projects/caspar-sup/issues"
|
||||
},
|
||||
"homepage": "https://github.com/nfp-projects/caspar-sup#readme"
|
||||
"homepage": "https://github.com/nfp-projects/caspar-sup#readme",
|
||||
"dependencies": {
|
||||
"app-root-path": "^1.0.0",
|
||||
"babel-plugin-syntax-async-functions": "^6.5.0",
|
||||
"babel-plugin-transform-async-to-generator": "^6.7.0",
|
||||
"babel-preset-es2015-node5": "^1.1.2",
|
||||
"babel-register": "^6.7.2",
|
||||
"bookshelf": "^0.9.2",
|
||||
"browserify": "^13.0.0",
|
||||
"bunyan": "^1.7.1",
|
||||
"knex": "^0.10.0",
|
||||
"koa": "^2.0.0-alpha.3",
|
||||
"koa-socket": "^4.3.0",
|
||||
"koa-static": "^3.0.0",
|
||||
"lodash": "^4.6.1",
|
||||
"mithril": "^0.2.3",
|
||||
"nconf": "^0.8.4",
|
||||
"parallelshell": "^2.0.0",
|
||||
"socket.io": "^1.4.5",
|
||||
"socket.io-client": "^1.4.5",
|
||||
"sqlite3": "^3.1.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"assert-extended": "^1.0.1",
|
||||
"babel-eslint": "^5.0.0",
|
||||
"eslint": "^2.2.0",
|
||||
"eslint-config-airbnb": "^6.1.0",
|
||||
"eslint-plugin-mocha": "^2.0.0",
|
||||
"live-reload": "^1.1.0",
|
||||
"mocha": "^2.4.5",
|
||||
"sinon": "^1.17.3",
|
||||
"sinon-as-promised": "^4.0.0",
|
||||
"watchify": "^3.7.0"
|
||||
}
|
||||
}
|
||||
|
|
9
public/client.css
Normal file
9
public/client.css
Normal file
|
@ -0,0 +1,9 @@
|
|||
.root-element {
|
||||
opacity: 0;
|
||||
transition: opacity 1s;
|
||||
}
|
||||
|
||||
.root-element-display {
|
||||
opacity: 1;
|
||||
transition: opacity 1s;
|
||||
}
|
10
public/client.html
Normal file
10
public/client.html
Normal file
|
@ -0,0 +1,10 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<title>CasparCG Client</title>
|
||||
<link href="client.css" rel="stylesheet" />
|
||||
</head>
|
||||
<body>
|
||||
<script src="client.js"></script>
|
||||
</body>
|
||||
</html>
|
7309
public/client.js
Normal file
7309
public/client.js
Normal file
File diff suppressed because one or more lines are too long
2555
public/foundation.css
vendored
Normal file
2555
public/foundation.css
vendored
Normal file
File diff suppressed because it is too large
Load diff
15
public/index.html
Normal file
15
public/index.html
Normal file
|
@ -0,0 +1,15 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<title>CasparCG Controller</title>
|
||||
<link href="foundation.css" rel="stylesheet" />
|
||||
<link href="main.css" rel="stylesheet" />
|
||||
</head>
|
||||
<body>
|
||||
<div class="row">
|
||||
<div class="small-3 columns" id="menu"></div>
|
||||
<div class="small-9 columns" id="content"></div>
|
||||
</div>
|
||||
<script src="main.js"></script>
|
||||
</body>
|
||||
</html>
|
4
public/main.css
Normal file
4
public/main.css
Normal file
|
@ -0,0 +1,4 @@
|
|||
a.button {
|
||||
margin: 0 1rem;
|
||||
width: 10rem;
|
||||
}
|
9604
public/main.js
Normal file
9604
public/main.js
Normal file
File diff suppressed because one or more lines are too long
45
script/setup.js
Normal file
45
script/setup.js
Normal file
|
@ -0,0 +1,45 @@
|
|||
#!/usr/bin/env node
|
||||
/* eslint-disable no-console */
|
||||
'use strict'
|
||||
|
||||
const _ = require('lodash')
|
||||
const appRoot = require('app-root-path')
|
||||
const config = require(appRoot.resolve('/config'))
|
||||
let log = require(appRoot.resolve('/log')).default
|
||||
|
||||
// This is important for setup to run cleanly.
|
||||
let knexConfig = _.cloneDeep(config.get('knex'))
|
||||
knexConfig.pool = { min: 1, max: 1 }
|
||||
|
||||
let knex = require('knex')(knexConfig)
|
||||
|
||||
log.info(knexConfig, 'Connected to database')
|
||||
|
||||
let setup = module.exports = () =>
|
||||
knex.migrate.latest({
|
||||
directory: appRoot.resolve('/migrations'),
|
||||
})
|
||||
.then((result) => {
|
||||
if (result[1].length === 0) {
|
||||
return log.info('Database is up to date')
|
||||
}
|
||||
for (let i = 0; i < result[1].length; i++) {
|
||||
log.info('Applied migration from', result[1][i].substr(result[1][i].lastIndexOf('\\') + 1))
|
||||
}
|
||||
return knex.destroy()
|
||||
})
|
||||
|
||||
if (require.main === module) {
|
||||
// Since we're running this as a script, we should output
|
||||
// directly to the console.
|
||||
log = console
|
||||
log.info = console.log.bind(console)
|
||||
|
||||
setup().then(() => {
|
||||
log.info('Setup ran successfully.')
|
||||
}).catch((error) => {
|
||||
log.error(error, 'Error while running setup.')
|
||||
}).then(() => {
|
||||
process.exit(0)
|
||||
})
|
||||
}
|
71
server/bookshelf.js
Normal file
71
server/bookshelf.js
Normal file
|
@ -0,0 +1,71 @@
|
|||
import _ from 'lodash'
|
||||
import knex from 'knex'
|
||||
import bookshelf from 'bookshelf'
|
||||
|
||||
import config from '../config'
|
||||
import log from '../log'
|
||||
|
||||
let host = config.get('knex:connection')
|
||||
/* istanbul ignore if */
|
||||
if (host.match && host.match(/@[^/]+/)) {
|
||||
host = host.match(/@[^/]+/)[0]
|
||||
}
|
||||
|
||||
log.info(host, 'Connecting to DB')
|
||||
|
||||
const client = knex(config.get('knex'))
|
||||
|
||||
// Check if we're running tests while connected to
|
||||
// potential production environment.
|
||||
/* istanbul ignore if */
|
||||
if (config.get('NODE_ENV') === 'test' &&
|
||||
config.get('knex:connection:database') !== 'test' ||
|
||||
config.get('knex:connection:connection')) {
|
||||
// There is an offchance that we're running tests on
|
||||
// production database. Exit NOW!
|
||||
log.error('Critical: potentially running test on production enviroment. Shutting down.')
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
let shelf = bookshelf(client)
|
||||
|
||||
// Helper method to create models
|
||||
shelf.createModel = (attr, opts) => {
|
||||
// Create default attributes to all models
|
||||
let attributes = _.defaults(attr, {
|
||||
initialize() {
|
||||
this.on('fetching', this.checkFetching)
|
||||
},
|
||||
|
||||
remove() {
|
||||
return this.destroy()
|
||||
},
|
||||
|
||||
checkFetching(model, columns, options) {
|
||||
// options.query.where({ is_deleted: false })
|
||||
},
|
||||
})
|
||||
|
||||
// Create default options for all models
|
||||
let options = _.defaults(opts, {
|
||||
create(data) {
|
||||
return this.forge(data).save()
|
||||
},
|
||||
|
||||
getSingle(id, withRelated = [], required = true) {
|
||||
let where = { id: Number(id) || 0 }
|
||||
|
||||
return this.query({ where })
|
||||
.fetch({ require, withRelated })
|
||||
},
|
||||
|
||||
getAll(where = {}, withRelated = []) {
|
||||
return this.query({ where })
|
||||
.fetchAll({ withRelated })
|
||||
},
|
||||
})
|
||||
|
||||
return shelf.Model.extend(attributes, options)
|
||||
}
|
||||
|
||||
export default shelf
|
1
server/index.js
Normal file
1
server/index.js
Normal file
|
@ -0,0 +1 @@
|
|||
import './koa'
|
34
server/io/connection.js
Normal file
34
server/io/connection.js
Normal file
|
@ -0,0 +1,34 @@
|
|||
import _ from 'lodash'
|
||||
import Store from './store/model'
|
||||
|
||||
export function register(ctx, name, method) {
|
||||
if (_.isPlainObject(method)) {
|
||||
Object.keys(method).forEach(key => {
|
||||
register(ctx, [name, key].join('.'), method[key])
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
ctx.socket.on(name, async (data) => {
|
||||
if (name !== 'store') {
|
||||
ctx.log.info(`Got event ${name}`)
|
||||
}
|
||||
|
||||
try {
|
||||
await method(ctx, data)
|
||||
}
|
||||
catch (error) {
|
||||
ctx.log.error(error, `Error processing ${name}`)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
export async function newConnection(ctx) {
|
||||
ctx.log.info('Got new socket connection')
|
||||
|
||||
let data = await Store.getAll()
|
||||
|
||||
data.forEach(item =>
|
||||
ctx.socket.emit('store', item.toJSON())
|
||||
)
|
||||
}
|
18
server/io/content/routes.js
Normal file
18
server/io/content/routes.js
Normal file
|
@ -0,0 +1,18 @@
|
|||
import _ from 'lodash'
|
||||
|
||||
export function display(ctx, data) {
|
||||
let compiled = _.template(data.html)
|
||||
let html = compiled(data)
|
||||
|
||||
ctx.io.emit('client.display', {
|
||||
key: 'content',
|
||||
html,
|
||||
css: data.css,
|
||||
})
|
||||
}
|
||||
|
||||
export function hide(ctx) {
|
||||
ctx.io.emit('client.hide', {
|
||||
key: 'content',
|
||||
})
|
||||
}
|
22
server/io/router.js
Normal file
22
server/io/router.js
Normal file
|
@ -0,0 +1,22 @@
|
|||
import logger from '../../log'
|
||||
import { register, newConnection } from './connection'
|
||||
|
||||
import * as content from './content/routes'
|
||||
import * as store from './store/routes'
|
||||
|
||||
function onConnection(server, data) {
|
||||
const io = server.socket
|
||||
const socket = data.socket
|
||||
const log = logger.child({
|
||||
id: socket.id,
|
||||
})
|
||||
|
||||
let ctx = { io, socket, log }
|
||||
|
||||
newConnection(ctx)
|
||||
|
||||
register(ctx, 'content', content)
|
||||
register(ctx, 'store', store.updateStore)
|
||||
}
|
||||
|
||||
export default onConnection
|
24
server/io/store/model.js
Normal file
24
server/io/store/model.js
Normal file
|
@ -0,0 +1,24 @@
|
|||
import bookshelf from '../../bookshelf'
|
||||
|
||||
const Store = bookshelf.createModel({
|
||||
tableName: 'store',
|
||||
|
||||
format(attributes) {
|
||||
attributes.value = JSON.stringify(attributes.value)
|
||||
return attributes
|
||||
},
|
||||
|
||||
parse(attributes) {
|
||||
attributes.value = JSON.parse(attributes.value)
|
||||
return attributes
|
||||
}
|
||||
}, {
|
||||
getSingle(name, withRelated = [], required = true) {
|
||||
let where = { name }
|
||||
|
||||
return this.query({ where })
|
||||
.fetch({ require, withRelated })
|
||||
},
|
||||
})
|
||||
|
||||
export default Store
|
11
server/io/store/routes.js
Normal file
11
server/io/store/routes.js
Normal file
|
@ -0,0 +1,11 @@
|
|||
import Store from './model'
|
||||
|
||||
export async function updateStore(ctx, data) {
|
||||
let item = await Store.getSingle(data.name)
|
||||
|
||||
item.set('value', data.value)
|
||||
|
||||
await item.save()
|
||||
|
||||
ctx.socket.broadcast.emit('store', item.toJSON())
|
||||
}
|
20
server/koa.js
Normal file
20
server/koa.js
Normal file
|
@ -0,0 +1,20 @@
|
|||
import serve from 'koa-static'
|
||||
import Koa from 'koa'
|
||||
import socket from 'koa-socket'
|
||||
import config from '../config'
|
||||
import log from '../log'
|
||||
import router from './io/router'
|
||||
import { bunyanLogger, errorHandler } from './middlewares'
|
||||
|
||||
const app = new Koa()
|
||||
const io = new socket()
|
||||
|
||||
io.attach(app)
|
||||
|
||||
io.on('connection', router.bind(this, io))
|
||||
|
||||
app.use(bunyanLogger(log))
|
||||
app.use(errorHandler())
|
||||
app.use(serve('public'))
|
||||
|
||||
app.listen(config.get('server:port'))
|
40
server/middlewares.js
Normal file
40
server/middlewares.js
Normal file
|
@ -0,0 +1,40 @@
|
|||
|
||||
export function bunyanLogger(logger) {
|
||||
return async (ctx, next) => {
|
||||
ctx.log = logger.child({
|
||||
})
|
||||
|
||||
const d1 = new Date().getTime()
|
||||
|
||||
await next()
|
||||
|
||||
const d2 = new Date().getTime()
|
||||
|
||||
let level = 'info'
|
||||
if (ctx.status >= 400) {
|
||||
level = 'warn'
|
||||
}
|
||||
if (ctx.status >= 500) {
|
||||
level = 'error'
|
||||
}
|
||||
|
||||
ctx.log[level]({
|
||||
duration: (d2 - d1),
|
||||
status: ctx.res.statusCode,
|
||||
}, `<-- ${ctx.request.method} ${ctx.request.url}`)
|
||||
}
|
||||
}
|
||||
|
||||
export function errorHandler() {
|
||||
return async (ctx, next) => {
|
||||
try {
|
||||
await next()
|
||||
} catch(err) {
|
||||
ctx.log.error(err, 'Unknown error occured')
|
||||
ctx.status = 500
|
||||
ctx.render('error', {
|
||||
error: err,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue