model_driven_api 3.5.4 → 3.5.5

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: 7b5dfed0ca77401f9f18a60d58a3477f3448dff4cf5d6267bb6adf4e3ef87325
4
- data.tar.gz: d1e83dcc5916b3d02b8e2df2300a11ffa77f37b0ebd3ee07651bf06a13df6c70
3
+ metadata.gz: 61a965405c27cbeed7e06d994c4cd97cd69ec81ba21e9eb1cd1f6feef3b32741
4
+ data.tar.gz: 414b957e13b3ccff5a2a3b999242ad3ae3a310f5c635f2edeab21264a2633cf2
5
5
  SHA512:
6
- metadata.gz: 148cfa51a0c173f52d7753e377b334b90fe1dabd115fc1c3c0c68fdc21911b901b85fb850c9dd68b11c9ec470b641b5d86e6f78f1c4c864ab0a919d12c547154
7
- data.tar.gz: ca5934d04808afbb4282adff3951d4b7cedac89f7d61b7371c6d69a1854621a5506b0440b371c78624a3bffcac2baebf93473c5dc505998a3607c095f2111011
6
+ metadata.gz: 269993f8be409f5f3fdea9509df7037cdbf6927d21efb28152c0a65e8e6c10dc549ed784490c39d0b758caa3140052e89499675aa3f55e391982d5887c5f97ad
7
+ data.tar.gz: d05fef4b0562fc228a3c5ff12ecb82f7a283fd5a5cbefe16a5482f8783fb9d03edac64689452f5b53c253b0432b02febcc233bf777faab4445fafae06863c1ee
@@ -3,7 +3,7 @@ class Api::V2::InfoController < Api::V2::ApplicationController
3
3
  # Info uses a different auth method: username and password
4
4
  skip_before_action :authenticate_request, only: [:version, :swagger, :openapi], raise: false
5
5
  skip_before_action :extract_model, except: [:heartbeat]
6
-
6
+
7
7
  # api :GET, '/api/v2/info/version', "Just prints the APPVERSION."
8
8
  def version
9
9
  render json: { version: "TODO: Find a Way to Dynamically Obtain It" }.to_json, status: 200
@@ -15,7 +15,6 @@ class Api::V2::InfoController < Api::V2::ApplicationController
15
15
  render json: ::Role.all.to_json, status: 200
16
16
  end
17
17
 
18
-
19
18
  # api :GET, '/api/v2/info/heartbeat'
20
19
  # Just keeps the session alive by returning a new token
21
20
  def heartbeat
@@ -39,20 +38,20 @@ class Api::V2::InfoController < Api::V2::ApplicationController
39
38
  if can? :read, d
40
39
  model = d.to_s.underscore.tableize
41
40
  pivot[model] ||= {}
42
- d.columns_hash.each_pair do |key, val|
41
+ d.columns_hash.each_pair do |key, val|
43
42
  pivot[model][key] = val.type unless key.ends_with? "_id"
44
43
  end
45
44
  # Only application record descendants to have a clean schema
46
45
  pivot[model][:associations] ||= {
47
- has_many: d.reflect_on_all_associations(:has_many).map { |a|
46
+ has_many: d.reflect_on_all_associations(:has_many).map { |a|
48
47
  a.name if (((a.options[:class_name].presence || a.name).to_s.classify.constantize.new.is_a? ApplicationRecord) rescue false)
49
48
  }.compact,
50
49
  has_one: d.reflect_on_all_associations(:has_one).map { |a|
51
50
  a.name if (((a.options[:class_name].presence || a.name).to_s.classify.constantize.new.is_a? ApplicationRecord) rescue false)
52
51
  }.compact,
53
- belongs_to: d.reflect_on_all_associations(:belongs_to).map { |a|
52
+ belongs_to: d.reflect_on_all_associations(:belongs_to).map { |a|
54
53
  a.name if (((a.options[:class_name].presence || a.name).to_s.classify.constantize.new.is_a? ApplicationRecord) rescue false)
55
- }.compact
54
+ }.compact,
56
55
  }
57
56
  pivot[model][:methods] ||= (d.instance_methods(false).include?(:json_attrs) && !d.json_attrs.blank?) ? d.json_attrs[:methods] : nil
58
57
  end
@@ -70,12 +69,12 @@ class Api::V2::InfoController < Api::V2::ApplicationController
70
69
  method_class = instance.send(key).class.to_s
71
70
  Rails.logger.debug "compute_type #{model} #{key} #{method_class}"
72
71
  method_key = model.columns_hash[key]
73
-
72
+
74
73
  # Not columns
75
74
  return nil if method_key.nil?
76
75
  return "object" if method_class == "ActiveStorage::Attached::One"
77
76
  return "array" if method_class == "ActiveStorage::Attached::Many" || method_class == "Array" || method_class.ends_with?("Array") || method_class.ends_with?("Collection") || method_class.ends_with?("Relation") || method_class.ends_with?("Set") || method_class.ends_with?("List") || method_class.ends_with?("Queue") || method_class.ends_with?("Stack") || method_class.ends_with?("ActiveRecord_Associations_CollectionProxy")
78
-
77
+
79
78
  # Columns
80
79
  case method_key.type
81
80
  when :json, :jsonb
@@ -106,9 +105,9 @@ class Api::V2::InfoController < Api::V2::ApplicationController
106
105
  parsed_json = JSON.parse(model.new.to_json(dsl))
107
106
  parsed_json.keys.map do |k|
108
107
  type = compute_type(model, k)
109
-
108
+
110
109
  # Remove fields that cannot be created or updated
111
- if remove_reserved && %w( id created_at updated_at lock_version).include?(k.to_s)
110
+ if remove_reserved && %w( id created_at updated_at lock_version ).include?(k.to_s)
112
111
  nil
113
112
  elsif type == "method" && (parsed_json[k].is_a?(FalseClass) || parsed_json[k].is_a?(TrueClass))
114
113
  [k, { "type": "boolean" }]
@@ -138,7 +137,7 @@ class Api::V2::InfoController < Api::V2::ApplicationController
138
137
  end unless type.blank?
139
138
  end.compact.to_h
140
139
  end
141
-
140
+
142
141
  def generate_paths
143
142
  pivot = {
144
143
  "/authenticate": {
@@ -147,7 +146,7 @@ class Api::V2::InfoController < Api::V2::ApplicationController
147
146
  "tags": ["Authentication"],
148
147
  "description": "Authenticate the user and return a JWT token in the header and the current user as body.",
149
148
  "security": [
150
- "basicAuth": []
149
+ "basicAuth": [],
151
150
  ],
152
151
  "requestBody": {
153
152
  "content": {
@@ -160,19 +159,19 @@ class Api::V2::InfoController < Api::V2::ApplicationController
160
159
  "properties": {
161
160
  "email": {
162
161
  "type": "string",
163
- "format": "email"
162
+ "format": "email",
164
163
  },
165
164
  "password": {
166
165
  "type": "string",
167
- "format": "password"
168
- }
169
- }
170
- }
166
+ "format": "password",
167
+ },
168
+ },
169
+ },
171
170
  },
