Prioritize sort by type

Prioritize sort by type

# Documentation generation

*Generate the dynamic portions of Shrub's documentation.*

    {exec} = require 'child_process'
    fs = require 'fs'
    path = require 'path'
    {Transform} = require 'stream'

    _ = require 'lodash'
    {LineStream} = require 'byline'
    glob = require 'grunt/node_modules/glob'
    Promise = require 'bluebird'

Implement a Transform stream to accumulate hook invocations from a source file.

    class HookInvocations extends Transform

      constructor: ->
        super

        @list = []

      _transform: (chunk, encoding, done) ->
        line = chunk.toString('utf8')
        if matches = line.match /^\#\#\#\# Invoke hook `([^`]+)`/
          @list.push matches[1]

        done()

Implement a Transform stream to accumulate hook implementations from a source
file.

    class HookImplementations extends Transform

      constructor: ->
        super

        @list = []

      _transform: (chunk, encoding, done) ->
        line = chunk.toString('utf8')
        if matches = line.match /^\#\#\#\# Implements hook `([^`]+)`/
          @list.push matches[1]

        done()

Implement a Transform stream fo accumulate TODOs from a source file. Also
caches lines to be able to build context around each TODO item.

    class Todos extends Transform

      @context = 4

      constructor: ->
        super

        @lines = []
        @todos = []

      _transform: (chunk, encoding, done) ->
        @lines.push line = chunk.toString('utf8')
        if matches = line.match /^\#\#\#\#\#\# TODO/
          @todos.push @lines.length - 1

        done()

      withContext: ->

        for todo in @todos

          start = Math.max 0, todo - Todos.context
          end = Math.min @lines.length - 1, todo + Todos.context

          index: todo
          lines: @lines.slice start, end

Gather all source files.

    _allSourceFiles = ->
      new Promise (resolve, reject) ->
        glob(
          '{{client,custom,packages,server}/**/*.litcoffee,*.litcoffee}'
          (error, files) ->
            return reject error if error?
            resolve files
        )

Generate an HTML ID from a hook name.

    _idFromHook = (hook) -> hook.replace(
      /[^0-9A-Za-z-]+/g, '-'
    ).toLowerCase()

Get the source path from a filename. This removes the extension and any /index
portion from the end of the filename.

    _sourcePath = (filename) ->

        dirname = path.dirname filename
        if dirname is '.' then dirname = '' else dirname += '/'
        extname = path.extname filename
        filename = "#{dirname}#{path.basename filename, extname}"

        parts = filename.split '/'
        parts.pop() if parts[parts.length - 1] is 'index'
        return parts.join '/'

Collate a list of files by type (client or server).

    _collateFilesByType = (files) ->

      client = []
      server = []

      for file in files
        parts = file.split '/'

        if parts[0] is 'client'
          client.push file
        else if parts[0] is 'custom' and parts[2] is 'client'
          client.push file
        else if parts[0] is 'packages' and parts[2] is 'client'
          client.push file
        else
          server.push file

      client: client, server: server

Gather statistics for all files.

    fileStatsListPromise = _allSourceFiles().then (allFiles) ->

      allFilesPromises = for type, files of _collateFilesByType allFiles

        typePromises = for file in files

          do (type, file) -> new Promise (resolve, reject) ->

            fstream = fs.createReadStream file
            fstream.pipe lineStream = new LineStream keepEmptyLines: true

Pass all files through the Transform list to parse out relevant information.

            lineStream.pipe hookImplementations = new HookImplementations()
            lineStream.pipe hookInvocations = new HookInvocations()
            lineStream.pipe todos = new Todos()

            fstream.on 'error', reject

            fstream.on 'end', ->

Include all information from Transform streams in the statistics.

              resolve(
                type: type
                file: file
                implementations: hookImplementations.list
                invocations: hookInvocations.list
                todos: todos.withContext()
              )

        Promise.all typePromises

      Promise.all(allFilesPromises).then (fileStatsLists) ->
        _.flatten fileStatsLists

Massage the statistics to help rendering the hooks page.

    fileStatsListPromise.then((fileStatsList) ->

      hooksIndex = {}

      indexes = {}
      keys = ['implementations', 'invocations']

      for fileStats in fileStatsList

        for key in keys
          indexes[key] ?= {}

          for hook in fileStats[key]
            indexes[key][hook] ?= {}
            (indexes[key][hook][fileStats.file] ?= []).push fileStats.type

            hooksIndex[hook] = true

      for key in keys
        for hook, stats of indexes[key]
          for file of stats
            stats[file] = _.unique stats[file] ? []

      hooks = (hook for hook of hooksIndex).sort()

      hookFiles = {}
      for hook in hooks
        hookFiles[hook] = try
          fs.readFileSync "docs/hook/#{hook}.md", 'utf8'
        catch error
          ''

      O =
        hookFiles: hookFiles
        hooks: hooks

      O[key] = indexes[key] for key in keys

      return O

Render the hooks page.

    ).then((O) ->

      {hookFiles, hooks} = O

      keys = ['implementations', 'invocations']

      wordingFor =
        implementations: 'implements'
        invocations: 'invoke'

      render = fs.readFileSync 'docs/hooks.template.md', 'utf8'

      for hook in hooks

        render += "## #{hook}\n\n"
        render += hookFiles[hook] + '\n\n' if hookFiles[hook]

        for key in keys

          if O[key][hook]?

            count = Object.keys(O[key][hook]).length
            render += "### #{count} #{key}(s)\n\n"

            for file, types of O[key][hook]

              file = _sourcePath file

              types = types.map (type) -> "[#{type}](source/#{file}##{wordingFor[key]}-hook-#{hook.toLowerCase()})"

              render += "* #{file} (#{types.join ','})\n"

            render += '\n'

      new Promise (resolve, reject) ->
        fs.writeFile 'docs/hooks.md', render, (error) ->
          return reject error if error?
          resolve()

    ).done()

Render the TODOs page.

    fileStatsListPromise.then((fileStatsList) ->

      render = fs.readFileSync 'docs/todos.template.md', 'utf8'

      for fileStats in fileStatsList

Keep track of used IDs, it will be necessary to link to the correct location
hash in the case of multiple TODO items with the same wording.

        idMap = {}

        for todo in fileStats.todos

          render += '\n'

          id = ''
          for line, index in todo.lines
            render += '>'

If this is the line with the TODO, parse the ID from the TODO item text, and
render it as h2 (TODO are h6) to increase visibility.

            if index is Todos.context
              id = _idFromHook(line).slice 1, -1

              render += line.slice 4
            else
              render += " #{line}"
            render += '\n'

          render += '\n'

          filename = _sourcePath fileStats.file

Keep track of ID usage and modify the location hash for subsequent uses.

          if idMap[id]?
            idMap[id] += 1
            id += "_#{idMap[id]}"
          else
            idMap[id] = 0

          render += "###### the above found in [#{fileStats.file}:#{todo.index}](source/#{filename}##{id})\n"

      new Promise (resolve, reject) ->
        fs.writeFile 'docs/todos.md', render, (error) ->
          return reject error if error?
          resolve()

    ).done()

Add all the source files to a generated mkdocs.yml

    _allSourceFiles().then (files) ->

      yml = fs.readFileSync 'docs/mkdocs.template.yml', 'utf8'

      for file in files
        parts = file.split '/'

        for i in [0...parts.length]
          try
            fs.mkdirSync "docs/source/#{parts.slice(0, i).join '/'}"
          catch error

        fs.createReadStream(file).pipe(
          fs.createWriteStream("docs/source/#{file}")
        )

