summaryrefslogtreecommitdiffstats
path: root/server.lua
blob: 11bfe55ebee48c6276a9c476f646a4b60e20d15c (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
--[[
Copyright (c) 2012 Kaarle Ritvanen
See LICENSE file for license details
--]]

require 'acf'
local isinstance = acf.object.isinstance

require 'json'
require 'stringy'


local function handle(env, txn, path)
   local method = env.REQUEST_METHOD
   local data
   if env.CONTENT_LENGTH then
      data = json.decode(env.input:read(env.CONTENT_LENGTH))
   end

   local parent, name
   if path ~= '/' then
      parent = txn:search(acf.path.parent(path))
      name = acf.path.name(path)
   end

   if method == 'GET' then
      local obj = txn:search(path)
      if obj == nil then return 404 end

      local res

      if isinstance(obj, acf.model.node.TreeNode) then
	 local node = {}
	 for _, k in ipairs(acf.model.node.members(obj)) do
	    local v = obj[k]
	    if isinstance(v, acf.model.node.TreeNode) then
	       v = acf.model.node.path(v)
	    end
	    node[k] = v
	 end
	 res = {data=node, meta=acf.model.node.meta(obj)}

      else res = {data=obj, meta=acf.model.node.mmeta(parent, name)} end

      local function f() coroutine.yield(json.encode(res)) end
      return 200, {['Content-Type']='application/json'}, coroutine.wrap(f)
   end

   if method == 'DELETE' then
      parent[name] = nil

   elseif method == 'PUT' then
      parent[name] = data

   -- TODO implement POST for invoking object-specific actions

   else return 405 end

   return 200
end


-- TODO implement transactions as threads or store their state in
-- shared storage
local last_id = 0
local txns = {}


return function(env)
	  local method = env.REQUEST_METHOD
	  local path = env.REQUEST_URI

	  -- TODO login session management

	  local txn_id = tonumber(env.HTTP_X_ACF_TRANSACTION_ID)
	  local txn
	  if txn_id then
	     txn = txns[txn_id]
	     if not txn then return 404 end
	  else txn = acf.transaction.start() end

	  if stringy.startswith(path, '/config/') then
	     -- TODO catch and forward relevant errors to the client
	     local code, hdr, body = handle(env,
					    txn,
					    string.sub(path, 8, -1))
	     if not txn_id and method ~= 'GET' and code == 200 then
		txn:commit()
	     end
	     return code, hdr, body
	  end

	  if path == '/' then
	     if method == 'GET' then return 301, {['Location']='/browser/'} end

	     if not ({DELETE=true, POST=true})[method] then
		return 405
	     end

	     if txn_id then
		if method == 'POST' then txn:commit() end
		txns[txn_id] = nil
		return 200
	     end

	     if method == 'DELETE' then return 405 end

	     last_id = last_id + 1
	     local txn_id = last_id
	     txns[txn_id] = txn
	     return 200, {['X-ACF-Transaction-ID']=txn_id}
	  end

	  return 404
       end