172
- "required": ["email", "password"]
173
- }
174
- }
175
- }
171
+ "required": ["email", "password"],
172
+ },
173
+ },
174
+ },
176
175
  },
177
176
  "responses": {
178
177
  "200": {
@@ -181,25 +180,25 @@ class Api::V2::InfoController < Api::V2::ApplicationController
181
180
  "token": {
182
181
  "description": "JWT",
183
182
  "schema": {
184
- "type": "string"
185
- }
186
- }
183
+ "type": "string",
184
+ },
185
+ },
187
186
  },
188
187
  "content": {
189
188
  "application/json": {
190
189
  "schema": {
191
190
  "type": "object",
192
191
  # ["id", "email", "created_at", "admin", "locked", "supplier_id", "location_id", "roles"]
193
- "properties": create_properties_from_model(User, User.json_attrs)
194
- }
195
- }
196
- }
192
+ "properties": create_properties_from_model(User, User.json_attrs),
193
+ },
194
+ },
195
+ },
197
196
  },
198
197
  "401": {
199
- "description": "Unauthorized"
200
- }
201
- }
202
- }
198
+ "description": "Unauthorized",
199
+ },
200
+ },
201
+ },
203
202
  },
204
203
  "/raw/sql": {
205
204
  "post": {
@@ -207,7 +206,7 @@ class Api::V2::InfoController < Api::V2::ApplicationController
207
206
  "description": "Executes a SQL query on the underlying PostgreSQL database, the query must return the JSON in a **result** key (please note in the examples the _SELECT json_agg(u) AS result_ or in the more complex one the _SELECT jsonb_agg(pick_data) AS result_, they always use the **result** return object).\n \nDesigned for SELECT queries that use the json_agg function to aggregate results into JSON arrays, which must return the JSON in a **result** key.\n \nOther query types are not recommended and may be restricted for security and performance reasons.\n \nOnly SELECT statements are allowed. DDL and DML statements (INSERT, UPDATE, DELETE) are forbidden.\n \nQueries can be as simple as:\n \n```sql\nSELECT json_agg(u) AS result\nFROM users u\nWHERE u.active = true;\n```\n \nor more complex, using joins, subqueries, CTEs, and other SQL features. like:\n \n```sql\nWITH pick_data AS (\n SELECT p.id,\n p.project_id,\n p.quantity,\n p.created_at,\n p.updated_at,\n p.notes,\n p.document_id,\n p.external_code,\n p.reference_project_id,\n p.reference_row,\n p.closed,\n p.parent_reference_row,\n p.packages,\n p.weight,\n p.dispatched_quantity,\n p.override_item_reference,\n p.override_item_description,\n p.override_item_measure_unit,\n p.lock_version,\n p.user_id,\n COALESCE(SUM(pr.quantity), 0) AS quantity_detected,\n COALESCE(p.quantity, 0) - COALESCE(SUM(pr.quantity), 0) AS quantity_remaining,\n json_agg(\n jsonb_build_object(\n 'id',\n pr.id,\n 'item_id',\n pr.item_id,\n 'location_id',\n pr.location_id,\n 'quantity',\n pr.quantity\n )\n ) AS project_rows,\n jsonb_build_object(\n 'id',\n l.id,\n 'name',\n l.name,\n 'description',\n l.description\n ) AS location,\n jsonb_build_object(\n 'id',\n i.id,\n 'code',\n i.code,\n 'created_at',\n i.created_at,\n 'updated_at',\n i.updated_at,\n 'description',\n i.description,\n 'has_serials',\n i.has_serials,\n 'external_code',\n i.external_code,\n 'barcode',\n i.barcode,\n 'weight',\n i.weight,\n 'quantity',\n i.quantity,\n 'package_quantity',\n i.package_quantity,\n 'locked_quantity',\n i.locked_quantity,\n 'disabled',\n i.disabled,\n 'measure_unit',\n jsonb_build_object('id', mu.id, 'name', mu.name),\n 'location',\n jsonb_build_object('id', il.id, 'name', il.name),\n 'locations',\n (\n SELECT jsonb_agg(\n jsonb_build_object('id', loc.id, 'name', loc.name)\n )\n FROM locations loc\n JOIN item_locations il ON il.location_id = loc.id\n WHERE il.item_id = i.id\n ),\n 'additional_barcodes',\n (\n SELECT jsonb_agg(\n jsonb_build_object('id', ab.id, 'code', ab.code)\n )\n FROM additional_barcodes ab\n WHERE ab.item_id = i.id\n )\n ) AS item\n FROM picks p\n LEFT JOIN project_rows pr ON pr.pick_id = p.id\n LEFT JOIN locations l ON l.id = p.location_id\n LEFT JOIN items i ON i.id = p.item_id\n LEFT JOIN measure_units mu ON mu.id = i.measure_unit_id\n LEFT JOIN locations il ON il.id = i.location_id\n WHERE p.project_id = 16130\n GROUP BY p.id,\n l.id,\n i.id,\n mu.id,\n il.id\n)\nSELECT jsonb_agg(pick_data) AS result\nFROM pick_data;\n```\n \nLet's break down the provided SQL query and understand why it uses a Common Table Expression (CTE) and how it can improve performance.\n \n### Explanation of the complex Query\n \nThe provided query uses a CTE named `pick_data` to gather and aggregate data from multiple tables (`picks`, `project_rows`, `locations`, `items`, `measure_units`, `item_locations`, and `additional_barcodes`). The final result is a JSON array of aggregated data.\n \n#### Key Components of the Query:\n \n1. **CTE Definition**:\n \n ```sql\n WITH pick_data AS (\n -- Subquery content\n )\n ```\n \n The CTE `pick_data` is defined to encapsulate the logic of the subquery. This makes the query more readable and modular.\n \n2. **Data Selection and Aggregation**:\n Inside the CTE, data is selected and aggregated from various tables. Key operations include:\n \n - **Column Selection**: Selecting specific columns from the `picks` table.\n - **Aggregation**: Using `COALESCE` and `SUM` to calculate `quantity_detected` and `quantity_remaining`.\n - **JSON Aggregation**: Using `json_agg` and `jsonb_build_object` to create JSON objects and arrays for nested data structures.\n \n3. **Final Selection**:\n ```sql\n SELECT jsonb_agg(pick_data) AS result FROM pick_data;\n ```\n The final selection aggregates all rows from the CTE `pick_data` into a single JSON array.\n \n### Why Use a CTE?\n \n1. **Readability and Maintainability**:\n \n - **Modular Code**: By using a CTE, the complex logic is encapsulated in a named subquery, making the main query easier to read and understand.\n - **Reusability**: The CTE can be reused within the same query if needed, avoiding duplication of code.\n \n2. **Performance**:\n - **Optimization**: Modern SQL engines can optimize CTEs effectively. They can be materialized (computed once and stored) or inlined (expanded in the main query) based on the query plan.\n - **Intermediate Results**: CTEs allow breaking down complex queries into simpler steps, which can sometimes help the SQL engine optimize each step more effectively.\n \n### Documentation for Editing Generic Queries\n \nWhen editing or creating new queries, consider the following steps and best practices:\n \n1. **Identify the Purpose**:\n \n - Clearly define what the query needs to achieve. Understand the data relationships and the final output format.\n \n2. **Use CTEs for Complex Logic**:\n \n - Break down complex queries into smaller, manageable parts using CTEs. This improves readability and maintainability.\n \n3. **Optimize Aggregations and Joins**:\n \n - Ensure that aggregations and joins are optimized. Use indexes where appropriate and avoid unnecessary computations.\n \n4. **Leverage JSON Functions**:\n \n - Use JSON functions (`json_agg`, `jsonb_build_object`, etc.) to handle nested data structures effectively.\n \n5. **Test and Validate**:\n - Test the query with different datasets to ensure it performs well and returns the correct results. Validate the output format.\n \n### Example of a Generic Query Using CTE\n \nHere's a generic example to illustrate how to use a CTE in a query:\n \n```sql\n \n\nWITH data_aggregation AS (\n SELECT\n t1.id,\n t1.name,\n SUM(t2.value) AS total_value,\n json_agg(\n jsonb_build_object(\n 'id', t2.id,\n 'value', t2.value\n )\n ) AS details\n FROM table1 t1\n LEFT JOIN table2 t2 ON t2.table1_id = t1.id\n GROUP BY t1.id\n)\nSELECT jsonb_agg(data_aggregation) AS result FROM data_aggregation;\n```\n \n### Conclusion\n \nUsing CTEs in SQL queries helps in organizing complex logic, improving readability, and potentially enhancing performance. When editing or creating new queries, follow best practices such as breaking down complex logic, optimizing joins and aggregations, and leveraging JSON functions for nested data structures.\n",
208
207
  "tags": ["Raw"],
209
208
  "security": [
210
- "bearerAuth": []
209
+ "bearerAuth": [],
211
210
  ],
212
211
  "responses": {
213
212
  "200": {
@@ -220,13 +219,13 @@ class Api::V2::InfoController < Api::V2::ApplicationController
220
219
  "type": "object",
221
220
  "properties": {
222
221
  "json_agg": {
223
- "type": "string"
224
- }
225
- }
226
- }
227
- }
228
- }
229
- }
222
+ "type": "string",
223
+ },
224
+ },
225
+ },
226
+ },
227
+ },
228
+ },
230
229
  },
