model_driven_api 2.3.8 → 2.3.13
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +52 -2
- data/app/controllers/api/v2/application_controller.rb +18 -8
- data/lib/concerns/api_exception_management.rb +1 -0
- data/lib/json_web_token.rb +2 -2
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: a5497bdb2727e103fa3727595ca62bbaf065b9135dd9bfbc8674f21506929dd0
|
4
|
+
data.tar.gz: ad09cdae9730b0f5c7983eb478c660ef8cc1c13d1d2c1cb13cd9a143b67f4029
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 719fcf3175dced8eaea5e238551d7d143edc185e9131c44ae9bf5358509de252da07491c27c16f485644c782f73c8f351379744c60f20bc803b074f024ecdbb8
|
7
|
+
data.tar.gz: ad157de69fbd575e5a9197594901b811a0cb1e74f25eb49146c011fbec9d32ebd6874e8712f2ef8edff4799d9e46cd04e2040c5cbe7c48b3a04e7f428917ab5a
|
data/README.md
CHANGED
@@ -1,8 +1,9 @@
|
|
1
1
|
# Model Driven Api
|
2
2
|
|
3
|
-
##
|
3
|
+
## Goals
|
4
4
|
|
5
|
-
To have a comprehensive and meaningful Model driven API right out of the box by just creating migrations in your rails app or engine. With all the CRUD operations in place out of the box and easily expandable with custom actions if needed.
|
5
|
+
* To have a comprehensive and meaningful Model driven API right out of the box by just creating migrations in your rails app or engine. With all the CRUD operations in place out of the box and easily expandable with custom actions if needed.
|
6
|
+
* To have a plain REST implementation which adapts the returned JSON to the specific needs of the client, **without the need to change backend code**, this may overcome the biggest disadvantage of REST vs GraphQL = client driver presentation.
|
6
7
|
|
7
8
|
## TL;DR 5-10 minutes adoption
|
8
9
|
|
@@ -27,6 +28,55 @@ I've always been interested in effortless, no-fuss, conventions' based developme
|
|
27
28
|
|
28
29
|
Doing this means also narrowing a bit the scope of the tools, taking decisions, at least for the first implementations and versions of this engine, so, this works well if the data is relational, this is a prerequisite (postgres, mysql, mssql, etc.).
|
29
30
|
|
31
|
+
## REST Enhanced
|
32
|
+
|
33
|
+
Thanks to the inclusion of [Ransack](https://github.com/activerecord-hackery/ransack/wiki) and [ActiveModel::Serializer](https://api.rubyonrails.org/classes/ActiveModel/Serializers/JSON.html), by just adding the querystring keys **q** and **a**, you can create complex queries (q) to obtain just the records you need, which present in the returnd JSON just the attributes (a) needed.
|
34
|
+
By combining the two keys, you can obtain just the data you want.
|
35
|
+
|
36
|
+
The *json_attrs* or *a* query string passed accepts these keys (Please see [ActiveModel::Serializer](https://api.rubyonrails.org/classes/ActiveModel/Serializers/JSON.html) for reference):
|
37
|
+
- only: list [] of model fields to be shown in JSON serialization
|
38
|
+
- except: exclude these fields from the JSON serialization, this is a list []
|
39
|
+
- methods: include the result of some methods defined in the model, this is a list []
|
40
|
+
- include: include associated models, it's an object {} which also accepts the keys described in this document (only, except, methods, include)
|
41
|
+
|
42
|
+
### Example
|
43
|
+
|
44
|
+
```
|
45
|
+
{{ base_url }}/{{ controller }}?a[only][]=locked&a[only][]=username&a[methods][]=jwe_data
|
46
|
+
```
|
47
|
+
|
48
|
+
Is translated to:
|
49
|
+
|
50
|
+
```
|
51
|
+
{a: {only: ["locked", "username"], methods: ["jwe_data"]}}
|
52
|
+
```
|
53
|
+
|
54
|
+
Which tells the API controller just to return this optimized serialization:
|
55
|
+
|
56
|
+
```
|
57
|
+
[
|
58
|
+
{
|
59
|
+
"username": "Administrator",
|
60
|
+
"locked": false,
|
61
|
+
"jwe_data": "eyJhbGciOiJkaXIiLCJlbmMiOiJSMTI4R0NNIn0..yz0tnC6y3BzgoOsO.BjHb9CRIb0vrv7nnEx54Ac8-cATPJ9sTlQSSxRbTmtcPHc5KhvtyE_hyBRnIcK92bzUBRwdy6ASB2XJVy1VfWxAmO8E.4tOzJlfuXi-shaRhDSkOyg"
|
62
|
+
}
|
63
|
+
]
|
64
|
+
```
|
65
|
+
|
66
|
+
By combinig with Ransack's **q** query string key (please read [Ransack](https://github.com/activerecord-hackery/ransack/wiki) documentation to discover all the possible and complex searches you can make), you can obtain right what you want:
|
67
|
+
|
68
|
+
```
|
69
|
+
{{ base_url }}/{{ controller }}?a[only][]=locked&a[only][]=username&a[methods][]=jwe_data&q[email_cont][]=adm
|
70
|
+
```
|
71
|
+
|
72
|
+
Which translates to:
|
73
|
+
|
74
|
+
```
|
75
|
+
{a: {only: ["locked", "username"], methods: ["jwe_data"]}, q: { email_cont: ["adm"]}}
|
76
|
+
```
|
77
|
+
|
78
|
+
For bigger searches, which may over crowd the querystring length, you can always use the default [Search](#Search) POST endpoint.
|
79
|
+
|
30
80
|
## v2?
|
31
81
|
|
32
82
|
Yes, this is the second version of such an effort and you can note it from the api calls, which are all under the ```/api/v2``` namespace the [/api/v1](https://github.com/gabrieletassoni/thecore_api) one, was were it all started, many ideas are ported from there, such as the generation of the automatic model based crud actions, as well as custom actions definitions and all the things that make also this gem useful for my daily job were already in place, but it was too coupled with [thecore](https://github.com/gabrieletassoni/thecore)'s [rails_admin](https://github.com/sferik/rails_admin) UI, making it impossible to create a complete UI-less, API only application, out of the box and directly based of the DB schema, with all the bells and whistles I needed (mainly self adapting, data and schema driven API functionalities).
|
@@ -20,7 +20,10 @@ class Api::V2::ApplicationController < ActionController::API
|
|
20
20
|
return render json: result, status: 200 if status == true
|
21
21
|
|
22
22
|
# Normal Index Action with Ransack querying
|
23
|
-
|
23
|
+
# Keeping this automation can be too dangerous and lead to unpredicted results
|
24
|
+
# TODO: Remove it
|
25
|
+
# @q = (@model.column_names.include?("user_id") ? @model.where(user_id: current_user.id) : @model).ransack(@query.presence|| params[:q])
|
26
|
+
@q = @model.ransack(@query.presence|| params[:q])
|
24
27
|
@records_all = @q.result # (distinct: true) Removing, but I'm not sure, with it I cannot sort in postgres for associated records (throws an exception on misuse of sort with distinct)
|
25
28
|
page = (@page.presence || params[:page])
|
26
29
|
per = (@per.presence || params[:per])
|
@@ -66,7 +69,9 @@ class Api::V2::ApplicationController < ActionController::API
|
|
66
69
|
return render json: result, status: 200 if status == true
|
67
70
|
|
68
71
|
# Normal Create Action
|
69
|
-
|
72
|
+
# Keeping this automation can be too dangerous and lead to unpredicted results
|
73
|
+
# TODO: Remove it
|
74
|
+
# @record.user_id = current_user.id if @model.column_names.include? "user_id"
|
70
75
|
@record.save!
|
71
76
|
render json: @record.to_json(json_attrs), status: 201
|
72
77
|
end
|
@@ -108,7 +113,8 @@ class Api::V2::ApplicationController < ActionController::API
|
|
108
113
|
resource = "custom_action_#{params[:do]}"
|
109
114
|
raise NoMethodError unless @model.respond_to?(resource)
|
110
115
|
# return true, MultiJson.dump(params[:id].blank? ? @model.send(resource, params) : @model.send(resource, params[:id].to_i, params))
|
111
|
-
|
116
|
+
puts json_attrs
|
117
|
+
return true, @model.send(resource, params).to_json(json_attrs)
|
112
118
|
end
|
113
119
|
# if it's here there is no custom action in the request querystring
|
114
120
|
return false
|
@@ -122,15 +128,12 @@ class Api::V2::ApplicationController < ActionController::API
|
|
122
128
|
end
|
123
129
|
|
124
130
|
def authenticate_request
|
125
|
-
# puts request.headers.inspect
|
126
131
|
@current_user = nil
|
127
|
-
# puts "Are there wbehooks headers to check for? #{Settings.ns(:security).allowed_authorization_headers}"
|
128
132
|
Settings.ns(:security).allowed_authorization_headers.split(",").each do |header|
|
129
133
|
# puts "Found header #{header}: #{request.headers[header.underscore.dasherize]}"
|
130
134
|
check_authorization("Authorize#{header}".constantize.call(request.headers, request.raw_post)) if request.headers[header.underscore.dasherize]
|
131
135
|
end
|
132
136
|
|
133
|
-
# This is the default one, if the header doesn't have a valid form for one of the other Auth methods, then use this Auth Class
|
134
137
|
check_authorization AuthorizeApiRequest.call(request.headers) unless @current_user
|
135
138
|
return unauthenticated!(OpenStruct.new({message: @auth_errors})) unless @current_user
|
136
139
|
|
@@ -143,12 +146,19 @@ class Api::V2::ApplicationController < ActionController::API
|
|
143
146
|
|
144
147
|
def find_record
|
145
148
|
record_id ||= (params[:path].split("/").second.to_i rescue nil)
|
146
|
-
|
149
|
+
# Keeping this automation can be too dangerous and lead to unpredicted results
|
150
|
+
# TODO: Remove it
|
151
|
+
# @record = @model.column_names.include?("user_id") ? @model.where(id: (record_id.presence || @record_id.presence || params[:id]), user_id: current_user.id).first : @model.find((@record_id.presence || params[:id]))
|
152
|
+
@record = @model.find((@record_id.presence || params[:id]))
|
147
153
|
return not_found! if @record.blank?
|
148
154
|
end
|
149
155
|
|
150
156
|
def json_attrs
|
151
|
-
|
157
|
+
# In order of importance: if you send the configuration via querystring you are ok
|
158
|
+
# has precedence over if you have setup the json_attrs in the model concern
|
159
|
+
from_params = params[:a].deep_symbolize_keys unless params[:a].blank?
|
160
|
+
from_params = params[:json_attrs].deep_symbolize_keys unless params[:json_attrs].blank?
|
161
|
+
from_params.presence || @json_attrs.presence || @model.json_attrs.presence || {} rescue {}
|
152
162
|
end
|
153
163
|
|
154
164
|
def extract_model
|
@@ -10,6 +10,7 @@ module ApiExceptionManagement
|
|
10
10
|
rescue_from ActiveModel::ForbiddenAttributesError, with: :fivehundred!
|
11
11
|
rescue_from ActiveRecord::RecordInvalid, with: :invalid!
|
12
12
|
rescue_from ActiveRecord::RecordNotFound, with: :not_found!
|
13
|
+
rescue_from ActiveRecord::RecordNotUnique, with: :invalid!
|
13
14
|
end
|
14
15
|
|
15
16
|
def unauthenticated! exception = AuthenticateUser::AccessDenied.new
|
data/lib/json_web_token.rb
CHANGED
@@ -1,11 +1,11 @@
|
|
1
1
|
class JsonWebToken
|
2
2
|
class << self
|
3
3
|
def encode(payload, expiry = 15.minutes.from_now.to_i)
|
4
|
-
::JWT.encode(payload.merge(exp: expiry), ::Rails.application.credentials.dig(:secret_key_base))
|
4
|
+
::JWT.encode(payload.merge(exp: expiry), ::Rails.application.credentials.dig(:secret_key_base).presence||ENV["SECRET_KEY_BASE"])
|
5
5
|
end
|
6
6
|
|
7
7
|
def decode(token)
|
8
|
-
body = ::JWT.decode(token, ::Rails.application.credentials.dig(:secret_key_base))[0]
|
8
|
+
body = ::JWT.decode(token, ::Rails.application.credentials.dig(:secret_key_base).presence||ENV["SECRET_KEY_BASE"])[0]
|
9
9
|
::HashWithIndifferentAccess.new body
|
10
10
|
rescue
|
11
11
|
nil
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: model_driven_api
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 2.3.
|
4
|
+
version: 2.3.13
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Gabriele Tassoni
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2021-
|
11
|
+
date: 2021-04-12 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: thecore_backend_commons
|