model_driven_api 3.2.4 → 3.2.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: 010f1acfbe42371a8465ba5eeae1f6a5cc0d8c7d5891c30b52ed2c9056bfd7d0
4
- data.tar.gz: fb81afbec2d0ce91297c68b212a97e213c0b0d7cf15630c82af753f5511ec1f7
3
+ metadata.gz: e73b3aaf951e74746e4f15fd020a5e9811b13f8222bee893e3b657323dbea0f7
4
+ data.tar.gz: 38f200e4c7550d9f15ff356e3323fe2a4bdaa5328f6c4c94e5d05401de1cf60a
5
5
  SHA512:
6
- metadata.gz: '01941ccd88639849a80e1068266b083911915793f0513835e6caf2c61b718d7c4c80d0c57c4a59f25f4a64ddf85a728d18fccada0cf7e53e813b39293b26b339'
7
- data.tar.gz: e9243797670a8dba2dbd87a2e79571dbb5e39dea8fa26379934a08fe74137db7bb053fc73bbb63d811962397bff5e4cb64c11fdf7b65b5abcfe363827a7185df
6
+ metadata.gz: 84c35cf76c50627514dc34702c136f3506fed645ccfb2228d26c21338de8b310e598d18fb302c11675056d0956310641fe0b1d0c60edfb5869ef88e718643833
7
+ data.tar.gz: 003ee1a957b5f8a2dc9c40c68acf8d57ec4d64ae5e29f26452ea7f763db37e93bb2cd6dd410ca4d31535e8b1613252a335b8efa75cd6462a0bbd2131df8a25c0
@@ -198,6 +198,98 @@ class Api::V2::InfoController < Api::V2::ApplicationController
198
198
  }
199
199
  }
200
200
  },
