midb 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
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: []