231
230
  "400": {
232
231
  "description": "SQL query must return a key called result otherwise cannot be parsed",
@@ -236,13 +235,13 @@ class Api::V2::InfoController < Api::V2::ApplicationController
236
235
  "type": "object",
237
236
  "properties": {
238
237
  "error": {
239
- "type": "string"
240
- }
241
- }
242
- }
243
- }
244
- }
245
- }
238
+ "type": "string",
239
+ },
240
+ },
241
+ },
242
+ },
243
+ },
244
+ },
246
245
 
247
246
  },
248
247
  "requestBody": {
@@ -253,14 +252,14 @@ class Api::V2::InfoController < Api::V2::ApplicationController
253
252
  "properties": {
254
253
  "query": {
255
254
  "type": "string",
256
- "example": "SELECT json_agg(u) FROM users u WHERE u.active = true;"
257
- }
258
- }
259
- }
260
- }
261
- }
262
- }
263
- }
255
+ "example": "SELECT json_agg(u) FROM users u WHERE u.active = true;",
256
+ },
257
+ },
258
+ },
259
+ },
260
+ },
261
+ },
262
+ },
264
263
  },
265
264
  "/info/version": {
266
265
  "get": {
@@ -273,13 +272,13 @@ class Api::V2::InfoController < Api::V2::ApplicationController
273
272
  "content": {
274
273
  "application/json": {
275
274
  "schema": {
276
- "type": "string"
277
- }
278
- }
279
- }
280
- }
281
- }
282
- }
275
+ "type": "string",
276
+ },
277
+ },
278
+ },
279
+ },
280
+ },
281
+ },
283
282
  },
284
283
  "/info/heartbeat": {
285
284
  "get": {
@@ -287,7 +286,7 @@ class Api::V2::InfoController < Api::V2::ApplicationController
287
286
  "description": "Just keeps the session alive by returning a new token",
288
287
  "tags": ["Info"],
289
288
  "security": [
290
- "bearerAuth": []
289
+ "bearerAuth": [],
291
290
  ],
292
291
  "responses": {
293
292
  "200": {
@@ -296,13 +295,13 @@ class Api::V2::InfoController < Api::V2::ApplicationController
296
295
  "token": {
297
296
  "description": "JWT",
298
297
  "schema": {
299
- "type": "string"
300
- }
301
- }
302
- }
303
- }
304
- }
305
- }
298
+ "type": "string",
299
+ },
300
+ },
301
+ },
302
+ },
303
+ },
304
+ },
306
305
  },
307
306
  "/info/roles": {
308
307
  "get": {
@@ -310,7 +309,7 @@ class Api::V2::InfoController < Api::V2::ApplicationController
310
309
  "description": "Returns the roles list",
311
310
  "tags": ["Info"],
312
311
  "security": [
313
- "bearerAuth": []
312
+ "bearerAuth": [],
314
313
  ],
315
314
  "responses": {
316
315
  "200": {
@@ -323,22 +322,22 @@ class Api::V2::InfoController < Api::V2::ApplicationController
323
322
  "type": "object",
324
323
  "properties": {
325
324
  "id": {
326
- "type": "integer"
325
+ "type": "integer",
327
326
  },
328
327
  "name": {
329
- "type": "string"
328
+ "type": "string",
330
329
  },
331
330
  "description": {
332
- "type": "string"
333
- }
334
- }
335
- }
336
- }
337
- }
338
- }
339
- }
340
- }
341
- }
331
+ "type": "string",
332
+ },
333
+ },
334
+ },
335
+ },
336
+ },
337
+ },
338
+ },
339
+ },
340
+ },
342
341
  },
