Updates all controllers to recursively search for their data.
authorDavid Schoonover <dsc@wikimedia.org>
Thu, 12 Jul 2012 21:13:00 +0000 (14:13 -0700)
committerDavid Schoonover <dsc@wikimedia.org>
Thu, 12 Jul 2012 22:13:46 +0000 (15:13 -0700)
src/server/controllers/datasource.co
src/server/controllers/graph.co
src/server/file-controller.co
src/server/middleware.co
src/server/server.co
www/data [deleted symlink]
www/docs [deleted symlink]
www/js/limn [moved from www/js/kraken with 100% similarity]

index bce9946..df2cd88 100644 (file)
@@ -22,13 +22,10 @@ YAML_OR_JSON_PAT = /\.(json|ya?ml)$/i
  */
 class DataSourceController extends FileBackedController
     name    : 'datasources'
-    dataDir : null
-    
     mapping :
         all : 'allData'
     
     -> super ...
-    toFile: @::toFileRecursive
     
     
     /**
index a3ed631..fa09476 100644 (file)
@@ -1,22 +1,26 @@
 fs         = require 'fs'
 path       = require 'path'
-{existsSync:exists} = path
+exists     = fs.existsSync or path.existsSync
 
-_          = require 'underscore'
 Seq        = require 'seq'
+glob       = require 'glob'
 yaml       = require 'js-yaml'
-{mkdirp, mkdirpAsync} = require '../mkdirp'
 
-{ readJSONFilesAsync,
+{ _, op,
+} = require '../../util'
+{ mkdirp, mkdirpAsync,
+} = require '../mkdirp'
+{ readFilesAsync, readJSONFilesAsync,
 } = require '../files'
 
-Controller = require '../controller'
+Controller           = require '../controller'
+FileBackedController = require '../file-controller'
 
 
 /**
  * @class Resource controller for graph requests.
  */
-class GraphController extends Controller
+class GraphController extends FileBackedController
     PROTECTED_GRAPH_IDS : <[
         unique_visitors
         pageviews
@@ -33,42 +37,12 @@ class GraphController extends Controller
     ]>
     PROTECT_GRAPHS : true
     
-    name    : 'graphs'
-    dataDir : null
+    name : 'graphs'
+    -> super ...
     
-    ->
-        super ...
-        limnOpts = @app.set 'limn options'
-        @dataDir = "#{limnOpts.dataDir}/graphs"
     
-    
-    
-    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 'ENOENT' is err?.code
-            return cb null, {}
-        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
+    toFileSimple: (id) ->
+        "#{@dataDir}/#id.json"
     
     # GET /graphs.:format?
     index: (req, res) ->
@@ -105,25 +79,33 @@ class GraphController extends Controller
     # 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 '#{data.id}' already exists!" }, 409 # 409: Resource Conflict
-        else
-            fs.writeFile file, JSON.stringify(data), "utf8", @errorHandler(res, "Error writing graph!")
+        Seq()
+            .seq @findFile, data.id, Seq
+            .seq (file) ->
+                res.send { result:"error", message:"Graph '#{data.id}' already exists!" }, 409 # 409: Resource Conflict
+            .catch (err) ->
+                fs.writeFile @toFileSimple(data.id), JSON.stringify(data), "utf8", @errorHandler(res, "Error writing graph!")
     
     # PUT /graphs/:graph
     update: (req, res) ->
         return unless data = @processBody req, res
         if @PROTECT_GRAPHS and _ @PROTECTED_GRAPH_IDS .contains data.id
             return res.send {result:"error", message:"Graph '#{data.id}' is read-only."}, 403 # 403: Forbidden