201
+ "/raw/sql": {
202
+ "post": {
203
+ "summary": "Raw SQL query execution of SELECT queries",
204
+ "description": "Executes a SQL query on the underlying PostgreSQL database.
205
+
206
+ Designed for SELECT queries that use the json_agg function to aggregate results into JSON arrays.
207
+
208
+ Other query types are not recommended and may be restricted for security and performance reasons.
209
+
210
+ Only SELECT statements are allowed. DDL and DML statements (INSERT, UPDATE, DELETE) are forbidden.
211
+
212
+ Queries can be as simple as
213
+
214
+ SELECT json_agg(u) AS result
215
+ FROM users u
216
+ WHERE u.active = true;
217
+
218
+ or more complex, using joins, subqueries, CTEs, and other SQL features. like:
219
+
220
+ WITH order_details AS (
221
+ SELECT
222
+ o.id AS order_id,
223
+ o.date AS order_date,
224
+ json_agg(
225
+ json_build_object(
226
+ 'item_id', i.id,
227
+ 'item_name', i.name,
228
+ 'quantity', oi.quantity,
229
+ 'price', oi.price
230
+ )
231
+ ) AS items
232
+ FROM orders o
233
+ JOIN order_items oi ON o.id = oi.order_id
234
+ JOIN items i ON i.id = oi.item_id
235
+ GROUP BY o.id
236
+ )
237
+ SELECT json_agg(
238
+ json_build_object(
239
+ 'order_id', od.order_id,
240
+ 'order_date', od.order_date,
241
+ 'customer', json_build_object(
242
+ 'id', c.id,
243
+ 'name', c.name
244
+ ),
245
+ 'items', od.items
246
+ )
247
+ ) AS result
248
+ FROM order_details od
249
+ JOIN customers c ON c.id = od.order_id;
250
+
251
+ ",
252
+ "tags": ["Raw"],
253
+ "security": [
254
+ "bearerAuth": []
255
+ ],
256
+ "responses": {
257
+ "200": {
258
+ "description": "SQL Query Result",
259
+ "content": {
260
+ "application/json": {
261
+ "schema": {
262
+ "type": "array",
263
+ "items": {
264
+ "type": "object",
265
+ "properties": {
266
+ "json_agg": {
267
+ "type": "string"
268
+ }
269
+ }
270
+ }
271
+ }
272
+ }
273
+ }
274
+ }
275
+ },
276
+ "requestBody": {
277
+ "content": {
278
+ "application/json": {
279
+ "schema": {
280
+ "type": "object",
281
+ "properties": {
282
+ "query": {
283
+ "type": "string",
284
+ "example": "SELECT json_agg(u) FROM users u WHERE u.active = true;"
285
+ }
286
+ }
287
+ }
288
+ }
289
+ }
290
+ }
291
+ }
292
+ },
201
293
  "/info/version": {
202
294
  "get": {
203
295
  "summary": "Version",
@@ -0,0 +1,17 @@
1
+ # require 'model_driven_api/version'
2
+ class Api::V2::RawController < Api::V2::ApplicationController
3
+ # Info uses a different auth method: username and password
4
+ # skip_before_action :authenticate_request, only: [:version, :swagger, :openapi], raise: false
5
+ skip_before_action :extract_model
6
+
7
+ # api :GET, '/api/v2/raw/sql'
8
+ def sql
9
+ # if params is nil, render 400
10
+ render json: { error: "Query is required" }, status: 400 and return if params[:query].nil?
11
+
12
+ query = params[:query]
13
+
14
+ render json: SafeSqlExecutor.execute_select(query).first["json_agg"], status: 200
15
+ end
16
+
17
+ end
data/config/routes.rb CHANGED
@@ -18,6 +18,10 @@ Rails.application.routes.draw do
18
18
  get :openapi
19
19
  end
20
20
 
21
+ namespace :raw do
22
+ post :sql
23
+ end
24
+
21
25
  post "authenticate" => "authentication#authenticate"
22
26
  post ":ctrl/search" => 'application#index'
23
27
 
@@ -1,3 +1,3 @@
1
1
  module ModelDrivenApi
2
- VERSION = "3.2.4".freeze
2
+ VERSION = "3.2.5".freeze
3
3
  end
@@ -15,6 +15,8 @@ require 'deep_merge/rails_compat'
15
15
 
16
16
  require "model_driven_api/engine"
17
17
 
18
+ require "safe_sql_executor"
19
+
18
20
  module ModelDrivenApi
19
21
  def self.smart_merge src, dest
20
22
  src.deeper_merge! dest, {
@@ -0,0 +1,20 @@
1
+ module SafeSqlExecutor
2
+ def self.execute_select(query)
3
+ # Validate the query
4
+ validate_select_query(query)
5
+
6
+ # Execute the query
7
+ ActiveRecord::Base.connection.execute(query)
8
+ end
9
+
10
+ private
11
+
12
+ def self.validate_select_query(query)
13
+ sanitized_query = query.strip.gsub(/\s+/, " ").upcase
14
+
15
+ # Allow SELECT or WITH...SELECT queries
16
+ unless sanitized_query.match?(/^(WITH .+)?SELECT /)
17
+ raise ArgumentError, "Only SELECT queries (including with CTEs) are allowed"
18
+ end
19
+ end
20
+ end
metadata CHANGED
@@ -1,14 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: model_driven_api
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.2.4
4
+ version: 3.2.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - Gabriele Tassoni
8
- autorequire:
9
8
  bindir: bin
10
9
  cert_chain: []
11
- date: 2024-07-22 00:00:00.000000000 Z
10
+ date: 2025-01-08 00:00:00.000000000 Z
12
11
  dependencies:
13
12
  - !ruby/object:Gem::Dependency
14
13
  name: thecore_backend_commons
@@ -124,6 +123,7 @@ files:
124
123
  - app/controllers/api/v2/application_controller.rb
125
124
  - app/controllers/api/v2/authentication_controller.rb
126
125
  - app/controllers/api/v2/info_controller.rb
126
+ - app/controllers/api/v2/raw_controller.rb
127
127
  - app/controllers/api/v2/users_controller.rb
128
128
  - app/models/endpoints/test_api.rb
129
129
  - app/models/test_api.rb
@@ -146,13 +146,13 @@ files:
146
146
  - lib/model_driven_api/engine.rb
147
147
  - lib/model_driven_api/version.rb
148
148
  - lib/non_crud_endpoints.rb
149
+ - lib/safe_sql_executor.rb
149
150
  - lib/tasks/model_driven_api_tasks.rake
150
151
  homepage: https://github.com/gabrieletassoni/model_driven_api
151
152
  licenses:
152
153
  - MIT
153
154
  metadata:
154
155
  allowed_push_host: https://rubygems.org
155
- post_install_message:
156
156
  rdoc_options: []
157
157
  require_paths:
158
158
  - lib
@@ -167,8 +167,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
167
167
  - !ruby/object:Gem::Version
168
168
  version: '0'
169
169
  requirements: []
170
- rubygems_version: 3.5.11
171
- signing_key:
170
+ rubygems_version: 3.6.2
172
171
  specification_version: 4
173
172
  summary: Convention based RoR engine which uses DB schema introspection to create
174
173
  REST APIs.