schemaless_rest_api 0.6.0 → 0.7.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/lib/schemaless_rest_api/entities.rb +48 -0
- data/lib/schemaless_rest_api/mongo_client.rb +49 -10
- data/lib/schemaless_rest_api/rest_server.rb +16 -27
- data/lib/schemaless_rest_api/seed.rb +27 -0
- data/lib/schemaless_rest_api/server_utils.rb +11 -1
- data/lib/schemaless_rest_api/swagger/swagger.html +1 -2
- data/lib/schemaless_rest_api/version.rb +1 -1
- data/lib/schemaless_rest_api.rb +5 -16
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 9f0bc22701cb597ffc52c1e9544b6537546001748ce65e92456e5af32e99fbdd
|
4
|
+
data.tar.gz: c40d93389fbfe7f98fa7d05f262b2da7b806dced3223f13643122f0359c522b0
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: ed664da78bf2832449243ef73597521432e6f71dddbddb34a0c3cbcfd3ab9de105b215ad83e7775380a9cf103d966981f2c4036f3215c1eb500a668ee8e0f736
|
7
|
+
data.tar.gz: 27438bf5b3196e88c7dc79171bc4baefd537fb0b049fa796207c7fb2ba131781680ca931b79f9660c5a4d325bfd0cc5f9cb26d41a4090b251b374e4675105dc2
|
@@ -8,5 +8,53 @@ class Entities
|
|
8
8
|
class << self
|
9
9
|
# @return [Hash] Hash of models
|
10
10
|
attr_accessor :models
|
11
|
+
|
12
|
+
def page_data(model, params, values)
|
13
|
+
page = params[:page].to_i.positive? ? params[:page].to_i : 1
|
14
|
+
page_size = params[:page_size].to_i.positive? ? params[:page_size].to_i : 10
|
15
|
+
total_count = values.count
|
16
|
+
total_pages = (total_count / page_size.to_f).ceil
|
17
|
+
page_res = {
|
18
|
+
current_page: page,
|
19
|
+
page_size: page_size,
|
20
|
+
total_count: total_count,
|
21
|
+
total_pages: total_pages,
|
22
|
+
_links: {
|
23
|
+
self: { href: "/#{model}?page=#{page}&page_size=#{page_size}" }
|
24
|
+
}
|
25
|
+
}
|
26
|
+
page_res[:_links][:next] = { href: "/#{model}?page=#{page + 1}&page_size=#{page_size}" } if page < total_pages
|
27
|
+
page_res[:_links][:prev] = { href: "/#{model}?page=#{page - 1}&page_size=#{page_size}" } if page > 1
|
28
|
+
page_res
|
29
|
+
end
|
30
|
+
|
31
|
+
def query_from_params(params)
|
32
|
+
query = params.dup
|
33
|
+
query.delete(:page)
|
34
|
+
query.delete(:page_size)
|
35
|
+
query
|
36
|
+
end
|
37
|
+
|
38
|
+
# Find all values for given model querying via params
|
39
|
+
def find_all(model, params)
|
40
|
+
query = query_from_params params
|
41
|
+
total_items = if query == {}
|
42
|
+
Entities.models[model].values
|
43
|
+
else
|
44
|
+
Entities.models[model].values.find_all do |val|
|
45
|
+
val[query.keys[0].to_s].to_s == query.values[0]
|
46
|
+
end
|
47
|
+
end
|
48
|
+
pagination = page_data(model, params, total_items)
|
49
|
+
skip = (pagination[:current_page] - 1) * pagination[:page_size]
|
50
|
+
|
51
|
+
items = total_items.drop(skip).take(pagination[:page_size])
|
52
|
+
response = {
|
53
|
+
_embedded: {},
|
54
|
+
pagination: pagination
|
55
|
+
}
|
56
|
+
response[:_embedded][model.to_sym] = items
|
57
|
+
response
|
58
|
+
end
|
11
59
|
end
|
12
60
|
end
|
@@ -1,7 +1,9 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
2
|
|
3
3
|
# typed: false - MongoClient new
|
4
4
|
|
5
|
+
require "mongo"
|
6
|
+
|
5
7
|
# Client to talk to Mongo database
|
6
8
|
module MongoClient
|
7
9
|
class << self
|
@@ -14,25 +16,62 @@ module MongoClient
|
|
14
16
|
end
|
15
17
|
|
16
18
|
def find(model, id)
|
17
|
-
find_all(model, { id: id })
|
19
|
+
find_all(model, { id: id }, false)[:items]
|
18
20
|
end
|
19
21
|
|
20
|
-
def
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
22
|
+
def page_data(model, params, collection)
|
23
|
+
page = params[:page].to_i.positive? ? params[:page].to_i : 1
|
24
|
+
page_size = params[:page_size].to_i.positive? ? params[:page_size].to_i : 10
|
25
|
+
total_count = collection.count_documents({})
|
26
|
+
total_pages = (total_count / page_size.to_f).ceil
|
27
|
+
page_res = {
|
28
|
+
current_page: page,
|
29
|
+
page_size: page_size,
|
30
|
+
total_count: total_count,
|
31
|
+
total_pages: total_pages,
|
32
|
+
_links: {
|
33
|
+
self: { href: "/#{model}?page=#{page}&page_size=#{page_size}" }
|
34
|
+
}
|
35
|
+
}
|
36
|
+
page_res[:_links][:next] = { href: "/#{model}?page=#{page + 1}&page_size=#{page_size}" } if page < total_pages
|
37
|
+
page_res[:_links][:prev] = { href: "/#{model}?page=#{page - 1}&page_size=#{page_size}" } if page > 1
|
38
|
+
page_res
|
26
39
|
end
|
27
40
|
|
28
|
-
def find_all(model,
|
41
|
+
def find_all(model, params, paginate: true)
|
29
42
|
collection = MongoClient.client[model]
|
30
|
-
|
43
|
+
pagination = page_data(model, params, collection)
|
44
|
+
skip = (pagination[:current_page] - 1) * pagination[:page_size]
|
45
|
+
query = query_from_params params
|
46
|
+
|
47
|
+
items = get_items collection, query, skip, pagination[:page_size]
|
48
|
+
results = { _embedded: {} }
|
49
|
+
results[:_embedded][model.to_sym] = items
|
50
|
+
results[:pagination] = pagination if paginate
|
51
|
+
results
|
52
|
+
end
|
53
|
+
|
54
|
+
def get_items(collection, query, skip, page_size)
|
55
|
+
if query == {}
|
56
|
+
return collection.find.skip(skip).limit(page_size).collect do |match|
|
57
|
+
match.delete("_id")
|
58
|
+
match
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
collection.find(query).skip(skip).limit(page_size).collect do |match|
|
31
63
|
match.delete("_id")
|
32
64
|
match
|
33
65
|
end
|
34
66
|
end
|
35
67
|
|
68
|
+
def query_from_params(params)
|
69
|
+
query = params.dup
|
70
|
+
query.delete(:page)
|
71
|
+
query.delete(:page_size)
|
72
|
+
query
|
73
|
+
end
|
74
|
+
|
36
75
|
def update(model, id, data)
|
37
76
|
collection = MongoClient.client[model]
|
38
77
|
collection.update_one({ id: id }, { id: id, **data })
|
@@ -33,7 +33,7 @@ class RestServer < Sinatra::Base
|
|
33
33
|
end
|
34
34
|
|
35
35
|
SWAGGER_FILES = %w[index.css swagger.html swagger-initializer.js swagger-ui-bundle.js swagger-ui-standalone-preset.js
|
36
|
-
swagger-ui.css]
|
36
|
+
swagger-ui.css].freeze
|
37
37
|
|
38
38
|
SWAGGER_FILES.each do |filename|
|
39
39
|
get "/#{filename.gsub(".html", "")}" do
|
@@ -54,25 +54,18 @@ class RestServer < Sinatra::Base
|
|
54
54
|
SwaggerBuilder.build_swagger_for(Entities.models, request.host_with_port)
|
55
55
|
end
|
56
56
|
|
57
|
-
def has_id?(model, id)
|
58
|
-
Entities.models[model].key?(id)
|
59
|
-
end
|
60
|
-
|
61
|
-
def not_have(id)
|
62
|
-
[404, JSON.generate({ error: "'#{id}' not found" })]
|
63
|
-
end
|
64
|
-
|
65
57
|
get "/" do
|
66
58
|
summary = { models: Entities.models.keys.to_s,
|
67
59
|
docs_url: "<a href='#{ENV["base_path"]}/swagger'>Swagger docs</a>" }
|
68
|
-
summary[:db] = MongoClient.client.
|
69
|
-
|
60
|
+
summary[:db] = MongoClient.client.options if ENV["mongodb"]
|
61
|
+
summary.to_json
|
70
62
|
rescue StandardError => e
|
71
63
|
[500, e.message]
|
72
64
|
end
|
73
65
|
|
74
66
|
Entities.models.each_key do |model|
|
75
67
|
post "/#{model.downcase}" do
|
68
|
+
content_type :json
|
76
69
|
request_body = request.body.read
|
77
70
|
data = {}
|
78
71
|
data = JSON.parse(request_body) unless request_body.empty?
|
@@ -99,34 +92,28 @@ class RestServer < Sinatra::Base
|
|
99
92
|
end
|
100
93
|
|
101
94
|
get "/#{model.downcase}" do
|
95
|
+
content_type :json
|
102
96
|
if ENV["mongodb"]
|
103
|
-
|
104
|
-
JSON.generate(MongoClient.get_all(model))
|
105
|
-
else
|
106
|
-
[200, JSON.generate(MongoClient.find_all(model, params))]
|
107
|
-
end
|
97
|
+
MongoClient.find_all(model, params).to_json
|
108
98
|
else
|
109
|
-
|
110
|
-
|
111
|
-
matching_values = Entities.models[model].values.find_all do |val|
|
112
|
-
val[params.keys[0]].to_s == params.values[0]
|
113
|
-
end
|
114
|
-
return JSON.generate(matching_values)
|
99
|
+
matching_values = Entities.find_all(model, params)
|
100
|
+
return matching_values.to_json
|
115
101
|
end
|
116
102
|
rescue StandardError => e
|
117
103
|
[404, "Nothing found using #{params}. Only first param considered. #{e.message}"]
|
118
104
|
end
|
119
105
|
|
120
106
|
get "/#{model.downcase}/:id" do |id|
|
107
|
+
content_type :json
|
121
108
|
if ENV["mongodb"]
|
122
109
|
results = MongoClient.find(model, id)
|
123
110
|
return not_have(id) unless results.first
|
124
111
|
|
125
|
-
|
112
|
+
results.first.to_json
|
126
113
|
else
|
127
|
-
return not_have(id) unless
|
114
|
+
return not_have(id) unless id?(model, id)
|
128
115
|
|
129
|
-
|
116
|
+
Entities.models[model][id].to_json
|
130
117
|
end
|
131
118
|
end
|
132
119
|
|
@@ -138,6 +125,7 @@ class RestServer < Sinatra::Base
|
|
138
125
|
end
|
139
126
|
|
140
127
|
put "/#{model.downcase}/:id" do |id|
|
128
|
+
content_type :json
|
141
129
|
data = JSON.parse(request.body.read)
|
142
130
|
if ENV["mongodb"]
|
143
131
|
results = MongoClient.find(model, id)
|
@@ -145,7 +133,7 @@ class RestServer < Sinatra::Base
|
|
145
133
|
|
146
134
|
MongoClient.update(model, id, data)
|
147
135
|
else
|
148
|
-
return not_have(id) unless
|
136
|
+
return not_have(id) unless id?(model, id)
|
149
137
|
|
150
138
|
Entities.models[model][id] = data
|
151
139
|
end
|
@@ -153,13 +141,14 @@ class RestServer < Sinatra::Base
|
|
153
141
|
end
|
154
142
|
|
155
143
|
delete "/#{model.downcase}/:id" do |id|
|
144
|
+
content_type :json
|
156
145
|
if ENV["mongodb"]
|
157
146
|
results = MongoClient.find(model, id)
|
158
147
|
return not_have(id) unless results.first
|
159
148
|
|
160
149
|
MongoClient.delete(model, id)
|
161
150
|
else
|
162
|
-
return not_have(id) unless
|
151
|
+
return not_have(id) unless id?(model, id)
|
163
152
|
|
164
153
|
Entities.models[model].delete(id)
|
165
154
|
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# typed: true
|
4
|
+
|
5
|
+
def init_based_on_seed
|
6
|
+
return if ENV["mongodb"]
|
7
|
+
|
8
|
+
puts "Seeding db based on '#{SEED_FILE}'"
|
9
|
+
seed_data = JSON.parse(File.read(SEED_FILE))
|
10
|
+
seed_data.each do |entity, values|
|
11
|
+
Entities.models[entity.to_sym] = {} unless Entities.models[entity.to_sym]
|
12
|
+
seed_in_memory_data(entity, values)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def seed_in_memory_data(entity, values)
|
17
|
+
unless values.is_a? Array
|
18
|
+
SchemalessRestApi.logger.warning "Entity '#{entity}' doesn't have array, skipping"
|
19
|
+
return
|
20
|
+
end
|
21
|
+
|
22
|
+
values.each do |value|
|
23
|
+
next unless value["id"]
|
24
|
+
|
25
|
+
Entities.models[entity.to_sym][value["id"].to_s] = value
|
26
|
+
end
|
27
|
+
end
|
@@ -13,7 +13,15 @@ module ServerUtils
|
|
13
13
|
end
|
14
14
|
end
|
15
15
|
|
16
|
-
|
16
|
+
def id?(model, id)
|
17
|
+
Entities.models[model].key?(id)
|
18
|
+
end
|
19
|
+
|
20
|
+
def not_have(id)
|
21
|
+
[404, JSON.generate({ error: "'#{id}' not found" })]
|
22
|
+
end
|
23
|
+
|
24
|
+
def log_structured(messages)
|
17
25
|
log_msg = {
|
18
26
|
method: request.request_method,
|
19
27
|
path: request.fullpath,
|
@@ -24,4 +32,6 @@ module ServerUtils
|
|
24
32
|
end
|
25
33
|
SchemalessRestApi.logger.info(log_msg)
|
26
34
|
end
|
35
|
+
|
36
|
+
private :log_structured
|
27
37
|
end
|
@@ -6,8 +6,7 @@
|
|
6
6
|
<title>Swagger UI</title>
|
7
7
|
<link rel="stylesheet" type="text/css" href="./swagger-ui.css" />
|
8
8
|
<link rel="stylesheet" type="text/css" href="index.css" />
|
9
|
-
<link rel="icon" type="image/png" href="./favicon
|
10
|
-
<link rel="icon" type="image/png" href="./favicon-16x16.png" sizes="16x16" />
|
9
|
+
<link rel="icon" type="image/png" href="./favicon.ico" sizes="32x32" />
|
11
10
|
</head>
|
12
11
|
|
13
12
|
<body>
|
data/lib/schemaless_rest_api.rb
CHANGED
@@ -5,6 +5,7 @@
|
|
5
5
|
require_relative "schemaless_rest_api/version"
|
6
6
|
require_relative "schemaless_rest_api/entities"
|
7
7
|
require_relative "schemaless_rest_api/swagger_builder"
|
8
|
+
require_relative "schemaless_rest_api/seed"
|
8
9
|
require "tapioca"
|
9
10
|
require "logger"
|
10
11
|
require "ougai"
|
@@ -23,6 +24,9 @@ module SchemalessRestApi
|
|
23
24
|
@logger = Logger.new($stdout)
|
24
25
|
@log_type = :basic
|
25
26
|
end
|
27
|
+
LOGLEVELS = %w[DEBUG INFO WARN ERROR FATAL UNKNOWN].freeze
|
28
|
+
log_level ||= LOGLEVELS.index ENV.fetch("LOG_LEVEL", "INFO")
|
29
|
+
@logger.level = log_level
|
26
30
|
|
27
31
|
class << self
|
28
32
|
# @return Logger
|
@@ -53,22 +57,7 @@ end
|
|
53
57
|
|
54
58
|
SEED_FILE = "db.json"
|
55
59
|
|
56
|
-
if File.exist? SEED_FILE
|
57
|
-
puts "Seeding db based on '#{SEED_FILE}'"
|
58
|
-
seed_data = JSON.parse(File.read(SEED_FILE))
|
59
|
-
seed_data.each do |entity, values|
|
60
|
-
Entities.models[entity.to_sym] = {} unless Entities.models[entity.to_sym]
|
61
|
-
if values.is_a? Array
|
62
|
-
values.each do |value|
|
63
|
-
next unless value["id"]
|
64
|
-
|
65
|
-
Entities.models[entity.to_sym][value["id"].to_s] = value
|
66
|
-
end
|
67
|
-
else
|
68
|
-
puts "Entity 'entity' doesn't have array, skipping"
|
69
|
-
end
|
70
|
-
end
|
71
|
-
end
|
60
|
+
init_based_on_seed if File.exist? SEED_FILE
|
72
61
|
|
73
62
|
extract_models.each do |model|
|
74
63
|
Entities.models[model.to_sym] = {}
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: schemaless_rest_api
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.7.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Samuel Garratt
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2024-
|
11
|
+
date: 2024-07-18 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: mongo
|
@@ -138,6 +138,7 @@ files:
|
|
138
138
|
- lib/schemaless_rest_api/mongo_client.rb
|
139
139
|
- lib/schemaless_rest_api/rest.ico
|
140
140
|
- lib/schemaless_rest_api/rest_server.rb
|
141
|
+
- lib/schemaless_rest_api/seed.rb
|
141
142
|
- lib/schemaless_rest_api/server_utils.rb
|
142
143
|
- lib/schemaless_rest_api/swagger/index.css
|
143
144
|
- lib/schemaless_rest_api/swagger/swagger-initializer.js
|