class Flack::App

/executions/*

helpers

/

/message

/messages/*

/pointers/*

/*.html /*.css /*.js

Constants

CURIES
METHS
STATIC_FILE

Attributes

unit[R]

Public Class Methods

new(root, opts={}) click to toggle source
# File lib/flack/app.rb, line 17
def initialize(root, opts={})

  @root = root

  conf_path =
    root.end_with?(File::SEPARATOR + 'conf.json') ?
    root :
    File.join(@root, 'etc', 'conf.json')

  @unit = Flor::Unit.new(conf_path)

  @unit.start unless opts[:start] == false

  self.class.unit = @unit

  @unit
end
unit() click to toggle source
# File lib/flack/app.rb, line 40
def self.unit
  @unit
end
unit=(u) click to toggle source
# File lib/flack/app.rb, line 43
def self.unit=(u)
  @unit = u
end

Public Instance Methods

call(env) click to toggle source
# File lib/flack/app.rb, line 47
def call(env)

  flack_path_info = (env['PATH_INFO'][1..-1] || '')
    .split('/')

  flack_path_info = [ 'index' ] if flack_path_info.empty?

  env['flack.path_info'] = flack_path_info

  METHS.each do |m|

    next if env['REQUEST_METHOD'] != m[1]
    next if flack_path_info.length != m[2].length

    match = true
    args = []

    for i in 0..(flack_path_info.length - 1)

      break unless match

      pi = flack_path_info[i]
      mi = m[2][i]

      break if mi == 'plus' || mi == 'star'

      if mi == 'i'
        match = pi.match(/\A\d+\z/)
        args << pi.to_i
      elsif mi == 's'
        args << pi
      elsif mi.is_a?(Regexp)
        match = pi.match(mi)
      else
        match = (pi == mi)
      end
    end

    next unless match

    env['flack.args'] = args
    env['flack.query_string'] = Rack::Utils.parse_query(env['QUERY_STRING'])
    env['flack.root_uri'] = determine_root_uri(env)

    return send(m[0], env)
  end

  respond_not_found(env)

rescue => err

  $stderr.puts '=' * 80
  $stderr.puts Time.now.to_s
  $stderr.puts '-' * 80
  $stderr.puts err.inspect
  $stderr.puts err.backtrace
  $stderr.puts '-' * 80
  PP.pp(env, $stderr)
  $stderr.puts ('=' * 79) + '.'

  respond_internal_server_error(env, err)
end
delete_executions_s(env) click to toggle source

DELETE /executions/<exid>

# File lib/flack/app/executions.rb, line 55
def delete_executions_s(env)

  exid = env['flack.args'][0]

  return respond_not_found(env) \
    unless @unit.executions.where(exid: exid).count > 0

  r = { exid: exid, counts: {} }
  cs = r[:counts]

  @unit.storage.db.transaction do

    cs[:messages] = @unit.messages.where(exid: exid).count
    cs[:executions] = @unit.executions.where(exid: exid).count
    cs[:pointers] = @unit.pointers.where(exid: exid).count
    cs[:timers] = @unit.timers.where(exid: exid).count
    cs[:traps] = @unit.traps.where(exid: exid).count
      #
      # not sure if the DB adapter returns the DELETE count,
      # so counting first

    @unit.messages.where(exid: exid).delete
    @unit.executions.where(exid: exid).delete
    @unit.pointers.where(exid: exid).delete
    @unit.timers.where(exid: exid).delete
    @unit.traps.where(exid: exid).delete
  end

  respond(env, r)
end
get_css_suffix(env)
Alias for: serve_file
get_debug(env) click to toggle source
# File lib/flack/app.rb, line 110
def get_debug(env); debug_respond(env); end
Also aliased as: get_debug_i
get_debug_i(env)
Alias for: get_debug
get_executions(env) click to toggle source

GET /executions

# File lib/flack/app/executions.rb, line 9
  def get_executions(env)

# TODO implement paging
    env['flack.rel'] = 'flack:executions'

    qs = CGI.parse(env['QUERY_STRING'] || '')
    statuses = qs['status']
    statuses = nil if statuses == []

    q = @unit.executions
    q = q.where(status: statuses) if statuses

    respond(env, q.all)
  end
get_executions_i(env) click to toggle source

GET /executions/<id>

# File lib/flack/app/executions.rb, line 26
def get_executions_i(env)

  env['flack.rel'] = 'flack:executions/id'

  if exe = @unit.executions[env['flack.args'][0]]
    respond(env, exe)
  else
    respond_not_found(env)
  end
end
get_executions_s(env) click to toggle source

GET /executions/<exid> GET /executions/<domain> GET /executions/<domain>* GET /executions/<domain>.*

# File lib/flack/app/executions.rb, line 42
def get_executions_s(env)

  arg = env['flack.args'][0]

  if arg.count('-') == 0
    get_executions_by_domain(env, arg)
  else
    get_executions_by_exid(env, arg)
  end
end
get_html_suffix(env)
Alias for: serve_file
get_index(env) click to toggle source
# File lib/flack/app/index.rb, line 7
  def get_index(env)

# TODO
    respond(env, nil)
  end
get_js_suffix(env)
Alias for: serve_file
get_messages(env) click to toggle source

GET /messages

# File lib/flack/app/messages.rb, line 8
  def get_messages(env)

# TODO implement paging
    env['flack.rel'] = 'flack:messages'

    respond(env, @unit.messages.all)
  end
get_messages_i(env) click to toggle source

GET /messages/<id>

# File lib/flack/app/messages.rb, line 17
def get_messages_i(env)

  env['flack.rel'] = 'flack:messages'

  if exe = @unit.messages[env['flack.args'][0]]
    respond(env, exe)
  else
    respond_not_found(env)
  end
end
get_messages_s(env) click to toggle source

GET /messages/<exid>

# File lib/flack/app/messages.rb, line 29
  def get_messages_s(env)

# TODO implement paging
    arg = env['flack.args'][0]

    if Flor.point?(arg)
      get_messages_by_point(env, arg)
    else
      get_messages_by_exid(env, arg)
    end
  end
get_messages_s_s(env) click to toggle source

GET /messages/<exid>/<point>

# File lib/flack/app/messages.rb, line 42
def get_messages_s_s(env)

  env['flack.rel'] = 'flack:messages/exid/point'

  exid, point = env['flack.args']

  respond(
    env,
    @unit.messages.where(exid: exid, point: point).all)
end
get_pointers(env) click to toggle source

GET /pointers

# File lib/flack/app/pointers.rb, line 9
  def get_pointers(env)

# TODO implement paging
    env['flack.rel'] = 'flack:pointers'

    qs = CGI.parse(env['QUERY_STRING'] || '')
    types = qs['types'].collect { |e| e.split(',') }.flatten
    types = nil if types == []

    q = @unit.pointers
    q = q.where(type: types) if types

    respond(env, q.all)
  end
get_pointers_s(env) click to toggle source

GET /pointers/<exid> GET /pointers/<domain> GET /pointers/<domain>* GET /pointers/<domain>.*

# File lib/flack/app/pointers.rb, line 29
def get_pointers_s(env)

  arg = env['flack.args'][0]

  qs = CGI.parse(env['QUERY_STRING'] || '')

  types = qs['types'].collect { |e| e.split(',') }.flatten
  types = nil if types == []

  if arg.count('-') == 0
    get_pointers_by_domain(env, arg, types)
  else
    get_pointers_by_exid(env, arg, types)
  end
end
post_message(env) click to toggle source
# File lib/flack/app/message.rb, line 7
def post_message(env)

  msg = JSON.load(env['rack.input'].read)

  pt = msg['point']

  return respond_bad_request(env, 'missing msg point') \
    unless pt
  return respond_bad_request(env, "bad msg point #{pt.inspect}") \
    unless %w[ launch cancel reply ].include?(pt)

  r = self.send("queue_#{pt}", env, msg, { point: pt })

  respond(env, r)
end
serve_file(env) click to toggle source
# File lib/flack/app/static.rb, line 14
def serve_file(env)

  STATIC_FILE.call(env)
end
shutdown() click to toggle source
# File lib/flack/app.rb, line 35
def shutdown

  @unit.shutdown
end

Protected Instance Methods

abs(env, href) click to toggle source
# File lib/flack/app/helpers.rb, line 94
def abs(env, href)

  return href if href[0, 4] == 'http'
  "#{env['flack.root_uri']}#{href[0, 1] == '/' ? '': '/'}#{href}"
end
debug_respond(env) click to toggle source
# File lib/flack/app/helpers.rb, line 9
def debug_respond(env)

  [ 200, { 'Content-Type' => 'application/json' }, [ JSON.dump(env) ] ]
end
determine_root_uri(env) click to toggle source
# File lib/flack/app/helpers.rb, line 87
def determine_root_uri(env)

  hh = env['HTTP_HOST'] || "#{env['SERVER_NAME']}:#{env['SERVER_PORT']}"

  "#{env['rack.url_scheme']}://#{hh}#{env['SCRIPT_NAME']}"
end
forms(env) click to toggle source
# File lib/flack/app/helpers.rb, line 158
def forms(env)

  h = {}

  h['curies'] = CURIES

  h['flack:forms/message'] = {
    action: rel(env, '/message'),
    method: 'POST',
    _inputs: { 'flack:forms/message-content' => { type: 'json' } } }

  h['flack:forms/execution-deletion'] = {
    action: rel(env, '/executions/{exid}'),
    method: 'DELETE',
    _inputs: {},
    templated: true }

  h
end
get_executions_by_domain(env, dom) click to toggle source
# File lib/flack/app/executions.rb, line 99
def get_executions_by_domain(env, dom)

  qs = CGI.parse(env['QUERY_STRING'] || '')
  statuses = qs['status']
  statuses = nil if statuses == []

  q = @unit.executions

  if m = dom.match(/\A([^*]+)\*+\z/)
    if m[1][-1, 1] == '.'
      env['flack.rel'] = 'flack:executions/domain-dot-star'
      q = q.where(Sequel.like(:domain, "#{m[1]}%"))
    else
      env['flack.rel'] = 'flack:executions/domain-star'
      q = q.where(
        Sequel[{ domain: m[1] }] |
        Sequel.like(:domain, "#{m[1]}.%"))
    end
  else
    env['flack.rel'] = 'flack:executions/domain'
    q = q.where(domain: dom)
  end

  q = q.where(status: statuses) if statuses

  respond(env, q.all)
end
get_executions_by_exid(env, exid) click to toggle source
# File lib/flack/app/executions.rb, line 88
def get_executions_by_exid(env, exid)

  env['flack.rel'] = 'flack:executions/exid'

  if exe = @unit.executions[exid: exid]
    respond(env, exe)
  else
    respond_not_found(env)
  end
end
get_messages_by_exid(env, exid) click to toggle source
# File lib/flack/app/messages.rb, line 55
def get_messages_by_exid(env, exid)

  env['flack.rel'] = 'flack:messages/exid'

  respond(env, @unit.messages.where(exid: exid).all)
end
get_messages_by_point(env, point) click to toggle source
# File lib/flack/app/messages.rb, line 62
def get_messages_by_point(env, point)

  env['flack.rel'] = 'flack:messages/point'

  respond(env, @unit.messages.where(point: point).all)
end
get_pointers_by_domain(env, dom, types) click to toggle source
# File lib/flack/app/pointers.rb, line 57
def get_pointers_by_domain(env, dom, types)

  q = @unit.pointers
  q = q.where(type: types) if types

  if m = dom.match(/\A([^*]+)\*+\z/)
    if m[1][-1, 1] == '.'
      env['flack.rel'] = 'flack:pointers/domain-dot-star'
      q = q.where(Sequel.like(:domain, "#{m[1]}%"))
    else
      env['flack.rel'] = 'flack:pointers/domain-star'
      q = q.where(
        Sequel[{ domain: m[1] }] |
        Sequel.like(:domain, "#{m[1]}.%"))
    end
  else
    env['flack.rel'] = 'flack:pointers/domain'
    q = q.where(domain: dom)
  end

  respond(env, q.all)
end
get_pointers_by_exid(env, exid, types) click to toggle source
# File lib/flack/app/pointers.rb, line 47
def get_pointers_by_exid(env, exid, types)

  env['flack.rel'] = 'flack:pointers/exid'

  q = @unit.pointers.where(exid: exid)
  q = q.where(type: types) if types

  respond(env, q.all)
end
is_response?(o) click to toggle source
# File lib/flack/app/helpers.rb, line 14
def is_response?(o)

  o.is_a?(Array) &&
  o.length == 3 &&
  o[0].is_a?(Integer) &&
  o[1].is_a?(Hash) &&
  o[2].is_a?(Array)
end
key(env) click to toggle source
# File lib/flack/app/helpers.rb, line 82
def key(env)

  env['flack.rel'] || 'flack:unspecified-rel'
end
queue_cancel(env, msg, ret) click to toggle source
# File lib/flack/app/message.rb, line 52
def queue_cancel(env, msg, ret)

  exid = msg['exid']
  nid = msg['nid'] || '0'

  return respond_bad_request(env, 'missing exid') \
    unless exid

  exe = @unit.executions[exid: exid]

  return respond_not_found(env, 'missing execution') \
    unless exe
  return respond_not_found(env, 'missing execution node') \
    unless exe.nodes[nid]

  ret['xxx'] = @unit.queue({ 'point' => 'cancel', 'exid' => exid, 'nid' => nid }) # FIXME change me

  ret['_status'] = 202
  ret['_location'] = rel(env, '/executions/' + exid)
  ret['_links'] = { 'flack:execution' => { 'href' => ret['_location'] } }

  ret
end
queue_launch(env, msg, ret) click to toggle source
# File lib/flack/app/message.rb, line 25
def queue_launch(env, msg, ret)

  dom = msg['domain'] || 'domain0'
  src = msg['tree'] || msg['name']
  vars = msg['vars'] || {}
  fields = msg['fields'] || {}

  return respond_bad_request(env, 'missing "tree" or "name" in launch msg') \
    unless src

  opts = {}
  opts[:domain] = dom
  opts[:vars] = vars
  opts[:fields] = fields

  r = @unit.launch(src, opts)

  ret['exid'] = r
  ret['_status'] = 201
  ret['_location'] = rel(env, '/executions/' + r)

  ret['_links'] = {
    'flack:forms/message-created' => { 'href' => ret['_location'] } }

  ret
end
queue_reply(env, msg, ret) click to toggle source
# File lib/flack/app/message.rb, line 76
def queue_reply(env, msg, ret)

  exid = msg['exid']
  nid = msg['nid']
  payload = msg['payload'] || {}

  return respond_bad_request(env, 'missing exid') \
    unless exid

  return respond_bad_request(env, 'missing nid') \
    unless nid

  exe = @unit.executions[exid: exid]

  return respond_not_found(env, 'missing execution') \
    unless exe
  return respond_not_found(env, 'missing execution node') \
    unless exe.nodes[nid]

  ret['xxx'] = @unit.queue({ 'point' => 'return', 'exid' => exid, 'nid' => nid, 'payload' => payload}) # FIXME change me

  ret['_status'] = 202
  ret['_location'] = rel(env, '/executions/' + exid)
  ret['_links'] = { 'flack:execution' => { 'href' => ret['_location'] } }

  ret
end
rel(env, href) click to toggle source
# File lib/flack/app/helpers.rb, line 100
def rel(env, href)

  return href if href[0, 4] == 'http'
  "#{env['SCRIPT_NAME']}#{href[0, 1] == '/' ? '': '/'}#{href}"
end
respond(env, data, opts={}) click to toggle source
# File lib/flack/app/helpers.rb, line 23
def respond(env, data, opts={})

  return data if opts == {} && is_response?(data)

  status = nil
  links = nil
  location = nil

  if data.is_a?(Hash)
    status = data['_status'] || data[:_status]
    links = data.delete('_links') || data.delete(:_links)
    location = data['_location'] || data[:_location]
  end

  status = status || opts[:code] || opts[:status] || 200

  headers = { 'Content-Type' => 'application/json' }
  headers['Location'] = location if location

  json = serialize(env, data, opts)

  json['_links'].merge!(links) if links

  json['_status'] = status
  json['_status_text'] = Rack::Utils::HTTP_STATUS_CODES[status]

  if e = opts[:error]; json['error'] = e; end

  [ status, headers, [ JSON.dump(json) ] ]
end
respond_bad_request(env, error=nil) click to toggle source
# File lib/flack/app/helpers.rb, line 178
def respond_bad_request(env, error=nil)

  respond(env, {}, code: 400, error: error)
end
respond_internal_server_error(env, error=nil) click to toggle source
# File lib/flack/app/helpers.rb, line 188
def respond_internal_server_error(env, error=nil)

  respond(env, {}, code: 500, error: error)
end
respond_not_found(env, error=nil) click to toggle source
# File lib/flack/app/helpers.rb, line 183
def respond_not_found(env, error=nil)

  respond(env, {}, code: 404, error: error)
end
serialize(env, data, opts) click to toggle source
# File lib/flack/app/helpers.rb, line 59
def serialize(env, data, opts)

  return serialize_array(env, data, opts) if data.is_a?(Array)

  r =
    (data.is_a?(Hash) ? Flor.dup(data) : nil) ||
    (data.nil? ? {} : nil) ||
    data.to_h

  #r['_klass'] = data.class.to_s # too rubyish
  r['_links'] = links(env)
  r['_forms'] = forms(env)

  r
end
serialize_array(env, data, opts) click to toggle source
# File lib/flack/app/helpers.rb, line 75
def serialize_array(env, data, opts)

  { '_links' => links(env),
    '_embedded' => {
      key(env) => data.collect { |e| serialize(env, e, opts) } } }
end
try(o, meth) click to toggle source
# File lib/flack/app/helpers.rb, line 54
def try(o, meth)

  o.respond_to?(meth) ? o.send(meth) : nil
end