343
342
  "/info/schema": {
344
343
  "get": {
@@ -346,7 +345,7 @@ class Api::V2::InfoController < Api::V2::ApplicationController
346
345
  "description": "Returns the schema of the models",
347
346
  "tags": ["Info"],
348
347
  "security": [
349
- "bearerAuth": []
348
+ "bearerAuth": [],
350
349
  ],
351
350
  "responses": {
352
351
  "200": {
@@ -359,24 +358,24 @@ class Api::V2::InfoController < Api::V2::ApplicationController
359
358
  "type": "object",
360
359
  "properties": {
361
360
  "id": {
362
- "type": "integer"
361
+ "type": "integer",
363
362
  },
364
363
  "created_at": {
365
364
  "type": "string",
366
- "format": "date-time"
365
+ "format": "date-time",
367
366
  },
368
367
  "updated_at": {
369
368
  "type": "string",
370
- "format": "date-time"
369
+ "format": "date-time",
371
370
  },
372
- }
373
- }
374
- }
375
- }
376
- }
377
- }
378
- }
379
- }
371
+ },
372
+ },
373
+ },
374
+ },
375
+ },
376
+ },
377
+ },
378
+ },
380
379
  },
381
380
  "/info/dsl": {
382
381
  "get": {
@@ -384,7 +383,7 @@ class Api::V2::InfoController < Api::V2::ApplicationController
384
383
  "description": "Returns the DSL of the models",
385
384
  "tags": ["Info"],
386
385
  "security": [
387
- "bearerAuth": []
386
+ "bearerAuth": [],
388
387
  ],
389
388
  "responses": {
390
389
  "200": {
@@ -395,23 +394,23 @@ class Api::V2::InfoController < Api::V2::ApplicationController
395
394
  "type": "object",
396
395
  "properties": {
397
396
  "id": {
398
- "type": "integer"
397
+ "type": "integer",
399
398
  },
400
399
  "created_at": {
401
400
  "type": "string",
402
- "format": "date-time"
401
+ "format": "date-time",
403
402
  },
404
403
  "updated_at": {
405
404
  "type": "string",
406
- "format": "date-time"
407
- }
408
- }
409
- }
410
- }
411
- }
412
- }
413
- }
414
- }
405
+ "format": "date-time",
406
+ },
407
+ },
408
+ },
409
+ },
410
+ },
411
+ },
412
+ },
413
+ },
415
414
  },
416
415
  "/info/translations": {
417
416
  "get": {
@@ -419,7 +418,7 @@ class Api::V2::InfoController < Api::V2::ApplicationController
419
418
  "description": "Returns the translations of the entire App",
420
419
  "tags": ["Info"],
421
420
  "security": [
422
- "bearerAuth": []
421
+ "bearerAuth": [],
423
422
  ],
424
423
  "responses": {
425
424
  "200": {
@@ -430,18 +429,18 @@ class Api::V2::InfoController < Api::V2::ApplicationController
430
429
  "type": "object",
431
430
  "properties": {
432
431
  "key": {
433
- "type": "string"
432
+ "type": "string",
434
433
  },
435
434
  "value": {
436
- "type": "string"
437
- }
438
- }
439
- }
440
- }
441
- }
442
- }
443
- }
444
- }
435
+ "type": "string",
436
+ },
437
+ },
438
+ },
439
+ },
440
+ },
441
+ },
442
+ },
443
+ },
445
444
  },
446
445
  "/info/settings": {
447
446
  "get": {
@@ -449,7 +448,7 @@ class Api::V2::InfoController < Api::V2::ApplicationController
449
448
  "description": "Returns the settings of the App",
450
449
  "tags": ["Info"],
451
450
  "security": [
452
- "bearerAuth": []
451
+ "bearerAuth": [],
453
452
  ],
454
453
  "responses": {
455
454
  "200": {
@@ -463,20 +462,20 @@ class Api::V2::InfoController < Api::V2::ApplicationController
463
462
  "type": "object",
464
463
  "properties": {
465
464
  "key": {
466
- "type": "string"
465
+ "type": "string",
467
466
  },
468
467
  "value": {
469
- "type": "string"
470
- }
471
- }
472
- }
473
- }
474
- }
475
- }
476
- }
477
- }
478
- }
479
- }
468
+ "type": "string",
469
+ },
470
+ },
471
+ },
472
+ },
473
+ },
474
+ },
475
+ },
476
+ },
477
+ },
478
+ },
480
479
  },
481
480
  "/info/swagger": {
482
481
  "get": {
@@ -492,24 +491,24 @@ class Api::V2::InfoController < Api::V2::ApplicationController
492
491
  "type": "object",
493
492
  "properties": {
494
493
  "id": {
495
- "type": "integer"
494
+ "type": "integer",
496
495
  },
497
496
  "created_at": {
498
497
  "type": "string",
499
- "format": "date-time"
498
+ "format": "date-time",
500
499
  },
501
500
  "updated_at": {
502
501
  "type": "string",
503
- "format": "date-time"
504
- }
505
- }
506
- }
507
- }
508
- }
509
- }
510
- }
511
- }
512
- }
502
+ "format": "date-time",
503
+ },
504
+ },
505
+ },
506
+ },
507
+ },
508
+ },
509
+ },
510
+ },
511
+ },
513
512
  }
514
513
  ApplicationRecord.subclasses.sort_by { |d| d.to_s }.each do |d|
515
514
  # Only if current user can read the model
@@ -522,7 +521,7 @@ class Api::V2::InfoController < Api::V2::ApplicationController
522
521
  "description": "Returns the list of #{model}",
523
522
  "tags": [model.classify],
524
523
  "security": [
525
- "bearerAuth": []
524
+ "bearerAuth": [],
526
525
  ],
527
526
  "responses": {
528
527
  "200": {
@@ -533,23 +532,23 @@ class Api::V2::InfoController < Api::V2::ApplicationController
533
532
  "type": "array",
534
533
  "items": {
535
534
  "type": "object",
536
- "properties": create_properties_from_model(d, (d.json_attrs rescue {}))
537
- }
538
- }
539
- }
540
- }
535
+ "properties": create_properties_from_model(d, (d.json_attrs rescue {})),
536
+ },
537
+ },
538
+ },
539
+ },
541
540
  },
542
541
  "404": {
543
- "description": "No #{model} found"
544
- }
545
- }
542
+ "description": "No #{model} found",
543
+ },
544
+ },
546
545
  },
547
546
  "post": {
548
547
  "summary": "Create",
549
548
  "description": "Creates a new #{model}",
550
549
  "tags": [model.classify],
551
550
  "security": [
552
- "bearerAuth": []
551
+ "bearerAuth": [],
553
552
  ],
554
553
  "requestBody": {
555
554
  "content": {
@@ -559,12 +558,12 @@ class Api::V2::InfoController < Api::V2::ApplicationController
559
558
  "properties": {
560
559
  "#{model.singularize}": {
561
560
  "type": "object",
562
- "properties": create_properties_from_model(d, {}, true)
563
- }
564
- }
565
- }
566
- }
567
- }
561
+ "properties": create_properties_from_model(d, {}, true),
562
+ },
563
+ },
564
+ },
565
+ },
566
+ },
568
567
  },