Add them under the 'Source code' path.

        yml += "- [source/#{file}, 'Source code', '#{file}']\n"

      fs.writeFileSync 'mkdocs.yml', yml

      return

Render the packages page.

    fileStatsListPromise.then((fileStatsList) ->

Sort by package name first.

      newList = []

      for fileStats in fileStatsList

        parts = fileStats.file.split '/'
        continue unless ~['custom', 'packages'].indexOf parts[0]

        sourcePath = _sourcePath fileStats.file

        parts = sourcePath.split '/'
        parts.pop() if parts[parts.length - 1] is 'client'
        pkg = parts.join '/'
        fileStats.pkg = pkg.split('/').slice(1).join '/'

        newList.push fileStats

      newList.sort (l, r) ->
        return -1 if l.type is 'client' and r.type is 'server'
        return 1 if l.type is 'server' and r.type is 'client'
        if l.pkg < r.pkg then -1 else if l.pkg > r.pkg then 1 else 0

    ).then((fileStatsList) ->

      render = fs.readFileSync 'docs/packages.template.md', 'utf8'
      render += '\n'

      type = null

      for fileStats in fileStatsList

        if fileStats.type isnt type
          type = fileStats.type

          render += if type is 'client'
            '## Client-side'
          else
            '## Server-side'

          render += '\n\n'

        isSubpackage = fileStats.pkg.split('/').length isnt 1

Link to the package.

        sourcePath = _sourcePath fileStats.file

        if isSubpackage
          render += '> '
          parts = fileStats.pkg.split '/'
          parentPkg = parts.shift()
          subPkg = parts.join '/'
          render += "## [<small>#{parentPkg}/</small>#{subPkg}](source/#{sourcePath})"
        else
          render += "## [#{fileStats.pkg}](source/#{sourcePath})"

Naively parse out the file description. It must be wrapped in asterisks, i.e.
italicized in markdown.

###### TODO: This 'chunk' parsing should be done with a Transform like the others.

        data = fs.readFileSync fileStats.file, 'utf8'
        chunks = data.split '\n\n'

        title = chunks[0]
        if 35 is title.charCodeAt 0
          render += " — #{title.substr 2}"

        render += '\n\n'

        description = chunks[1]
        if 42 is description.charCodeAt 0 and 42 is description.charCodeAt description.length - 1
          render += '> ' if isSubpackage
          render += "#{description}\n\n"

        if fileStats.implementations.length > 0
          render += '> ' if isSubpackage
          render += '!!! note "Implements hooks"\n    '
          render += fileStats.implementations.map((hook) ->
            "[#{hook}](source/#{sourcePath}#implements-hook-#{hook.toLowerCase()})"
          ).join ', '
          render += '\n\n'

        if fileStats.invocations.length > 0
          render += '> ' if isSubpackage
          render += '!!! note "Invokes hooks"\n    '
          render += fileStats.invocations.map((hook) ->
            "[#{hook}](source/#{sourcePath}#invoke-hook-#{hook.toLowerCase()})"
          ).join ', '
          render += '\n\n'

      fs.writeFileSync 'docs/packages.md', render

    )

# Documentation generation

*Generate the dynamic portions of Shrub's documentation.*

    {exec} = require 'child_process'
    fs = require 'fs'
    path = require 'path'
    {Transform} = require 'stream'

    _ = require 'lodash'
    {LineStream} = require 'byline'
    glob = require 'grunt/node_modules/glob'
    Promise = require 'bluebird'

Implement a Transform stream to accumulate hook invocations from a source file.

    class HookInvocations extends Transform

      constructor: ->
        super

        @list = []

      _transform: (chunk, encoding, done) ->
        line = chunk.toString('utf8')
        if matches = line.match /^\#\#\#\# Invoke hook `([^`]+)`/
          @list.push matches[1]

        done()

Implement a Transform stream to accumulate hook implementations from a source
file.

    class HookImplementations extends Transform

      constructor: ->
        super

        @list = []

      _transform: (chunk, encoding, done) ->
        line = chunk.toString('utf8')
        if matches = line.match /^\#\#\#\# Implements hook `([^`]+)`/
          @list.push matches[1]

        done()

Implement a Transform stream fo accumulate TODOs from a source file. Also
caches lines to be able to build context around each TODO item.

    class Todos extends Transform

      @context = 4

      constructor: ->
        super

        @lines = []
        @todos = []

      _transform: (chunk, encoding, done) ->
        @lines.push line = chunk.toString('utf8')
        if matches = line.match /^\#\#\#\#\#\# TODO/
          @todos.push @lines.length - 1

        done()

      withContext: ->

        for todo in @todos

          start = Math.max 0, todo - Todos.context
          end = Math.min @lines.length - 1, todo + Todos.context

          index: todo
          lines: @lines.slice start, end

Gather all source files.

    _allSourceFiles = ->
      new Promise (resolve, reject) ->
        glob(
          '{{client,custom,packages,server}/**/*.litcoffee,*.litcoffee}'
          (error, files) ->
            return reject error if error?
            resolve files
        )

Generate an HTML ID from a hook name.

    _idFromHook = (hook) -> hook.replace(
      /[^0-9A-Za-z-]+/g, '-'
    ).toLowerCase()

Get the source path from a filename. This removes the extension and any /index
portion from the end of the filename.

    _sourcePath = (filename) ->

        dirname = path.dirname filename
        if dirname is '.' then dirname = '' else dirname += '/'
        extname = path.extname filename
        filename = "#{dirname}#{path.basename filename, extname}"

        parts = filename.split '/'
        parts.pop() if parts[parts.length - 1] is 'index'
        return parts.join '/'

Collate a list of files by type (client or server).

    _collateFilesByType = (files) ->

      client = []
      server = []

      for file in files
        parts = file.split '/'

        if parts[0] is 'client'
          client.push file
        else if parts[0] is 'custom' and parts[2] is 'client'
          client.push file
        else if parts[0] is 'packages' and parts[2] is 'client'
          client.push file
        else
          server.push file

      client: client, server: server

Gather statistics for all files.

    fileStatsListPromise = _allSourceFiles().then (allFiles) ->

      allFilesPromises = for type, files of _collateFilesByType allFiles

        typePromises = for file in files

          do (type, file) -> new Promise (resolve, reject) ->

            fstream = fs.createReadStream file
            fstream.pipe lineStream = new LineStream keepEmptyLines: true

Pass all files through the Transform list to parse out relevant information.

            lineStream.pipe hookImplementations = new HookImplementations()
            lineStream.pipe hookInvocations = new HookInvocations()
            lineStream.pipe todos = new Todos()

            fstream.on 'error', reject

            fstream.on 'end', ->

Include all information from Transform streams in the statistics.

              resolve(
                type: type
                file: file
                implementations: hookImplementations.list
                invocations: hookInvocations.list
                todos: todos.withContext()
              )

        Promise.all typePromises

      Promise.all(allFilesPromises).then (fileStatsLists) ->
        _.flatten fileStatsLists