-        fs.writeFile @toFile(data.id), JSON.stringify(data), "utf8", @errorHandler(res, "Error writing graph!")
+        Seq()
+            .seq @findFile, data.id, (err, file) ->
+                @ok if err then @toFileSimple(data.id) else file
+            .seq (file) ->
+                fs.writeFile file, JSON.stringify(data), "utf8", @errorHandler(res, "Error writing graph!")
     
     # DELETE /graphs/:graph
     destroy: (req, res) ->
         id = req.param.graph
         if @PROTECT_GRAPHS and _ @PROTECTED_GRAPH_IDS .contains id
             return res.send {result:"error", message:"Graph '#{id}' is read-only."}, 403 # 403: Forbidden
-        fs.unlink @toFile(id), @errorHandler(res, "Graph '#{id}' does not exist!", 410) # 410: Resource Gone
+        Seq()
+            .seq @findFile, data.id, Seq
+            .seq   (file) -> fs.unlink file, @errorHandler(res, "Error destroying Graph '#id'!", 500)
+            .catch (err)  -> res.send {result:"error", message:"Graph '#id' does not exist!"}, 410 # 410: Resource Gone
     
     
     ### Helpers
index e94f93d..c5d4e6a 100644 (file)
@@ -4,6 +4,7 @@ exists  = fs.existsSync or path.existsSync
 
 _          = require 'underscore'
 Seq        = require 'seq'
