schemaless_rest_api 0.6.0 → 0.7.0

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