--- /dev/null
+express = require 'express'
+Resource = require 'express-resource'
+
+hasOwn = Object::hasOwnProperty
+
+/**
+ * @class Resource controller for easily subclassing an express Resource.
+ */
+class Controller extends Resource
+ /**
+ * Singular, lowercase resource-noun.
+ * @optional
+ * @type String
+ * @example "user"
+ */
+ id : null
+
+ /**
+ * Plural, lowercase resource-noun.
+ * @required
+ * @type String
+ * @example "users"
+ */
+ name : null
+
+ /**
+ * Resource routing prefix.
+ * @optional
+ * @type String
+ */
+ base : '/'
+
+ /**
+ * Default format.
+ * @type String
+ */
+ format : null
+
+ /**
+ * Hash of sub-routes. Keys are routes, and values are either:
+ * - String: the name of a method to be used for used for all HTTP-methods
+ * for this sub-route.
+ * - Object: Hash of HTTP-method (get, put, post, del, or all) to the name
+ * of a method on this Controller.
+ * @type Object
+ */
+ mapping : null
+
+
+
+ /**
+ * @constructor
+ */
+ (@app, name) ->
+
+ # Bind all methods and make actions object
+ actions = {}
+ for k, fn in this
+ continue unless typeof fn is 'function' and k is not 'constructor'
+ actions[k] = this[k] = fn.bind this
+
+ # Replace/remove .load, as by default it's Resource#load
+ delete actions.load
+ if typeof actions.autoload is 'function'
+ actions.load = actions.autoload
+
+ super (name or @name), actions, @app
+ (@app.resources or= {})[@name] = this
+ @applyControllerMapping()
+
+
+ /**
+ * Apply the contents of a mapping hash.
+ * @private
+ */
+ applyControllerMapping: (mapping=@mapping) ->
+ for subroute, methods in mapping
+ if typeof methods is 'string'
+ methods = { all:methods }
+ for verb, method in methods
+ @map verb, subroute, @[method]
+ this
+
+ toString: ->
+ "#{@.constructor.name}('#{@name}', base='#{@base}', app=#{@app})"
+
+
+express.HTTPServer::controller = \
+express.HTTPSServer::controller = (name, ControllerClass, opts) ->
+ [opts, ControllerClass, name] = [ControllerClass, name, null] if typeof name is 'function'
+ new ControllerClass this, name
+
+
+module.exports = exports = Controller
--- /dev/null
+fs = require 'fs'
+Seq = require 'seq'
+Controller = require '../controller'
+
+YAML_EXT_PAT = /\.ya?ml$/i
+
+
+
+/**
+ * @class Resource controller for graph requests.
+ */
+class DataSourceController extends Controller
+ name : 'datasources'
+ dataDir : 'data/graphs'
+
+ mapping :
+ all : 'getAllData'
+
+ -> super ...
+
+ /**
+ * Returns a JSON listing of the datasource metadata files.
+ */
+ index : (req, res, next) ->
+ fs.readdir @dataDir, (err, files) ->
+ res.send do
+ files.filter -> /\.(json|ya?ml)$/i.test it
+ .map -> "/#{@dataDir}#it".replace YAML_EXT_PAT, '.json'
+
+ /**
+ * Returns the aggregated JSON content of the datasource metadata files.
+ */
+ allData : (req, res, next) ->
+ data = {}
+ files = []
+ Seq()
+ .seq fs.readdir, @dataDir, Seq
+ .flatten()
+ .filter -> /\.(json|ya?ml)$/.test it
+ .seq ->
+ files := @stack.slice()
+ # console.log 'files:', files
+ @ok files
+ .flatten()
+ .parMap_ (next, f) ~>
+ # console.log "fs.readFile '#CWD/data/#f'"
+ fs.readFile "#{@dataDir}/#f", 'utf8', next
+ .parMap (text, i) ->
+ f = files[i]
+ # console.log "parsing file[#i]: '#f' -> text[#{text.length}]..."
+ k = f.replace YAML_EXT_PAT, '.json'
+ v = data[k] = {}
+ try
+ if YAML_EXT_PAT.test f
+ v = data[k] = yaml.load text
+ else
+ v = data[k] = JSON.parse text
+ # console.log "#f ok!", data
+ @ok v
+ catch err
+ console.error "[/data/all] catch! #err"
+ console.error err
+ console.error that if err.stack
+ res.send { error:String(err), partial_data:data }
+ .seq -> res.send data
+ .catch (err) ->
+ console.error '[/data/all] catch!'
+ console.error err
+ console.error that if err.stack
+ res.send { error:String(err), partial_data:data }
+
+
+
+module.exports = exports = DataSourceController
--- /dev/null
+_ = require 'underscore'
+fs = require 'fs'
+path = require 'path'
+yaml = require 'js-yaml'
+
+{existsSync:exists} = path
+{mkdirp, mkdirpAsync} = require '../mkdirp'
+Controller = require '../controller'
+
+
+/**
+ * @class Resource controller for graph requests.
+ */
+class GraphController extends Controller
+ name : 'graphs'
+ dataDir : 'data/graphs'
+ -> super ...
+
+
+ toFile: (id) -> "#{@dataDir}/#id.json"
+
+ /**
+ * Auto-load :id for related requests.
+ */
+ autoload: (id, cb) ->
+ file = @toFile id
+ parser = JSON.parse
+
+ yamlFile = file.replace /\.json$/i, '.yaml'
+ if exists yamlFile
+ file = yamlFile
+ parser = yaml.load
+
+ err, data <- fs.readFile file, 'utf8'
+ if err
+ console.error "GraphController.autoload(#id, #{typeof cb}) -->\nerr"
+ return cb err
+ try
+ cb null, parser data
+ catch err
+ console.error "GraphController.autoload(#id, #{typeof cb}) -->\nerr"
+ cb err
+
+ # GET /graphs
+ index: (req, res) ->
+ res.render 'dashboard'
+
+ # GET /graphs/:graph
+ show: (req, res) ->
+ res.send req.graph
+
+ # GET /graphs/:graph/edit
+ edit: (req, res) ->
+ res.send req.graph
+
+ # GET /graphs/new
+ new: (req, res) ->
+ ...
+
+ # POST /graphs
+ create: (req, res) ->
+ return unless data = @processBody req, res
+ file = @toFile data.id
+ if exists file
+ return res.send { result:"error", message:"Graph already exists!" }
+ else
+ fs.writeFile file, JSON.stringify(data), "utf8", @errorHandler(res, "Error writing graph!")
+
+ # PUT /graphs/:graph
+ update: (req, res) ->
+ return unless data = @processBody req, res
+ fs.writeFile @toFile(data.id), JSON.stringify(data), "utf8", @errorHandler(res, "Error writing graph!")
+
+ # DELETE /graphs/:graph
+ destroy: (req, res) ->
+ fs.unlink @toFile(req.param.graph), @errorHandler(res, "Graph does not exist!")
+
+
+ ### Helpers
+
+ processBody: (req, res) ->
+ if not req.body
+ res.send {result:"error", message:"Data required!"}, 501
+ return false
+
+ data = req.body
+ data.slug or= data.id
+ data.id or= data.slug
+
+ if not data.slug
+ res.send {result:"error", message:"Slug required!"}, 501
+ return false
+
+ mkdirp @dataDir if not exists @dataDir
+ return data
+
+ errorHandler: (res, msg) ->
+ (err) ->
+ if err
+ msg or= err.message or String(err)
+ console.error msg
+ res.send { result:"error", message:msg }, 501
+ else
+ res.send { result:"ok" }
+
+
+module.exports = exports = GraphController
fs = require 'fs'
path = require 'path'
-{parse} = require 'url'
{existsSync:exists} = path
{exec, spawn} = require 'child_process'
{mkdirp, mkdirpAsync} = require './mkdirp'
yaml = require 'js-yaml'
mime = require 'mime'
express = require 'express'
+Resource = require 'express-resource'
compiler = require 'connect-compiler-extras'
-
### Config
# TODO: read KRAKEN_PORT from ENV
VAR = "#CWD/var"
STATIC = "#CWD/static"
DIST = "#CWD/dist"
+DATA = "#CWD/data"
NODE_ENV = process.env.NODE_ENV or 'dev'
LOG_LEVEL = if _.startsWith NODE_ENV, 'dev' then 'INFO' else 'WARN'
+# LOG_LEVEL = 'DEBUG'
VERSION = 'dev'
} import require './view-helpers'
app.use express.logger() if LOG_LEVEL is 'DEBUG'
+ # app.use express.logger()
# Parse URL, fiddle
app.use require('./reqinfo')({})
options : stylus : { nib:true, include:"#WWW/css" }
log_level : LOG_LEVEL
+ app.use compiler do
+ enabled : 'yaml'
+ src : DATA
+ dest : "#VAR/data"
+ log_level : LOG_LEVEL
+
# wrap modules in commonjs closure for browser
app.use compiler do
enabled : 'commonjs_define'
app.use express.static VAR
app.use express.static STATIC
# app.use express.static DIST if exists DIST
- app.use express.static DIST
+ app.use express.static DIST if NODE_ENV is 'prod'
# Serve directory listings
# app.use express.directory WWW
showStack : true
-/* * * * Routes * * * {{{ */
-app.get '/', (req, res) ->
- res.render 'dashboard'
+/* * * * Routes and Controllers * * * {{{ */
-saveGraph = (req, res, next) ->
- if not req.body
- return res.send {result:"error", message:"JSON required!"}, 501
-
- data = req.body
- {id, slug} = data
- if not slug
- return res.send {result:"error", message:"slug required!"}, 501
- mkdirp "#VAR/presets" if not exists "#VAR/presets"
-
- id or= slug
- data.id = id
- err <- fs.writeFile "#VAR/presets/#id.json", JSON.stringify(data), "utf8"
- if err
- res.send { result:"error", message:err.message or String(err) }, 501
- else
- res.send { result:"ok" }
+Controller = require './controller'
+app.controller require './controllers/graph'
+app.controller require './controllers/datasource'
-
-app.post '/graph/save', saveGraph
-app.put '/graph/:slug\.json', saveGraph
-
-app.get '/graph/:slug\.json', (req, res, next) ->
- req.url .= replace /^\/graph\//i, '/presets/'
- next()
-
-app.get '/preset/:slug', (req, res, next) ->
- {slug} = req.params
- if exists("#VAR/presets/#slug.yaml") or exists("#VAR/presets/#slug.json")
- req.url .= replace /^\/preset\/[^\/?]/i, "/presets/#slug.json"
- # req.url += '.json'
- next()
-
-app.get '/graph(/:slug)?/?', (req, res, next) ->
- {slug} = req.params
- # console.log '/graph/:slug/?'
- # console.log ' slug: ', slug
- # console.log ' params:', req.params
- unless _.str.include slug, '.'
- {pathname, search or ''} = req.info
- req.url = path.join pathname, "view#search"
- req.params.action = 'view'
- next()
-
-app.get '/graph/:slug/:action/?', (req, res) ->
- {slug, action} = req.params
- res.render "graph/#action"
+app.get '/', (req, res) ->
+ res.render 'dashboard'
app.get '/:type/:action/?', (req, res, next) ->
{type, action} = req.params
else
next()
-
-
-# }}}
-/* * * * Data Source Oracle * * * {{{ */
-
-YAML_EXT_PAT = /\.ya?ml$/i
-
-/**
- * Returns a JSON listing of the datasource metadata files.
- */
-app.get '/data/list', (req, res, next) ->
- fs.readdir "#CWD/data", (err, files) ->
- res.send do
- files.filter -> /\.(json|ya?ml)$/i.test it
- .map -> "/data/#it".replace YAML_EXT_PAT, '.json'
-
-/**
- * Returns the aggregated JSON content of the datasource metadata files.
- */
-app.get '/data/all', (req, res, next) ->
- data = {}
- files = []
- Seq()
- .seq fs.readdir, "#CWD/data", Seq
- .flatten()
- .filter -> /\.(json|ya?ml)$/.test it
- .seq ->
- files := @stack.slice()
- # console.log 'files:', files
- @ok files
- .flatten()
- .parMap (f) ->
- # console.log "fs.readFile '#CWD/data/#f'"
- fs.readFile "#CWD/data/#f", 'utf8', this
- .parMap (text, i) ->
- f = files[i]
- # console.log "parsing file[#i]: '#f' -> text[#{text.length}]..."
- k = f.replace YAML_EXT_PAT, '.json'
- v = data[k] = {}
- try
- if YAML_EXT_PAT.test f
- v = data[k] = yaml.load text
- else
- v = data[k] = JSON.parse text
- # console.log "#f ok!", data
- @ok v
- catch err
- console.error "[/data/all] catch! #err"
- console.error err
- console.error that if err.stack
- res.send { error:String(err), partial_data:data }
- .seq -> res.send data
- .catch (err) ->
- console.error '[/data/all] catch!'
- console.error err
- console.error that if err.stack
- res.send { error:String(err), partial_data:data }
-
-# }}}
-
/**
* Handle webhook notification to pull from origin.
*/
app.all '/webhook/post-update', (req, res) ->
-
+
# exec the pull async...
- console.log '[/webhook/post-update] $ git pull origin master'
- child = exec 'git pull origin master', (err, stdout, stderr) ->
+ cmd = 'git pull origin master'
+ console.log "[/webhook/post-update] $ #cmd"
+ child = exec cmd, (err, stdout, stderr) ->
res.contentType '.txt'
console.log '[/webhook/post-update] ', stdout
console.log '[/webhook/post-update] ', stderr
if err
console.error '[/webhook/post-update] ERROR!', err
- res.send "#stdout\n\n#stderr\n\nERROR! #err", 503
+ res.send "$ #cmd\n\n#stdout\n\n#stderr\n\nERROR! #err", 503
else
- res.send "#stdout\n\n#stderr", 200
-
+ res.send "$ #cmd\n\n#stdout\n\n#stderr", 200
+# }}}
exports import {
CWD, WWW, VAR, STATIC,
'coco' : '>= 0.7.0'
'mime' : '>= 1.2.5'
'express' : '>= 2.5.8'
+ 'express-resource' : '>= 0.2.4'
'connect-compiler' : 'https://github.com/dsc/connect-compiler/tarball/master'
'connect-compiler-extras' : 'https://github.com/dsc/connect-compiler-extras/tarball/master'
'jade' : '>= 0.20.1'
"coco": ">= 0.7.0",
"mime": ">= 1.2.5",
"express": ">= 2.5.8",
+ "express-resource": ">= 0.2.4",
"connect-compiler": "https://github.com/dsc/connect-compiler/tarball/master",
"connect-compiler-extras": "https://github.com/dsc/connect-compiler-extras/tarball/master",
"jade": ">= 0.20.1",