569
568
  "responses": {
570
569
  "200": {
@@ -573,23 +572,23 @@ class Api::V2::InfoController < Api::V2::ApplicationController
573
572
  "application/json": {
574
573
  "schema": {
575
574
  "type": "object",
576
- "properties": create_properties_from_model(d, (d.json_attrs rescue {}))
577
- }
578
- }
579
- }
580
- }
581
- }
582
- }
575
+ "properties": create_properties_from_model(d, (d.json_attrs rescue {})),
576
+ },
577
+ },
578
+ },
579
+ },
580
+ },
581
+ },
583
582
  }
584
583
  # Non CRUD or Search, but custom, usually bulk operations endpoints
585
584
  new_custom_actions = ("Endpoints::#{d.model_name.name}".constantize.instance_methods(false) rescue [])
586
585
  Rails.logger.debug "New Custom Actions (#{d.model_name.name}): #{new_custom_actions}"
587
586
  new_custom_actions.each do |action|
588
587
  openapi_definition = "Endpoints::#{d.model_name.name}".constantize.definitions[d.model_name.name][action.to_sym] rescue []
589
-
588
+
590
589
  # Add the tag to the openapi definition
591
590
  openapi_definition.each do |k, v|
592
- v[:tags] = [ d.model_name.name ]
591
+ v[:tags] = [d.model_name.name]
593
592
  end
594
593
 
595
594
  pivot["/#{model}/custom_action/#{action}"] = openapi_definition if openapi_definition
@@ -598,10 +597,10 @@ class Api::V2::InfoController < Api::V2::ApplicationController
598
597
  # Complex queries are made using ranskac search via a post endpoint
599
598
  "post": {
600
599
  "summary": "Search",
601
- "description": "Searches the #{model} using complex queries. Please refer to the [documentation](https://activerecord-hackery.github.io/ransack/) for the query syntax. In this swagger are presented only some examples, please refer to the complete documentation for more complex queries.\nThe primary method of searching in Ransack is by using what is known as predicates.\nPredicates are used within Ransack search queries to determine what information to match. For instance, the cont predicate will check to see if an attribute called 'name' or 'description' contains a value using a wildcard query.",
600
+ "description": "Searches the #{model} using complex queries. Please refer to the [documentation](https://activerecord-hackery.github.io/ransack/) for the query syntax and to the general description of this swagger document to discover the usage of other, non ransack predicates for example to count records, select only some fields and more.",
602
601
  "tags": [model.classify],
603
602
  "security": [
604
- "bearerAuth": []
603
+ "bearerAuth": [],
605
604
  ],
606
605
  "requestBody": {
607
606
  "content": {
@@ -613,17 +612,17 @@ class Api::V2::InfoController < Api::V2::ApplicationController
613
612
  "type": "object",
614
613
  "properties": {
615
614
  "name_or_description_cont": {
616
- "type": "string"
615
+ "type": "string",
617
616
  },
618
617
  "first_name_eq": {
619
- "type": "string"
620
- }
621
- }
622
- }
623
- }
624
- }
625
- }
626
- }
618
+ "type": "string",
619
+ },
620
+ },
621
+ },
622
+ },
623
+ },
624
+ },
625
+ },
627
626
  },
628
627
  "responses": {
629
628
  "200": {
@@ -634,17 +633,17 @@ class Api::V2::InfoController < Api::V2::ApplicationController
634
633
  "type": "array",
635
634
  "items": {
636
635
  "type": "object",
637
- "properties": create_properties_from_model(d, (d.json_attrs rescue {}))
638
- }
639
- }
640
- }
641
- }
636
+ "properties": create_properties_from_model(d, (d.json_attrs rescue {})),
637
+ },
638
+ },
639
+ },
640
+ },
642
641
  },
643
642
  "404": {
644
- "description": "No #{model} found"
645
- }
646
- }
647
- }
643
+ "description": "No #{model} found",
644
+ },
645
+ },
646
+ },
648
647
  }
649
648
  pivot["/#{model}/{id}"] = {
650
649
  "put": {
@@ -656,13 +655,13 @@ class Api::V2::InfoController < Api::V2::ApplicationController
656
655
  "in": "path",
657
656
  "required": true,
658
657
  "schema": {
659
- "type": "integer"
660
- }
661
- }
658
+ "type": "integer",
659
+ },
660
+ },
662
661
  ],
663
662
  "tags": [model.classify],
664
663
  "security": [
665
- "bearerAuth": []
664
+ "bearerAuth": [],
666
665
  ],
667
666
  "requestBody": {
668
667
  "content": {
@@ -672,12 +671,12 @@ class Api::V2::InfoController < Api::V2::ApplicationController
672
671
  "properties": {
673
672
  "#{model.singularize}": {
674
673
  "type": "object",
675
- "properties": create_properties_from_model(d, {}, true)
676
- }
677
- }
678
- }
679
- }
680
- }
674
+ "properties": create_properties_from_model(d, {}, true),
675
+ },
676
+ },
677
+ },
678
+ },
679
+ },
681
680
  },
682
681
  "responses": {
683
682
  "200": {
@@ -686,15 +685,15 @@ class Api::V2::InfoController < Api::V2::ApplicationController
686
685
  "application/json": {
687
686
  "schema": {
688
687
  "type": "object",
689
- "properties": create_properties_from_model(d, (d.json_attrs rescue {}))
690
- }
691
- }
692
- }
688
+ "properties": create_properties_from_model(d, (d.json_attrs rescue {})),
689
+ },
690
+ },
691
+ },
693
692
  },
694
693
  "404": {
695
- "description": "No #{model} found"
696
- }
697
- }
694
+ "description": "No #{model} found",
695
+ },
696
+ },
698
697
  },
699
698
  "patch": {
700
699
  "summary": "Patch",
@@ -705,13 +704,13 @@ class Api::V2::InfoController < Api::V2::ApplicationController
705
704
  "in": "path",
706
705
  "required": true,
707
706
  "schema": {
708
- "type": "integer"
709
- }
710
- }
707
+ "type": "integer",
708
+ },
709
+ },
711
710
  ],
712
711
  "tags": [model.classify],
713
712
  "security": [
714
- "bearerAuth": []
713
+ "bearerAuth": [],
715
714
  ],
