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
|