wire-framework 0.1.4.26 → 0.1.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/LICENSE +201 -339
- data/README.md +16 -0
- data/lib/app.rb +38 -19
- data/lib/app/cache.rb +93 -84
- data/lib/app/db.rb +195 -196
- data/lib/app/file.rb +76 -72
- data/lib/app/history.rb +50 -49
- data/lib/app/history/svn.rb +43 -24
- data/lib/app/login.rb +23 -8
- data/lib/app/render.rb +16 -105
- data/lib/app/render/document.rb +57 -40
- data/lib/app/render/editor.rb +59 -44
- data/lib/app/render/error.rb +55 -35
- data/lib/app/render/instant.rb +71 -60
- data/lib/app/render/page.rb +104 -79
- data/lib/app/render/partial.rb +120 -99
- data/lib/app/render/style.rb +56 -42
- data/lib/app/repo.rb +141 -135
- data/lib/app/repo/svn.rb +203 -177
- data/lib/closet.rb +104 -92
- data/lib/closet/auth.rb +37 -53
- data/lib/closet/config.rb +34 -0
- data/lib/closet/context.rb +142 -98
- data/lib/closet/renderer.rb +44 -41
- data/lib/wire.rb +28 -16
- metadata +11 -40
- data/lib/closet/resource.rb +0 -20
data/README.md
CHANGED
@@ -4,3 +4,19 @@ Wire is a DSL and Rack interface for quickly building web applications, without
|
|
4
4
|
# Documentation
|
5
5
|
|
6
6
|
[Wire RubyDoc](http://www.rubydoc.info/github/DataDrake/Wire)
|
7
|
+
|
8
|
+
## License
|
9
|
+
|
10
|
+
Copyright 2017 Bryan T. Meyers
|
11
|
+
|
12
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
13
|
+
you may not use this file except in compliance with the License.
|
14
|
+
You may obtain a copy of the License at
|
15
|
+
|
16
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
17
|
+
|
18
|
+
Unless required by applicable law or agreed to in writing, software
|
19
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
20
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
21
|
+
See the License for the specific language governing permissions and
|
22
|
+
limitations under the License.
|
data/lib/app.rb
CHANGED
@@ -1,24 +1,43 @@
|
|
1
|
+
##
|
2
|
+
# Copyright 2017 Bryan T. Meyers
|
3
|
+
#
|
4
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
5
|
+
# you may not use this file except in compliance with the License.
|
6
|
+
# You may obtain a copy of the License at
|
7
|
+
#
|
8
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
9
|
+
#
|
10
|
+
# Unless required by applicable law or agreed to in writing, software
|
11
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
12
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
13
|
+
# See the License for the specific language governing permissions and
|
14
|
+
# limitations under the License.
|
15
|
+
##
|
16
|
+
|
17
|
+
require_relative 'closet/config'
|
18
|
+
|
1
19
|
module Wire
|
2
|
-
|
3
|
-
|
4
|
-
|
20
|
+
# App is a a REST endpoint for a Wire service
|
21
|
+
# @author Bryan T. Meyers
|
22
|
+
module App
|
23
|
+
|
24
|
+
# Callback for handling configs
|
25
|
+
# @param [Hash] conf the raw configuration
|
26
|
+
# @return [Hash] post-processed configuration
|
27
|
+
def self.configure(conf)
|
28
|
+
conf['type'] = Object.const_get(conf['type'])
|
29
|
+
if conf['type'].respond_to? :configure
|
30
|
+
conf = conf['type'].configure(conf)
|
31
|
+
end
|
32
|
+
conf
|
33
|
+
end
|
5
34
|
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
$current_uri = base_uri
|
13
|
-
$apps[base_uri] = { type: type, resources: {} }
|
14
|
-
$current_app = $apps[base_uri]
|
15
|
-
if ENV['RACK_ENV'].eql? 'development'
|
16
|
-
$stderr.puts "Starting App at: /#{base_uri}"
|
17
|
-
$stderr.puts 'Setting up resources...'
|
18
|
-
end
|
19
|
-
Docile.dsl_eval(type, &block)
|
20
|
-
end
|
21
|
-
end
|
35
|
+
# Read all of the configs in './configs/apps'
|
36
|
+
# @return [void]
|
37
|
+
def self.read_configs
|
38
|
+
$wire_apps = Wire::Config.read_config_dir('config/apps', method(:configure))
|
39
|
+
end
|
40
|
+
end
|
22
41
|
end
|
23
42
|
|
24
43
|
require_relative 'app/cache'
|
data/lib/app/cache.rb
CHANGED
@@ -1,96 +1,105 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
1
|
+
##
|
2
|
+
# Copyright 2017 Bryan T. Meyers
|
3
|
+
#
|
4
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
5
|
+
# you may not use this file except in compliance with the License.
|
6
|
+
# You may obtain a copy of the License at
|
7
|
+
#
|
8
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
9
|
+
#
|
10
|
+
# Unless required by applicable law or agreed to in writing, software
|
11
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
12
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
13
|
+
# See the License for the specific language governing permissions and
|
14
|
+
# limitations under the License.
|
15
|
+
##
|
4
16
|
|
5
17
|
require 'lmdb'
|
6
18
|
|
7
19
|
module Cache
|
8
|
-
|
9
|
-
include Wire::App
|
10
|
-
include Wire::Resource
|
11
|
-
extend Render
|
20
|
+
module Memory
|
12
21
|
|
13
|
-
|
22
|
+
$cache = {}
|
14
23
|
|
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
|
-
|
24
|
+
def self.update_cached(context)
|
25
|
+
uri = context.uri.join('/')
|
26
|
+
all = context.uri[0..2].join('/')
|
27
|
+
env = $cache[context.config['remote']]
|
28
|
+
db = env.database
|
29
|
+
if context.id
|
30
|
+
result = context.forward(:read)
|
31
|
+
else
|
32
|
+
result = context.forward(:readAll)
|
33
|
+
end
|
34
|
+
if result[0] == 200
|
35
|
+
env.transaction do
|
36
|
+
if context.action == :delete
|
37
|
+
if db[uri]
|
38
|
+
db.delete(uri)
|
39
|
+
end
|
40
|
+
else
|
41
|
+
db[uri] = result[2]
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
if [:create, :update, :delete].include? context.action
|
46
|
+
thing = context.forward(:readAll)
|
47
|
+
if thing[0] == 200
|
48
|
+
env.transaction do
|
49
|
+
db[all] = thing[2]
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
result
|
54
|
+
end
|
46
55
|
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
56
|
+
def self.get_cached(context)
|
57
|
+
uri = context.uri.join('/')
|
58
|
+
env = $cache[context.config['remote']]
|
59
|
+
db = env.database
|
60
|
+
result = nil
|
61
|
+
env.transaction do
|
62
|
+
result = db[uri]
|
63
|
+
end
|
64
|
+
result
|
65
|
+
end
|
57
66
|
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
67
|
+
def self.purge_cached(context)
|
68
|
+
uri = context.uri.join('/')
|
69
|
+
env = $cache[context.config['remote']]
|
70
|
+
db = env.database
|
71
|
+
result = 200
|
72
|
+
env.transaction do
|
73
|
+
begin
|
74
|
+
db.delete(uri)
|
75
|
+
rescue
|
76
|
+
result = 404
|
77
|
+
end
|
78
|
+
end
|
79
|
+
result
|
80
|
+
end
|
72
81
|
|
73
|
-
|
82
|
+
def self.invoke(actions, context)
|
74
83
|
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
84
|
+
# Create Cache if not set up
|
85
|
+
unless $cache[context.config['remote']]
|
86
|
+
$cache[context.config['remote']] = LMDB.new("/tmp/cache/#{context.config['remote']}", mapsize: 2**30)
|
87
|
+
end
|
79
88
|
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
89
|
+
case context.action
|
90
|
+
when :create, :update, :delete
|
91
|
+
result = context.forward(context.action)
|
92
|
+
update_cached(context) # write aware
|
93
|
+
result
|
94
|
+
when :read, :readAll
|
95
|
+
cached = get_cached(context)
|
96
|
+
unless cached
|
97
|
+
cached = update_cached(context)
|
98
|
+
end
|
99
|
+
cached
|
100
|
+
else
|
101
|
+
403
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
96
105
|
end
|
data/lib/app/db.rb
CHANGED
@@ -1,209 +1,208 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
1
|
+
##
|
2
|
+
# Copyright 2017 Bryan T. Meyers
|
3
|
+
#
|
4
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
5
|
+
# you may not use this file except in compliance with the License.
|
6
|
+
# You may obtain a copy of the License at
|
7
|
+
#
|
8
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
9
|
+
#
|
10
|
+
# Unless required by applicable law or agreed to in writing, software
|
11
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
12
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
13
|
+
# See the License for the specific language governing permissions and
|
14
|
+
# limitations under the License.
|
15
|
+
##
|
16
|
+
|
17
|
+
require 'base64'
|
18
|
+
require 'sequel'
|
5
19
|
|
6
20
|
# DB is a Wire::App for generating REST wrappers for DataMapper
|
7
21
|
# @author Bryan T. Meyers
|
8
22
|
module DB
|
9
|
-
include Wire::App
|
10
|
-
include Wire::Resource
|
11
23
|
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
24
|
+
# DB-specific configuration
|
25
|
+
# @param [Hash] conf the existing configuration
|
26
|
+
# @return [Hash] post-processed configuration
|
27
|
+
def self.configure(conf)
|
28
|
+
Sequel.connect($environment['db'][conf['db']])
|
29
|
+
conf['models'].each do |m|
|
30
|
+
conf['models'][m] = Object.const_get(m)
|
31
|
+
end
|
32
|
+
conf
|
33
|
+
end
|
34
|
+
|
35
|
+
# Add a new object to the DB table
|
36
|
+
# @param [Hash] context the context for this request
|
37
|
+
# @return [Response] a valid Rack response triplet, or status code
|
38
|
+
def self.do_create(context)
|
39
|
+
model = context.config['models'][context.resource]
|
40
|
+
return 404 unless model
|
41
|
+
|
42
|
+
file = context.json[:file]
|
43
|
+
if file
|
44
|
+
if file[:mime].eql? 'text/csv'
|
45
|
+
file[:content].match(/.*base64,(.*)/) do
|
46
|
+
csv = Base64.decode64($1)
|
47
|
+
columns = []
|
48
|
+
errors = []
|
49
|
+
csv.split("\n").each_with_index do |v, i|
|
50
|
+
if i == 0
|
51
|
+
columns = v.split(/(?<!\[),(?!=\])/)
|
52
|
+
columns.map! { |c| c.delete('"').to_sym }
|
53
|
+
else
|
54
|
+
values = v.split(',')
|
55
|
+
values.map! do |c|
|
56
|
+
c.include?(';') ? c.split(';') : c
|
57
|
+
end
|
58
|
+
hash = {}
|
59
|
+
columns.each_with_index do |c, j|
|
60
|
+
if values[j].is_a? String
|
61
|
+
values[j].delete!('"')
|
62
|
+
end
|
63
|
+
hash[c] = values[j]
|
64
|
+
end
|
65
|
+
m = model.find_or_create(hash)
|
66
|
+
unless m.modified?
|
67
|
+
errors << "row: #{i} errors: #{m.errors.delete("\n")}"
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
if errors.length > 0
|
72
|
+
[400, nil, errors]
|
73
|
+
else
|
74
|
+
200
|
75
|
+
end
|
76
|
+
end
|
77
|
+
else
|
78
|
+
415
|
79
|
+
end
|
80
|
+
else
|
81
|
+
#TODO user_id needs to happen in the context
|
82
|
+
if model.respond_to? :updated_by_id
|
83
|
+
context.json[:updated_by_id] = context.user
|
84
|
+
end
|
85
|
+
if model.respond_to? :created_by_id
|
86
|
+
context.json[:created_by_id] = context.user
|
87
|
+
end
|
88
|
+
begin
|
89
|
+
instance = model.create(context.json)
|
90
|
+
instance.save
|
91
|
+
if instance.modified?
|
92
|
+
200
|
93
|
+
else
|
94
|
+
errors = ''
|
95
|
+
instance.errors.each { |e| errors += "#{e.to_s}\n" }
|
96
|
+
[504, {}, errors]
|
97
|
+
end
|
98
|
+
rescue => e
|
99
|
+
[500, {}, e.message]
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
21
103
|
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
104
|
+
# Get all objects from the DB table
|
105
|
+
# @param [Hash] context the context for this request
|
106
|
+
# @return [Response] all objects, or status code
|
107
|
+
def self.do_read_all(context)
|
108
|
+
return 404 unless context.resource
|
109
|
+
model = context.config['models'][context.resource]
|
110
|
+
if model
|
111
|
+
hash = '[ '
|
112
|
+
model.each do |e|
|
113
|
+
hash << (e.to_json)
|
114
|
+
hash << ','
|
115
|
+
end
|
116
|
+
hash = hash[0...-1]
|
117
|
+
hash << ']'
|
118
|
+
[200, {}, hash]
|
119
|
+
else
|
120
|
+
404
|
121
|
+
end
|
122
|
+
end
|
29
123
|
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
else
|
49
|
-
values = v.split(',')
|
50
|
-
values.map! do |c|
|
51
|
-
c.include?(';') ? c.split(';') : c
|
52
|
-
end
|
53
|
-
hash = {}
|
54
|
-
columns.each_with_index do |c, j|
|
55
|
-
if values[j].is_a? String
|
56
|
-
values[j].delete!('"')
|
57
|
-
end
|
58
|
-
hash[c] = values[j]
|
59
|
-
end
|
60
|
-
m = model.first_or_create(hash)
|
61
|
-
unless m.saved?
|
62
|
-
errors << "row: #{i} errors: #{m.errors.delete("\n")}"
|
63
|
-
end
|
64
|
-
end
|
65
|
-
end
|
66
|
-
if errors.length > 0
|
67
|
-
[400, nil, errors]
|
68
|
-
else
|
69
|
-
200
|
70
|
-
end
|
71
|
-
end
|
72
|
-
else
|
73
|
-
415
|
74
|
-
end
|
75
|
-
else
|
76
|
-
if model.instance_methods.include?(:updated_by)
|
77
|
-
context.json[:updated_by] = context.user
|
78
|
-
end
|
79
|
-
if model.instance_methods.include?(:created_by)
|
80
|
-
context.json[:created_by] = context.user
|
81
|
-
end
|
82
|
-
begin
|
83
|
-
instance = model.create(context.json)
|
84
|
-
instance.save
|
85
|
-
if instance.saved?
|
86
|
-
200
|
87
|
-
else
|
88
|
-
errors = ''
|
89
|
-
instance.errors.each { |e| errors += "#{e.to_s}\n"}
|
90
|
-
[504,{}, errors]
|
91
|
-
end
|
92
|
-
rescue => e
|
93
|
-
case e.class
|
94
|
-
when DataObjects::IntegrityError.class
|
95
|
-
[400,{},e.message]
|
96
|
-
else
|
97
|
-
[500,{},e.message]
|
98
|
-
end
|
99
|
-
end
|
100
|
-
end
|
101
|
-
else
|
102
|
-
404
|
103
|
-
end
|
104
|
-
end
|
124
|
+
# Get a specific object from the DB table
|
125
|
+
# @param [Hash] context the context for this request
|
126
|
+
# @return [Response] an object, or status code
|
127
|
+
def self.do_read(context)
|
128
|
+
model = context.config['models'][context.resource]
|
129
|
+
return 404 unless model
|
130
|
+
id = context.id
|
131
|
+
if id.eql?('new') or id.eql? 'upload'
|
132
|
+
return '{}'
|
133
|
+
end
|
134
|
+
if model
|
135
|
+
object = model[id]
|
136
|
+
if object
|
137
|
+
return [200, {}, object.to_json]
|
138
|
+
end
|
139
|
+
end
|
140
|
+
[404, {}, []]
|
141
|
+
end
|
105
142
|
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
if model
|
113
|
-
hash = '[ '
|
114
|
-
model.all.each do |e|
|
115
|
-
hash << (e.to_json)
|
116
|
-
if e != model.all.last
|
117
|
-
hash << ','
|
118
|
-
end
|
119
|
-
end
|
120
|
-
hash << ']'
|
121
|
-
[200,{},hash]
|
122
|
-
else
|
123
|
-
404
|
124
|
-
end
|
125
|
-
end
|
143
|
+
# Update a specific object in the DB table
|
144
|
+
# @param [Hash] context the context for this request
|
145
|
+
# @return [Response] an object, or status code
|
146
|
+
def self.do_update(context)
|
147
|
+
model = context.config['models'][context.resource]
|
148
|
+
return 404 unless model
|
126
149
|
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
object = model.get(id)
|
139
|
-
if object
|
140
|
-
return [200,{},object.to_json]
|
141
|
-
end
|
142
|
-
end
|
143
|
-
[404,{},[]]
|
144
|
-
end
|
150
|
+
id = context.id
|
151
|
+
if model
|
152
|
+
if model.respond_to?(:updated_by_id)
|
153
|
+
context.json[:updated_by_id] = context.user
|
154
|
+
end
|
155
|
+
instance = model[id]
|
156
|
+
instance.update(context.json)
|
157
|
+
else
|
158
|
+
404
|
159
|
+
end
|
160
|
+
end
|
145
161
|
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
id = context.uri[3]
|
153
|
-
if model
|
154
|
-
if model.respond_to?(:updated_by)
|
155
|
-
context.json[:updated_by] = context.user
|
156
|
-
end
|
157
|
-
instance = model.get(id)
|
158
|
-
instance.update(context.json)
|
159
|
-
else
|
160
|
-
404
|
161
|
-
end
|
162
|
-
end
|
162
|
+
# Remove a specific object from the DB table
|
163
|
+
# @param [Hash] context the context for this request
|
164
|
+
# @return [Response] an object, or status code
|
165
|
+
def self.do_delete(context)
|
166
|
+
model = context.config['models'][context.resource]
|
167
|
+
return 404 unless model
|
163
168
|
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
404
|
181
|
-
end
|
182
|
-
else
|
183
|
-
404
|
184
|
-
end
|
185
|
-
end
|
169
|
+
id = context.id
|
170
|
+
if model
|
171
|
+
instance = model[id]
|
172
|
+
if instance
|
173
|
+
if instance.destroy
|
174
|
+
200
|
175
|
+
else
|
176
|
+
[500, {}, 'Failed to delete instance']
|
177
|
+
end
|
178
|
+
else
|
179
|
+
404
|
180
|
+
end
|
181
|
+
else
|
182
|
+
404
|
183
|
+
end
|
184
|
+
end
|
186
185
|
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
186
|
+
# Proxy method used when routing
|
187
|
+
# @param [Array] actions the allowed actions for this URI
|
188
|
+
# @param [Hash] context the context for this request
|
189
|
+
# @return [Response] a Rack Response triplet, or status code
|
190
|
+
def self.invoke(actions, context)
|
191
|
+
case context.action
|
192
|
+
when :create
|
193
|
+
do_create(context)
|
194
|
+
when :read
|
195
|
+
if context.uri[3]
|
196
|
+
do_read(context)
|
197
|
+
else
|
198
|
+
do_read_all(context)
|
199
|
+
end
|
200
|
+
when :update
|
201
|
+
do_update(context)
|
202
|
+
when :delete
|
203
|
+
do_delete(context)
|
204
|
+
else
|
205
|
+
403
|
206
|
+
end
|
207
|
+
end
|
209
208
|
end
|