716
715
  "requestBody": {
717
716
  "content": {
@@ -721,12 +720,12 @@ class Api::V2::InfoController < Api::V2::ApplicationController
721
720
  "properties": {
722
721
  "#{model.singularize}": {
723
722
  "type": "object",
724
- "properties": create_properties_from_model(d, {}, true)
725
- }
726
- }
727
- }
728
- }
729
- }
723
+ "properties": create_properties_from_model(d, {}, true),
724
+ },
725
+ },
726
+ },
727
+ },
728
+ },
730
729
  },
731
730
  "responses": {
732
731
  "200": {
@@ -735,15 +734,15 @@ class Api::V2::InfoController < Api::V2::ApplicationController
735
734
  "application/json": {
736
735
  "schema": {
737
736
  "type": "object",
738
- "properties": create_properties_from_model(d, (d.json_attrs rescue {}))
739
- }
740
- }
741
- }
737
+ "properties": create_properties_from_model(d, (d.json_attrs rescue {})),
738
+ },
739
+ },
740
+ },
742
741
  },
743
742
  "404": {
744
- "description": "No #{model} found"
745
- }
746
- }
743
+ "description": "No #{model} found",
744
+ },
745
+ },
747
746
  },
748
747
  "delete": {
749
748
  "summary": "Delete",
@@ -754,22 +753,22 @@ class Api::V2::InfoController < Api::V2::ApplicationController
754
753
  "in": "path",
755
754
  "required": true,
756
755
  "schema": {
757
- "type": "integer"
758
- }
759
- }
756
+ "type": "integer",
757
+ },
758
+ },
760
759
  ],
761
760
  "tags": [model.classify],
762
761
  "security": [
763
- "bearerAuth": []
762
+ "bearerAuth": [],
764
763
  ],
765
764
  "responses": {
766
765
  "200": {
767
- "description": "#{model} Deleted"
766
+ "description": "#{model} Deleted",
768
767
  },
769
768
  "404": {
770
- "description": "No #{model} found"
771
- }
772
- }
769
+ "description": "No #{model} found",
770
+ },
771
+ },
773
772
  },
774
773
  "get": {
775
774
  "summary": "Show",
@@ -780,13 +779,13 @@ class Api::V2::InfoController < Api::V2::ApplicationController
780
779
  "in": "path",
781
780
  "required": true,
782
781
  "schema": {
783
- "type": "integer"
784
- }
785
- }
782
+ "type": "integer",
783
+ },
784
+ },
786
785
  ],
787
786
  "tags": [model.classify],
788
787
  "security": [
789
- "bearerAuth": []
788
+ "bearerAuth": [],
790
789
  ],
791
790
  "responses": {
792
791
  "200": {
@@ -795,26 +794,26 @@ class Api::V2::InfoController < Api::V2::ApplicationController
795
794
  "application/json": {
796
795
  "schema": {
797
796
  "type": "object",
798
- "properties": create_properties_from_model(d, (d.json_attrs rescue {}))
799
- }
800
- }
801
- }
797
+ "properties": create_properties_from_model(d, (d.json_attrs rescue {})),
798
+ },
799
+ },
800
+ },
802
801
  },
803
802
  "404": {
804
- "description": "No #{model} found"
805
- }
806
- }
807
- }
803
+ "description": "No #{model} found",
804
+ },
805
+ },
806
+ },
808
807
  }
809
- # d.columns_hash.each_pair do |key, val|
808
+ # d.columns_hash.each_pair do |key, val|
810
809
  # pivot[model][key] = val.type unless key.ends_with? "_id"
811
810
  # end
812
811
  # # Only application record descendants in order to have a clean schema
813
812
  # pivot[model][:associations] ||= {
814
- # has_many: d.reflect_on_all_associations(:has_many).map { |a|
813
+ # has_many: d.reflect_on_all_associations(:has_many).map { |a|
815
814
  # a.name if (((a.options[:class_name].presence || a.name).to_s.classify.constantize.new.is_a? ApplicationRecord) rescue false)
816
- # }.compact,
817
- # belongs_to: d.reflect_on_all_associations(:belongs_to).map { |a|
815
+ # }.compact,
816
+ # belongs_to: d.reflect_on_all_associations(:belongs_to).map { |a|
818
817
  # a.name if (((a.options[:class_name].presence || a.name).to_s.classify.constantize.new.is_a? ApplicationRecord) rescue false)
819
818
  # }.compact
820
819
  # }
@@ -824,6 +823,323 @@ class Api::V2::InfoController < Api::V2::ApplicationController
824
823
  pivot
825
824
  end
826
825
 
