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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 6f42e7bccaab28e6dc750c4351e18500689d1fb13a72bda6d0f1417cc5cef630
4
- data.tar.gz: d47a29830a56c986c04bdc92c0c07758efcbe197de71a0183552af628ee66dc0
3
+ metadata.gz: 9f0bc22701cb597ffc52c1e9544b6537546001748ce65e92456e5af32e99fbdd
4
+ data.tar.gz: c40d93389fbfe7f98fa7d05f262b2da7b806dced3223f13643122f0359c522b0
5
5
  SHA512:
6
- metadata.gz: 366cd2cf898fc3cca3ba5b90d29d44fa08e43b5a0a8154eec309c21d9a7012a8c2498912818f2251e664bf2568bfdbcead72ac59e6d837f6a844fd43b64b2a07
7
- data.tar.gz: 65cfacda5630298c9e50165b3a2a5420ecf6965347f32f92226afcb6137716207732377714d9b59d7bdda228e5aacc6f23adb39a97ea0be062bc269804b61568
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
- require "mongo"
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 get_all(model)
21
- collection = MongoClient.client[model]
22
- collection.find.collect do |match|
23
- match.delete("_id")
24
- match
25
- end
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, query)
41
+ def find_all(model, params, paginate: true)
29
42
  collection = MongoClient.client[model]
30
- collection.find(query).collect do |match|
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.summary.to_s if ENV["mongodb"]
69
- JSON.generate(summary)
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
- if params == {}
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
- return JSON.generate(Entities.models[model].values) if params == {}
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
- JSON.generate(results.first)
112
+ results.first.to_json
126
113
  else
127
- return not_have(id) unless has_id?(model, id)
114
+ return not_have(id) unless id?(model, id)
128
115
 
129
- JSON.generate(Entities.models[model][id])
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 has_id?(model, id)
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 has_id?(model, id)
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
- private def log_structured(messages)
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-32x32.png" sizes="32x32" />
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>
@@ -3,5 +3,5 @@
3
3
  # typed: true
4
4
 
5
5
  module SchemalessRestApi
6
- VERSION = "0.6.0"
6
+ VERSION = "0.7.0"
7
7
  end
@@ -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.6.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-06-24 00:00:00.000000000 Z
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