midb 1.0.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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: f6a12da2fe9657a3beacfe2a00aed774bede4101
4
+ data.tar.gz: 7af6d67d1a748311d665af0b1330d7ac4ba69dcb
5
+ SHA512:
6
+ metadata.gz: 7eaee7eccb98dbf4001039eea0f2d11a012b305bff7fb822597137b631689cfbae555188f1f5384e2a035067ad836a85920864086c65e1ec03b7f98f35cb3dea
7
+ data.tar.gz: 1deda596c389b6804abe4cd55202165f4a3897a6169899e5aa4e2f3f3af4cf7101f66a5a474ed5c91bceb8f0b42c09a8163be42de5ca5935d1168a51b4997cd7
data/bin/midb ADDED
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ ## midb: middleware for databases! ##
4
+ # 08/31/15, unrar
5
+ require 'midb'
6
+
7
+ # Pass the arguments to the controler, we don't want the action here ;-)
8
+ MIDB::ServerController.args = ARGV
9
+
10
+ # And start the server
11
+ MIDB::ServerController.init()
12
+
13
+ # Save data in case we didn't actually start the server but change the configuration
14
+ MIDB::ServerController.save()
@@ -0,0 +1,50 @@
1
+ require 'midb/server_controller'
2
+ require 'sqlite3'
3
+ require 'mysql2'
4
+
5
+ module MIDB
6
+ class DbengineModel
7
+ attr_accessor :engine, :host, :uname, :pwd, :port, :db
8
+ def initialize()
9
+ @engine = MIDB::ServerController.config["dbengine"]
10
+ @host = MIDB::ServerController.config["dbhost"]
11
+ @port = MIDB::ServerController.config["dbport"]
12
+ @uname = MIDB::ServerController.config["dbuser"]
13
+ @pwd = MIDB::ServerController.config["dbpassword"]
14
+ @db = MIDB::ServerController.db
15
+ end
16
+ # Method: connect
17
+ # Connect to the specified database
18
+ def connect()
19
+ if @engine == :sqlite3
20
+ sq = SQLite3::Database.open("./db/#{@db}.db")
21
+ sq.results_as_hash = true
22
+ return sq
23
+ elsif @engine == :mysql
24
+ return Mysql2::Client.new(:host => @host, :username => @uname, :password => @pwd, :database => @db)
25
+ end
26
+ end
27
+
28
+ # Method: query
29
+ # Perform a query, return a hash
30
+ def query(res, query)
31
+ if @engine == :sqlite3
32
+ return res.execute(query)
33
+ elsif @engine == :mysql
34
+ return res.query(query)
35
+ end
36
+ end
37
+
38
+ # Method: extract
39
+ # Extract a field from a query
40
+ def extract(result, field)
41
+ if @engine == :sqlite3
42
+ return result[0][field] || result[field]
43
+ elsif @engine == :mysql
44
+ result.each do |row|
45
+ return row[field]
46
+ end
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,31 @@
1
+ require 'midb/server_controller'
2
+
3
+ # This controller handles errors.
4
+ module MIDB
5
+ class ErrorsView
6
+ # Method: die
7
+ # Handles arguments that cause program termination.
8
+ # Errors: :noargs, :server_already_started
9
+ def self.die(err)
10
+ errmsg = case err
11
+ when :noargs then "No command supplied. See `midb help`."
12
+ when :server_already_started then "The server has already been started and is running."
13
+ when :server_not_running then "The server isn't running."
14
+ when :server_error then "Error while starting server."
15
+ when :no_serves then "No files are being served. Try running `midb serve file.json`"
16
+ when :syntax then "Syntax error. See `midb help`"
17
+ when :file_404 then "File not found."
18
+ when :not_json then "Specified file isn't JSON!"
19
+ when :json_exists then "Specified file is already being served."
20
+ when :json_not_exists then "The JSON file isn't being served."
21
+ when :unsupported_engine then "The specified database engine isn't supported by midb."
22
+ when :already_project then "This directory already contains a midb project."
23
+ when :bootstrap then "midb hasn't been bootstraped in this folder. Run `midb bootstrap`."
24
+ when :no_help then "No help available for this command. See a list of commands with `midb help`."
25
+ else "Unknown error: #{err.to_s}"
26
+ end
27
+ abort("Fatal error: #{errmsg}")
28
+
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,31 @@
1
+ require 'hmac-sha1'
2
+ require 'base64'
3
+ require 'cgi'
4
+
5
+ # midb security controller - handles API authentication
6
+ # this will probably become another different project soon!
7
+ module MIDB
8
+ class SecurityController
9
+
10
+ # Method: is_auth?
11
+ # Checks if an HTTP header is the authorization one
12
+ def self.is_auth?(header)
13
+ return header.split(":")[0].downcase == "authentication"
14
+ end
15
+
16
+ # Method: parse_auth
17
+ # Parses an authentication header
18
+ def self.parse_auth(header)
19
+ return header.split(" ")[1]
20
+ end
21
+
22
+ # Method: check?
23
+ # Checks if an HMAC digest is properly authenticated
24
+ def self.check?(header, params, key)
25
+ signature = params
26
+ hmac = HMAC::SHA1.new(key)
27
+ hmac.update(signature)
28
+ return self.parse_auth(header) == CGI.escape(Base64.encode64("#{hmac.digest}"))
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,337 @@
1
+ require 'midb/server_model'
2
+ require 'midb/server_view'
3
+ require 'midb/errors_view'
4
+ require 'midb/security_controller'
5
+
6
+ require 'yaml'
7
+ require 'socket'
8
+ require 'uri'
9
+ require 'json'
10
+ require 'sqlite3'
11
+
12
+ module MIDB
13
+ # This controller controls the behavior of the midb server.
14
+ class ServerController
15
+ # Variable declaration
16
+ class << self
17
+ # args[] => passed by the binary
18
+ # config => configuration array saved and loaded from .midb.yaml
19
+ # db => the database we're using
20
+ # http_status => the HTTP status, sent by the model
21
+ attr_accessor :args, :config, :db, :http_status, :port
22
+ end
23
+ # status => server status
24
+ # serves[] => JSON files served by the API
25
+ @http_status = "200 OK"
26
+ @args = []
27
+ @config = Hash.new()
28
+ @port = 8081
29
+
30
+ # Method: init
31
+ # Decide what to do according to the supplied command!
32
+ def self.init()
33
+ # We should have at least one argument, which can be `run` or `serve`
34
+ MIDB::ErrorsView.die(:noargs) if @args.length < 1
35
+
36
+ # Load the config
37
+ if File.file?(".midb.yaml")
38
+ @config = YAML.load_file(".midb.yaml")
39
+ else
40
+ # If the file doesn't exist, we need to bootstrap
41
+ MIDB::ErrorsView.die(:bootstrap) if @args[0] != "help" && args[0] != "bootstrap"
42
+ end
43
+
44
+ case @args[0]
45
+
46
+ # Command: help
47
+ # Shows the help
48
+ when "help"
49
+ if @args.length > 1
50
+ case @args[1]
51
+ when "bootstrap"
52
+ MIDB::ServerView.help(:bootstrap)
53
+ when "set"
54
+ MIDB::ServerView.help(:set)
55
+ when "start"
56
+ MIDB::ServerView.help(:start)
57
+ when "serve"
58
+ MIDB::ServerView.help(:serve)
59
+ when "unserve"
60
+ MIDB::ServerView.help(:unserve)
61
+ else
62
+ MIDB::ErrorsView.die(:no_help)
63
+ end
64
+ else
65
+ MIDB::ServerView.help(:list)
66
+ end
67
+
68
+ # Command: bootstrap
69
+ # Create config file and initial directories
70
+ when "bootstrap"
71
+ if File.file?(".midb.yaml")
72
+ MIDB::ErrorsView.die(:already_project)
73
+ else
74
+ # If the file doesn't exist it, create it with the default stuff
75
+ @config["serves"] = []
76
+ @config["status"] = :asleep # The server is initially asleep
77
+ @config["apikey"] = "midb-api" # This should be changed, it's the private API key
78
+ @config["dbengine"] = :sqlite3 # SQLite is the default engine
79
+ # Default DB configuration for MySQL and other engines
80
+ @config["dbhost"] = "localhost"
81
+ @config["dbport"] = 3306
82
+ @config["dbuser"] = "nobody"
83
+ @config["dbpassword"] = "openaccess"
84
+ File.open(".midb.yaml", 'w') do |l|
85
+ l.write @config.to_yaml
86
+ end
87
+ # Create json/ and db/ directory if it doesn't exist
88
+ Dir.mkdir("json") unless File.exists?("json")
89
+ Dir.mkdir("db") unless File.exists?("db")
90
+ MIDB::ServerView.info(:bootstrap)
91
+ end
92
+
93
+ # Command: set
94
+ # Sets configuration factors.
95
+ when "set"
96
+ # Check syntax
97
+ MIDB::ErrorsView.die(:syntax) if @args.length < 2
98
+ subset = @args[1].split(":")[0]
99
+ subcmd = @args[1].split(":")[1]
100
+ set = @args.length < 3 ? false : true
101
+ setter = @args[2] if set
102
+ case subset
103
+ when "db"
104
+ # DB Config
105
+ case subcmd
106
+ when "engine"
107
+ if set
108
+ @config["dbengine"] = case setter.downcase
109
+ when "sqlite3" then :sqlite3
110
+ when "mysql" then :mysql
111
+ else :undef
112
+ end
113
+ if @config["dbengine"] == :undef
114
+ MIDB::ErrorsView.die(:unsupported_engine)
115
+ @config["dbengine"] = :sqlite3
116
+ end
117
+ end
118
+ MIDB::ServerView.out_config(:dbengine)
119
+ when "host"
120
+ @config["dbhost"] = setter if set
121
+ MIDB::ServerView.out_config(:dbhost)
122
+ when "port"
123
+ @config["dbport"] = setter if set
124
+ MIDB::ServerView.out_config(:dbport)
125
+ when "user"
126
+ @config["dbuser"] = setter if set
127
+ MIDB::ServerView.out_config(:dbuser)
128
+ when "password"
129
+ @config["dbpassword"] = setter if set
130
+ MIDB::ServerView.out_config(:dbpassword)
131
+ else
132
+ MIDB::ErrorsView.die(:synax)
133
+ end
134
+ when "api"
135
+ case subcmd
136
+ when "key"
137
+ @config["apikey"] = setter if set
138
+ MIDB::ServerView.out_config(:apikey)
139
+ end
140
+ else
141
+ MIDB::ErrorsView.die(:syntax)
142
+ end
143
+
144
+
145
+ # Command: start
146
+ # Starts the server
147
+ when "start"
148
+ # Check syntax
149
+ MIDB::ErrorsView.die(:syntax) if @args.length < 2
150
+ MIDB::ErrorsView.die(:syntax) if @args[1].split(":")[0] != "db"
151
+ # Is the server already started?
152
+ MIDB::ErrorsView.die(:server_already_started) if @config["status"] == :running
153
+ # Are any files being served?
154
+ MIDB::ErrorsView.die(:no_serves) if @config["serves"].length == 0
155
+ # If it successfully starts, change our status and notify thru view
156
+ @args.each do |arg|
157
+ if arg.split(":")[0] == "db"
158
+ @db = arg.split(":")[1]
159
+ elsif arg.split(":")[0] == "port"
160
+ @port = arg.split(":")[1]
161
+ end
162
+ end
163
+
164
+ if self.start(@port)
165
+ @config["status"] = :running
166
+ MIDB::ServerView.success()
167
+ else
168
+ MIDB::ErrorsView.die(:server_error)
169
+ end
170
+
171
+ # Command: serve
172
+ # Serves a JSON file
173
+ when "serve"
174
+ # Check if there's a second argument
175
+ MIDB::ErrorsView.die(:syntax) if @args.length < 2
176
+ # Is the server running? It shouldn't
177
+ MIDB::ErrorsView.die(:server_already_started) if @config["status"] == :running
178
+ # Is there such file as @args[1]?
179
+ MIDB::ErrorsView.die(:file_404) unless File.file?("./json/" + @args[1])
180
+ # Is the file a JSON file?
181
+ MIDB::ErrorsView.die(:not_json) unless File.extname(@args[1]) == ".json"
182
+ # Is the file already loaded?
183
+ MIDB::ErrorsView.die(:json_exists) if @config["serves"].include? @args[1]
184
+
185
+ # Tests passed, so let's add the file to the served list!
186
+ @config["serves"].push @args[1]
187
+ MIDB::ServerView.show_serving()
188
+
189
+ # Command: unserve
190
+ # Stop serving a JSON file.
191
+ when "unserve"
192
+ # Check if there's a second argument
193
+ MIDB::ErrorsView.die(:syntax) if @args.length < 2
194
+ # Is the server running? It shouldn't
195
+ MIDB::ErrorsView.die(:server_already_started) if @config["status"] == :running
196
+ # Is the file already loaded?
197
+ MIDB::ErrorsView.die(:json_not_exists) unless @config["serves"].include? @args[1]
198
+
199
+ # Delete it!
200
+ @config["serves"].delete @args[1]
201
+ MIDB::ServerView.show_serving()
202
+
203
+ # Command: stop
204
+ # Stops the server.
205
+ when "stop"
206
+ # Is the server running?
207
+ MIDB::ErrorsView.die(:server_not_running) unless @config["status"] == :running
208
+
209
+ @config["status"] = :asleep
210
+ MIDB::ServerView.server_stopped()
211
+ end
212
+ end
213
+
214
+ # Method: start
215
+ # Starts the server on the given port (default: 8080)
216
+ def self.start(port=8081)
217
+ serv = TCPServer.new("localhost", port)
218
+ MIDB::ServerView.info(:start, port)
219
+
220
+ # Manage the requests
221
+ loop do
222
+ socket = serv.accept
223
+ MIDB::ServerView.info(:incoming_request, socket.addr[3])
224
+
225
+ request = self.parse_request(socket.gets)
226
+
227
+ # Get a hash with the headers
228
+ headers = {}
229
+ while line = socket.gets.split(' ', 2)
230
+ break if line[0] == ""
231
+ headers[line[0].chop] = line[1].strip
232
+ end
233
+ data = socket.read(headers["Content-Length"].to_i)
234
+
235
+
236
+ MIDB::ServerView.info(:request, request)
237
+ response_json = Hash.new()
238
+
239
+ # Endpoint syntax: ["", FILE, ID, (ACTION)]
240
+ endpoint = request[1].split("/")
241
+ ep_file = endpoint[1]
242
+
243
+ method = request[0]
244
+ endpoints = [] # Valid endpoints
245
+
246
+ # Load the JSON served files
247
+ @config["serves"].each do |js|
248
+ # The filename is a valid endpoint
249
+ endpoints.push File.basename(js, ".*")
250
+ end
251
+
252
+ # Load the endpoints
253
+ found = false
254
+ endpoints.each do |ep|
255
+ if ep_file == ep
256
+ found = true
257
+ MIDB::ServerView.info(:match_json, ep)
258
+ # Analyze the request and pass it to the model
259
+ if method == "GET"
260
+ case endpoint.length
261
+ when 2
262
+ # No ID has been specified. Return all the entries
263
+ # Pass it to the model and get the JSON
264
+ response_json = MIDB::ServerModel.get_all_entries(@db, ep).to_json
265
+ when 3
266
+ # An ID has been specified. Should it exist, return all of its entries.
267
+ response_json = MIDB::ServerModel.get_entries(@db, ep, endpoint[2]).to_json
268
+ end
269
+ else
270
+ # An action has been specified. We're going to need HTTP authentification here.
271
+ MIDB::ServerView.info(:auth_required)
272
+
273
+ if (not headers.has_key? "Authentication") ||
274
+ (not MIDB::SecurityController.check?(headers["Authentication"], data, @config["apikey"]))
275
+ @http_status = "401 Unauthorized"
276
+ response_json = MIDB::ServerView.json_error(401, "Unauthorized").to_json
277
+ MIDB::ServerView.info(:no_auth)
278
+
279
+ else
280
+ MIDB::ServerView.info(:auth_success)
281
+ if method == "POST"
282
+ response_json = MIDB::ServerModel.post(@db, ep, data).to_json
283
+ else
284
+ if endpoint.length >= 3
285
+ if method == "DELETE"
286
+ response_json = MIDB::ServerModel.delete(@db, ep, endpoint[2]).to_json
287
+ elsif method == "PUT"
288
+ response_json = MIDB::ServerModel.put(@db, ep, endpoint[2], data).to_json
289
+ end
290
+ else
291
+ @http_status = "404 Not Found"
292
+ response_json = MIDB::ServerView.json_error(404, "Must specify an ID.").to_json
293
+ end
294
+ end
295
+ end
296
+ end
297
+ MIDB::ServerView.info(:response, response_json)
298
+ # Return the results via HTTP
299
+ socket.print "HTTP/1.1 #{@http_status}\r\n" +
300
+ "Content-Type: text/json\r\n" +
301
+ "Content-Length: #{response_json.size}\r\n" +
302
+ "Connection: close\r\n"
303
+ socket.print "\r\n"
304
+ socket.print response_json
305
+ socket.print "\r\n"
306
+ MIDB::ServerView.info(:success)
307
+ end
308
+ end
309
+ unless found
310
+ MIDB::ServerView.info(:not_found)
311
+ response = MIDB::ServerView.json_error(404, "Invalid API endpoint.").to_json
312
+
313
+ socket.print "HTTP/1.1 404 Not Found\r\n" +
314
+ "Content-Type: text/json\r\n" +
315
+ "Content-Length: #{response.size}\r\n" +
316
+ "Connection: close\r\n"
317
+ socket.print "\r\n"
318
+ socket.print response
319
+ end
320
+ end
321
+ end
322
+
323
+ # Method: parse_request
324
+ # Parses an HTTP requests and returns an array [method, uri]
325
+ def self.parse_request(req)
326
+ [req.split(" ")[0], req.split(" ")[1]]
327
+ end
328
+
329
+ # Method: save
330
+ # Saves config to .midb.yaml
331
+ def self.save()
332
+ File.open(".midb.yaml", 'w') do |l|
333
+ l.write @config.to_yaml
334
+ end
335
+ end
336
+ end
337
+ end
@@ -0,0 +1,293 @@
1
+ require 'midb/server_controller'
2
+ require 'midb/dbengine_model'
3
+ require 'midb/server_view'
4
+
5
+ require 'sqlite3'
6
+ require 'json'
7
+ require 'cgi'
8
+ module MIDB
9
+ class ServerModel
10
+ attr_accessor :jsf
11
+
12
+ # Method: get_structure
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
23
+
24
+ # Method: post
25
+ # Act on POST requests - create a new resource
26
+ def self.post(db, jsf, data)
27
+ @jsf = jsf
28
+ jss = self.get_structure() # For referencing purposes
29
+
30
+ input = self.query_to_hash(data)
31
+ bad_request = false
32
+ resp = nil
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
40
+ end
41
+ input.each do |key, value|
42
+ # Check if we have it on the structure too
43
+ unless jss.has_key? key
44
+ resp = MIDB::ServerView.json_error(400, "Bad Request - Wrong argument #{key}")
45
+ MIDB::ServerController.http_status = 400
46
+ bad_request = true
47
+ end
48
+ end
49
+
50
+
51
+ # Insert the values if we have a good request
52
+ unless bad_request
53
+ fields = Hash.new
54
+ inserts = Hash.new
55
+ main_table = self.get_structure.values[0].split('/')[0]
56
+ input.each do |key, value|
57
+ struct = jss[key]
58
+ table = struct.split("/")[0]
59
+ inserts[table] ||= []
60
+ fields[table] ||= []
61
+ inserts[table].push "\"" + value + "\""
62
+ fields[table].push struct.split("/")[1]
63
+ if struct.split("/").length > 2
64
+ match = struct.split("/")[2]
65
+ matching_field = match.split("->")[0]
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
73
+ end
74
+ end
75
+ queries = []
76
+ inserts.each do |table, values|
77
+ queries.push "INSERT INTO #{table}(#{fields[table].join(',')}) VALUES (#{inserts[table].join(',')});"
78
+ end
79
+ # Connect to the database
80
+ dbe = MIDB::DbengineModel.new
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")
91
+ end
92
+ end
93
+ MIDB::ServerController.http_status = "201 Created"
94
+ resp = {"status": "201 created", "id": rid}
95
+ end
96
+ return resp
97
+ end
98
+
99
+ # Method: put
100
+ # Update an already existing resource
101
+ def self.put(db, jsf, id, data)
102
+ @jsf = jsf
103
+ jss = self.get_structure() # For referencing purposes
104
+
105
+ input = self.query_to_hash(data)
106
+ bad_request = false
107
+ resp = nil
108
+ input.each do |key, value|
109
+ # Check if we have it on the structure too
110
+ unless jss.has_key? key
111
+ resp = MIDB::ServerView.json_error(400, "Bad Request - Wrong argument #{key}")
112
+ MIDB::ServerController.http_status = 400
113
+ bad_request = true
114
+ end
115
+ end
116
+
117
+ # Check if the ID exists
118
+ db = MIDB::DbengineModel.new
119
+ dbc = db.connect()
120
+ dbq = db.query(dbc, "SELECT * FROM #{self.get_structure.values[0].split('/')[0]} WHERE id=#{id};")
121
+ unless dbq.length > 0
122
+ resp = MIDB::ServerView.json_error(404, "ID not found")
123
+ MIDB::ServerController.http_status = 404
124
+ bad_request = true
125
+ end
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}"
134
+ input.each do |key, value|
135
+ struct = jss[key]
136
+ table = struct.split("/")[0]
137
+ inserts[table] ||= []
138
+ fields[table] ||= []
139
+ inserts[table].push "\"" + value + "\""
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]});"
146
+ end
147
+ end
148
+ queries = []
149
+ updates = Hash.new
150
+ # Turn it into a hash
151
+ inserts.each do |table, values|
152
+ updates[table] ||= Hash.new
153
+ updates[table] = Hash[fields[table].zip(inserts[table])]
154
+ query = "UPDATE #{table} SET "
155
+ updates[table].each do |f, v|
156
+ query = query + "#{f}=#{v} "
157
+ end
158
+ queries.push query + "WHERE #{where_clause[table]};"
159
+ end
160
+ # Run the queries
161
+ results = []
162
+ queries.each do |q|
163
+ results.push db.query(dbc, q)
164
+ end
165
+ MIDB::ServerController.http_status = "200 OK"
166
+ resp = {"status": "200 OK"}
167
+ end
168
+ return resp
169
+ end
170
+
171
+
172
+ def self.delete(db, jsf, id)
173
+ # Check if the ID exists
174
+ db = MIDB::DbengineModel.new
175
+ dbc = db.connect()
176
+ dbq = db.query(dbc, "SELECT * FROM #{self.get_structure.values[0].split('/')[0]} WHERE id=#{id};")
177
+ if not dbq.length > 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
185
+
186
+ where_clause = {}
187
+ tables = []
188
+ main_table = jss.values[0].split('/')[0]
189
+ where_clause[main_table] = "id=#{id}"
190
+
191
+ jss.each do |k, v|
192
+ table = v.split("/")[0]
193
+ tables.push table unless tables.include? table
194
+ # Check if it's a linked resource, generate WHERE clause accordingly
195
+ if v.split("/").length > 2
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}"
207
+ end
208
+ end
209
+
210
+ # Generate and run queries
211
+ results = []
212
+ tables.each do |tb|
213
+ query = "DELETE FROM #{tb} WHERE #{where_clause[tb]};"
214
+ results.push db.query(dbc, query)
215
+ end
216
+ MIDB::ServerController.http_status = "200 OK"
217
+ resp = {"status": "200 OK"}
218
+ end
219
+ return resp
220
+ end
221
+
222
+ # Method: get_entries
223
+ # Get the entries from a given ID.
224
+ def self.get_entries(db, jsf, id)
225
+ @jsf = jsf
226
+ jso = Hash.new()
227
+
228
+ dbe = MIDB::DbengineModel.new()
229
+ dblink = dbe.connect()
230
+ rows = dbe.query(dblink, "SELECT * FROM #{self.get_structure.values[0].split('/')[0]} WHERE id=#{id};")
231
+ if rows.length > 0
232
+ rows.each do |row|
233
+ jso[row["id"]] = self.get_structure
234
+
235
+ self.get_structure.each do |name, dbi|
236
+ table = dbi.split("/")[0]
237
+ field = dbi.split("/")[1]
238
+ # Must-match relations ("table2/field/table2-field->row-field")
239
+ if dbi.split("/").length > 2
240
+ match = dbi.split("/")[2]
241
+ matching_field = match.split("->")[0]
242
+ row_field = match.split("->")[1]
243
+ query = dbe.query(dblink, "SELECT #{field} FROM #{table} WHERE #{matching_field}=#{row[row_field]};")
244
+ else
245
+ query = dbe.query(dblink, "SELECT #{field} from #{table} WHERE id=#{row['id']};")
246
+ end
247
+ jso[row["id"]][name] = query.length > 0 ? dbe.extract(query,field) : "unknown"
248
+ end
249
+ end
250
+ MIDB::ServerController.http_status = "200 OK"
251
+ else
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] = query.length > 0 ? dbe.extract(query,field) : "unknown"
288
+ end
289
+ end
290
+ return jso
291
+ end
292
+ end
293
+ end
@@ -0,0 +1,99 @@
1
+ require 'midb/server_controller'
2
+ module MIDB
3
+ class ServerView
4
+ def self.success()
5
+ puts "Ayyy great"
6
+ end
7
+
8
+ # Method: json_error
9
+ # Return a JSON error response
10
+ def self.json_error(errno, msg)
11
+ return {"error" => {"errno" => errno, "msg" => msg}}
12
+ end
13
+
14
+ # Method: show_serving
15
+ # Shows the files being served
16
+ def self.show_serving()
17
+ puts "The follow JSON files are being served as APIs:"
18
+ MIDB::ServerController.config["serves"].each do |serv|
19
+ puts "- #{serv}"
20
+ end
21
+ end
22
+
23
+ # Method: server_stopped
24
+ # Notice that the server has been stopped.
25
+ def self.server_stopped()
26
+ puts "The server has been successfully stopped!"
27
+ end
28
+
29
+ # Method: info
30
+ # Send some info
31
+ def self.info(what, info=nil)
32
+ msg = case what
33
+ when :start then "Server started on port #{info}. Listening for connections..."
34
+ when :incoming_request then "> Incoming request from #{info}."
35
+ when :request then ">> Request method: #{info[0]}\n>>> Endpoint: #{info[1]}"
36
+ when :match_json then ">> The request matched a JSON file: #{info}.json\n>> Creating response..."
37
+ when :response then ">> Sending JSON response (RAW):\n#{info}"
38
+ when :success then "> Successfully managed this request!"
39
+ when :not_found then "> Invalid endpoint - sending a 404 error."
40
+ when :auth_required then ">> Authentication required. Checking for the HTTP header..."
41
+ when :no_auth then ">> No authentication header - sending a 401 error."
42
+ when :auth_success then ">> Successfully authenticated the request."
43
+ when :bootstrap then "> Successfully bootstraped!"
44
+ end
45
+ puts msg
46
+ end
47
+
48
+ # Method: out_config
49
+ # Output some config
50
+ def self.out_config(what)
51
+ msg = case what
52
+ when :dbengine then "Database engine: #{MIDB::ServerController.config['dbengine']}."
53
+ when :dbhost then "Database server host: #{MIDB::ServerController.config['dbhost']}."
54
+ when :dbport then "Database server port: #{MIDB::ServerController.config['dbport']}."
55
+ when :dbuser then "Database server user: #{MIDB::ServerController.config['dbuser']}."
56
+ when :dbpassword then "Database server password: #{MIDB::ServerController.config['dbpassword']}."
57
+ when :apikey then "Private API key: #{MIDB::ServerController.config['apikey']}"
58
+ else "Error??"
59
+ end
60
+ puts msg
61
+ end
62
+
63
+ # Method: help
64
+ # Shows the help
65
+ def self.help(what)
66
+ case what
67
+ when :list
68
+ puts "midb has several commands that you can use. For detailed information, see `midb help command`."
69
+ puts " "
70
+ puts "bootstrap\tCreate the basic files and directories that midb needs to be ran in a folder."
71
+ puts "set\tModify this project's settings. See the detailed help for a list of options."
72
+ puts "serve\tServes a JSON file - creates an API endpoint."
73
+ puts "unserve\tStops serving a JSON file - the endpoint is no longer valid."
74
+ puts "start\tStarts an API server. See detailed help for more."
75
+ when :bootstrap
76
+ puts "This command creates the `.midb.yaml` config file, and the `db` and `json` directories if they don't exist."
77
+ puts "You must bootstrap before running any other commands."
78
+ when :set
79
+ puts "Sets config options. If no value is given, it shows the current value."
80
+ puts "db:host\tHost name of the database (for MySQL)"
81
+ puts "db:user\tUsername for the database (for MySQL)"
82
+ puts "db:password\tPassword for the database (for MySQL)"
83
+ puts "db:engine\t(sqlite3, mysql) Changes the database engine."
84
+ puts "api:key\tChanges the private API key, used for authentication over HTTP."
85
+ when :serve
86
+ puts "This command will create an API endpoint pointing to a JSON file in the json/ directory."
87
+ puts "It will support GET, POST, PUT and DELETE requests."
88
+ puts "For detailed information on how to format your file, see the GitHub README and/or wiki."
89
+ when :unserve
90
+ puts "Stops serving a JSON file under the json/ directory."
91
+ when :start
92
+ puts "Starts the server. You must run the serve/unserve commands beforehand, so to set some endpoints."
93
+ puts "Options:"
94
+ puts "db:DATABASE\tSets DATABASE as the database where to get the data. Mandatory."
95
+ puts "port:PORT\tSets PORT as the port where the server will listen to. Default: 8081."
96
+ end
97
+ end
98
+ end
99
+ end
data/lib/midb.rb ADDED
@@ -0,0 +1,6 @@
1
+ require 'midb/dbengine_model'
2
+ require 'midb/errors_view'
3
+ require 'midb/security_controller'
4
+ require 'midb/server_controller'
5
+ require 'midb/server_model'
6
+ require 'midb/server_view'
metadata ADDED
@@ -0,0 +1,133 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: midb
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - unrar
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2015-08-31 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: mysql2
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '0.3'
20
+ - - ">="
21
+ - !ruby/object:Gem::Version
22
+ version: 0.3.20
23
+ type: :runtime
24
+ prerelease: false
25
+ version_requirements: !ruby/object:Gem::Requirement
26
+ requirements:
27
+ - - "~>"
28
+ - !ruby/object:Gem::Version
29
+ version: '0.3'
30
+ - - ">="
31
+ - !ruby/object:Gem::Version
32
+ version: 0.3.20
33
+ - !ruby/object:Gem::Dependency
34
+ name: sqlite3
35
+ requirement: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - "~>"
38
+ - !ruby/object:Gem::Version
39
+ version: '1.3'
40
+ - - ">="
41
+ - !ruby/object:Gem::Version
42
+ version: 1.3.10
43
+ type: :runtime
44
+ prerelease: false
45
+ version_requirements: !ruby/object:Gem::Requirement
46
+ requirements:
47
+ - - "~>"
48
+ - !ruby/object:Gem::Version
49
+ version: '1.3'
50
+ - - ">="
51
+ - !ruby/object:Gem::Version
52
+ version: 1.3.10
53
+ - !ruby/object:Gem::Dependency
54
+ name: httpclient
55
+ requirement: !ruby/object:Gem::Requirement
56
+ requirements:
57
+ - - "~>"
58
+ - !ruby/object:Gem::Version
59
+ version: '2.6'
60
+ - - ">="
61
+ - !ruby/object:Gem::Version
62
+ version: 2.6.0.1
63
+ type: :runtime
64
+ prerelease: false
65
+ version_requirements: !ruby/object:Gem::Requirement
66
+ requirements:
67
+ - - "~>"
68
+ - !ruby/object:Gem::Version
69
+ version: '2.6'
70
+ - - ">="
71
+ - !ruby/object:Gem::Version
72
+ version: 2.6.0.1
73
+ - !ruby/object:Gem::Dependency
74
+ name: ruby-hmac
75
+ requirement: !ruby/object:Gem::Requirement
76
+ requirements:
77
+ - - "~>"
78
+ - !ruby/object:Gem::Version
79
+ version: '0.4'
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: 0.4.0
83
+ type: :runtime
84
+ prerelease: false
85
+ version_requirements: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: '0.4'
90
+ - - ">="
91
+ - !ruby/object:Gem::Version
92
+ version: 0.4.0
93
+ description: Automatically create a RESTful API for your database, all you need to
94
+ write is a JSON file!
95
+ email: joszaynka@gmail.com
96
+ executables:
97
+ - midb
98
+ extensions: []
99
+ extra_rdoc_files: []
100
+ files:
101
+ - bin/midb
102
+ - lib/midb.rb
103
+ - lib/midb/dbengine_model.rb
104
+ - lib/midb/errors_view.rb
105
+ - lib/midb/security_controller.rb
106
+ - lib/midb/server_controller.rb
107
+ - lib/midb/server_model.rb
108
+ - lib/midb/server_view.rb
109
+ homepage: http://www.github.com/unrar/midb
110
+ licenses:
111
+ - TPOL
112
+ metadata: {}
113
+ post_install_message:
114
+ rdoc_options: []
115
+ require_paths:
116
+ - lib
117
+ required_ruby_version: !ruby/object:Gem::Requirement
118
+ requirements:
119
+ - - ">="
120
+ - !ruby/object:Gem::Version
121
+ version: '0'
122
+ required_rubygems_version: !ruby/object:Gem::Requirement
123
+ requirements:
124
+ - - ">="
125
+ - !ruby/object:Gem::Version
126
+ version: '0'
127
+ requirements: []
128
+ rubyforge_project:
129
+ rubygems_version: 2.4.8
130
+ signing_key:
131
+ specification_version: 4
132
+ summary: Middleware for databases
133
+ test_files: []