826
+ def info_description
827
+ info = <<-MARKDOWN
828
+
829
+ ## About this API Documentation
830
+
831
+ Model Driven Backend [API](https://github.com/gabrieletassoni/thecore/blob/master/docs/04_REST_API.md) created to reflect the actual Active Record Models present in the project in a dynamic way.
832
+
833
+ This swagger describes all the CRUD endpoints provided by the application, as well as all the custom endpoints and gives a deep dive into the parameters accepted in GET (Index) requests and POST (Search) requests. Since the controller unifies params (via request.parameters), the filtering logic is identical whether parameters are passed in the Query String (GET) or the JSON Body (POST).
834
+
835
+ The documentation starts from the authentication mechanism, integrated with the details from the provided code.
836
+ Here is the updated and integrated documentation for the Authentication mechanism. It now covers the full lifecycle: obtaining the initial token via the login endpoint and maintaining the session via the sliding expiration mechanism found in the controller.
837
+
838
+ ---
839
+
840
+ ## Authentication & Token Management
841
+
842
+ The API implements a **stateless JWT (JSON Web Token)** authentication mechanism. It consists of two distinct phases:
843
+
844
+ 1. **Initial Authentication:** Exchanging credentials for the first Token.
845
+ 2. **Session Maintenance:** Using a **Sliding Expiration** strategy where every subsequent successful request issues a fresh token.
846
+
847
+ ### 1. Initial Authentication (Login)
848
+
849
+ To begin a session, the client must POST user credentials to the authentication endpoint. This is the only request that does not require an `Authorization` header.
850
+
851
+ #### Request
852
+
853
+ **Endpoint:** `POST /api/v2/authenticate`
854
+
855
+ **Body:**
856
+
857
+ ```json
858
+ {
859
+ "auth": {
860
+ "email": "admin@example.com",
861
+ "password": "Change#1"
862
+ }
863
+ }
864
+
865
+ ```
866
+
867
+ #### Response
868
+
869
+ Upon successful authentication, the server returns two critical pieces of data:
870
+
871
+ 1. **Response Body:** Contains the User object details.
872
+ 2. **Response Headers:** Contains the initial JWT in the `token` header.
873
+
874
+ **Example Body:**
875
+
876
+ ```json
877
+ {
878
+ "id": 219,
879
+ "email": "admin@example.com",
880
+ "created_at": "2025-12-10T07:57:54.336Z",
881
+ "admin": true,
882
+ "locked": false,
883
+ "locale": "en",
884
+ // ... other user attributes
885
+ "roles": []
886
+ }
887
+
888
+ ```
889
+
890
+ **Example Headers:**
891
+ Note the presence of the `token` header.
892
+
893
+ ```http
894
+ HTTP/1.1 200 OK
895
+ content-type: application/json; charset=utf-8
896
+ token: eyJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoyMTksImV4cCI6MTc2NjQ3ODMwN30.I0qJzOwA0Jxx0frL5-9jVH2PsakdZjSEY8Kqb9S3GKo
897
+ x-request-id: 113cad63-11f8-4daf-b684-19322a053bcc
898
+ ...
899
+
900
+ ```
901
+
902
+ ---
903
+
904
+ ### 2. Sliding Expiration (Token Renewal)
905
+
906
+ Once the client has the initial token, the `Api::V2::ApplicationController` handles the lifecycle. Instead of a fixed expiration time that forces a re-login, the API issues a **brand new token** with every successfully authenticated request.
907
+
908
+ #### The Renewal Mechanism
909
+
910
+ 1. **Client Request:** The client sends the *current* token in the `Authorization` header.
911
+ ```http
912
+ Authorization: Bearer <Current_Token>
913
+
914
+ ```
915
+
916
+
917
+ 2. **Verification:** The `authenticate_request` method verifies the token. If valid, it sets `@current_user`.
918
+ 3. **Renewal:** Before sending the response, the controller generates a new JWT encoded with the current user's ID and sets it in the response header.
919
+ ```ruby
920
+ response.set_header("Token", JsonWebToken.encode(user_id: current_user.id))
921
+
922
+ ```
923
+
924
+ #### Client-Side Implementation Guide
925
+
926
+ To maintain a valid session, the client must implement an interceptor to handle token rotation:
927
+
928
+ 1. **Login:** Call `/api/v2/authenticate` and store the `token` from the response header.
929
+ 2. **Subsequent Requests:** Attach the stored token to the `Authorization: Bearer ...` header.
930
+ 3. **Update Storage:**
931
+ * Check every response for a `Token` header.
932
+ * **If present:** Immediately replace the stored token with this new value.
933
+ * **If missing:** Continue using the existing token (unless the response was a 401/403 error).
934
+
935
+ #### Failure Scenarios
936
+
937
+ If the `authenticate_request` fails (e.g., token expired, invalid signature):
938
+
939
+ * The controller returns an unauthenticated error (`unauthenticated!`).
940
+ * The execution halts, and the line generating the new header is never reached.
941
+ * **Result:** The client receives a 401 error and **no new token**, signaling that the user must perform the **Initial Authentication** (login) again.
942
+
943
+ ---
944
+
945
+ ## API Documentation: Search, Filtering, and Pagination Parameters
946
+
947
+ ### 1. Pagination and Counting
948
+
949
+ These parameters control the amount of data returned and navigation through result pages.
950
+
951
+ * **`page`** (Integer): Indicates the page number to retrieve. If omitted, pagination is not applied (or defaults to the model's Kaminari configuration).
952
+ * **`per`** (Integer): Indicates the number of records per page. Works in conjunction with `page`.
953
+ * **`count`** (Boolean/Any value): If present and not empty, the API **does not** return the list of records but a JSON object containing only the total count of records matching the search criteria (e.g., `{ "count": 42 }`). Useful for displaying total results before loading them.
954
+
955
+ ### 2. Field Selection
956
+
957
+ It is possible to limit the fields returned in the JSON response or include associations. The controller looks for these parameters in `a` or `json_attrs`.
958
+
959
+ * **`a`** (or `json_attrs`): An object or hash defining the output JSON structure.
960
+ * **`only`**: Array of strings. Returns only the specified attributes of the main model.
961
+ * **`methods`**: Array of strings. Includes model methods that are not database columns.
962
+ * **`include`**: Object for including relationships (associations). Associations can also have their own `only` or `methods`.
963
+
964
+ ### 3. Custom Actions
965
+
966
+ * **`do`**: Specifies a custom action (`custom_action`) to execute on the model instead of the standard `index`.
967
+ * Format: `?do=action_name` or `?do=action_name-token`.
968
+ * The controller will look for a class method `custom_action_action_name` or a module `Endpoints::Model`.
969
+
970
+ ### 4. Filters and Sorting (Ransack)
971
+
972
+ The core of the search functionality lies in the **`q`** parameter. The controller implements the **Ransack** gem, allowing for complex queries, filtering on associations, and dynamic sorting.
973
+
974
+ Basic structure: `q[field_name_predicate]=value`
975
+
976
+ #### Common Predicates (Suffixes)
977
+
978
+ * `_eq`: Equal to (e.g., `status_eq`).
979
+ * `_cont`: Contains (LIKE %value%, case insensitive).
980
+ * `_start`: Starts with.
981
+ * `_end`: Ends with.
982
+ * `_gt` / `_lt`: Greater than / Less than (for numbers or dates).
983
+ * `_gteq` / `_lteq`: Greater than or equal to / Less than or equal to.
984
+ * `_in`: Included in a list (accepts an array).
985
+ * `_present`: If set to `1` or `true`, filters for non-null values. `_blank` for nulls.
986
+
987
+ #### Sorting
988
+
989
+ * `s`: Defines the sorting order. Format: `field_name asc` or `field_name desc`.
990
+
991
+ ---
992
+
993
+ ## Practical Examples
994
+
995
+ Below are two usage scenarios to achieve the same result: a **GET** request (URL parameters) and a **POST** request (JSON Body parameters).
996
+
997
+ #### Scenario A: Simple Search and Pagination
998
+
999
+ **Goal:** Find users whose name contains "Mario", paginated (page 2, 10 per page).
1000
+
1001
+ ##### 1. Using GET (Query String)
1002
+
1003
+ Parameters are "flattened" and encoded in the URL.
1004
+
1005
+ ```text
1006
+ GET /api/v2/users?q[name_cont]=Mario&page=2&per=10
1007
+
1008
+ ```
1009
+
1010
+ ##### 2. Using POST (JSON Body)
1011
+
1012
+ Ideal for complex searches to avoid exceeding URL length limits.
1013
+
1014
+ ```json
1015
+ POST /api/v2/users/search
1016
+ Content-Type: application/json
1017
+
1018
+ {
1019
+ "q": {
1020
+ "name_cont": "Mario"
1021
+ },
1022
+ "page": 2,
1023
+ "per": 10
1024
+ }
1025
+
1026
+ ```
1027
+
1028
+ ---
1029
+
1030
+ #### Scenario B: Advanced Search, Sorting, and Field Selection
1031
+
1032
+ **Goal:**
1033
+
1034
+ 1. Search for orders (`orders`) where `total_price` is greater than 50.
1035
+ 2. Belonging to a user (`user`) whose email ends with `@test.com`.
1036
+ 3. Sort by creation date descending.
1037
+ 4. Return only the `id` and `total_price` of the order, including the `email` of the associated user.
1038
+
1039
+ ##### 1. Using GET (Query String)
1040
+
1041
+ Note the square bracket syntax for nested structures (`q`, `a`).
1042
+
1043
+ ```text
1044
+ GET /api/v2/orders?q[total_price_gt]=50&q[user_email_end]=@test.com&q[s]=created_at desc&a[only][]=id&a[only][]=total_price&a[include][user][only][]=email
1045
+
1046
+ ```
1047
+
1048
+ ##### 2. Using POST (JSON Body)
1049
+
1050
+ Much more readable for nested structures like `a` (json_attrs).
1051
+
1052
+ ```json
1053
+ POST /api/v2/orders/search
1054
+ Content-Type: application/json
1055
+
1056
+ {
1057
+ "q": {
1058
+ "total_price_gt": 50,
1059
+ "user_email_end": "@test.com",
1060
+ "s": "created_at desc"
1061
+ },
1062
+ "a": {
1063
+ "only": ["id", "total_price"],
1064
+ "include": {
1065
+ "user": {
1066
+ "only": ["email"]
1067
+ }
1068
+ }
1069
+ }
1070
+ }
1071
+
1072
+ ```
1073
+
1074
+ ---
1075
+
1076
+ #### Scenario C: Multiple Search (OR) and Arrays
1077
+
1078
+ **Goal:** Find products where status is "new" **OR** "refurbished" (using `_in`).
1079
+
1080
+ ##### 1. Using GET (Query String)
1081
+
1082
+ To pass an array in GET, repeat the empty square brackets `[]`.
1083
+
1084
+ ```text
1085
+ GET /api/v2/products?q[status_in][]=new&q[status_in][]=refurbished
1086
+
1087
+ ```
1088
+
1089
+ ##### 2. Using POST (JSON Body)
1090
+
1091
+ ```json
1092
+ POST /api/v2/products/search
1093
+ Content-Type: application/json
1094
+
1095
+ {
1096
+ "q": {
1097
+ "status_in": ["new", "refurbished"]
1098
+ }
1099
+ }
1100
+
1101
+ ```
1102
+
1103
+ ---
1104
+
1105
+ #### Scenario D: Count Only
1106
+
1107
+ **Goal:** Know how many users are active without downloading the data.
1108
+
1109
+ ##### 1. Using GET
1110
+
1111
+ ```text
1112
+ GET /api/v2/users?q[active_eq]=true&count=true
1113
+
1114
+ ```
1115
+
1116
+ ##### 2. Using POST
1117
+
1118
+ ```json
1119
+ POST /api/v2/users/search
1120
+ Content-Type: application/json
1121
+
1122
+ {
1123
+ "q": {
1124
+ "active_eq": true
1125
+ },
1126
+ "count": true
1127
+ }
1128
+
1129
+ ```
1130
+
1131
+ **Expected Response:**
1132
+
1133
+ ```json
1134
+ {
1135
+ "count": 156
1136
+ }
1137
+
1138
+ ```
1139
+ MARKDOWN
1140
+ info
1141
+ end
1142
+
827
1143
  # GET '/api/v2/info/schema'
828
1144
  def openapi
829
1145
  uri = URI(request.url)
@@ -831,39 +1147,39 @@ class Api::V2::InfoController < Api::V2::ApplicationController
831
1147
  "openapi": "3.0.0",
832
1148
  "info": {
833
1149
  "title": "#{Settings.ns(:main).app_name} API",
834
- "description": "Model Driven Backend [API](https://github.com/gabrieletassoni/thecore/blob/master/docs/04_REST_API.md) created to reflect the actual Active Record Models present in the project in a dynamic way",
835
- "version": "v2"
1150
+ "description": info_description,
1151
+ "version": "v2",
836
1152
  },
837
1153
  "servers": [
838
1154
  {
839
1155
  # i.e. "http://localhost:3001/api/v2"
840
1156
  "url": "#{uri.scheme}://#{uri.host}#{":#{uri.port}" if uri.port.present?}/api/v2",
841
- "description": "The URL at which this API responds."
842
- }
1157
+ "description": "The URL at which this API responds.",
1158
+ },
843
1159
  ],
844
1160
  # 1) Define the security scheme type (HTTP bearer)
845
- "components":{
1161
+ "components": {
846
1162
  "securitySchemes": {
847
1163
  "basicAuth": {
848
1164
  "type": "http",
849
- "scheme": "basic"
1165
+ "scheme": "basic",
850
1166
  },
851
1167
  "bearerAuth": { # arbitrary name for the security scheme
852
1168
  "type": "http",
853
1169
  "scheme": "bearer",
854
- "bearerFormat": "JWT" # optional, arbitrary value for documentation purposes
855
- }
856
- }
1170
+ "bearerFormat": "JWT", # optional, arbitrary value for documentation purposes
1171
+ },
1172
+ },
857
1173
  },
