model_driven_api 3.6.3 → 3.6.4
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 +4 -4
- data/README.md +521 -54
- data/Rakefile +3 -0
- data/app/controllers/api/v2/application_controller.rb +6 -47
- data/app/controllers/api/v2/info_controller.rb +2 -1308
- data/app/controllers/api/v3/application_controller.rb +132 -0
- data/app/controllers/api/v3/auth/oauth_controller.rb +4 -0
- data/app/controllers/api/v3/authentication_controller.rb +2 -0
- data/app/controllers/api/v3/info_controller.rb +37 -0
- data/app/controllers/api/v3/raw_controller.rb +14 -0
- data/app/controllers/api/v3/users_controller.rb +10 -0
- data/config/routes.rb +44 -0
- data/lib/api/custom_action_dispatcher.rb +41 -0
- data/lib/api/model_resolver.rb +20 -0
- data/lib/api/open_api/base.rb +91 -0
- data/lib/api/open_api/v2.rb +1238 -0
- data/lib/api/open_api/v3.rb +349 -0
- data/lib/api/resource_attribute_set.rb +25 -0
- data/lib/api/v3/serializer_factory.rb +65 -0
- data/lib/model_driven_api/engine.rb +7 -1
- data/lib/model_driven_api/version.rb +1 -1
- data/lib/model_driven_api.rb +8 -0
- metadata +75 -6
|
@@ -0,0 +1,349 @@
|
|
|
1
|
+
module Api
|
|
2
|
+
module OpenApi
|
|
3
|
+
class V3 < Base
|
|
4
|
+
def generate
|
|
5
|
+
paths = {}
|
|
6
|
+
paths.merge!(v3_authenticate_path)
|
|
7
|
+
paths.merge!(v3_raw_sql_paths)
|
|
8
|
+
paths.merge!(v3_info_paths)
|
|
9
|
+
ApplicationRecord.subclasses.sort_by(&:to_s).each do |model_class|
|
|
10
|
+
paths.merge!(v3_crud_paths(model_class))
|
|
11
|
+
paths.merge!(v3_custom_action_paths(model_class))
|
|
12
|
+
end
|
|
13
|
+
paths
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def description
|
|
17
|
+
v3_description
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
private
|
|
21
|
+
|
|
22
|
+
def v3_authenticate_path
|
|
23
|
+
{
|
|
24
|
+
"/authenticate" => {
|
|
25
|
+
"post" => {
|
|
26
|
+
"summary" => "Authenticate",
|
|
27
|
+
"tags" => ["Authentication"],
|
|
28
|
+
"description" => "Exchange email/password for a JWT. The token is returned in the `Token` response header.",
|
|
29
|
+
"requestBody" => {
|
|
30
|
+
"required" => true,
|
|
31
|
+
"content" => {
|
|
32
|
+
"application/json" => {
|
|
33
|
+
"schema" => {
|
|
34
|
+
"type" => "object",
|
|
35
|
+
"properties" => {
|
|
36
|
+
"auth" => {
|
|
37
|
+
"type" => "object",
|
|
38
|
+
"properties" => {
|
|
39
|
+
"email" => { "type" => "string", "format" => "email" },
|
|
40
|
+
"password" => { "type" => "string", "format" => "password" },
|
|
41
|
+
},
|
|
42
|
+
},
|
|
43
|
+
},
|
|
44
|
+
},
|
|
45
|
+
},
|
|
46
|
+
},
|
|
47
|
+
},
|
|
48
|
+
"responses" => {
|
|
49
|
+
"200" => {
|
|
50
|
+
"description" => "Authenticated — JWT in Token header",
|
|
51
|
+
"headers" => { "Token" => { "description" => "JWT", "schema" => { "type" => "string" } } },
|
|
52
|
+
},
|
|
53
|
+
"401" => { "description" => "Unauthorized" },
|
|
54
|
+
},
|
|
55
|
+
},
|
|
56
|
+
},
|
|
57
|
+
}
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def v3_raw_sql_paths
|
|
61
|
+
response_schema = {
|
|
62
|
+
"type" => "array",
|
|
63
|
+
"items" => { "type" => "object", "additionalProperties" => true },
|
|
64
|
+
}
|
|
65
|
+
query_param = {
|
|
66
|
+
"name" => "query",
|
|
67
|
+
"in" => "query",
|
|
68
|
+
"required" => true,
|
|
69
|
+
"schema" => { "type" => "string" },
|
|
70
|
+
"example" => "SELECT id, name FROM roles LIMIT 10",
|
|
71
|
+
}
|
|
72
|
+
body_schema = {
|
|
73
|
+
"type" => "object",
|
|
74
|
+
"properties" => { "query" => { "type" => "string" } },
|
|
75
|
+
}
|
|
76
|
+
{
|
|
77
|
+
"/raw/sql" => {
|
|
78
|
+
"get" => {
|
|
79
|
+
"summary" => "Raw SQL (GET)",
|
|
80
|
+
"tags" => ["Raw"],
|
|
81
|
+
"description" => "Execute a SELECT query. Returns rows as a plain JSON array (not JSON:API).",
|
|
82
|
+
"security" => [{ "bearerAuth" => [] }],
|
|
83
|
+
"parameters" => [query_param],
|
|
84
|
+
"responses" => {
|
|
85
|
+
"200" => { "description" => "Rows", "content" => { "application/json" => { "schema" => response_schema } } },
|
|
86
|
+
"400" => { "description" => "Only SELECT statements are allowed" },
|
|
87
|
+
},
|
|
88
|
+
},
|
|
89
|
+
"post" => {
|
|
90
|
+
"summary" => "Raw SQL (POST)",
|
|
91
|
+
"tags" => ["Raw"],
|
|
92
|
+
"description" => "Execute a SELECT query. Returns rows as a plain JSON array (not JSON:API).",
|
|
93
|
+
"security" => [{ "bearerAuth" => [] }],
|
|
94
|
+
"requestBody" => { "required" => true, "content" => { "application/json" => { "schema" => body_schema } } },
|
|
95
|
+
"responses" => {
|
|
96
|
+
"200" => { "description" => "Rows", "content" => { "application/json" => { "schema" => response_schema } } },
|
|
97
|
+
"400" => { "description" => "Only SELECT statements are allowed" },
|
|
98
|
+
},
|
|
99
|
+
},
|
|
100
|
+
},
|
|
101
|
+
}
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
def v3_info_paths
|
|
105
|
+
{
|
|
106
|
+
"/info/version" => {
|
|
107
|
+
"get" => {
|
|
108
|
+
"summary" => "Version", "tags" => ["Info"],
|
|
109
|
+
"responses" => { "200" => { "description" => "App version string" } },
|
|
110
|
+
},
|
|
111
|
+
},
|
|
112
|
+
"/info/heartbeat" => {
|
|
113
|
+
"get" => {
|
|
114
|
+
"summary" => "Heartbeat", "tags" => ["Info"],
|
|
115
|
+
"security" => [{ "bearerAuth" => [] }],
|
|
116
|
+
"responses" => { "200" => { "description" => "Renews token, returns current user as plain JSON" } },
|
|
117
|
+
},
|
|
118
|
+
},
|
|
119
|
+
"/info/roles" => {
|
|
120
|
+
"get" => {
|
|
121
|
+
"summary" => "Roles", "tags" => ["Info"],
|
|
122
|
+
"security" => [{ "bearerAuth" => [] }],
|
|
123
|
+
"responses" => { "200" => { "description" => "All roles as plain JSON array" } },
|
|
124
|
+
},
|
|
125
|
+
},
|
|
126
|
+
"/info/schema" => {
|
|
127
|
+
"get" => {
|
|
128
|
+
"summary" => "Schema", "tags" => ["Info"],
|
|
129
|
+
"security" => [{ "bearerAuth" => [] }],
|
|
130
|
+
"responses" => { "200" => { "description" => "DB schema for models the user can read" } },
|
|
131
|
+
},
|
|
132
|
+
},
|
|
133
|
+
"/info/dsl" => {
|
|
134
|
+
"get" => {
|
|
135
|
+
"summary" => "DSL", "tags" => ["Info"],
|
|
136
|
+
"security" => [{ "bearerAuth" => [] }],
|
|
137
|
+
"responses" => { "200" => { "description" => "json_attrs DSL for each model" } },
|
|
138
|
+
},
|
|
139
|
+
},
|
|
140
|
+
"/info/translations" => {
|
|
141
|
+
"get" => {
|
|
142
|
+
"summary" => "Translations", "tags" => ["Info"],
|
|
143
|
+
"security" => [{ "bearerAuth" => [] }],
|
|
144
|
+
"parameters" => [{ "name" => "locale", "in" => "query", "schema" => { "type" => "string" } }],
|
|
145
|
+
"responses" => { "200" => { "description" => "Full i18n translation tree" } },
|
|
146
|
+
},
|
|
147
|
+
},
|
|
148
|
+
"/info/settings" => {
|
|
149
|
+
"get" => {
|
|
150
|
+
"summary" => "Settings", "tags" => ["Info"],
|
|
151
|
+
"security" => [{ "bearerAuth" => [] }],
|
|
152
|
+
"responses" => { "200" => { "description" => "All ThecoreSettings::Setting values" } },
|
|
153
|
+
},
|
|
154
|
+
},
|
|
155
|
+
"/info/swagger" => {
|
|
156
|
+
"get" => {
|
|
157
|
+
"summary" => "OpenAPI v3 spec", "tags" => ["Info"],
|
|
158
|
+
"responses" => { "200" => { "description" => "This OpenAPI 3.0 specification" } },
|
|
159
|
+
},
|
|
160
|
+
},
|
|
161
|
+
}
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
def v3_crud_paths(model_class)
|
|
165
|
+
resource_type = model_class.model_name.plural
|
|
166
|
+
tag = model_class.model_name.name
|
|
167
|
+
|
|
168
|
+
attrs = begin
|
|
169
|
+
props = create_properties_from_model(model_class, model_class.json_attrs || {})
|
|
170
|
+
props.reject { |k, _| k.to_s == "id" }
|
|
171
|
+
rescue
|
|
172
|
+
{}
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
writable_attrs = begin
|
|
176
|
+
create_properties_from_model(model_class, {}, true)
|
|
177
|
+
rescue
|
|
178
|
+
{}
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
resource_schema = {
|
|
182
|
+
"type" => "object",
|
|
183
|
+
"properties" => {
|
|
184
|
+
"id" => { "type" => "string" },
|
|
185
|
+
"type" => { "type" => "string", "example" => resource_type },
|
|
186
|
+
"attributes" => { "type" => "object", "properties" => attrs },
|
|
187
|
+
},
|
|
188
|
+
}
|
|
189
|
+
collection_schema = {
|
|
190
|
+
"type" => "object",
|
|
191
|
+
"properties" => {
|
|
192
|
+
"data" => { "type" => "array", "items" => resource_schema },
|
|
193
|
+
"meta" => { "type" => "object", "properties" => { "total" => { "type" => "integer" } } },
|
|
194
|
+
},
|
|
195
|
+
}
|
|
196
|
+
single_schema = { "type" => "object", "properties" => { "data" => resource_schema } }
|
|
197
|
+
request_schema = {
|
|
198
|
+
"type" => "object",
|
|
199
|
+
"properties" => {
|
|
200
|
+
"data" => {
|
|
201
|
+
"type" => "object",
|
|
202
|
+
"properties" => {
|
|
203
|
+
"type" => { "type" => "string", "example" => resource_type },
|
|
204
|
+
"attributes" => { "type" => "object", "properties" => writable_attrs },
|
|
205
|
+
},
|
|
206
|
+
},
|
|
207
|
+
},
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
filter_params = begin
|
|
211
|
+
model_class.ransackable_attributes.map do |attr|
|
|
212
|
+
{ "name" => "filter[#{attr}]", "in" => "query", "schema" => { "type" => "string" } }
|
|
213
|
+
end
|
|
214
|
+
rescue
|
|
215
|
+
[]
|
|
216
|
+
end
|
|
217
|
+
|
|
218
|
+
id_param = { "name" => "id", "in" => "path", "required" => true, "schema" => { "type" => "integer" } }
|
|
219
|
+
sort_param = { "name" => "sort", "in" => "query", "schema" => { "type" => "string" }, "description" => "field or -field (descending), comma-separated" }
|
|
220
|
+
page_params = [
|
|
221
|
+
{ "name" => "page[number]", "in" => "query", "schema" => { "type" => "integer" } },
|
|
222
|
+
{ "name" => "page[size]", "in" => "query", "schema" => { "type" => "integer" } },
|
|
223
|
+
]
|
|
224
|
+
include_param = { "name" => "include", "in" => "query", "schema" => { "type" => "string" }, "description" => "Associations to sideload (empty to suppress defaults)" }
|
|
225
|
+
fields_param = { "name" => "fields[#{resource_type}]", "in" => "query", "schema" => { "type" => "string" }, "description" => "Sparse fieldsets" }
|
|
226
|
+
|
|
227
|
+
vnd = "application/vnd.api+json"
|
|
228
|
+
|
|
229
|
+
{
|
|
230
|
+
"/#{resource_type}" => {
|
|
231
|
+
"get" => {
|
|
232
|
+
"summary" => "Index #{tag}",
|
|
233
|
+
"tags" => [tag],
|
|
234
|
+
"security" => [{ "bearerAuth" => [] }],
|
|
235
|
+
"parameters" => [*page_params, *filter_params, sort_param, include_param, fields_param],
|
|
236
|
+
"responses" => {
|
|
237
|
+
"200" => { "description" => "Collection", "content" => { vnd => { "schema" => collection_schema } } },
|
|
238
|
+
},
|
|
239
|
+
},
|
|
240
|
+
"post" => {
|
|
241
|
+
"summary" => "Create #{tag}",
|
|
242
|
+
"tags" => [tag],
|
|
243
|
+
"security" => [{ "bearerAuth" => [] }],
|
|
244
|
+
"requestBody" => { "required" => true, "content" => { vnd => { "schema" => request_schema } } },
|
|
245
|
+
"responses" => {
|
|
246
|
+
"201" => { "description" => "Created", "content" => { vnd => { "schema" => single_schema } } },
|
|
247
|
+
},
|
|
248
|
+
},
|
|
249
|
+
},
|
|
250
|
+
"/#{resource_type}/{id}" => {
|
|
251
|
+
"get" => {
|
|
252
|
+
"summary" => "Show #{tag}",
|
|
253
|
+
"tags" => [tag],
|
|
254
|
+
"security" => [{ "bearerAuth" => [] }],
|
|
255
|
+
"parameters" => [id_param, include_param, fields_param],
|
|
256
|
+
"responses" => {
|
|
257
|
+
"200" => { "description" => "Resource", "content" => { vnd => { "schema" => single_schema } } },
|
|
258
|
+
"404" => { "description" => "Not found" },
|
|
259
|
+
},
|
|
260
|
+
},
|
|
261
|
+
"patch" => {
|
|
262
|
+
"summary" => "Update #{tag}",
|
|
263
|
+
"tags" => [tag],
|
|
264
|
+
"security" => [{ "bearerAuth" => [] }],
|
|
265
|
+
"parameters" => [id_param],
|
|
266
|
+
"requestBody" => { "required" => true, "content" => { vnd => { "schema" => request_schema } } },
|
|
267
|
+
"responses" => {
|
|
268
|
+
"200" => { "description" => "Updated", "content" => { vnd => { "schema" => single_schema } } },
|
|
269
|
+
"404" => { "description" => "Not found" },
|
|
270
|
+
},
|
|
271
|
+
},
|
|
272
|
+
"delete" => {
|
|
273
|
+
"summary" => "Destroy #{tag}",
|
|
274
|
+
"tags" => [tag],
|
|
275
|
+
"security" => [{ "bearerAuth" => [] }],
|
|
276
|
+
"parameters" => [id_param],
|
|
277
|
+
"responses" => {
|
|
278
|
+
"204" => { "description" => "No Content" },
|
|
279
|
+
"404" => { "description" => "Not found" },
|
|
280
|
+
},
|
|
281
|
+
},
|
|
282
|
+
},
|
|
283
|
+
}
|
|
284
|
+
end
|
|
285
|
+
|
|
286
|
+
def v3_custom_action_paths(model_class)
|
|
287
|
+
paths = {}
|
|
288
|
+
resource_type = model_class.model_name.plural
|
|
289
|
+
tag = model_class.model_name.name
|
|
290
|
+
custom_actions = ("Endpoints::#{tag}".constantize.instance_methods(false) rescue [])
|
|
291
|
+
custom_actions.each do |action|
|
|
292
|
+
definition = ("Endpoints::#{tag}".constantize.definitions[tag][action.to_sym] rescue nil)
|
|
293
|
+
next unless definition
|
|
294
|
+
definition.each { |_verb, spec| spec[:tags] = [tag] if spec.is_a?(Hash) }
|
|
295
|
+
has_id = definition.any? { |_v, spec| spec.is_a?(Hash) && spec[:parameters]&.any? { |p| p[:in] == "path" } }
|
|
296
|
+
path = "/#{resource_type}/custom_action/#{action}#{has_id ? "/{id}" : ""}"
|
|
297
|
+
paths[path] = definition
|
|
298
|
+
end
|
|
299
|
+
paths
|
|
300
|
+
end
|
|
301
|
+
|
|
302
|
+
def v3_description
|
|
303
|
+
<<~MD
|
|
304
|
+
## API v3 — JSON:API
|
|
305
|
+
|
|
306
|
+
All resource endpoints follow the [JSON:API 1.0](https://jsonapi.org) specification.
|
|
307
|
+
|
|
308
|
+
### Authentication
|
|
309
|
+
|
|
310
|
+
`POST /authenticate` → JWT in `Token` response header. Pass as `Authorization: Bearer <token>`.
|
|
311
|
+
Every successful request renews the token — always read and store the new `Token` header.
|
|
312
|
+
|
|
313
|
+
### Content negotiation
|
|
314
|
+
|
|
315
|
+
`Accept: application/vnd.api+json` (GET) · `Content-Type: application/vnd.api+json` (write requests).
|
|
316
|
+
|
|
317
|
+
### Filtering
|
|
318
|
+
|
|
319
|
+
`?filter[field]=value` — validated against each model's `ransackable_attributes`.
|
|
320
|
+
|
|
321
|
+
### Sorting
|
|
322
|
+
|
|
323
|
+
`?sort=field` (asc) · `?sort=-field` (desc) · `?sort=field1,-field2` (multi-field).
|
|
324
|
+
|
|
325
|
+
### Pagination
|
|
326
|
+
|
|
327
|
+
`?page[number]=N&page[size]=N` — response includes `meta.total`.
|
|
328
|
+
|
|
329
|
+
### Sparse fieldsets
|
|
330
|
+
|
|
331
|
+
`?fields[type]=field1,field2` — restrict attributes returned per resource type.
|
|
332
|
+
|
|
333
|
+
### Sideloading
|
|
334
|
+
|
|
335
|
+
Default sideloads come from each model's `json_attrs[:include]`.
|
|
336
|
+
Override with `?include=assoc1,assoc2` · suppress all with `?include=` (empty string).
|
|
337
|
+
|
|
338
|
+
### Info & utility endpoints
|
|
339
|
+
|
|
340
|
+
`GET /info/version|heartbeat|roles|schema|dsl|translations|settings|swagger` — return plain JSON (not JSON:API).
|
|
341
|
+
|
|
342
|
+
### Raw SQL escape hatch
|
|
343
|
+
|
|
344
|
+
`GET|POST /raw/sql` — SELECT-only; returns plain JSON array (not JSON:API).
|
|
345
|
+
MD
|
|
346
|
+
end
|
|
347
|
+
end
|
|
348
|
+
end
|
|
349
|
+
end
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
module Api
|
|
2
|
+
ResourceAttributeSet = Struct.new(:attributes, :methods_list, :includes) do
|
|
3
|
+
def self.for(model_class, jattrs: nil)
|
|
4
|
+
jattrs ||= model_class.respond_to?(:json_attrs) ? (model_class.json_attrs || {}) : {}
|
|
5
|
+
only = Array(jattrs[:only]).map(&:to_sym)
|
|
6
|
+
if only.empty?
|
|
7
|
+
except = Array(jattrs[:except]).map(&:to_sym)
|
|
8
|
+
only = model_class.column_names.map(&:to_sym) - except
|
|
9
|
+
end
|
|
10
|
+
new(only.reject { |a| a == :id }, Array(jattrs[:methods]).map(&:to_sym), jattrs[:include])
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
# Parse json_attrs[:include] into { assoc_name => spec_or_nil }.
|
|
14
|
+
# Handles both symbol items (:roles) and hash items (users: { only: [:id] }).
|
|
15
|
+
def parsed_includes
|
|
16
|
+
return {} unless includes
|
|
17
|
+
Array(includes).each_with_object({}) do |item, hash|
|
|
18
|
+
case item
|
|
19
|
+
when Hash then item.each { |k, v| hash[k] = v }
|
|
20
|
+
when Symbol then hash[item] = nil
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
module Api
|
|
2
|
+
module V3
|
|
3
|
+
class SerializerFactory
|
|
4
|
+
# Generate (and cache) a JSONAPI::Serializer subclass for model_class.
|
|
5
|
+
#
|
|
6
|
+
# nested_attrs: when set, use these attrs instead of model_class.json_attrs.
|
|
7
|
+
# Used for included associations — avoids reading the associated model's own
|
|
8
|
+
# json_attrs and prevents infinite recursion.
|
|
9
|
+
# parent_name: when set, the generated constant is named
|
|
10
|
+
# "#{model_class.name}For#{parent_name}Serializer" and include: is NOT
|
|
11
|
+
# processed (one level deep only).
|
|
12
|
+
def self.serializer_for(model_class, nested_attrs: nil, parent_name: nil)
|
|
13
|
+
const_name = parent_name ? "#{model_class.name}For#{parent_name}Serializer" : "#{model_class.name}Serializer"
|
|
14
|
+
return Api::V3.const_get(const_name) if Api::V3.const_defined?(const_name)
|
|
15
|
+
|
|
16
|
+
jattrs = nested_attrs || (model_class.respond_to?(:json_attrs) ? (model_class.json_attrs || {}) : {})
|
|
17
|
+
attr_set = Api::ResourceAttributeSet.for(model_class, jattrs: jattrs)
|
|
18
|
+
|
|
19
|
+
# Nested serializers are always flat — no recursive includes — to prevent
|
|
20
|
+
# infinite loops on circular associations (e.g. Role↔User).
|
|
21
|
+
includes_map = parent_name ? {} : attr_set.parsed_includes
|
|
22
|
+
|
|
23
|
+
type = model_class.model_name.plural.to_sym
|
|
24
|
+
|
|
25
|
+
reflections = model_class.reflect_on_all_associations.each_with_object({}) { |r, h| h[r.name] = r }
|
|
26
|
+
nested_info = includes_map.each_with_object({}) do |(assoc_name, assoc_spec), hash|
|
|
27
|
+
reflection = reflections[assoc_name]
|
|
28
|
+
next unless reflection
|
|
29
|
+
nested_ser = assoc_spec \
|
|
30
|
+
? serializer_for(reflection.klass, nested_attrs: assoc_spec, parent_name: model_class.name) \
|
|
31
|
+
: serializer_for(reflection.klass)
|
|
32
|
+
hash[assoc_name] = { serializer: nested_ser, macro: reflection.macro }
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
klass = Class.new do
|
|
36
|
+
include JSONAPI::Serializer
|
|
37
|
+
set_type type
|
|
38
|
+
attributes(*attr_set.attributes) if attr_set.attributes.any?
|
|
39
|
+
|
|
40
|
+
attr_set.methods_list.each do |method_name|
|
|
41
|
+
attribute(method_name) { |object| object.send(method_name) }
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
nested_info.each do |assoc_name, info|
|
|
45
|
+
ser = info[:serializer]
|
|
46
|
+
case info[:macro]
|
|
47
|
+
when :has_many then has_many assoc_name, serializer: ser
|
|
48
|
+
when :has_one then has_one assoc_name, serializer: ser
|
|
49
|
+
when :belongs_to then belongs_to assoc_name, serializer: ser
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
Api::V3.const_set(const_name, klass)
|
|
55
|
+
klass
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# Parse json_attrs[:include] into { assoc_name => spec_or_nil }.
|
|
59
|
+
# Delegates to Api::ResourceAttributeSet#parsed_includes.
|
|
60
|
+
def self.extract_includes(include_spec)
|
|
61
|
+
Api::ResourceAttributeSet.new([], [], include_spec).parsed_includes
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|
|
@@ -1,8 +1,14 @@
|
|
|
1
1
|
module ModelDrivenApi
|
|
2
2
|
class Engine < ::Rails::Engine
|
|
3
3
|
# appending migrations to the main app's ones
|
|
4
|
+
initializer :register_json_api_mime_type do
|
|
5
|
+
Mime::Type.register "application/vnd.api+json", :json_api unless Mime[:json_api]
|
|
6
|
+
ActionDispatch::Request.parameter_parsers[:json_api] =
|
|
7
|
+
ActionDispatch::Request.parameter_parsers[:json]
|
|
8
|
+
end
|
|
9
|
+
|
|
4
10
|
initializer :append_migrations do |app|
|
|
5
|
-
unless app.root.to_s
|
|
11
|
+
unless app.root.to_s == root.to_s
|
|
6
12
|
config.paths["db/migrate"].expanded.each do |expanded_path|
|
|
7
13
|
app.config.paths["db/migrate"] << expanded_path
|
|
8
14
|
end
|
data/lib/model_driven_api.rb
CHANGED
|
@@ -6,6 +6,7 @@ require 'ransack'
|
|
|
6
6
|
require 'jwt'
|
|
7
7
|
require 'json_web_token'
|
|
8
8
|
require "kaminari"
|
|
9
|
+
require "pagy"
|
|
9
10
|
# require "multi_json"
|
|
10
11
|
require "simple_command"
|
|
11
12
|
|
|
@@ -16,6 +17,13 @@ require 'deep_merge/rails_compat'
|
|
|
16
17
|
require "model_driven_api/engine"
|
|
17
18
|
|
|
18
19
|
require "safe_sql_executor"
|
|
20
|
+
require "api/resource_attribute_set"
|
|
21
|
+
require "api/model_resolver"
|
|
22
|
+
require "api/custom_action_dispatcher"
|
|
23
|
+
require "api/open_api/base"
|
|
24
|
+
require "api/open_api/v2"
|
|
25
|
+
require "api/open_api/v3"
|
|
26
|
+
require "api/v3/serializer_factory"
|
|
19
27
|
|
|
20
28
|
module ModelDrivenApi
|
|
21
29
|
def self.smart_merge src, dest
|
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.6.
|
|
4
|
+
version: 3.6.4
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Gabriele Tassoni
|
|
@@ -94,19 +94,75 @@ dependencies:
|
|
|
94
94
|
- !ruby/object:Gem::Version
|
|
95
95
|
version: '1.2'
|
|
96
96
|
- !ruby/object:Gem::Dependency
|
|
97
|
-
name:
|
|
97
|
+
name: jsonapi-serializer
|
|
98
98
|
requirement: !ruby/object:Gem::Requirement
|
|
99
99
|
requirements:
|
|
100
|
-
- - "
|
|
100
|
+
- - "~>"
|
|
101
|
+
- !ruby/object:Gem::Version
|
|
102
|
+
version: '2.2'
|
|
103
|
+
type: :runtime
|
|
104
|
+
prerelease: false
|
|
105
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
106
|
+
requirements:
|
|
107
|
+
- - "~>"
|
|
108
|
+
- !ruby/object:Gem::Version
|
|
109
|
+
version: '2.2'
|
|
110
|
+
- !ruby/object:Gem::Dependency
|
|
111
|
+
name: pagy
|
|
112
|
+
requirement: !ruby/object:Gem::Requirement
|
|
113
|
+
requirements:
|
|
114
|
+
- - "~>"
|
|
115
|
+
- !ruby/object:Gem::Version
|
|
116
|
+
version: '9.0'
|
|
117
|
+
type: :runtime
|
|
118
|
+
prerelease: false
|
|
119
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
120
|
+
requirements:
|
|
121
|
+
- - "~>"
|
|
122
|
+
- !ruby/object:Gem::Version
|
|
123
|
+
version: '9.0'
|
|
124
|
+
- !ruby/object:Gem::Dependency
|
|
125
|
+
name: pg
|
|
126
|
+
requirement: !ruby/object:Gem::Requirement
|
|
127
|
+
requirements:
|
|
128
|
+
- - "~>"
|
|
101
129
|
- !ruby/object:Gem::Version
|
|
102
|
-
version: '
|
|
130
|
+
version: '1.1'
|
|
103
131
|
type: :development
|
|
104
132
|
prerelease: false
|
|
105
133
|
version_requirements: !ruby/object:Gem::Requirement
|
|
106
134
|
requirements:
|
|
107
|
-
- - "
|
|
135
|
+
- - "~>"
|
|
136
|
+
- !ruby/object:Gem::Version
|
|
137
|
+
version: '1.1'
|
|
138
|
+
- !ruby/object:Gem::Dependency
|
|
139
|
+
name: rspec-rails
|
|
140
|
+
requirement: !ruby/object:Gem::Requirement
|
|
141
|
+
requirements:
|
|
142
|
+
- - "~>"
|
|
143
|
+
- !ruby/object:Gem::Version
|
|
144
|
+
version: '7.0'
|
|
145
|
+
type: :development
|
|
146
|
+
prerelease: false
|
|
147
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
148
|
+
requirements:
|
|
149
|
+
- - "~>"
|
|
150
|
+
- !ruby/object:Gem::Version
|
|
151
|
+
version: '7.0'
|
|
152
|
+
- !ruby/object:Gem::Dependency
|
|
153
|
+
name: factory_bot_rails
|
|
154
|
+
requirement: !ruby/object:Gem::Requirement
|
|
155
|
+
requirements:
|
|
156
|
+
- - "~>"
|
|
157
|
+
- !ruby/object:Gem::Version
|
|
158
|
+
version: '6.4'
|
|
159
|
+
type: :development
|
|
160
|
+
prerelease: false
|
|
161
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
162
|
+
requirements:
|
|
163
|
+
- - "~>"
|
|
108
164
|
- !ruby/object:Gem::Version
|
|
109
|
-
version: '
|
|
165
|
+
version: '6.4'
|
|
110
166
|
description: Ruby on Rails REST APIs built by convention using the DB schema as the
|
|
111
167
|
foundation, please see README for mode of use.
|
|
112
168
|
email:
|
|
@@ -126,6 +182,12 @@ files:
|
|
|
126
182
|
- app/controllers/api/v2/info_controller.rb
|
|
127
183
|
- app/controllers/api/v2/raw_controller.rb
|
|
128
184
|
- app/controllers/api/v2/users_controller.rb
|
|
185
|
+
- app/controllers/api/v3/application_controller.rb
|
|
186
|
+
- app/controllers/api/v3/auth/oauth_controller.rb
|
|
187
|
+
- app/controllers/api/v3/authentication_controller.rb
|
|
188
|
+
- app/controllers/api/v3/info_controller.rb
|
|
189
|
+
- app/controllers/api/v3/raw_controller.rb
|
|
190
|
+
- app/controllers/api/v3/users_controller.rb
|
|
129
191
|
- app/models/endpoints/test_api.rb
|
|
130
192
|
- app/models/test_api.rb
|
|
131
193
|
- app/models/used_token.rb
|
|
@@ -138,6 +200,13 @@ files:
|
|
|
138
200
|
- config/routes.rb
|
|
139
201
|
- db/migrate/20210519145438_create_used_tokens.rb
|
|
140
202
|
- db/migrate/20210528111450_rename_valid_to_is_valid_in_used_token.rb
|
|
203
|
+
- lib/api/custom_action_dispatcher.rb
|
|
204
|
+
- lib/api/model_resolver.rb
|
|
205
|
+
- lib/api/open_api/base.rb
|
|
206
|
+
- lib/api/open_api/v2.rb
|
|
207
|
+
- lib/api/open_api/v3.rb
|
|
208
|
+
- lib/api/resource_attribute_set.rb
|
|
209
|
+
- lib/api/v3/serializer_factory.rb
|
|
141
210
|
- lib/concerns/api_exception_management.rb
|
|
142
211
|
- lib/concerns/model_driven_api_application_record.rb
|
|
143
212
|
- lib/concerns/model_driven_api_role.rb
|