+glob       = require 'glob'
 yaml       = require 'js-yaml'
 
 { mkdirp, mkdirpAsync,
@@ -30,34 +31,25 @@ class FileBackedController extends Controller
         @noun ?= @name.charAt(0).toUpperCase() + @name.slice(1)
     
     
-    
     /**
-     * Look up the resource in the dataDir.
-     * 
      * @param {String} id ID of this resource.
-     * @returns {String} Path to file for this resource.
-     * @see toFileRecursive
+     * @returns {String} Glob path to file for this resource.
      */
-    toFileFlat: (id) ->
-        return j if exists f = "#{@dataDir}/#id.json"
-        return y if exists y = "#{@dataDir}/#id.yaml"
+    toFile: (id) ->
+        "#{@dataDir}/**/#id.@(yaml|json)"
     
     /**
-     * Look up the resource by recursively searching the dataDir.
+     * Finds the reified filepath for the resource `id`.
      * 
      * @param {String} id ID of this resource.
-     * @returns {String} Path to file for this resource.
-     * @see toFileFlat
+     * @param {Function} cb Callback `(err, filepath)`.
      */
-    toFileRecursive: (id, cb) ->
-        files = glob "#{@dataDir}/**/#id.@(yaml|json)", {+nocase, +nosort, +sync}
-        files[0]
+    findFile: (id, cb) ->
+        glob @toFile(id), {+nocase, +nosort}, (err, files) ->
+            return cb err if err
+            return cb 'ENOENT' unless files.length
+            cb null, files[0]
     
-    /**
-     * Function for looking up the filepath to a resource. Defaults to a recursive lookup; 
-     * use `toFileFlat()` to only check the top-level directory.
-     */
-    toFile: @::toFileRecursive
     
     
     /**
@@ -67,24 +59,10 @@ class FileBackedController extends Controller
      * @param {Function} cb Callback to invoke with the loaded object.
      */
     autoload: (id, cb) ->
-        parser   = JSON.parse
-        file     = @toFile id
-        
-        if _.endsWith file, "#id.yaml"
-            parser = yaml.load
-        
-        err, data <- fs.readFile file, 'utf8'
-        
-        if 'ENOENT' is err?.code
-            return cb null, {}
-        if err
-            console.error "#{this}.autoload(#id, #{typeof cb}) --> Error reading data!\n", err
-            return cb err
-        try
-            cb null, parser data
-        catch err
-            console.error "#{this}.autoload(#id, #{typeof cb}) --> Error parsing data!\n", err
-            cb err
+        Seq()
+            .seq readJSONFilesAsync, @toFile(id), {+yaml, -appendExt}, Seq
+            .seq   (data) -> cb null, _.values(data)[0]
+            .catch (err)  -> cb err
     
     
     
index 708cc0a..d193129 100755 (executable)
@@ -115,7 +115,7 @@ exports = module.exports = limn
 function limn(options)
     app = express.createServer()
     app = _.extend app, application
-    app.init()
+    app.init options
     app
 
 
@@ -129,6 +129,7 @@ application = limn.application =
      * @constructor
      */
     init : (opts={}) ->
+        app   = this
         @REV  = REV
         @BASE = BASE
         
@@ -138,9 +139,14 @@ application = limn.application =
         opx.enabled = true if opx.enabled is false and (opx.whitelist or opx.blacklist)
         if opx.enabled
             opx.whitelist or= [ /.*/ ]
+            opx.whitelist = [opx.whitelist] unless _.isArray opx.whitelist
             opx.blacklist or= []
+            opx.blacklist = [opx.blacklist] unless _.isArray opx.blacklist
         @set 'limn options', opts
         
+        # create data directory
+        mkdirp opts.dataDir
+        
         
         ### apply configuration
         
@@ -235,7 +241,7 @@ application = limn.application =
         
         # serve static files
         @configure ->
-            {staticMaxAge} = @set 'limn options'
+            {varDir, staticMaxAge} = @set 'limn options'
             maxAge = staticMaxAge[NODE_ENV] ? staticMaxAge.default
             
             @use express.static varDir, {maxAge}
@@ -250,7 +256,7 @@ application = limn.application =
         
         YAML_EXT_PAT = /\.ya?ml$/i
         @get '/datasources/all', (req, res, next) ->
-            opts = {varDir, dataDir} = @set 'limn options'
+            opts = {varDir, dataDir} = app.set 'limn options'
             
             data = {}
             Seq()
index 47e943c..b866a13 100755 (executable)
@@ -13,14 +13,26 @@ LimnMiddleware = require './middleware'
  */
 app = exports = module.exports = express.createServer()
 
+
+/**
+ * Load Limn middleware
+ */
+app.use limn = app.limn = LimnMiddleware do
+    varDir  : './var'
+    dataDir : './var/data'
+    proxy :
+        enabled   : true
+        whitelist : /.*/
+
+
 /**
  * Handle webhook notification to pull from origin.
  */
 app.all '/webhook/post-update', (req, res) ->
-
-    # exec the pull async...
     cmd = 'git pull origin master'
     console.log "[/webhook/post-update] $ #cmd"
+    
+    # exec the pull async
     child = exec cmd, (err, stdout, stderr) ->
         res.contentType '.txt'
         console.log '[/webhook/post-update]  ', stdout
@@ -31,11 +43,6 @@ app.all '/webhook/post-update', (req, res) ->
         else
             res.send "$ #cmd\n\n#stdout\n\n#stderr", 200
 
-/**
- * Load Limn middleware
- */
-app.use limn = app.limn = LimnMiddleware()
-
 
 
 mainfile = path.basename require.main?.filename
@@ -51,7 +58,7 @@ if require.main is module or 'Cokefile' is mainfile
     throw err if err
     REV = stdout.trim! unless REV
     
-    console.log s = "Limn Server (port=#PORT, env=#NODE_ENV, rev=#REV, base_dir=#{limn.BASE})"
+    console.log s = "Limn Server (port=#PORT, env=#NODE_ENV, rev=#REV)"
     console.log '=' * s.length
     app.listen PORT
 
diff --git a/www/data b/www/data
deleted file mode 120000 (symlink)
index 4909e06..0000000
--- a/www/data
+++ /dev/null
@@ -1 +0,0 @@
-../data
\ No newline at end of file
diff --git a/www/docs b/www/docs
deleted file mode 120000 (symlink)
index a9594bf..0000000
--- a/www/docs
+++ /dev/null
@@ -1 +0,0 @@
-../docs
\ No newline at end of file
similarity index 100%
rename from www/js/kraken
rename to www/js/limn