Massage the statistics to help rendering the hooks page.

    fileStatsListPromise.then((fileStatsList) ->

      hooksIndex = {}

      indexes = {}
      keys = ['implementations', 'invocations']

      for fileStats in fileStatsList

        for key in keys
          indexes[key] ?= {}

          for hook in fileStats[key]
            indexes[key][hook] ?= {}
            (indexes[key][hook][fileStats.file] ?= []).push fileStats.type

            hooksIndex[hook] = true

      for key in keys
        for hook, stats of indexes[key]
          for file of stats
            stats[file] = _.unique stats[file] ? []

      hooks = (hook for hook of hooksIndex).sort()

      hookFiles = {}
      for hook in hooks
        hookFiles[hook] = try
          fs.readFileSync "docs/hook/#{hook}.md", 'utf8'
        catch error
          ''

      O =
        hookFiles: hookFiles
        hooks: hooks

      O[key] = indexes[key] for key in keys

      return O

Render the hooks page.

    ).then((O) ->

      {hookFiles, hooks} = O

      keys = ['implementations', 'invocations']

      wordingFor =
        implementations: 'implements'
        invocations: 'invoke'

      render = fs.readFileSync 'docs/hooks.template.md', 'utf8'

      for hook in hooks

        render += "## #{hook}\n\n"
        render += hookFiles[hook] + '\n\n' if hookFiles[hook]

        for key in keys

          if O[key][hook]?

            count = Object.keys(O[key][hook]).length
            render += "### #{count} #{key}(s)\n\n"

            for file, types of O[key][hook]

              file = _sourcePath file

              types = types.map (type) -> "[#{type}](source/#{file}##{wordingFor[key]}-hook-#{hook.toLowerCase()})"

              render += "* #{file} (#{types.join ','})\n"

            render += '\n'

      new Promise (resolve, reject) ->
        fs.writeFile 'docs/hooks.md', render, (error) ->
          return reject error if error?
          resolve()

    ).done()

Render the TODOs page.

    fileStatsListPromise.then((fileStatsList) ->

      render = fs.readFileSync 'docs/todos.template.md', 'utf8'

      for fileStats in fileStatsList

Keep track of used IDs, it will be necessary to link to the correct location
hash in the case of multiple TODO items with the same wording.

        idMap = {}

        for todo in fileStats.todos

          render += '\n'

          id = ''
          for line, index in todo.lines
            render += '>'

If this is the line with the TODO, parse the ID from the TODO item text, and
render it as h2 (TODO are h6) to increase visibility.

            if index is Todos.context
              id = _idFromHook(line).slice 1, -1

              render += line.slice 4
            else
              render += " #{line}"
            render += '\n'

          render += '\n'

          filename = _sourcePath fileStats.file

Keep track of ID usage and modify the location hash for subsequent uses.

          if idMap[id]?
            idMap[id] += 1
            id += "_#{idMap[id]}"
          else
            idMap[id] = 0

          render += "###### the above found in [#{fileStats.file}:#{todo.index}](source/#{filename}##{id})\n"

      new Promise (resolve, reject) ->
        fs.writeFile 'docs/todos.md', render, (error) ->
          return reject error if error?
          resolve()

    ).done()

Add all the source files to a generated mkdocs.yml

    _allSourceFiles().then (files) ->

      yml = fs.readFileSync 'docs/mkdocs.template.yml', 'utf8'

      for file in files
        parts = file.split '/'

        for i in [0...parts.length]
          try
            fs.mkdirSync "docs/source/#{parts.slice(0, i).join '/'}"
          catch error

        fs.createReadStream(file).pipe(
          fs.createWriteStream("docs/source/#{file}")
        )

Add them under the 'Source code' path.

        yml += "- [source/#{file}, 'Source code', '#{file}']\n"

      fs.writeFileSync 'mkdocs.yml', yml

      return

Render the packages page.

    fileStatsListPromise.then((fileStatsList) ->

Sort by package name first.

      newList = []

      for fileStats in fileStatsList

        parts = fileStats.file.split '/'
        continue unless ~['custom', 'packages'].indexOf parts[0]

        sourcePath = _sourcePath fileStats.file

        parts = sourcePath.split '/'
        parts.pop() if parts[parts.length - 1] is 'client'
        pkg = parts.join '/'
        fileStats.pkg = pkg.split('/').slice(1).join '/'

        newList.push fileStats

      newList.sort (l, r) ->
        if l.pkg < r.pkg then -1 else if l.pkg > r.pkg then 1 else 0

    ).then((fileStatsList) ->

      render = fs.readFileSync 'docs/packages.template.md', 'utf8'
      render += '\n'

      type = null

      for fileStats in fileStatsList

        if fileStats.type isnt type
          type = fileStats.type

          render += if type is 'client'
            '## Client-side'
          else
            '## Server-side'

          render += '\n\n'

        isSubpackage = fileStats.pkg.split('/').length isnt 1

Link to the package.

        sourcePath = _sourcePath fileStats.file

        if isSubpackage
          render += '> '
          parts = fileStats.pkg.split '/'
          parentPkg = parts.shift()
          subPkg = parts.join '/'
          render += "## [<small>#{parentPkg}/</small>#{subPkg}](source/#{sourcePath})"
        else
          render += "## [#{fileStats.pkg}](source/#{sourcePath})"

Naively parse out the file description. It must be wrapped in asterisks, i.e.
italicized in markdown.

###### TODO: This 'chunk' parsing should be done with a Transform like the others.

        data = fs.readFileSync fileStats.file, 'utf8'
        chunks = data.split '\n\n'

        title = chunks[0]
        if 35 is title.charCodeAt 0
          render += " — #{title.substr 2}"

        render += '\n\n'

        description = chunks[1]
        if 42 is description.charCodeAt 0 and 42 is description.charCodeAt description.length - 1
          render += '> ' if isSubpackage
          render += "#{description}\n\n"

        if fileStats.implementations.length > 0
          render += '> ' if isSubpackage
          render += '!!! note "Implements hooks"\n    '
          render += fileStats.implementations.map((hook) ->
            "[#{hook}](source/#{sourcePath}#implements-hook-#{hook.toLowerCase()})"
          ).join ', '
          render += '\n\n'

        if fileStats.invocations.length > 0
          render += '> ' if isSubpackage
          render += '!!! note "Invokes hooks"\n    '
          render += fileStats.invocations.map((hook) ->
            "[#{hook}](source/#{sourcePath}#invoke-hook-#{hook.toLowerCase()})"
          ).join ', '
          render += '\n\n'

      fs.writeFileSync 'docs/packages.md', render

    )


Two changes to PngWalls:

Two changes to PngWalls:

1. Update `do_render` signature to accept `ctx` param
2. `PngWalls` no longer generates the data it uses to render walls,
   so all that logic was pulled out. Now it simply iterates over
   all the json data, building walls appropriatly

    PngRenderer = require './png_renderer'

    class PngWalls extends PngRenderer

      do_render: (ctx, path) =>
        cell_width  = @config.width
        cell_height = @config.height

        wall_segments  = @config.wall.segments
        segment_height = 1 / wall_segments
        wall_height    = cell_height * segment_height

        fill_segment = (side, block, segment, width, color) =>
          top  = (cell_height * block.y) + (segment * (cell_height * segment_height))
          left = cell_width * block.x

          left += cell_width - width if side == 'RIGHT'

          ctx.fillStyle = color

          ctx.beginPath()
          ctx.rect left, top, width, @config.multiplier
          ctx.fill()

        for block in path
          for side in [ 'left', 'right' ]
            if block["#{side}_wall"]?
              for seg, i in block["#{side}_wall"]
                fill_segment side.toUpperCase(), block, i, seg.thickness, @config.wall.color

    module.exports = PngWalls

    PngRenderer = require './png_renderer'

    class PngWalls extends PngRenderer

      do_render: (path) =>
        ctx    = PngRenderer.ctx
        width  = @config.width
        height = @config.height

        for step, i in path
          ctx.lineWidth   = @config.wall.width
          ctx.strokeStyle = @config.wall.color

          if i > 0
            previous = path[i - 1]
            top      =  height * previous.row  + 1
            bottom   = (height * previous.row) + (height - 1)
            left     = (width  * previous.col) + 1
            right    = (width  * previous.col) + (width - 1)

            switch step.direction
              when 'RIGHT', 'LEFT'
                if previous.direction != 'DOWN'
                  ctx.beginPath()
                  ctx.moveTo left,  top
                  ctx.lineTo right, top
                  ctx.stroke()
                else if step.direction == 'RIGHT'
                  ctx.beginPath()
                  ctx.moveTo left, top
                  ctx.lineTo left, bottom
                  ctx.stroke()
                else if step.direction == 'LEFT'
                  ctx.beginPath()
                  ctx.moveTo right, top
                  ctx.lineTo right, bottom
                  ctx.stroke()

                if previous.direction != 'UP'
                  ctx.beginPath()
                  ctx.moveTo left,  bottom
                  ctx.lineTo right, bottom
                  ctx.stroke()
                else if step.direction == 'RIGHT'
                  ctx.beginPath()
                  ctx.moveTo left, top
                  ctx.lineTo left, bottom
                  ctx.stroke()
                else if step.direction == 'LEFT'
                  ctx.beginPath()
                  ctx.moveTo right, top
                  ctx.lineTo right, bottom
                  ctx.stroke()

                if previous.direction == undefined
                  if step.direction == 'RIGHT'
                    ctx.beginPath()
                    ctx.moveTo left, top
                    ctx.lineTo left, bottom
                    ctx.stroke()
                  else if step.direction == 'LEFT'
                    ctx.beginPath()
                    ctx.moveTo right, top
                    ctx.lineTo right, bottom
                    ctx.stroke()

              when 'UP', 'DOWN'
                if previous.direction != 'LEFT'
                  ctx.beginPath()
                  ctx.moveTo right, top
                  ctx.lineTo right, bottom
                  ctx.stroke()
                else if step.direction == 'DOWN'
                  ctx.beginPath()
                  ctx.moveTo left,  top
                  ctx.lineTo right, top
                  ctx.stroke()
                else if step.direction == 'UP'
                  ctx.beginPath()
                  ctx.moveTo left,  bottom
                  ctx.lineTo right, bottom
                  ctx.stroke()

                if previous.direction != 'RIGHT'
                  ctx.beginPath()
                  ctx.moveTo left, top
                  ctx.lineTo left, bottom
                  ctx.stroke()
                else if step.direction == 'DOWN'
                  ctx.beginPath()
                  ctx.moveTo left,  top
                  ctx.lineTo right, top
                  ctx.stroke()
                else if step.direction == 'UP'
                  ctx.beginPath()
                  ctx.moveTo left,  bottom
                  ctx.lineTo right, bottom
                  ctx.stroke()

                if previous.direction == undefined
                  if step.direction == 'DOWN'
                    ctx.beginPath()
                    ctx.moveTo left,  top
                    ctx.lineTo right, top
                    ctx.stroke()
                  else if step.direction == 'UP'
                    ctx.beginPath()
                    ctx.moveTo left,  bottom
                    ctx.lineTo right, bottom
                    ctx.stroke()

          if i == path.length - 1
            previous = path[i - 1]
            top      =  height * step.row  + 1
            bottom   = (height * step.row) + (height - 1)
            left     = (width  * step.col) + 1
            right    = (width  * step.col) + (width - 1)

            switch step.direction
              when 'RIGHT', 'LEFT'
                ctx.beginPath()
                ctx.moveTo left,  top
                ctx.lineTo right, top
                ctx.stroke()

                ctx.beginPath()
                ctx.moveTo left,  bottom
                ctx.lineTo right, bottom
                ctx.stroke()

                if step.direction == 'RIGHT'
                  ctx.beginPath()
                  ctx.moveTo right, top
                  ctx.lineTo right, bottom
                  ctx.stroke()

                if step.direction == 'LEFT'
                  ctx.beginPath()
                  ctx.moveTo left, top
                  ctx.lineTo left, bottom
                  ctx.stroke()

              when 'UP', 'DOWN'
                ctx.beginPath()
                ctx.moveTo right, top
                ctx.lineTo right, bottom
                ctx.stroke()

                ctx.beginPath()
                ctx.moveTo left, top
                ctx.lineTo left, bottom
                ctx.stroke()

                if step.direction == 'UP'
                  ctx.beginPath()
                  ctx.moveTo left,  top
                  ctx.lineTo right, top
                  ctx.stroke()

                if step.direction == 'DOWN'
                  ctx.beginPath()
                  ctx.moveTo left,  bottom
                  ctx.lineTo right, bottom
                  ctx.stroke()

    module.exports = PngWalls


fix connectionId update null schema error

# Queue Collection

GetNameSchema is used to create the autoform to take a ticket.

    @NameSchema = new SimpleSchema
      name:
        type: String
        label: 'Name'
        max: 100

ConnectionSchema has an ascending index on `ticketNumber`

    ConnectionSchema = new SimpleSchema
      ticketNumber:
        type: Number
        index: 1
      connectionId:
        type: String
        max: 20
      name:
        type: String
        max: 100
      timeEnqueued:
        type: Date

    QueueMetaSchema = new SimpleSchema
      theOnlyConnectionAllowedIn:
        type: ConnectionSchema
        optional: yes
      hasCurrentConnectionPosted:
        type: Boolean
      currentlyServingTicketNumber:
        type: Number
      timeCurrentTicketExpires:
        type: Date
      averageWaitTimeSeconds:
        type: Number
      totalWaitTimeSeconds:
        type: Number
      totalVisitors:
        type: Number
      nextTicketNumber:
        type: Number
      html:
        type: String
        optional: yes

    QueueProcessedSchema = new SimpleSchema
      connection:
        type: ConnectionSchema
      waitTimeSeconds:
        type: Number
      postID:
        type: String
        max: 20
        optional: yes

    @Queue = new Meteor.Collection 'queue'
    Queue.attachSchema ConnectionSchema

There is only ever one Queue Meta.

    @QueueMeta = new Meteor.Collection 'queueMeta'
    QueueMeta.attachSchema QueueMetaSchema

    @QueueProcessed = new Meteor.Collection 'queueProcessed'
    QueueProcessed.attachSchema QueueProcessedSchema

    Meteor.methods
      getInQueue: (doc) ->

        return if this.isSimulation

Validate doc

        check doc, NameSchema

Check that the connection is not already in the queue.

        criteria =
          connectionId: this.connection.id

        if (Queue.find criteria).count() isnt 0
          Meteor.Error 'ditch-attempt', "Don't try to cut in line...it's not nice."

Get the next ticketNumber

        queueMeta = QueueMeta.findOne()

        if not queueMeta?
          Meteor.Error 'no-queue-meta', "There is no queue meta, something's wrong."

        nextTicketNumber = queueMeta.nextTicketNumber

Create queue object

        connection =
          ticketNumber: nextTicketNumber
          connectionId: this.connection.id
          name: doc.name
          timeEnqueued: moment.utc().toDate()

Push onto the queue

        Queue.insert connection