858
1174
  # 2) Apply the security globally to all operations
859
1175
  "security": [
860
1176
  {
861
- "bearerAuth": [] # use the same name as above
862
- }
1177
+ "bearerAuth": [], # use the same name as above
1178
+ },
863
1179
  ],
864
- "paths": generate_paths
1180
+ "paths": generate_paths,
865
1181
  }
866
-
1182
+
867
1183
  render json: pivot.to_json, status: 200
868
1184
  end
869
1185
 
@@ -883,7 +1199,6 @@ class Api::V2::InfoController < Api::V2::ApplicationController
883
1199
  end
884
1200
 
885
1201
  def settings
886
- render json: ThecoreSettings::Setting.pluck(:ns, :key, :raw).inject({}){|result, array| (result[array.first] ||= {})[array.second] = array.third; result }.to_json, status: 200
1202
+ render json: ThecoreSettings::Setting.pluck(:ns, :key, :raw).inject({}) { |result, array| (result[array.first] ||= {})[array.second] = array.third; result }.to_json, status: 200
887
1203
  end
888
-
889
1204
  end
@@ -1,3 +1,3 @@
1
1
  module ModelDrivenApi
2
- VERSION = "3.5.4".freeze
2
+ VERSION = "3.5.5".freeze
3
3
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: model_driven_api
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.5.4
4
+ version: 3.5.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - Gabriele Tassoni
@@ -169,7 +169,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
169
169
  - !ruby/object:Gem::Version
170
170
  version: '0'
171
171
  requirements: []
172
- rubygems_version: 3.6.7
172
+ rubygems_version: 3.6.9
173
173
  specification_version: 4
174
174
  summary: Convention based RoR engine which uses DB schema introspection to create
175
175
  REST APIs.