belt 0.2.19 → 0.3.1
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.
Potentially problematic release.
This version of belt might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/CHANGELOG.md +47 -0
- data/README.md +42 -3
- data/lib/belt/action_router.rb +21 -17
- data/lib/belt/cli/app_detection.rb +4 -2
- data/lib/belt/cli/auth_command.rb +75 -4
- data/lib/belt/cli/console_command.rb +17 -2
- data/lib/belt/cli/deploy_command.rb +10 -2
- data/lib/belt/cli/destroy_command.rb +3 -1
- data/lib/belt/cli/doctor_command.rb +166 -22
- data/lib/belt/cli/generate_command.rb +37 -37
- data/lib/belt/cli/index_command.rb +192 -0
- data/lib/belt/cli/lambda_config_command.rb +2 -1
- data/lib/belt/cli/routes_command/route_inference.rb +4 -3
- data/lib/belt/cli/routes_command.rb +0 -19
- data/lib/belt/cli/tables_command.rb +3 -1
- data/lib/belt/cli/views_command.rb +3 -9
- data/lib/belt/cli.rb +1 -0
- data/lib/belt/inflector.rb +8 -0
- data/lib/belt/route_dsl.rb +196 -85
- data/lib/belt/version.rb +1 -1
- data/lib/templates/generate/auth/cognito.tf.erb +0 -2
- data/lib/templates/generate/auth/frontend/ConfirmEmail.jsx +1 -1
- data/lib/templates/generate/auth/frontend/Login.jsx +2 -4
- data/lib/templates/generate/auth/frontend/SignUp.jsx +1 -1
- data/lib/templates/generate/auth/frontend/auth.css +157 -0
- data/lib/templates/generate/auth/frontend/auth.js +5 -1
- data/lib/templates/generate/model.rb.erb +4 -6
- data/lib/templates/new_app/AGENTS.md.erb +2 -2
- data/lib/templates/new_app/config/lambda/api.yml.erb +1 -0
- data/lib/templates/new_app/config/routes.rb.erb +3 -1
- data/lib/templates/new_app/lambda/api.rb.erb +1 -1
- metadata +31 -1
data/lib/belt/cli.rb
CHANGED
|
@@ -6,6 +6,7 @@ require_relative 'cli/env_resolver'
|
|
|
6
6
|
require_relative 'cli/new_command'
|
|
7
7
|
require_relative 'cli/generate_command'
|
|
8
8
|
require_relative 'cli/destroy_command'
|
|
9
|
+
require_relative 'cli/index_command'
|
|
9
10
|
require_relative 'cli/frontend_command'
|
|
10
11
|
require_relative 'cli/frontend_setup_command'
|
|
11
12
|
require_relative 'cli/frontend_deploy_command'
|
data/lib/belt/inflector.rb
CHANGED
|
@@ -42,6 +42,14 @@ module Belt
|
|
|
42
42
|
word.to_s.split('_').map(&:capitalize).join
|
|
43
43
|
end
|
|
44
44
|
|
|
45
|
+
# "conversation_id" → "conversationId"
|
|
46
|
+
def camelize_lower(word)
|
|
47
|
+
parts = word.to_s.split('_')
|
|
48
|
+
return word if parts.empty?
|
|
49
|
+
|
|
50
|
+
parts.first + parts[1..].map(&:capitalize).join
|
|
51
|
+
end
|
|
52
|
+
|
|
45
53
|
# "BlogPost" → "blog_post"
|
|
46
54
|
def underscore(word)
|
|
47
55
|
word.to_s
|
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,20 @@ module Belt
|
|
|
107
105
|
|
|
108
106
|
private
|
|
109
107
|
|
|
108
|
+
def add_nested_resource_routes(resource_name, param_name, resource_options, actions)
|
|
109
|
+
@gateway.send(:add_route, :get, "#{@prefix}/#{resource_name}", resource_options) if actions.include?(:index)
|
|
110
|
+
@gateway.send(:add_route, :post, "#{@prefix}/#{resource_name}", resource_options) if actions.include?(:create)
|
|
111
|
+
if actions.include?(:show)
|
|
112
|
+
@gateway.send(:add_route, :get, "#{@prefix}/#{resource_name}/{#{param_name}}", resource_options)
|
|
113
|
+
end
|
|
114
|
+
if actions.include?(:update)
|
|
115
|
+
@gateway.send(:add_route, :put, "#{@prefix}/#{resource_name}/{#{param_name}}", resource_options)
|
|
116
|
+
end
|
|
117
|
+
return unless actions.include?(:destroy)
|
|
118
|
+
|
|
119
|
+
@gateway.send(:add_route, :delete, "#{@prefix}/#{resource_name}/{#{param_name}}", resource_options)
|
|
120
|
+
end
|
|
121
|
+
|
|
110
122
|
def merge_inherited_options(options)
|
|
111
123
|
result = options.dup
|
|
112
124
|
if @inherited_tables.any?
|
|
@@ -115,17 +127,20 @@ module Belt
|
|
|
115
127
|
end
|
|
116
128
|
result[:auth] ||= @inherited_auth if @inherited_auth
|
|
117
129
|
result[:controller] ||= @inherited_controller if @inherited_controller
|
|
130
|
+
result[:lambda] ||= @inherited_lambda if @inherited_lambda
|
|
118
131
|
result
|
|
119
132
|
end
|
|
120
133
|
end
|
|
121
134
|
|
|
122
135
|
class MemberCollectionBuilder
|
|
123
|
-
def initialize(gateway, prefix, inherited_tables, inherited_auth,
|
|
136
|
+
def initialize(gateway, prefix, inherited_tables, inherited_auth, # rubocop:disable Metrics/ParameterLists
|
|
137
|
+
inherited_controller = nil, inherited_lambda = nil)
|
|
124
138
|
@gateway = gateway
|
|
125
139
|
@prefix = prefix
|
|
126
140
|
@inherited_tables = inherited_tables
|
|
127
141
|
@inherited_auth = inherited_auth
|
|
128
142
|
@inherited_controller = inherited_controller
|
|
143
|
+
@inherited_lambda = inherited_lambda
|
|
129
144
|
end
|
|
130
145
|
|
|
131
146
|
%i[get post put delete patch].each do |method|
|
|
@@ -146,6 +161,7 @@ module Belt
|
|
|
146
161
|
end
|
|
147
162
|
result[:auth] ||= @inherited_auth if @inherited_auth
|
|
148
163
|
result[:controller] ||= @inherited_controller if @inherited_controller
|
|
164
|
+
result[:lambda] ||= @inherited_lambda if @inherited_lambda
|
|
149
165
|
result
|
|
150
166
|
end
|
|
151
167
|
end
|
|
@@ -197,9 +213,11 @@ module Belt
|
|
|
197
213
|
resource_tables = Array(options[:tables] || [])
|
|
198
214
|
inherited_tables = (@default_tables + resource_tables).uniq
|
|
199
215
|
inherited_auth = options[:auth] || @default_auth
|
|
216
|
+
inherited_lambda = options[:lambda]
|
|
200
217
|
nested_builder = NestedResourceBuilder.new(self, member_prefix, collection_prefix,
|
|
201
218
|
inherited_tables: inherited_tables,
|
|
202
|
-
inherited_auth: inherited_auth
|
|
219
|
+
inherited_auth: inherited_auth,
|
|
220
|
+
inherited_lambda: inherited_lambda)
|
|
203
221
|
nested_builder.instance_eval(&)
|
|
204
222
|
end
|
|
205
223
|
|
|
@@ -282,11 +300,15 @@ module Belt
|
|
|
282
300
|
@dsl
|
|
283
301
|
end
|
|
284
302
|
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
303
|
+
# Primary DSL keyword: defines an API Gateway with a default Lambda function.
|
|
304
|
+
def gateway(name, options = {}, &)
|
|
305
|
+
gw = Belt::ApiGateway.new(name, options)
|
|
306
|
+
RouteBuilder.new(gw).instance_eval(&) if block_given?
|
|
307
|
+
@dsl.api_gateways << gw
|
|
289
308
|
end
|
|
309
|
+
|
|
310
|
+
# Legacy alias — existing routes files using `namespace` still work.
|
|
311
|
+
alias namespace gateway
|
|
290
312
|
end
|
|
291
313
|
|
|
292
314
|
def routes
|
|
@@ -302,11 +324,40 @@ module Belt
|
|
|
302
324
|
@gateway = gateway
|
|
303
325
|
@scope_prefix = ''
|
|
304
326
|
@scope_module = nil
|
|
327
|
+
@lambda_target = nil
|
|
305
328
|
@scope_auth = nil
|
|
306
329
|
@scope_tables = []
|
|
307
330
|
@scope_controller = nil
|
|
308
331
|
end
|
|
309
332
|
|
|
333
|
+
# Rails-like `namespace` — adds both a path prefix AND a module prefix.
|
|
334
|
+
# Equivalent to: scope path: "admin", module: "admin"
|
|
335
|
+
#
|
|
336
|
+
# Example:
|
|
337
|
+
# namespace :admin do
|
|
338
|
+
# resources :users # → /admin/users, controller: "admin/users"
|
|
339
|
+
# end
|
|
340
|
+
def namespace(name, options = {}, &)
|
|
341
|
+
segment = name.to_s
|
|
342
|
+
merged = { path: segment, module: segment }.merge(options)
|
|
343
|
+
scope(merged, &)
|
|
344
|
+
end
|
|
345
|
+
|
|
346
|
+
# Rails-like `scope` — groups routes with shared options (path prefix, module,
|
|
347
|
+
# auth, tables, controller).
|
|
348
|
+
#
|
|
349
|
+
# Examples:
|
|
350
|
+
# scope path: "admin" do
|
|
351
|
+
# resources :users # → /admin/users
|
|
352
|
+
# end
|
|
353
|
+
#
|
|
354
|
+
# scope module: "v2" do
|
|
355
|
+
# resources :users # → /users, controller: "v2/users"
|
|
356
|
+
# end
|
|
357
|
+
#
|
|
358
|
+
# scope path: "v1", module: "v1", auth: :cognito do
|
|
359
|
+
# resources :posts # → /v1/posts, controller: "v1/posts", auth: cognito
|
|
360
|
+
# end
|
|
310
361
|
def scope(options = {}, &)
|
|
311
362
|
previous_prefix = @scope_prefix
|
|
312
363
|
previous_module = @scope_module
|
|
@@ -314,12 +365,16 @@ module Belt
|
|
|
314
365
|
previous_tables = @scope_tables
|
|
315
366
|
previous_controller = @scope_controller
|
|
316
367
|
|
|
317
|
-
# Nest path segments
|
|
368
|
+
# Nest path segments: scope path: "a" { scope path: "b" } → "a/b"
|
|
318
369
|
if options.key?(:path)
|
|
319
370
|
segment = options[:path].to_s.gsub(%r{^/|/$}, '')
|
|
320
371
|
@scope_prefix = @scope_prefix.to_s.empty? ? segment : "#{@scope_prefix}/#{segment}"
|
|
321
372
|
end
|
|
322
|
-
|
|
373
|
+
# Nest module segments: namespace :admin { namespace :v2 } → "admin/v2"
|
|
374
|
+
if options.key?(:module)
|
|
375
|
+
mod_segment = options[:module].to_s
|
|
376
|
+
@scope_module = @scope_module.to_s.empty? ? mod_segment : "#{@scope_module}/#{mod_segment}"
|
|
377
|
+
end
|
|
323
378
|
@scope_auth = options[:auth] || @scope_auth
|
|
324
379
|
@scope_tables = (@scope_tables + Array(options[:tables] || [])).uniq
|
|
325
380
|
@scope_controller = options[:controller] || @scope_controller
|
|
@@ -333,85 +388,76 @@ module Belt
|
|
|
333
388
|
@scope_controller = previous_controller
|
|
334
389
|
end
|
|
335
390
|
|
|
391
|
+
# Target a different Lambda function for enclosed routes.
|
|
392
|
+
# Routes within this block will have their :lambda field set to `name`.
|
|
393
|
+
# Does NOT affect path prefixes or controller module resolution.
|
|
394
|
+
#
|
|
395
|
+
# Example:
|
|
396
|
+
# gateway :api, auth: :cognito do
|
|
397
|
+
# resources :posts # → lambda: "api"
|
|
398
|
+
#
|
|
399
|
+
# function :onboarding do
|
|
400
|
+
# resources :stuff # → lambda: "onboarding"
|
|
401
|
+
# end
|
|
402
|
+
#
|
|
403
|
+
# function :custom do
|
|
404
|
+
# get '/blah' # → lambda: "custom"
|
|
405
|
+
# end
|
|
406
|
+
# end
|
|
407
|
+
def function(name, options = {}, &)
|
|
408
|
+
previous_lambda = @lambda_target
|
|
409
|
+
@lambda_target = name.to_s
|
|
410
|
+
# function blocks can also carry auth/tables
|
|
411
|
+
previous_auth = @scope_auth
|
|
412
|
+
previous_tables = @scope_tables
|
|
413
|
+
@scope_auth = options[:auth] if options[:auth]
|
|
414
|
+
@scope_tables = (@scope_tables + Array(options[:tables] || [])).uniq
|
|
415
|
+
|
|
416
|
+
instance_eval(&) if block_given?
|
|
417
|
+
|
|
418
|
+
@lambda_target = previous_lambda
|
|
419
|
+
@scope_auth = previous_auth
|
|
420
|
+
@scope_tables = previous_tables
|
|
421
|
+
end
|
|
422
|
+
|
|
336
423
|
%i[get post put delete patch].each do |method|
|
|
337
424
|
define_method(method) do |path, options = {}|
|
|
338
425
|
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
|
|
426
|
+
route_options = apply_scope_to_route(options)
|
|
347
427
|
@gateway.send(method, full_path, route_options)
|
|
348
428
|
end
|
|
349
429
|
end
|
|
350
430
|
|
|
351
|
-
def resources(name, options = {}, &
|
|
431
|
+
def resources(name, options = {}, &)
|
|
352
432
|
options = apply_scope_options(options)
|
|
353
433
|
|
|
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").
|
|
434
|
+
if @scope_prefix.empty? && @scope_module.nil?
|
|
435
|
+
@gateway.resources(name, options, &)
|
|
436
|
+
elsif @scope_prefix.empty?
|
|
360
437
|
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
|
|
438
|
+
controller = determine_scoped_controller(resource_name)
|
|
439
|
+
options = options.merge(controller: controller) unless options[:controller]
|
|
440
|
+
@gateway.resources(name, options, &)
|
|
441
|
+
else
|
|
442
|
+
build_scoped_resources(name, options, &)
|
|
381
443
|
end
|
|
382
444
|
end
|
|
383
445
|
|
|
384
446
|
def resource(name, options = {})
|
|
385
447
|
options = apply_scope_options(options)
|
|
386
448
|
|
|
387
|
-
if @scope_prefix.empty?
|
|
449
|
+
if @scope_prefix.empty? && @scope_module.nil?
|
|
388
450
|
@gateway.resource(name, options)
|
|
389
|
-
|
|
451
|
+
elsif @scope_prefix.empty?
|
|
390
452
|
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
|
|
453
|
+
controller = determine_scoped_controller(resource_name)
|
|
454
|
+
options = options.merge(controller: controller) unless options[:controller]
|
|
455
|
+
@gateway.resource(name, options)
|
|
456
|
+
else
|
|
457
|
+
build_scoped_resource(name, options)
|
|
408
458
|
end
|
|
409
459
|
end
|
|
410
460
|
|
|
411
|
-
def lambda(name, &)
|
|
412
|
-
name
|
|
413
|
-
end
|
|
414
|
-
|
|
415
461
|
def mount(mountable, options = {})
|
|
416
462
|
prefix = options[:at]&.to_s&.gsub(%r{^/|/$}, '') || ''
|
|
417
463
|
extra_tables = Array(options[:tables] || [])
|
|
@@ -456,6 +502,16 @@ module Belt
|
|
|
456
502
|
@scope_prefix.empty? ? path : "/#{@scope_prefix}#{path}"
|
|
457
503
|
end
|
|
458
504
|
|
|
505
|
+
def determine_scoped_controller(resource_name)
|
|
506
|
+
if @scope_module && !@scope_module.empty?
|
|
507
|
+
"#{@scope_module}/#{resource_name}"
|
|
508
|
+
elsif !@scope_prefix.empty?
|
|
509
|
+
"#{@scope_prefix}/#{resource_name}"
|
|
510
|
+
else
|
|
511
|
+
resource_name
|
|
512
|
+
end
|
|
513
|
+
end
|
|
514
|
+
|
|
459
515
|
def determine_scoped_actions(options, default: %i[index create show update destroy])
|
|
460
516
|
if options[:only]
|
|
461
517
|
Array(options[:only])
|
|
@@ -480,10 +536,65 @@ module Belt
|
|
|
480
536
|
@gateway.send(:add_route, :delete, build_path("/#{resource_name}/{#{param_name}}"), resource_options)
|
|
481
537
|
end
|
|
482
538
|
|
|
539
|
+
def build_scoped_resources(name, options, &block)
|
|
540
|
+
resource_name = name.to_s
|
|
541
|
+
singular = Belt::Inflector.singularize(resource_name)
|
|
542
|
+
param_name = options[:param] || "#{singular}_id"
|
|
543
|
+
controller = options[:controller] || determine_scoped_controller(resource_name)
|
|
544
|
+
resource_options = options.merge(route_type: :resources, controller: controller)
|
|
545
|
+
actions = determine_scoped_actions(options)
|
|
546
|
+
|
|
547
|
+
add_scoped_resource_routes(resource_name, param_name, resource_options, actions)
|
|
548
|
+
build_nested_resource_block(resource_name, param_name, options, controller, &block) if block
|
|
549
|
+
end
|
|
550
|
+
|
|
551
|
+
def build_nested_resource_block(resource_name, param_name, options, controller, &)
|
|
552
|
+
collection_prefix = build_path("/#{resource_name}")
|
|
553
|
+
member_prefix = build_path("/#{resource_name}/{#{param_name}}")
|
|
554
|
+
resource_tables = Array(options[:tables] || [])
|
|
555
|
+
inherited_tables = (@gateway.default_tables + resource_tables).uniq
|
|
556
|
+
inherited_auth = options[:auth] || @gateway.default_auth
|
|
557
|
+
nested_builder = NestedResourceBuilder.new(@gateway, member_prefix, collection_prefix,
|
|
558
|
+
inherited_tables: inherited_tables,
|
|
559
|
+
inherited_auth: inherited_auth,
|
|
560
|
+
inherited_controller: controller,
|
|
561
|
+
inherited_lambda: @lambda_target)
|
|
562
|
+
nested_builder.instance_eval(&)
|
|
563
|
+
end
|
|
564
|
+
|
|
565
|
+
def build_scoped_resource(name, options)
|
|
566
|
+
resource_name = name.to_s
|
|
567
|
+
controller = options[:controller] || determine_scoped_controller(resource_name)
|
|
568
|
+
resource_options = options.merge(route_type: :resource, controller: controller)
|
|
569
|
+
actions = determine_scoped_actions(options, default: %i[show update destroy create])
|
|
570
|
+
|
|
571
|
+
@gateway.send(:add_route, :get, build_path("/#{resource_name}"), resource_options) if actions.include?(:show)
|
|
572
|
+
@gateway.send(:add_route, :put, build_path("/#{resource_name}"), resource_options) if actions.include?(:update)
|
|
573
|
+
if actions.include?(:destroy)
|
|
574
|
+
@gateway.send(:add_route, :delete, build_path("/#{resource_name}"),
|
|
575
|
+
resource_options)
|
|
576
|
+
end
|
|
577
|
+
@gateway.send(:add_route, :post, build_path("/#{resource_name}"), resource_options) if actions.include?(:create)
|
|
578
|
+
end
|
|
579
|
+
|
|
580
|
+
def apply_scope_to_route(options)
|
|
581
|
+
route_options = options.dup
|
|
582
|
+
# Lambda target: only explicit function block affects it
|
|
583
|
+
route_options[:lambda] ||= @lambda_target if @lambda_target
|
|
584
|
+
route_options[:auth] ||= @scope_auth if @scope_auth
|
|
585
|
+
route_options[:controller] ||= @scope_controller if @scope_controller
|
|
586
|
+
if @scope_tables.any? || route_options[:tables]
|
|
587
|
+
route_options[:tables] =
|
|
588
|
+
(@scope_tables + Array(route_options[:tables] || [])).uniq
|
|
589
|
+
end
|
|
590
|
+
route_options
|
|
591
|
+
end
|
|
592
|
+
|
|
483
593
|
def apply_scope_options(options)
|
|
484
594
|
result = options.dup
|
|
485
595
|
result[:auth] ||= @scope_auth if @scope_auth
|
|
486
|
-
|
|
596
|
+
# Lambda target: only explicit function block affects it
|
|
597
|
+
result[:lambda] ||= @lambda_target if @lambda_target
|
|
487
598
|
result[:controller] ||= @scope_controller if @scope_controller
|
|
488
599
|
result[:tables] = (@scope_tables + Array(result[:tables] || [])).uniq if @scope_tables.any? || result[:tables]
|
|
489
600
|
result
|
data/lib/belt/version.rb
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { useState } from 'react'
|
|
2
|
-
import { signIn, completeNewPassword } from '
|
|
2
|
+
import { signIn, completeNewPassword } from '../../lib/auth'
|
|
3
|
+
import './auth.css'
|
|
3
4
|
|
|
4
5
|
export default function Login({ onLogin }) {
|
|
5
6
|
const [email, setEmail] = useState('')
|
|
@@ -72,9 +73,6 @@ export default function Login({ onLogin }) {
|
|
|
72
73
|
<button type="submit" disabled={loading}>
|
|
73
74
|
{loading ? 'Signing in...' : 'Sign In'}
|
|
74
75
|
</button>
|
|
75
|
-
<p className="auth-link">
|
|
76
|
-
Don't have an account? <a href="/signup">Sign up</a>
|
|
77
|
-
</p>
|
|
78
76
|
</form>
|
|
79
77
|
</div>
|
|
80
78
|
)
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
.auth-page {
|
|
2
|
+
--auth-bg: #f6f8fa;
|
|
3
|
+
--auth-card-bg: #ffffff;
|
|
4
|
+
--auth-border: #d0d7de;
|
|
5
|
+
--auth-text: #1f2328;
|
|
6
|
+
--auth-muted: #656d76;
|
|
7
|
+
--auth-input-bg: #f6f8fa;
|
|
8
|
+
--auth-btn: #2da44e;
|
|
9
|
+
--auth-btn-hover: #2c974b;
|
|
10
|
+
--auth-btn-disabled-bg: #94d3a2;
|
|
11
|
+
--auth-btn-disabled-text: #fff;
|
|
12
|
+
--auth-error-bg: rgba(248, 81, 73, 0.08);
|
|
13
|
+
--auth-error-border: rgba(248, 81, 73, 0.4);
|
|
14
|
+
--auth-error-text: #cf222e;
|
|
15
|
+
--auth-link: #0969da;
|
|
16
|
+
--auth-focus: #0969da;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
@media (prefers-color-scheme: dark) {
|
|
20
|
+
.auth-page {
|
|
21
|
+
--auth-bg: #0d1117;
|
|
22
|
+
--auth-card-bg: #161b22;
|
|
23
|
+
--auth-border: #30363d;
|
|
24
|
+
--auth-text: #e6edf3;
|
|
25
|
+
--auth-muted: #8b949e;
|
|
26
|
+
--auth-input-bg: #0d1117;
|
|
27
|
+
--auth-btn: #238636;
|
|
28
|
+
--auth-btn-hover: #2ea043;
|
|
29
|
+
--auth-btn-disabled-bg: #21262d;
|
|
30
|
+
--auth-btn-disabled-text: #484f58;
|
|
31
|
+
--auth-error-bg: rgba(248, 81, 73, 0.1);
|
|
32
|
+
--auth-error-border: rgba(248, 81, 73, 0.4);
|
|
33
|
+
--auth-error-text: #f85149;
|
|
34
|
+
--auth-link: #58a6ff;
|
|
35
|
+
--auth-focus: #58a6ff;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
.auth-page {
|
|
40
|
+
min-height: 100vh;
|
|
41
|
+
display: flex;
|
|
42
|
+
align-items: center;
|
|
43
|
+
justify-content: center;
|
|
44
|
+
background: var(--auth-bg);
|
|
45
|
+
padding: 1rem;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
.auth-form {
|
|
49
|
+
background: var(--auth-card-bg);
|
|
50
|
+
border: 1px solid var(--auth-border);
|
|
51
|
+
border-radius: 12px;
|
|
52
|
+
padding: 2.5rem;
|
|
53
|
+
width: 100%;
|
|
54
|
+
max-width: 380px;
|
|
55
|
+
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.1);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
.auth-form h2 {
|
|
59
|
+
color: var(--auth-text);
|
|
60
|
+
font-size: 1.5rem;
|
|
61
|
+
margin: 0 0 0.5rem;
|
|
62
|
+
text-align: center;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
.auth-form p {
|
|
66
|
+
color: var(--auth-muted);
|
|
67
|
+
font-size: 0.85rem;
|
|
68
|
+
text-align: center;
|
|
69
|
+
margin: 0 0 1.5rem;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
.auth-form input {
|
|
73
|
+
display: block;
|
|
74
|
+
width: 100%;
|
|
75
|
+
padding: 0.75rem 1rem;
|
|
76
|
+
margin-bottom: 0.75rem;
|
|
77
|
+
background: var(--auth-input-bg);
|
|
78
|
+
border: 1px solid var(--auth-border);
|
|
79
|
+
border-radius: 8px;
|
|
80
|
+
color: var(--auth-text);
|
|
81
|
+
font-size: 0.9rem;
|
|
82
|
+
outline: none;
|
|
83
|
+
transition: border-color 0.2s;
|
|
84
|
+
box-sizing: border-box;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
.auth-form input:focus {
|
|
88
|
+
border-color: var(--auth-focus);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
.auth-form input::placeholder {
|
|
92
|
+
color: var(--auth-muted);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
.auth-form button {
|
|
96
|
+
display: block;
|
|
97
|
+
width: 100%;
|
|
98
|
+
padding: 0.75rem;
|
|
99
|
+
margin-top: 0.5rem;
|
|
100
|
+
background: var(--auth-btn);
|
|
101
|
+
border: none;
|
|
102
|
+
border-radius: 8px;
|
|
103
|
+
color: #fff;
|
|
104
|
+
font-size: 0.9rem;
|
|
105
|
+
font-weight: 600;
|
|
106
|
+
cursor: pointer;
|
|
107
|
+
transition: background 0.2s;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
.auth-form button:hover {
|
|
111
|
+
background: var(--auth-btn-hover);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
.auth-form button:disabled {
|
|
115
|
+
background: var(--auth-btn-disabled-bg);
|
|
116
|
+
color: var(--auth-btn-disabled-text);
|
|
117
|
+
cursor: not-allowed;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
.auth-error {
|
|
121
|
+
background: var(--auth-error-bg);
|
|
122
|
+
border: 1px solid var(--auth-error-border);
|
|
123
|
+
border-radius: 8px;
|
|
124
|
+
color: var(--auth-error-text);
|
|
125
|
+
padding: 0.6rem 0.75rem;
|
|
126
|
+
font-size: 0.8rem;
|
|
127
|
+
margin-bottom: 1rem;
|
|
128
|
+
text-align: center;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
.auth-link {
|
|
132
|
+
color: var(--auth-muted);
|
|
133
|
+
font-size: 0.8rem;
|
|
134
|
+
text-align: center;
|
|
135
|
+
margin-top: 1.25rem !important;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
.auth-link a {
|
|
139
|
+
color: var(--auth-link);
|
|
140
|
+
text-decoration: none;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
.auth-link a:hover {
|
|
144
|
+
text-decoration: underline;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
.auth-btn {
|
|
148
|
+
display: inline-block;
|
|
149
|
+
padding: 0.6rem 1.5rem;
|
|
150
|
+
background: var(--auth-btn);
|
|
151
|
+
border-radius: 8px;
|
|
152
|
+
color: #fff;
|
|
153
|
+
text-decoration: none;
|
|
154
|
+
font-weight: 600;
|
|
155
|
+
font-size: 0.85rem;
|
|
156
|
+
margin-top: 1rem;
|
|
157
|
+
}
|
|
@@ -55,7 +55,11 @@ export async function completeNewPassword(username, newPassword, session) {
|
|
|
55
55
|
ChallengeName: 'NEW_PASSWORD_REQUIRED',
|
|
56
56
|
ClientId: CLIENT_ID,
|
|
57
57
|
Session: session,
|
|
58
|
-
ChallengeResponses: {
|
|
58
|
+
ChallengeResponses: {
|
|
59
|
+
USERNAME: username,
|
|
60
|
+
NEW_PASSWORD: newPassword,
|
|
61
|
+
'userAttributes.email': username
|
|
62
|
+
}
|
|
59
63
|
}))
|
|
60
64
|
setTokens(response.AuthenticationResult)
|
|
61
65
|
return { success: true }
|