Increment nextTicketNumber (because we've pulled the current next ticket number)

        nextTicketNumber += 1
        criteria =
          _id: queueMeta._id
        update =
          $set: nextTicketNumber: nextTicketNumber

        QueueMeta.update criteria, update

Send a notification email, unblock

        this.unblock()

        try
          if Meteor.settings.notifyEmail? and Meteor.settings.mailgunOptions?
            Mailguns = new Mailgun Meteor.settings.mailgunOptions
            Mailguns.send
              to: Meteor.settings.notifyEmail
              from: 'notify@mostexclusivewebsite.com'
              subject: 'Ticket Pulled!'
              text: "Ticket ##{connection.ticketNumber}. #{Meteor.absoluteUrl()}"
        catch error
          console.log error

        return connection

      updateConnectionId: (oldConnectionId) ->
        return if this.isSimulation
        this.unblock()
        newConnectionId = this.connection.id
        return if not newConnectionId? or not oldConnectionId?
        criteria =
          connectionId: oldConnectionId
        action =
          $set:
            connectionId: newConnectionId

        console.log 'update queue', criteria, action
        Queue.update criteria, action

Update QueueMeta (TODO: way around lots of failed updates?)

        criteria =
          'theOnlyConnectionAllowedIn.connectionId': oldConnectionId
        action =
          $set:
            'theOnlyConnectionAllowedIn.connectionId': newConnectionId

        console.log 'update queuemeta', criteria, action
        QueueMeta.update criteria, action

# Queue Collection

GetNameSchema is used to create the autoform to take a ticket.

    @NameSchema = new SimpleSchema
      name:
        type: String
        label: 'Name'
        max: 100

ConnectionSchema has an ascending index on `ticketNumber`

    ConnectionSchema = new SimpleSchema
      ticketNumber:
        type: Number
        index: 1
      connectionId:
        type: String
        max: 20
      name:
        type: String
        max: 100
      timeEnqueued:
        type: Date

    QueueMetaSchema = new SimpleSchema
      theOnlyConnectionAllowedIn:
        type: ConnectionSchema
        optional: yes
      hasCurrentConnectionPosted:
        type: Boolean
      currentlyServingTicketNumber:
        type: Number
      timeCurrentTicketExpires:
        type: Date
      averageWaitTimeSeconds:
        type: Number
      totalWaitTimeSeconds:
        type: Number
      totalVisitors:
        type: Number
      nextTicketNumber:
        type: Number
      html:
        type: String
        optional: yes

    QueueProcessedSchema = new SimpleSchema
      connection:
        type: ConnectionSchema
      waitTimeSeconds:
        type: Number
      postID:
        type: String
        max: 20
        optional: yes

    @Queue = new Meteor.Collection 'queue'
    Queue.attachSchema ConnectionSchema

There is only ever one Queue Meta.

    @QueueMeta = new Meteor.Collection 'queueMeta'
    QueueMeta.attachSchema QueueMetaSchema

    @QueueProcessed = new Meteor.Collection 'queueProcessed'
    QueueProcessed.attachSchema QueueProcessedSchema

    Meteor.methods
      getInQueue: (doc) ->

        return if this.isSimulation

Validate doc

        check doc, NameSchema

Check that the connection is not already in the queue.

        criteria =
          connectionId: this.connection.id

        if (Queue.find criteria).count() isnt 0
          Meteor.Error 'ditch-attempt', "Don't try to cut in line...it's not nice."

Get the next ticketNumber

        queueMeta = QueueMeta.findOne()

        if not queueMeta?
          Meteor.Error 'no-queue-meta', "There is no queue meta, something's wrong."

        nextTicketNumber = queueMeta.nextTicketNumber

Create queue object

        connection =
          ticketNumber: nextTicketNumber
          connectionId: this.connection.id
          name: doc.name
          timeEnqueued: moment.utc().toDate()

Push onto the queue

        Queue.insert connection

Increment nextTicketNumber (because we've pulled the current next ticket number)

        nextTicketNumber += 1
        criteria =
          _id: queueMeta._id
        update =
          $set: nextTicketNumber: nextTicketNumber

        QueueMeta.update criteria, update

Send a notification email, unblock

        this.unblock()

        try
          if Meteor.settings.notifyEmail? and Meteor.settings.mailgunOptions?
            Mailguns = new Mailgun Meteor.settings.mailgunOptions
            Mailguns.send
              to: Meteor.settings.notifyEmail
              from: 'notify@mostexclusivewebsite.com'
              subject: 'Ticket Pulled!'
              text: "Ticket ##{connection.ticketNumber}. #{Meteor.absoluteUrl()}"
        catch error
          console.log error

        return connection

      updateConnectionId: (oldConnectionId) ->
        return if this.isSimulation
        newConnectionId = this.connection.id
        this.unblock()
        criteria =
          connectionId: oldConnectionId
        action =
          $set:
            connectionId: newConnectionId

        Queue.update criteria, action

Update QueueMeta (TODO: way around lots of failed updates?)

        criteria =
          'theOnlyConnectionAllowedIn.connectionId': oldConnectionId
        action =
          $set:
            'theOnlyConnectionAllowedIn.connectionId': newConnectionId

        QueueMeta.update criteria, action


docs(document): annotate http endpoints

docs(document): annotate http endpoints

    schema   = require './helper/schema'
    sentry   = require './db/sentry'
    Document = require './model/Document'

    JSONStream = require 'JSONStream'

## param()

    exports.param = (req, res, next, id) ->
      return res.status(400).json message: 'Invalid ObjectId' if not /^[a-f0-9]{24}$/.test id
      return next() if req.method is 'OPTIONS'

      req.doc = new Document(req.type, id).once('error', next).once 'ready', ->
        req.isOwner = @exists() and req.user.tilbyder is @get 'tilbyder'

        if not @exists() or (@get('status') isnt 'Offentlig' and not req.isOwner)
          return res.status(404).json message: 'Not Found' if req.method isnt 'HEAD'
          return res.status(404).end()

        next()

## all()

    exports.all = (req, res, next) ->
      res.set 'X-Cache-Hit', req.doc.wasCacheHit()
      res.set 'ETag', "\"#{req.doc.get 'checksum'}\""
      res.set 'Last-Modified', new Date(req.doc.get 'endret').toUTCString()

      if req.method not in ['GET', 'HEAD'] and not req.isOwner
        return res.status(403).json message: 'Request Denied'

      if req.get 'If-Match'
        return res.status(412).end() if req.doc.isNoneMatch req.get 'If-Match'

      else if req.get 'If-None-Match'
        return res.status(304).end() if req.doc.isMatch req.get 'If-None-Match'

      else
        if req.doc.isNotModifiedSince req.get 'If-Modified-Since'
          return res.status(304).end()

        if req.doc.isModifiedSince req.get 'If-Unmodified-Since'
          return res.status(412).end()

      next()

## OPTIONS

```http
OPTIONS /{type}/{id}
```

    exports.options = (req, res, next) ->
      res.set 'Access-Control-Allow-Methods', [
        'HEAD', 'GET', 'PUT', 'PATCH', 'DELETE'
      ].join ', '
      res.set 'Access-Control-Allow-Headers', [
        'Content-Type'
        'If-Match'
        'If-Modified-Since'
        'If-None-Match'
        'If-Unmodified-Since'
      ].join ', '
      res.sendStatus 204

## HEAD and GET

```http
HEAD /{type}/{id}
GET /{type}/{id}
```

    exports.head = exports.get = (req, res, next) ->
      return res.sendStatus 200 if req.method is 'HEAD'

      opts =
        query: $or: [{status: 'Offentlig'}, {tilbyder: req.user.tilbyder}]
        fields: {}

### Fields Projection

```
&fields=field1[,field2[,..]]
```

Private (`privat.`) fields are hidden unless the current API user is the owner
of the document. This is to prevent unauthorized information disclosure.

      opts.fields.privat = false if not req.isOwner

If you only want some of the document properties returned you can use the
`fields` query parameter to control the projection of document properties.
Multiple fields must be comma separated without any additional spacing.

This projection also applies to expanded sub-documents when using the `&expand=`
query parameter below.

      if req.query?.fields
        opts.fields[field] = true for field in req.query.fields.split ','
        opts.fields.privat = false if not req.isOwner

The list of default fields below will always be returned for compliance reasons.

        opts.fields = Object.assign opts.fields,
          endret: true
          lisens: true
          status: true
          navn  : true
          tilbyder  : true
          navngiving: true

### Sub-document Expansion

```
&expand=field1[,field2[..]]
```

Sub-document expansion is an easy and convenient way to get connected documents
such as `grupper`, `bilder`, and `områder` as their full objects instead of a
list of `ObjectIDs`. It works by setting the `expand` query parameter to the
fields to expand.

**Rate limit**

Sub-document expansion will decrease your remaining API rate limit with `1` per
collection expanded in addition to the request itself. Thus, if you request a
document (1) with two collection expanded (2) your remaining API rate limit will
be decremented with 3. This is to encurrage resonable expansion of relevant
collections only.

**Order and limit**

Sub-document expansion will return at most 10 first sub-documents for each
collection expanded and out of order. Only sub-documents avaiable to the current
API user will be return as sub-documents my not be published or may have been
deleted.

      if req.query?.expand
        opts.expand = req.query.expand.split ','

### Sub-document Limit

```
&limit={n}
```

If you wisth to return less sub-documents in order to increase response time and
body size you may do so by using the `limit` query parameter. This will affect
all sub-documents expanded by the `expand` query parameter.

      if req.query?.limit and parseInt req.query.limit, 10
        opts.limit = parseInt req.query.limit, 10

---

      req.doc.getExpanded opts, (err, doc) ->
        return next err if err
        res.json doc

## PUT and PATCH

```http
PUT /{type}/{id}
PATCH /{type}/{id}
```

    exports.patch = exports.put = (req, res, next) ->
      return res.status(400).json message: 'Body is missing' if Object.keys(req.body).length is 0
      return res.status(400).json message: 'Body should be a JSON Hash' if req.body instanceof Array

      req.body.tilbyder = req.user.tilbyder

      method = (if req.method is 'PUT' then 'replace' else 'update')
      req.doc[method] req.body, (err, warn, data) ->
        if err
          return next(err) if err.name isnt 'ValidationError'

          sentry.captureDocumentError req, err

          return res.status(422).json
            document: req.body
            message: 'Validation Failed'
            errors: err.details #TODO(starefossen) document this

        res.set 'ETag', "\"#{data.checksum}\""
        res.set 'Last-Modified', new Date(data.endret).toUTCString()

        return res.status(200).json
          document: data
          message: 'Validation Warnings' if warn.length > 0
          warnings: warn if warn.length > 0

## DELETE

```http
DELETE /{type}/{id}
```

    exports.delete = (req, res, next) ->
      req.doc.delete (err) ->
        return next err if err
        return res.sendStatus 204

    schema   = require './helper/schema'
    sentry   = require './db/sentry'
    Document = require './model/Document'

    JSONStream = require 'JSONStream'

## param()

    exports.param = (req, res, next, id) ->
      return res.status(400).json message: 'Invalid ObjectId' if not /^[a-f0-9]{24}$/.test id
      return next() if req.method is 'OPTIONS'

      req.doc = new Document(req.type, id).once('error', next).once 'ready', ->
        req.isOwner = @exists() and req.user.tilbyder is @get 'tilbyder'

        if not @exists() or (@get('status') isnt 'Offentlig' and not req.isOwner)
          return res.status(404).json message: 'Not Found' if req.method isnt 'HEAD'
          return res.status(404).end()

        next()

## all()

    exports.all = (req, res, next) ->
      res.set 'X-Cache-Hit', req.doc.wasCacheHit()
      res.set 'ETag', "\"#{req.doc.get 'checksum'}\""
      res.set 'Last-Modified', new Date(req.doc.get 'endret').toUTCString()

      if req.method not in ['GET', 'HEAD'] and not req.isOwner
        return res.status(403).json message: 'Request Denied'

      if req.get 'If-Match'
        return res.status(412).end() if req.doc.isNoneMatch req.get 'If-Match'

      else if req.get 'If-None-Match'
        return res.status(304).end() if req.doc.isMatch req.get 'If-None-Match'

      else
        if req.doc.isNotModifiedSince req.get 'If-Modified-Since'
          return res.status(304).end()

        if req.doc.isModifiedSince req.get 'If-Unmodified-Since'
          return res.status(412).end()

      next()

## OPTIONS

    exports.options = (req, res, next) ->
      res.set 'Access-Control-Allow-Methods', [
        'HEAD', 'GET', 'PUT', 'PATCH', 'DELETE'
      ].join ', '
      res.set 'Access-Control-Allow-Headers', [
        'Content-Type'
        'If-Match'
        'If-Modified-Since'
        'If-None-Match'
        'If-Unmodified-Since'
      ].join ', '
      res.sendStatus 204

## HEAD and GET

    exports.head = exports.get = (req, res, next) ->
      return res.sendStatus 200 if req.method is 'HEAD'

      opts =
        query: $or: [{status: 'Offentlig'}, {tilbyder: req.user.tilbyder}]
        fields: {}

### Fields Projection

```
&fields=field1[,field2[,..]]
```

Private (`privat.`) fields are hidden unless the current API user is the owner
of the document. This is to prevent unauthorized information disclosure.

      opts.fields.privat = false if not req.isOwner

If you only want some of the document properties returned you can use the
`fields` query parameter to control the projection of document properties.
Multiple fields must be comma separated without any additional spacing.

This projection also applies to expanded sub-documents when using the `&expand=`
query parameter below.

      if req.query?.fields
        opts.fields[field] = true for field in req.query.fields.split ','
        opts.fields.privat = false if not req.isOwner

The list of default fields below will always be returned for compliance reasons.

        opts.fields = Object.assign opts.fields,
          endret: true
          lisens: true
          status: true
          navn  : true
          tilbyder  : true
          navngiving: true

### Sub-document Expansion

```
&expand=field1[,field2[..]]
```

Sub-document expansion is an easy and convenient way to get connected documents
such as `grupper`, `bilder`, and `områder` as their full objects instead of a
list of `ObjectIDs`. It works by setting the `expand` query parameter to the
fields to expand.

**Rate limit**

Sub-document expansion will decrease your remaining API rate limit with `1` per
collection expanded in addition to the request itself. Thus, if you request a
document (1) with two collection expanded (2) your remaining API rate limit will
be decremented with 3. This is to encurrage resonable expansion of relevant
collections only.

**Order and limit**

Sub-document expansion will return at most 10 first sub-documents for each
collection expanded and out of order. Only sub-documents avaiable to the current
API user will be return as sub-documents my not be published or may have been
deleted.

      if req.query?.expand
        opts.expand = req.query.expand.split ','

### Sub-document Limit

```
&limit={n}
```

If you wisth to return less sub-documents in order to increase response time and
body size you may do so by using the `limit` query parameter. This will affect
all sub-documents expanded by the `expand` query parameter.

      if req.query?.limit and parseInt req.query.limit, 10
        opts.limit = parseInt req.query.limit, 10

---

      req.doc.getExpanded opts, (err, doc) ->
        return next err if err
        res.json doc

## PUT and PATCH

    exports.patch = exports.put = (req, res, next) ->
      return res.status(400).json message: 'Body is missing' if Object.keys(req.body).length is 0
      return res.status(400).json message: 'Body should be a JSON Hash' if req.body instanceof Array

      req.body.tilbyder = req.user.tilbyder

      method = (if req.method is 'PUT' then 'replace' else 'update')
      req.doc[method] req.body, (err, warn, data) ->
        if err
          return next(err) if err.name isnt 'ValidationError'

          sentry.captureDocumentError req, err

          return res.status(422).json
            document: req.body
            message: 'Validation Failed'
            errors: err.details #TODO(starefossen) document this

        res.set 'ETag', "\"#{data.checksum}\""
        res.set 'Last-Modified', new Date(data.endret).toUTCString()

        return res.status(200).json
          document: data
          message: 'Validation Warnings' if warn.length > 0
          warnings: warn if warn.length > 0

## DELETE

    exports.delete = (req, res, next) ->
      req.doc.delete (err) ->
        return next err if err
        return res.sendStatus 204


Set proper http status code for bad origin

    express       = require 'express'
    logger        = require 'morgan'
    url           = require 'url'

    compression   = require 'compression'
    responseTime  = require 'response-time'

    raven         = require 'raven'
    sentry        = require './sentry'

    #process.env.LIBRATO_INTERVAL ?= 1
    #librato =
    #  middleware: require('./librato').middleware.use
    #  count     : require('./librato').middleware.routeCount

## Configuration

    process.env.PORT_WWW ?= 8080

    app = module.exports = express()

    app.set 'json spaces', 2
    app.set 'x-powered-by', false

    app.use compression()
    app.use responseTime()

    if app.get('env').toLowerCase() isnt 'test'
      app.use logger 'dev'
      #app.use librato.middleware
      #app.use librato.count name: 'request', period: 1

## CORS

Cross-site HTTP requests are HTTP requests for resources from a different domain
than the domain of the resource making the request. [Read
More](https://developer.mozilla.org/en-US/docs/Web/HTTP/Access_control_CORS).

    origins = process.env.ALLOW_ORIGINS?.split(',') or []

    app.use (req, res, next) ->
      if req.get 'Origin'
        origin = url.parse req.get('Origin') or ''

Check if the request Origin is on in the `ALLOW_ORIGINS` list. If not return a
403 to prevent this Origin from misusing the service.

        if origin.hostname not in origins
          error = new Error "Bad Origin Header #{req.get('Origin')}"
          error.statusCode = 403
          return next error

Set the correct CORS headers.

        res.set 'Access-Control-Allow-Origin', req.get('Origin')
        res.set 'Access-Control-Allow-Methods', 'GET, POST'
        res.set 'Access-Control-Allow-Headers', 'X-Requested-With, Content-Type'
        res.set 'Access-Control-Expose-Headers', 'X-Response-Time'
        res.set 'Access-Control-Allow-Max-Age', 0

Browsers check for CORS support by doing a "preflight". This means sending a
HTTP `OPTIONS` request to the server and checking the returned CORS-headers. No
body is required for this request so we can safely end this request now.

      if req.method is 'OPTIONS' and req.path isnt '/CloudHealthCheck'
        return res.status(200).end()

      return next()

## Routes

    app.all '/CloudHealthCheck', (req, res, next) ->
      res.status 200
      return res.end() if req.method is 'HEAD'
      return res.json message: 'System OK'

    app.use '/', require './routes/api_v1'
    app.use '/api/v1/', require './routes/api_v1'

    app.get '/', (req, res, next) ->
      res.redirect '/api/v1'

## Not Found

    app.use (req, res, next) ->
      res.status(404).json message: 'Not Found'

### Error Handling

Before handling the error ours self make sure that it is propperly logged in
Sentry by using the express/connect middleware.

    app.use raven.middleware.express sentry

All errors passed to `next` or exceptions ends up here. We set the status code
to `500` if it is not already defined in the `Error` object. We then print the
error mesage and stack trace to the console for debug purposes.

Before returning a response to the user the request method is check. HEAD
requests shall not contain any body – this applies for errors as well.

    app.use (err, req, res, next) ->
      if err.code is 'LIMIT_UNEXPECTED_FILE'
        err.message = "Unknown form field \"#{err.field}\""
        err.statusCode = 400

      if not err.statusCode or err.statusCode >= 500
        console.error err
        console.error err.message
        console.error err.stack

      res.status err.statusCode or 500

      return res.end() if req.method is 'HEAD'
      return res.json message: err.message or 'Unknown error'

### Start Server

    if not module.parent
      app.listen process.env.PORT_WWW
      console.log "Server is listening on port #{process.env.PORT_WWW}"

    express       = require 'express'
    logger        = require 'morgan'
    url           = require 'url'

    compression   = require 'compression'
    responseTime  = require 'response-time'

    raven         = require 'raven'
    sentry        = require './sentry'

    #process.env.LIBRATO_INTERVAL ?= 1
    #librato =
    #  middleware: require('./librato').middleware.use
    #  count     : require('./librato').middleware.routeCount

## Configuration

    process.env.PORT_WWW ?= 8080

    app = module.exports = express()

    app.set 'json spaces', 2
    app.set 'x-powered-by', false

    app.use compression()
    app.use responseTime()

    if app.get('env').toLowerCase() isnt 'test'
      app.use logger 'dev'
      #app.use librato.middleware
      #app.use librato.count name: 'request', period: 1

## CORS

Cross-site HTTP requests are HTTP requests for resources from a different domain
than the domain of the resource making the request. [Read
More](https://developer.mozilla.org/en-US/docs/Web/HTTP/Access_control_CORS).

    origins = process.env.ALLOW_ORIGINS?.split(',') or []

    app.use (req, res, next) ->
      if req.get 'Origin'
        origin = url.parse req.get('Origin') or ''

Check if the request Origin is on in the `ALLOW_ORIGINS` list. If not return a
403 to prevent this Origin from misusing the service.

        if origin.hostname not in origins
          error = new Error "Bad Origin Header #{req.get('Origin')}"
          error.status = 403
          return next error

Set the correct CORS headers.

        res.set 'Access-Control-Allow-Origin', req.get('Origin')
        res.set 'Access-Control-Allow-Methods', 'GET, POST'
        res.set 'Access-Control-Allow-Headers', 'X-Requested-With, Content-Type'
        res.set 'Access-Control-Expose-Headers', 'X-Response-Time'
        res.set 'Access-Control-Allow-Max-Age', 0

Browsers check for CORS support by doing a "preflight". This means sending a
HTTP `OPTIONS` request to the server and checking the returned CORS-headers. No
body is required for this request so we can safely end this request now.

      if req.method is 'OPTIONS' and req.path isnt '/CloudHealthCheck'
        return res.status(200).end()

      return next()

## Routes

    app.all '/CloudHealthCheck', (req, res, next) ->
      res.status 200
      return res.end() if req.method is 'HEAD'
      return res.json message: 'System OK'

    app.use '/', require './routes/api_v1'
    app.use '/api/v1/', require './routes/api_v1'

    app.get '/', (req, res, next) ->
      res.redirect '/api/v1'

## Not Found

    app.use (req, res, next) ->
      res.status(404).json message: 'Not Found'

### Error Handling

Before handling the error ours self make sure that it is propperly logged in
Sentry by using the express/connect middleware.

    app.use raven.middleware.express sentry

All errors passed to `next` or exceptions ends up here. We set the status code
to `500` if it is not already defined in the `Error` object. We then print the
error mesage and stack trace to the console for debug purposes.

Before returning a response to the user the request method is check. HEAD
requests shall not contain any body – this applies for errors as well.

    app.use (err, req, res, next) ->
      if err.code is 'LIMIT_UNEXPECTED_FILE'
        err.message = "Unknown form field \"#{err.field}\""
        err.statusCode = 400

      if not err.statusCode or err.statusCode >= 500
        console.error err
        console.error err.message
        console.error err.stack

      res.status err.statusCode or 500

      return res.end() if req.method is 'HEAD'
      return res.json message: err.message or 'Unknown error'

### Start Server

    if not module.parent
      app.listen process.env.PORT_WWW
      console.log "Server is listening on port #{process.env.PORT_WWW}"


Multiple type docs

Multiple type docs

# typed-bacon [![](https://travis-ci.org/johannhof/typed-bacon.svg)](https://travis-ci.org/johannhof/typed-bacon)
Type checks in your Bacon stream!

> This document is an executable source file that contains tests to stay up to date.

    assert = require('assert')

## Usage

Requiring Bacon before typed-bacon ensures that the custom methods can hook into Bacon correctly.
You need to require `typed-bacon` to add the custom type methods to Bacon observables.
You will also want to get access to the Types object.

    Bacon = require('baconjs')
    {Types} = require('typed-bacon')

## API

### Observable.typeCheck

Transforms a value to an error if the input is not of the specified type.

    Bacon.fromArray([1, 2, "three"])
         .typeCheck(Types.Number)
         .onError (e) ->
           assert(e instanceof Error)
           assert.equal(e.message, "Expected three to be of type number.")

### Observable.typeFilter

Filters out all stream values that are not of the specified type.

    Bacon.fromArray([1, "two", 3, true, 4])
         .typeFilter(Types.Number)
         .slidingWindow(3,3)
         .onValue (arr) ->
           assert.deepEqual(arr, [1, 3, 4])

### Observable.typeWarn

Coming soon!

### Multiple types

In some (hopefully rare) cases you might be forced to have different stream value types in your streams.
A common use case is allowing a stream to contain `null` if it currently doesn't have a value.
This can be supported by passing multiple parameters to the Observable methods.

    Bacon.fromArray([1, "two", 3, null, 4])
         .typeFilter(Types.Number, Types.Null)
         .slidingWindow(4,4)
         .onValue (arr) ->
           assert.deepEqual(arr, [1, 3, null, 4])

### Types.Object

### Types.Array

# typed-bacon [![](https://travis-ci.org/johannhof/typed-bacon.svg)](https://travis-ci.org/johannhof/typed-bacon)
Type checks in your Bacon stream!

_This document is a source file that can be run and contains tests to stay up to date._

    assert = require('assert')

## Usage

Requiring Bacon before typed-bacon ensures that the custom methods can hook into Bacon correctly.

    Bacon = require('baconjs')

You need to require `typed-bacon` to add the custom type methods to Bacon observables.
You will also want to get access to the Types object.

    {Types} = require('typed-bacon')

## API

### Observable.typeCheck

Transforms a value to an error if the input is not of the specified type.

    Bacon.fromArray([1, 2, "3"])
         .typeCheck(Types.Number)
         .onError (e) ->
           assert(e instanceof Error)
           assert.equal(e.message, "Expected 3 to be of type number.")

### Observable.typeFilter

Filters out all stream values that are not of the specified type.

    Bacon.fromArray([1, "2", 3, true, 4])
         .typeFilter(Types.Number)
         .slidingWindow(3,3)
         .onValue (arr) ->
           assert.deepEqual(arr, [1, 3, 4])

### Observable.typeWarn

### Types.Object

### Types.Array


onDisconnect remove from queue; close #10

# Super secret queue methods

    @QueueMethods =
      popAndServeNextTicket: ->

Move the current connection to the processed queue

TODO: get postID

        queueMeta = QueueMeta.findOne()

Update object, extended at various point for updating the QueueMeta in one swoop

        update =
          $set: {}

If `theOnlyConnectionAllowedIn` is set to something, check if the connection
has expired and process the connection if it has.

        if queueMeta.theOnlyConnectionAllowedIn isnt undefined

Return if timeCurrentTicketExpires is in the future (hasn't expired)

          hasntExpired = queueMeta.timeCurrentTicketExpires > moment.utc().toDate()
          return if hasntExpired

Okay then, the current connection has expired, time to get bumped.
Insert the processed connection into the ProcessedQueue

          try

Calculate how long the connection had to wait in line.

            waitTimeSeconds = moment.utc().diff (moment.utc queueMeta.theOnlyConnectionAllowedIn.timeEnqueued), 'seconds'

Make the processed object.

            processed =
              connection: queueMeta.theOnlyConnectionAllowedIn
              waitTimeSeconds: waitTimeSeconds

Insert the processed connection to `QueueProcessed`

            QueueProcessed.insert processed
          catch err
            console.log err

Remove the connection from the `Queue`

          criteria = connectionId: queueMeta.theOnlyConnectionAllowedIn.connectionId
          Queue.remove criteria

Update `QueueMeta` stats and ticket number.

          try
            averageWaitTimeSeconds = Math.round (((queueMeta.currentlyServingTicketNumber - 1) * queueMeta.averageWaitTimeSeconds) + waitTimeSeconds) / queueMeta.currentlyServingTicketNumber
            totalWaitTimeSeconds = queueMeta.totalWaitTimeSeconds + waitTimeSeconds
            currentlyServingTicketNumber = queueMeta.currentlyServingTicketNumber + 1
            _.extend update.$set,
              currentlyServingTicketNumber: currentlyServingTicketNumber
              hasCurrentConnectionPosted: no
              averageWaitTimeSeconds: averageWaitTimeSeconds
              totalWaitTimeSeconds: totalWaitTimeSeconds
          catch err
            console.log err

Lookup the lowest ticket # waiting in the `Queue`

        nextConnection = Queue.findOne {}, $sort: ticketNumber: 1

Set the allowed connection and expireTime if one exists

If there is a nextConnection (i.e. someone waiting in line), then serve them.
Otherwise clear out theOnlyConnectionAllowedIn.

        if nextConnection?
          timeCurrentTicketExpires = moment.utc()
          timeCurrentTicketExpires.add 1, 'minutes'
          _.extend update.$set,
            theOnlyConnectionAllowedIn: nextConnection
            timeCurrentTicketExpires: timeCurrentTicketExpires.toDate()
            currentlyServingTicketNumber: nextConnection.ticketNumber

If there is no nextConnection, unset theOnlyConnectionAllowedIn.
Note, fake data is filled in to pass schema, even though we're $unset'ing

        else
          update.$unset = theOnlyConnectionAllowedIn: ""

        criteria = _id: queueMeta._id

Delete `update.$set` if it's empty

        if _.isEmpty update.$set
          delete update.$set

If `update` isn't empty, update!

        if not _.isEmpty update
          QueueMeta.update criteria, update

TODO: keep it DRY; logic isn't exactly shared and want to keep update atomic to
avoid extraneous DDP messages.

## Disconnect, remove from queue

    Meteor.onConnection (connection) ->
      connection.onClose ->

Remove the connection from the `Queue`

          criteria = connectionId: connection.id
          Queue.remove criteria