belt 0.2.17 → 0.3.15
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/CHANGELOG.md +241 -0
- data/README.md +42 -3
- data/lib/belt/action_router.rb +34 -19
- data/lib/belt/cli/app_detection.rb +30 -3
- data/lib/belt/cli/auth_command.rb +395 -0
- data/lib/belt/cli/console_command.rb +17 -2
- data/lib/belt/cli/deploy_command.rb +51 -17
- data/lib/belt/cli/destroy_command.rb +62 -25
- data/lib/belt/cli/doctor_command.rb +166 -22
- data/lib/belt/cli/environment_command.rb +12 -0
- data/lib/belt/cli/explain_command.rb +112 -0
- data/lib/belt/cli/frontend_command.rb +31 -10
- data/lib/belt/cli/frontend_deploy_command.rb +110 -21
- data/lib/belt/cli/frontend_env_command.rb +56 -19
- data/lib/belt/cli/frontend_env_map.rb +30 -10
- data/lib/belt/cli/frontend_registry.rb +373 -0
- data/lib/belt/cli/frontend_setup_command.rb +71 -5
- data/lib/belt/cli/generate_command.rb +61 -42
- data/lib/belt/cli/index_command.rb +192 -0
- data/lib/belt/cli/lambda_config_command.rb +9 -3
- data/lib/belt/cli/routes_command/request_model_inference.rb +131 -0
- data/lib/belt/cli/routes_command/route_inference.rb +4 -3
- data/lib/belt/cli/routes_command.rb +31 -19
- data/lib/belt/cli/server_command.rb +28 -15
- data/lib/belt/cli/tables_command.rb +3 -1
- data/lib/belt/cli/terraform_command.rb +9 -0
- data/lib/belt/cli/views_command.rb +15 -15
- data/lib/belt/cli/zip_artifact_builder.rb +199 -0
- data/lib/belt/cli.rb +17 -9
- data/lib/belt/docs/backups.md +101 -0
- data/lib/belt/docs/console.md +65 -0
- data/lib/belt/docs/controllers.md +155 -0
- data/lib/belt/docs/deployment.md +166 -0
- data/lib/belt/docs/frontend.md +103 -0
- data/lib/belt/docs/generators.md +137 -0
- data/lib/belt/docs/lambda_handler.md +105 -0
- data/lib/belt/docs/models.md +160 -0
- data/lib/belt/docs/observability.md +94 -0
- data/lib/belt/docs/plugins.md +94 -0
- data/lib/belt/docs/routing.md +138 -0
- data/lib/belt/docs/structure.md +96 -0
- data/lib/belt/inflector.rb +8 -0
- data/lib/belt/route_dsl.rb +318 -100
- data/lib/belt/version.rb +1 -1
- data/lib/templates/frontend/react/env.yml.example +2 -2
- data/lib/templates/generate/auth/cognito.tf.erb +71 -0
- data/lib/templates/generate/auth/cognito_outputs.tf.erb +19 -0
- data/lib/templates/generate/auth/frontend/ConfirmEmail.jsx +55 -0
- data/lib/templates/generate/auth/frontend/Login.jsx +79 -0
- data/lib/templates/generate/auth/frontend/ProtectedRoute.jsx +9 -0
- data/lib/templates/generate/auth/frontend/SignUp.jsx +58 -0
- data/lib/templates/generate/auth/frontend/apiClient.js +34 -0
- data/lib/templates/generate/auth/frontend/auth.css +157 -0
- data/lib/templates/generate/auth/frontend/auth.js +134 -0
- data/lib/templates/generate/model.rb.erb +4 -6
- data/lib/templates/module/frontend.tf.erb +49 -43
- data/lib/templates/module/outputs.tf.erb +1 -1
- data/lib/templates/new_app/AGENTS.md.erb +170 -20
- data/lib/templates/new_app/config/lambda/api.yml.erb +1 -0
- data/lib/templates/new_app/config/routes.rb.erb +8 -3
- data/lib/templates/new_app/lambda/api.rb.erb +1 -1
- metadata +56 -1
data/lib/belt/route_dsl.rb
CHANGED
|
@@ -52,48 +52,46 @@ module Belt
|
|
|
52
52
|
|
|
53
53
|
class NestedResourceBuilder
|
|
54
54
|
def initialize(gateway, prefix, collection_prefix, inherited_tables: [], inherited_auth: nil, # rubocop:disable Metrics/ParameterLists
|
|
55
|
-
inherited_controller: nil)
|
|
55
|
+
inherited_controller: nil, inherited_lambda: nil)
|
|
56
56
|
@gateway = gateway
|
|
57
57
|
@prefix = prefix
|
|
58
58
|
@collection_prefix = collection_prefix
|
|
59
59
|
@inherited_tables = inherited_tables
|
|
60
60
|
@inherited_auth = inherited_auth
|
|
61
61
|
@inherited_controller = inherited_controller
|
|
62
|
+
@inherited_lambda = inherited_lambda
|
|
62
63
|
end
|
|
63
64
|
|
|
64
|
-
def resources(name, options = {})
|
|
65
|
+
def resources(name, options = {}, &block)
|
|
65
66
|
resource_name = name.to_s
|
|
66
67
|
singular = @gateway.send(:singularize, resource_name)
|
|
67
68
|
param_name = options[:param] || "#{singular}_id"
|
|
69
|
+
options = options.merge(tables: [resource_name.to_sym]) unless options.key?(:tables)
|
|
68
70
|
options = merge_inherited_options(options)
|
|
69
|
-
options = @gateway.send(:auto_infer_tables, resource_name, options)
|
|
70
71
|
resource_options = options.merge(route_type: :resources)
|
|
71
72
|
actions = @gateway.send(:determine_actions, options)
|
|
72
73
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
if actions.include?(:show)
|
|
76
|
-
@gateway.send(:add_route, :get, "#{@prefix}/#{resource_name}/{#{param_name}}",
|
|
77
|
-
resource_options)
|
|
78
|
-
end
|
|
79
|
-
if actions.include?(:update)
|
|
80
|
-
@gateway.send(:add_route, :put, "#{@prefix}/#{resource_name}/{#{param_name}}",
|
|
81
|
-
resource_options)
|
|
82
|
-
end
|
|
83
|
-
return unless actions.include?(:destroy)
|
|
74
|
+
add_nested_resource_routes(resource_name, param_name, resource_options, actions)
|
|
75
|
+
return unless block
|
|
84
76
|
|
|
85
|
-
|
|
86
|
-
|
|
77
|
+
nested_member_prefix = "#{@prefix}/#{resource_name}/{#{param_name}}"
|
|
78
|
+
nested_collection_prefix = "#{@prefix}/#{resource_name}"
|
|
79
|
+
nested_builder = NestedResourceBuilder.new(@gateway, nested_member_prefix, nested_collection_prefix,
|
|
80
|
+
inherited_tables: Array(options[:tables] || []),
|
|
81
|
+
inherited_auth: options[:auth] || @inherited_auth,
|
|
82
|
+
inherited_controller: @inherited_controller,
|
|
83
|
+
inherited_lambda: @inherited_lambda)
|
|
84
|
+
nested_builder.instance_eval(&block)
|
|
87
85
|
end
|
|
88
86
|
|
|
89
87
|
def member(&)
|
|
90
88
|
MemberCollectionBuilder.new(@gateway, @prefix, @inherited_tables, @inherited_auth,
|
|
91
|
-
@inherited_controller).instance_eval(&)
|
|
89
|
+
@inherited_controller, @inherited_lambda).instance_eval(&)
|
|
92
90
|
end
|
|
93
91
|
|
|
94
92
|
def collection(&)
|
|
95
93
|
MemberCollectionBuilder.new(@gateway, @collection_prefix, @inherited_tables,
|
|
96
|
-
@inherited_auth, @inherited_controller).instance_eval(&)
|
|
94
|
+
@inherited_auth, @inherited_controller, @inherited_lambda).instance_eval(&)
|
|
97
95
|
end
|
|
98
96
|
|
|
99
97
|
%i[get post put delete patch].each do |method|
|
|
@@ -107,6 +105,40 @@ module Belt
|
|
|
107
105
|
|
|
108
106
|
private
|
|
109
107
|
|
|
108
|
+
def add_nested_resource_routes(resource_name, param_name, resource_options, actions)
|
|
109
|
+
if actions.include?(:index)
|
|
110
|
+
@gateway.send(:add_route, :get, "#{@prefix}/#{resource_name}",
|
|
111
|
+
resolve_request_model_for(resource_options, :index))
|
|
112
|
+
end
|
|
113
|
+
if actions.include?(:create)
|
|
114
|
+
@gateway.send(:add_route, :post, "#{@prefix}/#{resource_name}",
|
|
115
|
+
resolve_request_model_for(resource_options, :create))
|
|
116
|
+
end
|
|
117
|
+
if actions.include?(:show)
|
|
118
|
+
@gateway.send(:add_route, :get, "#{@prefix}/#{resource_name}/{#{param_name}}",
|
|
119
|
+
resolve_request_model_for(resource_options, :show))
|
|
120
|
+
end
|
|
121
|
+
if actions.include?(:update)
|
|
122
|
+
@gateway.send(:add_route, :put, "#{@prefix}/#{resource_name}/{#{param_name}}",
|
|
123
|
+
resolve_request_model_for(resource_options, :update))
|
|
124
|
+
end
|
|
125
|
+
return unless actions.include?(:destroy)
|
|
126
|
+
|
|
127
|
+
@gateway.send(:add_route, :delete, "#{@prefix}/#{resource_name}/{#{param_name}}",
|
|
128
|
+
resolve_request_model_for(resource_options, :destroy))
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
def resolve_request_model_for(options, action)
|
|
132
|
+
rm = options[:request_model]
|
|
133
|
+
case rm
|
|
134
|
+
when Hash
|
|
135
|
+
resolved = rm[action] || rm[action.to_s]
|
|
136
|
+
options.merge(request_model: resolved)
|
|
137
|
+
else
|
|
138
|
+
options
|
|
139
|
+
end
|
|
140
|
+
end
|
|
141
|
+
|
|
110
142
|
def merge_inherited_options(options)
|
|
111
143
|
result = options.dup
|
|
112
144
|
if @inherited_tables.any?
|
|
@@ -115,17 +147,20 @@ module Belt
|
|
|
115
147
|
end
|
|
116
148
|
result[:auth] ||= @inherited_auth if @inherited_auth
|
|
117
149
|
result[:controller] ||= @inherited_controller if @inherited_controller
|
|
150
|
+
result[:lambda] ||= @inherited_lambda if @inherited_lambda
|
|
118
151
|
result
|
|
119
152
|
end
|
|
120
153
|
end
|
|
121
154
|
|
|
122
155
|
class MemberCollectionBuilder
|
|
123
|
-
def initialize(gateway, prefix, inherited_tables, inherited_auth,
|
|
156
|
+
def initialize(gateway, prefix, inherited_tables, inherited_auth, # rubocop:disable Metrics/ParameterLists
|
|
157
|
+
inherited_controller = nil, inherited_lambda = nil)
|
|
124
158
|
@gateway = gateway
|
|
125
159
|
@prefix = prefix
|
|
126
160
|
@inherited_tables = inherited_tables
|
|
127
161
|
@inherited_auth = inherited_auth
|
|
128
162
|
@inherited_controller = inherited_controller
|
|
163
|
+
@inherited_lambda = inherited_lambda
|
|
129
164
|
end
|
|
130
165
|
|
|
131
166
|
%i[get post put delete patch].each do |method|
|
|
@@ -146,6 +181,7 @@ module Belt
|
|
|
146
181
|
end
|
|
147
182
|
result[:auth] ||= @inherited_auth if @inherited_auth
|
|
148
183
|
result[:controller] ||= @inherited_controller if @inherited_controller
|
|
184
|
+
result[:lambda] ||= @inherited_lambda if @inherited_lambda
|
|
149
185
|
result
|
|
150
186
|
end
|
|
151
187
|
end
|
|
@@ -156,7 +192,7 @@ module Belt
|
|
|
156
192
|
def initialize(name, options = {})
|
|
157
193
|
@name = name.to_s
|
|
158
194
|
@routes = []
|
|
159
|
-
@default_auth = options[:auth] || :
|
|
195
|
+
@default_auth = options[:auth] || :none
|
|
160
196
|
@default_lambda = options[:lambda] || name
|
|
161
197
|
@default_cors = options.fetch(:cors, true)
|
|
162
198
|
@default_tables = Array(options[:tables] || [])
|
|
@@ -184,11 +220,7 @@ module Belt
|
|
|
184
220
|
resource_options = options.merge(route_type: :resources)
|
|
185
221
|
actions = determine_actions(options)
|
|
186
222
|
|
|
187
|
-
|
|
188
|
-
add_route(:post, "/#{resource_name}", resource_options) if actions.include?(:create)
|
|
189
|
-
add_route(:get, "/#{resource_name}/{#{param_name}}", resource_options) if actions.include?(:show)
|
|
190
|
-
add_route(:put, "/#{resource_name}/{#{param_name}}", resource_options) if actions.include?(:update)
|
|
191
|
-
add_route(:delete, "/#{resource_name}/{#{param_name}}", resource_options) if actions.include?(:destroy)
|
|
223
|
+
add_resource_routes(resource_name, param_name, resource_options, actions)
|
|
192
224
|
|
|
193
225
|
return unless block_given?
|
|
194
226
|
|
|
@@ -197,9 +229,11 @@ module Belt
|
|
|
197
229
|
resource_tables = Array(options[:tables] || [])
|
|
198
230
|
inherited_tables = (@default_tables + resource_tables).uniq
|
|
199
231
|
inherited_auth = options[:auth] || @default_auth
|
|
232
|
+
inherited_lambda = options[:lambda]
|
|
200
233
|
nested_builder = NestedResourceBuilder.new(self, member_prefix, collection_prefix,
|
|
201
234
|
inherited_tables: inherited_tables,
|
|
202
|
-
inherited_auth: inherited_auth
|
|
235
|
+
inherited_auth: inherited_auth,
|
|
236
|
+
inherited_lambda: inherited_lambda)
|
|
203
237
|
nested_builder.instance_eval(&)
|
|
204
238
|
end
|
|
205
239
|
|
|
@@ -208,10 +242,22 @@ module Belt
|
|
|
208
242
|
actions = determine_actions(options, default: %i[show update destroy])
|
|
209
243
|
resource_options = options.merge(route_type: :resource)
|
|
210
244
|
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
245
|
+
if actions.include?(:show)
|
|
246
|
+
add_route(:get, "/#{resource_name}",
|
|
247
|
+
resolve_request_model_for(resource_options, :show))
|
|
248
|
+
end
|
|
249
|
+
if actions.include?(:update)
|
|
250
|
+
add_route(:put, "/#{resource_name}",
|
|
251
|
+
resolve_request_model_for(resource_options, :update))
|
|
252
|
+
end
|
|
253
|
+
if actions.include?(:destroy)
|
|
254
|
+
add_route(:delete, "/#{resource_name}",
|
|
255
|
+
resolve_request_model_for(resource_options, :destroy))
|
|
256
|
+
end
|
|
257
|
+
return unless actions.include?(:create)
|
|
258
|
+
|
|
259
|
+
add_route(:post, "/#{resource_name}",
|
|
260
|
+
resolve_request_model_for(resource_options, :create))
|
|
215
261
|
end
|
|
216
262
|
|
|
217
263
|
private
|
|
@@ -253,6 +299,39 @@ module Belt
|
|
|
253
299
|
options.merge(tables: [resource_name.to_sym])
|
|
254
300
|
end
|
|
255
301
|
|
|
302
|
+
def add_resource_routes(resource_name, param_name, resource_options, actions)
|
|
303
|
+
if actions.include?(:index)
|
|
304
|
+
add_route(:get, "/#{resource_name}",
|
|
305
|
+
resolve_request_model_for(resource_options, :index))
|
|
306
|
+
end
|
|
307
|
+
if actions.include?(:create)
|
|
308
|
+
add_route(:post, "/#{resource_name}",
|
|
309
|
+
resolve_request_model_for(resource_options, :create))
|
|
310
|
+
end
|
|
311
|
+
if actions.include?(:show)
|
|
312
|
+
add_route(:get, "/#{resource_name}/{#{param_name}}",
|
|
313
|
+
resolve_request_model_for(resource_options, :show))
|
|
314
|
+
end
|
|
315
|
+
if actions.include?(:update)
|
|
316
|
+
add_route(:put, "/#{resource_name}/{#{param_name}}",
|
|
317
|
+
resolve_request_model_for(resource_options, :update))
|
|
318
|
+
end
|
|
319
|
+
return unless actions.include?(:destroy)
|
|
320
|
+
|
|
321
|
+
add_route(:delete, "/#{resource_name}/{#{param_name}}", resolve_request_model_for(resource_options, :destroy))
|
|
322
|
+
end
|
|
323
|
+
|
|
324
|
+
def resolve_request_model_for(options, action)
|
|
325
|
+
rm = options[:request_model]
|
|
326
|
+
case rm
|
|
327
|
+
when Hash
|
|
328
|
+
resolved = rm[action] || rm[action.to_s]
|
|
329
|
+
options.merge(request_model: resolved)
|
|
330
|
+
else
|
|
331
|
+
options
|
|
332
|
+
end
|
|
333
|
+
end
|
|
334
|
+
|
|
256
335
|
def determine_actions(options, default: %i[index create show update destroy])
|
|
257
336
|
if options[:only]
|
|
258
337
|
Array(options[:only])
|
|
@@ -282,11 +361,15 @@ module Belt
|
|
|
282
361
|
@dsl
|
|
283
362
|
end
|
|
284
363
|
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
364
|
+
# Primary DSL keyword: defines an API Gateway with a default Lambda function.
|
|
365
|
+
def gateway(name, options = {}, &)
|
|
366
|
+
gw = Belt::ApiGateway.new(name, options)
|
|
367
|
+
RouteBuilder.new(gw).instance_eval(&) if block_given?
|
|
368
|
+
@dsl.api_gateways << gw
|
|
289
369
|
end
|
|
370
|
+
|
|
371
|
+
# Legacy alias — existing routes files using `namespace` still work.
|
|
372
|
+
alias namespace gateway
|
|
290
373
|
end
|
|
291
374
|
|
|
292
375
|
def routes
|
|
@@ -302,11 +385,40 @@ module Belt
|
|
|
302
385
|
@gateway = gateway
|
|
303
386
|
@scope_prefix = ''
|
|
304
387
|
@scope_module = nil
|
|
388
|
+
@lambda_target = nil
|
|
305
389
|
@scope_auth = nil
|
|
306
390
|
@scope_tables = []
|
|
307
391
|
@scope_controller = nil
|
|
308
392
|
end
|
|
309
393
|
|
|
394
|
+
# Rails-like `namespace` — adds both a path prefix AND a module prefix.
|
|
395
|
+
# Equivalent to: scope path: "admin", module: "admin"
|
|
396
|
+
#
|
|
397
|
+
# Example:
|
|
398
|
+
# namespace :admin do
|
|
399
|
+
# resources :users # → /admin/users, controller: "admin/users"
|
|
400
|
+
# end
|
|
401
|
+
def namespace(name, options = {}, &)
|
|
402
|
+
segment = name.to_s
|
|
403
|
+
merged = { path: segment, module: segment }.merge(options)
|
|
404
|
+
scope(merged, &)
|
|
405
|
+
end
|
|
406
|
+
|
|
407
|
+
# Rails-like `scope` — groups routes with shared options (path prefix, module,
|
|
408
|
+
# auth, tables, controller).
|
|
409
|
+
#
|
|
410
|
+
# Examples:
|
|
411
|
+
# scope path: "admin" do
|
|
412
|
+
# resources :users # → /admin/users
|
|
413
|
+
# end
|
|
414
|
+
#
|
|
415
|
+
# scope module: "v2" do
|
|
416
|
+
# resources :users # → /users, controller: "v2/users"
|
|
417
|
+
# end
|
|
418
|
+
#
|
|
419
|
+
# scope path: "v1", module: "v1", auth: :cognito do
|
|
420
|
+
# resources :posts # → /v1/posts, controller: "v1/posts", auth: cognito
|
|
421
|
+
# end
|
|
310
422
|
def scope(options = {}, &)
|
|
311
423
|
previous_prefix = @scope_prefix
|
|
312
424
|
previous_module = @scope_module
|
|
@@ -314,12 +426,16 @@ module Belt
|
|
|
314
426
|
previous_tables = @scope_tables
|
|
315
427
|
previous_controller = @scope_controller
|
|
316
428
|
|
|
317
|
-
# Nest path segments
|
|
429
|
+
# Nest path segments: scope path: "a" { scope path: "b" } → "a/b"
|
|
318
430
|
if options.key?(:path)
|
|
319
431
|
segment = options[:path].to_s.gsub(%r{^/|/$}, '')
|
|
320
432
|
@scope_prefix = @scope_prefix.to_s.empty? ? segment : "#{@scope_prefix}/#{segment}"
|
|
321
433
|
end
|
|
322
|
-
|
|
434
|
+
# Nest module segments: namespace :admin { namespace :v2 } → "admin/v2"
|
|
435
|
+
if options.key?(:module)
|
|
436
|
+
mod_segment = options[:module].to_s
|
|
437
|
+
@scope_module = @scope_module.to_s.empty? ? mod_segment : "#{@scope_module}/#{mod_segment}"
|
|
438
|
+
end
|
|
323
439
|
@scope_auth = options[:auth] || @scope_auth
|
|
324
440
|
@scope_tables = (@scope_tables + Array(options[:tables] || [])).uniq
|
|
325
441
|
@scope_controller = options[:controller] || @scope_controller
|
|
@@ -333,85 +449,93 @@ module Belt
|
|
|
333
449
|
@scope_controller = previous_controller
|
|
334
450
|
end
|
|
335
451
|
|
|
452
|
+
# Target a different Lambda function for enclosed routes.
|
|
453
|
+
# Routes within this block will have their :lambda field set to `name`.
|
|
454
|
+
# Does NOT affect path prefixes or controller module resolution.
|
|
455
|
+
#
|
|
456
|
+
# Example:
|
|
457
|
+
# gateway :api, auth: :cognito do
|
|
458
|
+
# resources :posts # → lambda: "api"
|
|
459
|
+
#
|
|
460
|
+
# function :onboarding do
|
|
461
|
+
# resources :stuff # → lambda: "onboarding"
|
|
462
|
+
# end
|
|
463
|
+
#
|
|
464
|
+
# function :custom do
|
|
465
|
+
# get '/blah' # → lambda: "custom"
|
|
466
|
+
# end
|
|
467
|
+
# end
|
|
468
|
+
def function(name, options = {}, &)
|
|
469
|
+
previous_lambda = @lambda_target
|
|
470
|
+
@lambda_target = name.to_s
|
|
471
|
+
# function blocks can also carry auth/tables
|
|
472
|
+
previous_auth = @scope_auth
|
|
473
|
+
previous_tables = @scope_tables
|
|
474
|
+
@scope_auth = options[:auth] if options[:auth]
|
|
475
|
+
@scope_tables = (@scope_tables + Array(options[:tables] || [])).uniq
|
|
476
|
+
|
|
477
|
+
instance_eval(&) if block_given?
|
|
478
|
+
|
|
479
|
+
@lambda_target = previous_lambda
|
|
480
|
+
@scope_auth = previous_auth
|
|
481
|
+
@scope_tables = previous_tables
|
|
482
|
+
end
|
|
483
|
+
|
|
336
484
|
%i[get post put delete patch].each do |method|
|
|
337
485
|
define_method(method) do |path, options = {}|
|
|
338
486
|
full_path = build_path(path)
|
|
339
|
-
route_options = options
|
|
340
|
-
route_options[:lambda] ||= @scope_module if @scope_module
|
|
341
|
-
route_options[:auth] ||= @scope_auth if @scope_auth
|
|
342
|
-
route_options[:controller] ||= @scope_controller if @scope_controller
|
|
343
|
-
if @scope_tables.any? || route_options[:tables]
|
|
344
|
-
route_options[:tables] =
|
|
345
|
-
(@scope_tables + Array(route_options[:tables] || [])).uniq
|
|
346
|
-
end
|
|
487
|
+
route_options = apply_scope_to_route(options)
|
|
347
488
|
@gateway.send(method, full_path, route_options)
|
|
348
489
|
end
|
|
349
490
|
end
|
|
350
491
|
|
|
351
|
-
|
|
492
|
+
# Rails-like `root` — defines a GET / route.
|
|
493
|
+
#
|
|
494
|
+
# Examples:
|
|
495
|
+
# root to: "welcome#show"
|
|
496
|
+
# root action: :show, controller: :welcome
|
|
497
|
+
# root "pages#home"
|
|
498
|
+
def root(options_or_target = nil, **options)
|
|
499
|
+
if options_or_target.is_a?(String)
|
|
500
|
+
options[:to] = options_or_target
|
|
501
|
+
elsif options_or_target.is_a?(Hash)
|
|
502
|
+
options = options_or_target.merge(options)
|
|
503
|
+
end
|
|
504
|
+
options[:auth] ||= :none
|
|
505
|
+
route_options = apply_scope_to_route(options)
|
|
506
|
+
@gateway.send(:get, '/', route_options)
|
|
507
|
+
end
|
|
508
|
+
|
|
509
|
+
def resources(name, options = {}, &)
|
|
352
510
|
options = apply_scope_options(options)
|
|
353
511
|
|
|
354
|
-
if @scope_prefix.empty?
|
|
355
|
-
@gateway.resources(name, options, &
|
|
356
|
-
|
|
357
|
-
# When inside a scope, generate routes with the prefix applied.
|
|
358
|
-
# Also set the controller explicitly so inference resolves correctly
|
|
359
|
-
# (e.g., scope "admin" + resources :users → controller "admin/users").
|
|
512
|
+
if @scope_prefix.empty? && @scope_module.nil?
|
|
513
|
+
@gateway.resources(name, options, &)
|
|
514
|
+
elsif @scope_prefix.empty?
|
|
360
515
|
resource_name = name.to_s
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
add_scoped_resource_routes(resource_name, param_name, resource_options, actions)
|
|
368
|
-
|
|
369
|
-
if block
|
|
370
|
-
collection_prefix = build_path("/#{resource_name}")
|
|
371
|
-
member_prefix = build_path("/#{resource_name}/{#{param_name}}")
|
|
372
|
-
resource_tables = Array(options[:tables] || [])
|
|
373
|
-
inherited_tables = (@gateway.default_tables + resource_tables).uniq
|
|
374
|
-
inherited_auth = options[:auth] || @gateway.default_auth
|
|
375
|
-
nested_builder = NestedResourceBuilder.new(@gateway, member_prefix, collection_prefix,
|
|
376
|
-
inherited_tables: inherited_tables,
|
|
377
|
-
inherited_auth: inherited_auth,
|
|
378
|
-
inherited_controller: controller)
|
|
379
|
-
nested_builder.instance_eval(&block)
|
|
380
|
-
end
|
|
516
|
+
controller = determine_scoped_controller(resource_name)
|
|
517
|
+
options = options.merge(controller: controller) unless options[:controller]
|
|
518
|
+
@gateway.resources(name, options, &)
|
|
519
|
+
else
|
|
520
|
+
build_scoped_resources(name, options, &)
|
|
381
521
|
end
|
|
382
522
|
end
|
|
383
523
|
|
|
384
524
|
def resource(name, options = {})
|
|
385
525
|
options = apply_scope_options(options)
|
|
386
526
|
|
|
387
|
-
if @scope_prefix.empty?
|
|
527
|
+
if @scope_prefix.empty? && @scope_module.nil?
|
|
388
528
|
@gateway.resource(name, options)
|
|
389
|
-
|
|
529
|
+
elsif @scope_prefix.empty?
|
|
390
530
|
resource_name = name.to_s
|
|
391
|
-
controller =
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
if actions.include?(:update)
|
|
397
|
-
@gateway.send(:add_route, :put, build_path("/#{resource_name}"),
|
|
398
|
-
resource_options)
|
|
399
|
-
end
|
|
400
|
-
if actions.include?(:destroy)
|
|
401
|
-
@gateway.send(:add_route, :delete, build_path("/#{resource_name}"),
|
|
402
|
-
resource_options)
|
|
403
|
-
end
|
|
404
|
-
if actions.include?(:create)
|
|
405
|
-
@gateway.send(:add_route, :post, build_path("/#{resource_name}"),
|
|
406
|
-
resource_options)
|
|
407
|
-
end
|
|
531
|
+
controller = determine_scoped_controller(resource_name)
|
|
532
|
+
options = options.merge(controller: controller) unless options[:controller]
|
|
533
|
+
@gateway.resource(name, options)
|
|
534
|
+
else
|
|
535
|
+
build_scoped_resource(name, options)
|
|
408
536
|
end
|
|
409
537
|
end
|
|
410
538
|
|
|
411
|
-
def lambda(name, &)
|
|
412
|
-
name
|
|
413
|
-
end
|
|
414
|
-
|
|
415
539
|
def mount(mountable, options = {})
|
|
416
540
|
prefix = options[:at]&.to_s&.gsub(%r{^/|/$}, '') || ''
|
|
417
541
|
extra_tables = Array(options[:tables] || [])
|
|
@@ -456,6 +580,16 @@ module Belt
|
|
|
456
580
|
@scope_prefix.empty? ? path : "/#{@scope_prefix}#{path}"
|
|
457
581
|
end
|
|
458
582
|
|
|
583
|
+
def determine_scoped_controller(resource_name)
|
|
584
|
+
if @scope_module && !@scope_module.empty?
|
|
585
|
+
"#{@scope_module}/#{resource_name}"
|
|
586
|
+
elsif !@scope_prefix.empty?
|
|
587
|
+
"#{@scope_prefix}/#{resource_name}"
|
|
588
|
+
else
|
|
589
|
+
resource_name
|
|
590
|
+
end
|
|
591
|
+
end
|
|
592
|
+
|
|
459
593
|
def determine_scoped_actions(options, default: %i[index create show update destroy])
|
|
460
594
|
if options[:only]
|
|
461
595
|
Array(options[:only])
|
|
@@ -467,23 +601,107 @@ module Belt
|
|
|
467
601
|
end
|
|
468
602
|
|
|
469
603
|
def add_scoped_resource_routes(resource_name, param_name, resource_options, actions)
|
|
470
|
-
|
|
471
|
-
|
|
604
|
+
if actions.include?(:index)
|
|
605
|
+
@gateway.send(:add_route, :get, build_path("/#{resource_name}"),
|
|
606
|
+
resolve_request_model_for(resource_options, :index))
|
|
607
|
+
end
|
|
608
|
+
if actions.include?(:create)
|
|
609
|
+
@gateway.send(:add_route, :post, build_path("/#{resource_name}"),
|
|
610
|
+
resolve_request_model_for(resource_options, :create))
|
|
611
|
+
end
|
|
472
612
|
if actions.include?(:show)
|
|
473
|
-
@gateway.send(:add_route, :get, build_path("/#{resource_name}/{#{param_name}}"),
|
|
613
|
+
@gateway.send(:add_route, :get, build_path("/#{resource_name}/{#{param_name}}"),
|
|
614
|
+
resolve_request_model_for(resource_options, :show))
|
|
474
615
|
end
|
|
475
616
|
if actions.include?(:update)
|
|
476
|
-
@gateway.send(:add_route, :put, build_path("/#{resource_name}/{#{param_name}}"),
|
|
617
|
+
@gateway.send(:add_route, :put, build_path("/#{resource_name}/{#{param_name}}"),
|
|
618
|
+
resolve_request_model_for(resource_options, :update))
|
|
477
619
|
end
|
|
478
620
|
return unless actions.include?(:destroy)
|
|
479
621
|
|
|
480
|
-
@gateway.send(:add_route, :delete, build_path("/#{resource_name}/{#{param_name}}"),
|
|
622
|
+
@gateway.send(:add_route, :delete, build_path("/#{resource_name}/{#{param_name}}"),
|
|
623
|
+
resolve_request_model_for(resource_options, :destroy))
|
|
624
|
+
end
|
|
625
|
+
|
|
626
|
+
def resolve_request_model_for(options, action)
|
|
627
|
+
rm = options[:request_model]
|
|
628
|
+
case rm
|
|
629
|
+
when Hash
|
|
630
|
+
resolved = rm[action] || rm[action.to_s]
|
|
631
|
+
options.merge(request_model: resolved)
|
|
632
|
+
else
|
|
633
|
+
options
|
|
634
|
+
end
|
|
635
|
+
end
|
|
636
|
+
|
|
637
|
+
def build_scoped_resources(name, options, &block)
|
|
638
|
+
resource_name = name.to_s
|
|
639
|
+
singular = Belt::Inflector.singularize(resource_name)
|
|
640
|
+
param_name = options[:param] || "#{singular}_id"
|
|
641
|
+
controller = options[:controller] || determine_scoped_controller(resource_name)
|
|
642
|
+
resource_options = options.merge(route_type: :resources, controller: controller)
|
|
643
|
+
actions = determine_scoped_actions(options)
|
|
644
|
+
|
|
645
|
+
add_scoped_resource_routes(resource_name, param_name, resource_options, actions)
|
|
646
|
+
build_nested_resource_block(resource_name, param_name, options, controller, &block) if block
|
|
647
|
+
end
|
|
648
|
+
|
|
649
|
+
def build_nested_resource_block(resource_name, param_name, options, controller, &)
|
|
650
|
+
collection_prefix = build_path("/#{resource_name}")
|
|
651
|
+
member_prefix = build_path("/#{resource_name}/{#{param_name}}")
|
|
652
|
+
resource_tables = Array(options[:tables] || [])
|
|
653
|
+
inherited_tables = (@gateway.default_tables + resource_tables).uniq
|
|
654
|
+
inherited_auth = options[:auth] || @gateway.default_auth
|
|
655
|
+
nested_builder = NestedResourceBuilder.new(@gateway, member_prefix, collection_prefix,
|
|
656
|
+
inherited_tables: inherited_tables,
|
|
657
|
+
inherited_auth: inherited_auth,
|
|
658
|
+
inherited_controller: controller,
|
|
659
|
+
inherited_lambda: @lambda_target)
|
|
660
|
+
nested_builder.instance_eval(&)
|
|
661
|
+
end
|
|
662
|
+
|
|
663
|
+
def build_scoped_resource(name, options)
|
|
664
|
+
resource_name = name.to_s
|
|
665
|
+
controller = options[:controller] || determine_scoped_controller(resource_name)
|
|
666
|
+
resource_options = options.merge(route_type: :resource, controller: controller)
|
|
667
|
+
actions = determine_scoped_actions(options, default: %i[show update destroy create])
|
|
668
|
+
|
|
669
|
+
if actions.include?(:show)
|
|
670
|
+
@gateway.send(:add_route, :get, build_path("/#{resource_name}"),
|
|
671
|
+
resolve_request_model_for(resource_options, :show))
|
|
672
|
+
end
|
|
673
|
+
if actions.include?(:update)
|
|
674
|
+
@gateway.send(:add_route, :put, build_path("/#{resource_name}"),
|
|
675
|
+
resolve_request_model_for(resource_options, :update))
|
|
676
|
+
end
|
|
677
|
+
if actions.include?(:destroy)
|
|
678
|
+
@gateway.send(:add_route, :delete, build_path("/#{resource_name}"),
|
|
679
|
+
resolve_request_model_for(resource_options, :destroy))
|
|
680
|
+
end
|
|
681
|
+
return unless actions.include?(:create)
|
|
682
|
+
|
|
683
|
+
@gateway.send(:add_route, :post, build_path("/#{resource_name}"),
|
|
684
|
+
resolve_request_model_for(resource_options, :create))
|
|
685
|
+
end
|
|
686
|
+
|
|
687
|
+
def apply_scope_to_route(options)
|
|
688
|
+
route_options = options.dup
|
|
689
|
+
# Lambda target: only explicit function block affects it
|
|
690
|
+
route_options[:lambda] ||= @lambda_target if @lambda_target
|
|
691
|
+
route_options[:auth] ||= @scope_auth if @scope_auth
|
|
692
|
+
route_options[:controller] ||= @scope_controller if @scope_controller
|
|
693
|
+
if @scope_tables.any? || route_options[:tables]
|
|
694
|
+
route_options[:tables] =
|
|
695
|
+
(@scope_tables + Array(route_options[:tables] || [])).uniq
|
|
696
|
+
end
|
|
697
|
+
route_options
|
|
481
698
|
end
|
|
482
699
|
|
|
483
700
|
def apply_scope_options(options)
|
|
484
701
|
result = options.dup
|
|
485
702
|
result[:auth] ||= @scope_auth if @scope_auth
|
|
486
|
-
|
|
703
|
+
# Lambda target: only explicit function block affects it
|
|
704
|
+
result[:lambda] ||= @lambda_target if @lambda_target
|
|
487
705
|
result[:controller] ||= @scope_controller if @scope_controller
|
|
488
706
|
result[:tables] = (@scope_tables + Array(result[:tables] || [])).uniq if @scope_tables.any? || result[:tables]
|
|
489
707
|
result
|
data/lib/belt/version.rb
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Declarative frontend env map for belt.
|
|
2
2
|
# Left: env var your build tool reads. Right: terraform output name.
|
|
3
|
-
# Copy to frontend
|
|
3
|
+
# Copy to <frontend>/env.yml (or .belt/frontend_env.yml for the default frontend/) and adjust.
|
|
4
4
|
#
|
|
5
5
|
# belt deploy frontend <env> → injects these into npm run build
|
|
6
6
|
# belt frontend env <env> → smart-merges into frontend/.env for local dev
|
|
@@ -9,5 +9,5 @@
|
|
|
9
9
|
|
|
10
10
|
VITE_API_URL: api_url
|
|
11
11
|
# VITE_COGNITO_USER_POOL_ID: cognito_user_pool_id
|
|
12
|
-
# VITE_COGNITO_CLIENT_ID:
|
|
12
|
+
# VITE_COGNITO_CLIENT_ID: cognito_client_id
|
|
13
13
|
# VITE_AWS_REGION: cognito_region
|