midb 1.0.5 → 1.1.0
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/bin/midb +6 -3
- data/lib/midb/dbengine_model.rb +78 -74
- data/lib/midb/errors_view.rb +26 -27
- data/lib/midb/hooks.rb +13 -0
- data/lib/midb/security_controller.rb +33 -31
- data/lib/midb/server_controller.rb +228 -221
- data/lib/midb/server_model.rb +248 -232
- data/lib/midb/server_view.rb +97 -87
- data/lib/midb/serverengine_controller.rb +113 -105
- data/lib/midb.rb +2 -1
- metadata +3 -2
data/lib/midb/server_model.rb
CHANGED
@@ -1,235 +1,289 @@
|
|
1
1
|
require 'midb/server_controller'
|
2
2
|
require 'midb/dbengine_model'
|
3
3
|
require 'midb/server_view'
|
4
|
+
require 'midb/hooks'
|
4
5
|
|
5
6
|
require 'sqlite3'
|
6
7
|
require 'json'
|
7
8
|
require 'cgi'
|
8
9
|
module MIDB
|
9
|
-
|
10
|
-
|
10
|
+
module API
|
11
|
+
class Model
|
11
12
|
|
12
|
-
|
13
|
-
# Safely get the structure
|
14
|
-
def self.get_structure()
|
15
|
-
JSON.parse(IO.read("./json/#{@jsf}.json"))["id"]
|
16
|
-
end
|
17
|
-
|
18
|
-
# Method: query_to_hash
|
19
|
-
# Convert a HTTP query string to a JSONable hash
|
20
|
-
def self.query_to_hash(query)
|
21
|
-
Hash[CGI.parse(query).map {|key,values| [key, values[0]||true]}]
|
22
|
-
end
|
13
|
+
attr_accessor :jsf, :db, :engine
|
23
14
|
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
@
|
28
|
-
|
15
|
+
# Constructor
|
16
|
+
#
|
17
|
+
# @param jsf [String] JSON file with the schema
|
18
|
+
# @param db [String] Database to operate on.
|
19
|
+
# @param engine [Object] Reference to the API engine.
|
20
|
+
def initialize(jsf, db, engine)
|
21
|
+
@jsf = jsf
|
22
|
+
@db = db
|
23
|
+
@engine = engine
|
24
|
+
end
|
29
25
|
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
jss.each do |key, value|
|
34
|
-
# Check if we have it on the query too
|
35
|
-
unless input.has_key? key
|
36
|
-
resp = MIDB::ServerView.json_error(400, "Bad Request - Not enough data for a new resource")
|
37
|
-
MIDB::ServerController.http_status = 400
|
38
|
-
bad_request = true
|
39
|
-
end
|
26
|
+
# Safely get the structure
|
27
|
+
def get_structure()
|
28
|
+
JSON.parse(IO.read("./json/#{@jsf}.json"))["id"]
|
40
29
|
end
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
end
|
30
|
+
|
31
|
+
# Convert a HTTP query string to a JSONable hash
|
32
|
+
#
|
33
|
+
# @param query [String] HTTP query string
|
34
|
+
def query_to_hash(query)
|
35
|
+
Hash[CGI.parse(query).map {|key,values| [key, values[0]||true]}]
|
48
36
|
end
|
49
|
-
|
50
37
|
|
51
|
-
#
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
row_field = match.split("->")[1]
|
67
|
-
fields[table].push matching_field
|
68
|
-
if MIDB::ServerController.config["dbengine"] == :mysql
|
69
|
-
inserts[table].push "(SELECT #{row_field} FROM #{main_table} WHERE id=(SELECT LAST_INSERT_ID()))"
|
70
|
-
else
|
71
|
-
inserts[table].push "(SELECT #{row_field} FROM #{main_table} WHERE id=(last_insert_rowid()))"
|
72
|
-
end
|
38
|
+
# Act on POST requests - create a new resource
|
39
|
+
#
|
40
|
+
# @param data [String] The HTTP query string containing what to POST.
|
41
|
+
def post(data)
|
42
|
+
jss = self.get_structure() # For referencing purposes
|
43
|
+
|
44
|
+
input = self.query_to_hash(data)
|
45
|
+
bad_request = false
|
46
|
+
resp = nil
|
47
|
+
jss.each do |key, value|
|
48
|
+
# Check if we have it on the query too
|
49
|
+
unless input.has_key? key
|
50
|
+
resp = MIDB::Interface::Server.json_error(400, "Bad Request - Not enough data for a new resource")
|
51
|
+
@engine.http_status = 400
|
52
|
+
bad_request = true
|
73
53
|
end
|
74
54
|
end
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
dblink = dbe.connect()
|
82
|
-
results = []
|
83
|
-
rid = nil
|
84
|
-
# Find the ID to return in the response (only for the first query)
|
85
|
-
queries.each do |q|
|
86
|
-
results.push dbe.query(dblink, q)
|
87
|
-
if MIDB::ServerController.config["dbengine"] == :mysql
|
88
|
-
rid ||= dbe.extract(dbe.query(dblink, "SELECT id FROM #{main_table} WHERE id=(SELECT LAST_INSERT_ID());"), "id")
|
89
|
-
else
|
90
|
-
rid ||= dbe.extract(dbe.query(dblink, "SELECT id FROM #{main_table} WHERE id=(last_insert_rowid());"), "id")
|
55
|
+
input.each do |key, value|
|
56
|
+
# Check if we have it on the structure too
|
57
|
+
unless jss.has_key? key
|
58
|
+
resp = MIDB::Interface::Server.json_error(400, "Bad Request - Wrong argument #{key}")
|
59
|
+
@engine.http_status = 400
|
60
|
+
bad_request = true
|
91
61
|
end
|
92
62
|
end
|
93
|
-
|
94
|
-
resp = {"status": "201 created", "id": rid}
|
95
|
-
end
|
96
|
-
return resp
|
97
|
-
end
|
63
|
+
|
98
64
|
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
65
|
+
# Insert the values if we have a good request
|
66
|
+
unless bad_request
|
67
|
+
fields = Hash.new
|
68
|
+
inserts = Hash.new
|
69
|
+
main_table = self.get_structure.values[0].split('/')[0]
|
70
|
+
input.each do |key, value|
|
71
|
+
struct = jss[key]
|
72
|
+
table = struct.split("/")[0]
|
73
|
+
inserts[table] ||= []
|
74
|
+
fields[table] ||= []
|
75
|
+
inserts[table].push "\"" + value + "\""
|
76
|
+
fields[table].push struct.split("/")[1]
|
77
|
+
if struct.split("/").length > 2
|
78
|
+
match = struct.split("/")[2]
|
79
|
+
matching_field = match.split("->")[0]
|
80
|
+
row_field = match.split("->")[1]
|
81
|
+
fields[table].push matching_field
|
82
|
+
if @engine.config["dbengine"] == :mysql
|
83
|
+
inserts[table].push "(SELECT #{row_field} FROM #{main_table} WHERE id=(SELECT LAST_INSERT_ID()))"
|
84
|
+
else
|
85
|
+
inserts[table].push "(SELECT #{row_field} FROM #{main_table} WHERE id=(last_insert_rowid()))"
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
queries = []
|
90
|
+
inserts.each do |table, values|
|
91
|
+
queries.push "INSERT INTO #{table}(#{fields[table].join(',')}) VALUES (#{inserts[table].join(',')});"
|
92
|
+
end
|
93
|
+
# Connect to the database
|
94
|
+
dbe = MIDB::API::Dbengine.new(@engine.config, @db)
|
95
|
+
dblink = dbe.connect()
|
96
|
+
results = []
|
97
|
+
rid = nil
|
98
|
+
# Find the ID to return in the response (only for the first query)
|
99
|
+
queries.each do |q|
|
100
|
+
results.push dbe.query(dblink, q)
|
101
|
+
if @engine.config["dbengine"] == :mysql
|
102
|
+
rid ||= dbe.extract(dbe.query(dblink, "SELECT id FROM #{main_table} WHERE id=(SELECT LAST_INSERT_ID());"), "id")
|
103
|
+
else
|
104
|
+
rid ||= dbe.extract(dbe.query(dblink, "SELECT id FROM #{main_table} WHERE id=(last_insert_rowid());"), "id")
|
105
|
+
end
|
106
|
+
end
|
107
|
+
@engine.http_status = "201 Created"
|
108
|
+
resp = {status: "201 created", id: rid}
|
114
109
|
end
|
110
|
+
return resp
|
115
111
|
end
|
116
112
|
|
117
|
-
#
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
# Update the values if we have a good request
|
128
|
-
unless bad_request
|
129
|
-
fields = Hash.new
|
130
|
-
inserts = Hash.new
|
131
|
-
where_clause = Hash.new
|
132
|
-
main_table = self.get_structure.values[0].split('/')[0]
|
133
|
-
where_clause[main_table] = "id=#{id}"
|
113
|
+
# Update an already existing resource
|
114
|
+
#
|
115
|
+
# @param id [Fixnum] ID to alter
|
116
|
+
# @param data [String] HTTP query string
|
117
|
+
def put(id, data)
|
118
|
+
jss = self.get_structure() # For referencing purposes
|
119
|
+
|
120
|
+
input = self.query_to_hash(data)
|
121
|
+
bad_request = false
|
122
|
+
resp = nil
|
134
123
|
input.each do |key, value|
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
fields[table].push struct.split("/")[1]
|
141
|
-
if struct.split("/").length > 2
|
142
|
-
match = struct.split("/")[2]
|
143
|
-
matching_field = match.split("->")[0]
|
144
|
-
row_field = match.split("->")[1]
|
145
|
-
where_clause[table] = "#{matching_field}=(SELECT #{row_field} FROM #{main_table} WHERE #{where_clause[main_table]});"
|
124
|
+
# Check if we have it on the structure too
|
125
|
+
unless jss.has_key? key
|
126
|
+
resp = MIDB::Interface::Server.json_error(400, "Bad Request - Wrong argument #{key}")
|
127
|
+
@engine.http_status = 400
|
128
|
+
bad_request = true
|
146
129
|
end
|
147
130
|
end
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
end
|
158
|
-
queries.push query + "WHERE #{where_clause[table]};"
|
131
|
+
|
132
|
+
# Check if the ID exists
|
133
|
+
db = MIDB::API::Dbengine.new(@engine.config, @db)
|
134
|
+
dbc = db.connect()
|
135
|
+
dbq = db.query(dbc, "SELECT * FROM #{self.get_structure.values[0].split('/')[0]} WHERE id=#{id};")
|
136
|
+
unless db.length(dbq) > 0
|
137
|
+
resp = MIDB::Interface::Server.json_error(404, "ID not found")
|
138
|
+
@engine.http_status = 404
|
139
|
+
bad_request = true
|
159
140
|
end
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
141
|
+
|
142
|
+
# Update the values if we have a good request
|
143
|
+
unless bad_request
|
144
|
+
fields = Hash.new
|
145
|
+
inserts = Hash.new
|
146
|
+
where_clause = Hash.new
|
147
|
+
main_table = self.get_structure.values[0].split('/')[0]
|
148
|
+
where_clause[main_table] = "id=#{id}"
|
149
|
+
input.each do |key, value|
|
150
|
+
struct = jss[key]
|
151
|
+
table = struct.split("/")[0]
|
152
|
+
inserts[table] ||= []
|
153
|
+
fields[table] ||= []
|
154
|
+
inserts[table].push "\"" + value + "\""
|
155
|
+
fields[table].push struct.split("/")[1]
|
156
|
+
if struct.split("/").length > 2
|
157
|
+
match = struct.split("/")[2]
|
158
|
+
matching_field = match.split("->")[0]
|
159
|
+
row_field = match.split("->")[1]
|
160
|
+
where_clause[table] = "#{matching_field}=(SELECT #{row_field} FROM #{main_table} WHERE #{where_clause[main_table]});"
|
161
|
+
end
|
162
|
+
end
|
163
|
+
queries = []
|
164
|
+
updates = Hash.new
|
165
|
+
# Turn it into a hash
|
166
|
+
inserts.each do |table, values|
|
167
|
+
updates[table] ||= Hash.new
|
168
|
+
updates[table] = Hash[fields[table].zip(inserts[table])]
|
169
|
+
query = "UPDATE #{table} SET "
|
170
|
+
updates[table].each do |f, v|
|
171
|
+
query = query + "#{f}=#{v} "
|
172
|
+
end
|
173
|
+
queries.push query + "WHERE #{where_clause[table]};"
|
174
|
+
end
|
175
|
+
# Run the queries
|
176
|
+
results = []
|
177
|
+
queries.each do |q|
|
178
|
+
results.push db.query(dbc, q)
|
179
|
+
end
|
180
|
+
@engine.http_status = "200 OK"
|
181
|
+
resp = {status: "200 OK"}
|
164
182
|
end
|
165
|
-
|
166
|
-
resp = {"status": "200 OK"}
|
183
|
+
return resp
|
167
184
|
end
|
168
|
-
return resp
|
169
|
-
end
|
170
185
|
|
186
|
+
# Delete a resource
|
187
|
+
#
|
188
|
+
# @param id [Fixnum] ID to delete
|
189
|
+
def delete(id)
|
190
|
+
# Check if the ID exists
|
191
|
+
db = MIDB::API::Dbengine.new(@engine.config, @db)
|
192
|
+
dbc = db.connect()
|
193
|
+
dbq = db.query(dbc, "SELECT * FROM #{self.get_structure.values[0].split('/')[0]} WHERE id=#{id};")
|
194
|
+
if not db.length(dbq) > 0
|
195
|
+
resp = MIDB::Interface::Server.json_error(404, "ID not found").to_json
|
196
|
+
@engine.http_status = 404
|
197
|
+
bad_request = true
|
198
|
+
else
|
199
|
+
# ID Found, so let's delete it. (including linked resources!)
|
200
|
+
jss = self.get_structure() # Referencing
|
171
201
|
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
dbq = db.query(dbc, "SELECT * FROM #{self.get_structure.values[0].split('/')[0]} WHERE id=#{id};")
|
177
|
-
if not db.length(dbq) > 0
|
178
|
-
resp = MIDB::ServerView.json_error(404, "ID not found").to_json
|
179
|
-
MIDB::ServerController.http_status = 404
|
180
|
-
bad_request = true
|
181
|
-
else
|
182
|
-
# ID Found, so let's delete it. (including linked resources!)
|
183
|
-
@jsf = jsf
|
184
|
-
jss = self.get_structure() # Referencing
|
202
|
+
where_clause = {}
|
203
|
+
tables = []
|
204
|
+
main_table = jss.values[0].split('/')[0]
|
205
|
+
where_clause[main_table] = "id=#{id}"
|
185
206
|
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
207
|
+
jss.each do |k, v|
|
208
|
+
table = v.split("/")[0]
|
209
|
+
tables.push table unless tables.include? table
|
210
|
+
# Check if it's a linked resource, generate WHERE clause accordingly
|
211
|
+
if v.split("/").length > 2
|
212
|
+
match = v.split("/")[2]
|
213
|
+
matching_field = match.split("->")[0]
|
214
|
+
row_field = match.split("->")[1]
|
215
|
+
# We have to run the subquery now because it'll be deleted later!
|
216
|
+
subq = "SELECT #{row_field} FROM #{main_table} WHERE #{where_clause[main_table]};"
|
217
|
+
res = db.query(dbc, subq)
|
218
|
+
subqres = db.extract(res, row_field)
|
219
|
+
where_clause[table] ||= "#{matching_field}=#{subqres}"
|
220
|
+
else
|
221
|
+
# Normal WHERE clause
|
222
|
+
where_clause[table] ||= "id=#{id}"
|
223
|
+
end
|
224
|
+
end
|
190
225
|
|
191
|
-
|
192
|
-
|
193
|
-
tables.
|
194
|
-
|
195
|
-
|
196
|
-
match = v.split("/")[2]
|
197
|
-
matching_field = match.split("->")[0]
|
198
|
-
row_field = match.split("->")[1]
|
199
|
-
# We have to run the subquery now because it'll be deleted later!
|
200
|
-
subq = "SELECT #{row_field} FROM #{main_table} WHERE #{where_clause[main_table]};"
|
201
|
-
res = db.query(dbc, subq)
|
202
|
-
subqres = db.extract(res, row_field)
|
203
|
-
where_clause[table] ||= "#{matching_field}=#{subqres}"
|
204
|
-
else
|
205
|
-
# Normal WHERE clause
|
206
|
-
where_clause[table] ||= "id=#{id}"
|
226
|
+
# Generate and run queries
|
227
|
+
results = []
|
228
|
+
tables.each do |tb|
|
229
|
+
query = "DELETE FROM #{tb} WHERE #{where_clause[tb]};"
|
230
|
+
results.push db.query(dbc, query)
|
207
231
|
end
|
232
|
+
@engine.http_status = "200 OK"
|
233
|
+
resp = {status: "200 OK"}
|
208
234
|
end
|
235
|
+
return resp
|
236
|
+
end
|
209
237
|
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
238
|
+
# Get an entry with a given id.
|
239
|
+
#
|
240
|
+
# @param id [Fixnum] ID of the entry
|
241
|
+
def get_entries(id)
|
242
|
+
jso = Hash.new()
|
243
|
+
|
244
|
+
dbe = MIDB::API::Dbengine.new(@engine.config, @db)
|
245
|
+
dblink = dbe.connect()
|
246
|
+
rows = dbe.query(dblink, "SELECT * FROM #{self.get_structure.values[0].split('/')[0]} WHERE id=#{id};")
|
247
|
+
if dbe.length(rows) > 0
|
248
|
+
rows.each do |row|
|
249
|
+
jso[row["id"]] = self.get_structure
|
250
|
+
|
251
|
+
self.get_structure.each do |name, dbi|
|
252
|
+
table = dbi.split("/")[0]
|
253
|
+
field = dbi.split("/")[1]
|
254
|
+
# Must-match relations ("table2/field/table2-field->row-field")
|
255
|
+
if dbi.split("/").length > 2
|
256
|
+
match = dbi.split("/")[2]
|
257
|
+
matching_field = match.split("->")[0]
|
258
|
+
row_field = match.split("->")[1]
|
259
|
+
query = dbe.query(dblink, "SELECT #{field} FROM #{table} WHERE #{matching_field}=#{row[row_field]};")
|
260
|
+
else
|
261
|
+
query = dbe.query(dblink, "SELECT #{field} from #{table} WHERE id=#{row['id']};")
|
262
|
+
end
|
263
|
+
jso[row["id"]][name] = dbe.length(query) > 0 ? dbe.extract(query,field) : "unknown"
|
264
|
+
end
|
265
|
+
end
|
266
|
+
@engine.http_status = "200 OK"
|
267
|
+
else
|
268
|
+
@engine.http_status = "404 Not Found"
|
269
|
+
jso = MIDB::Interface::Server.json_error(404, "Not Found")
|
215
270
|
end
|
216
|
-
|
217
|
-
|
271
|
+
return jso
|
272
|
+
|
218
273
|
end
|
219
|
-
return resp
|
220
|
-
end
|
221
274
|
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
275
|
+
# Get all the entries from the database
|
276
|
+
def get_all_entries()
|
277
|
+
jso = Hash.new()
|
278
|
+
|
279
|
+
# Connect to database
|
280
|
+
dbe = MIDB::API::Dbengine.new(@engine.config, @db)
|
281
|
+
dblink = dbe.connect()
|
282
|
+
rows = dbe.query(dblink, "SELECT * FROM #{self.get_structure.values[0].split('/')[0]};")
|
227
283
|
|
228
|
-
|
229
|
-
dblink = dbe.connect()
|
230
|
-
rows = dbe.query(dblink, "SELECT * FROM #{self.get_structure.values[0].split('/')[0]} WHERE id=#{id};")
|
231
|
-
if dbe.length(rows) > 0
|
284
|
+
# Iterate over all rows of this table
|
232
285
|
rows.each do |row|
|
286
|
+
# Replace the "id" in the given JSON with the actual ID and expand it with the fields
|
233
287
|
jso[row["id"]] = self.get_structure
|
234
288
|
|
235
289
|
self.get_structure.each do |name, dbi|
|
@@ -247,47 +301,9 @@ module MIDB
|
|
247
301
|
jso[row["id"]][name] = dbe.length(query) > 0 ? dbe.extract(query,field) : "unknown"
|
248
302
|
end
|
249
303
|
end
|
250
|
-
MIDB::
|
251
|
-
|
252
|
-
MIDB::ServerController.http_status = "404 Not Found"
|
253
|
-
jso = MIDB::ServerView.json_error(404, "Not Found")
|
254
|
-
end
|
255
|
-
return jso
|
256
|
-
|
257
|
-
end
|
258
|
-
|
259
|
-
# Method: get_all_entries
|
260
|
-
# Get all the entries from the fields specified in a JSON-parsed hash
|
261
|
-
def self.get_all_entries(db, jsf)
|
262
|
-
@jsf = jsf
|
263
|
-
jso = Hash.new()
|
264
|
-
|
265
|
-
# Connect to database
|
266
|
-
dbe = MIDB::DbengineModel.new()
|
267
|
-
dblink = dbe.connect()
|
268
|
-
rows = dbe.query(dblink, "SELECT * FROM #{self.get_structure.values[0].split('/')[0]};")
|
269
|
-
|
270
|
-
# Iterate over all rows of this table
|
271
|
-
rows.each do |row|
|
272
|
-
# Replace the "id" in the given JSON with the actual ID and expand it with the fields
|
273
|
-
jso[row["id"]] = self.get_structure
|
274
|
-
|
275
|
-
self.get_structure.each do |name, dbi|
|
276
|
-
table = dbi.split("/")[0]
|
277
|
-
field = dbi.split("/")[1]
|
278
|
-
# Must-match relations ("table2/field/table2-field->row-field")
|
279
|
-
if dbi.split("/").length > 2
|
280
|
-
match = dbi.split("/")[2]
|
281
|
-
matching_field = match.split("->")[0]
|
282
|
-
row_field = match.split("->")[1]
|
283
|
-
query = dbe.query(dblink, "SELECT #{field} FROM #{table} WHERE #{matching_field}=#{row[row_field]};")
|
284
|
-
else
|
285
|
-
query = dbe.query(dblink, "SELECT #{field} from #{table} WHERE id=#{row['id']};")
|
286
|
-
end
|
287
|
-
jso[row["id"]][name] = dbe.length(query) > 0 ? dbe.extract(query,field) : "unknown"
|
288
|
-
end
|
304
|
+
MIDB::API::Hooks.after_get_all_entries()
|
305
|
+
return jso
|
289
306
|
end
|
290
|
-
return jso
|
291
307
|
end
|
292
308
|
end
|
293
309
|
end
|