jets 1.9.32 → 2.0.0
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 +9 -0
- data/lib/jets/application/defaults.rb +6 -0
- data/lib/jets/cli.rb +9 -0
- data/lib/jets/commands/help/generate.md +25 -19
- data/lib/jets/commands/main.rb +3 -3
- data/lib/jets/commands/rake_tasks.rb +3 -0
- data/lib/jets/commands/templates/skeleton/app/views/layouts/application.html.erb.tt +1 -0
- data/lib/jets/commands/templates/skeleton/config/application.rb.tt +4 -0
- data/lib/jets/commands/templates/webpacker/app/javascript/src/jets/crud.js +3 -0
- data/lib/jets/commands/upgrade.rb +42 -109
- data/lib/jets/commands/upgrade/version1.rb +136 -0
- data/lib/jets/controller/base.rb +16 -0
- data/lib/jets/controller/error.rb +4 -0
- data/lib/jets/controller/error/invalid_authenticity_token.rb +6 -0
- data/lib/jets/controller/forgery_protection.rb +43 -0
- data/lib/jets/controller/middleware/local.rb +3 -3
- data/lib/jets/controller/middleware/local/route_matcher.rb +1 -1
- data/lib/jets/controller/middleware/main.rb +7 -1
- data/lib/jets/controller/rack/adapter.rb +1 -1
- data/lib/jets/controller/rack/env.rb +1 -1
- data/lib/jets/controller/rendering/rack_renderer.rb +44 -37
- data/lib/jets/controller/stage.rb +2 -1
- data/lib/jets/generator.rb +72 -8
- data/lib/jets/generator/templates/active_job/job/templates/application_job.rb.tt +6 -0
- data/lib/jets/generator/templates/active_job/job/templates/job.rb.tt +8 -0
- data/lib/jets/generator/templates/erb/scaffold/_form.html.erb +11 -16
- data/lib/jets/generator/templates/erb/scaffold/edit.html.erb +2 -2
- data/lib/jets/generator/templates/erb/scaffold/index.html.erb +5 -5
- data/lib/jets/generator/templates/erb/scaffold/new.html.erb +1 -1
- data/lib/jets/generator/templates/erb/scaffold/show.html.erb +3 -3
- data/lib/jets/generator/templates/rails/scaffold_controller/controller.rb +5 -5
- data/lib/jets/internal/app/controllers/jets/rack_controller.rb +1 -0
- data/lib/jets/overrides/rails.rb +2 -1
- data/lib/jets/overrides/rails/action_controller.rb +12 -0
- data/lib/jets/overrides/rails/url_helper.rb +66 -5
- data/lib/jets/resource/api_gateway/rest_api/routes/change/base.rb +1 -1
- data/lib/jets/resource/api_gateway/rest_api/routes/collision.rb +1 -1
- data/lib/jets/router.rb +32 -46
- data/lib/jets/router/dsl.rb +136 -0
- data/lib/jets/router/error.rb +4 -0
- data/lib/jets/router/helpers.rb +4 -0
- data/lib/jets/router/helpers/core_helper.rb +17 -0
- data/lib/jets/router/helpers/named_routes_helper.rb +8 -0
- data/lib/jets/router/method_creator.rb +54 -0
- data/lib/jets/router/method_creator/code.rb +98 -0
- data/lib/jets/router/method_creator/edit.rb +7 -0
- data/lib/jets/router/method_creator/generic.rb +11 -0
- data/lib/jets/router/method_creator/index.rb +42 -0
- data/lib/jets/router/method_creator/new.rb +7 -0
- data/lib/jets/router/method_creator/root.rb +15 -0
- data/lib/jets/router/method_creator/show.rb +7 -0
- data/lib/jets/router/resources/base.rb +7 -0
- data/lib/jets/router/resources/filter.rb +15 -0
- data/lib/jets/router/resources/options.rb +13 -0
- data/lib/jets/router/route.rb +226 -0
- data/lib/jets/router/scope.rb +65 -4
- data/lib/jets/router/util.rb +38 -0
- data/lib/jets/turbo/project/config/application.rb +1 -0
- data/lib/jets/version.rb +1 -1
- metadata +26 -2
- data/lib/jets/route.rb +0 -166
@@ -0,0 +1,17 @@
|
|
1
|
+
module Jets::Router::Helpers
|
2
|
+
module CoreHelper
|
3
|
+
# Used for form_for helper
|
4
|
+
def polymorphic_path(record, _)
|
5
|
+
url_for(record)
|
6
|
+
end
|
7
|
+
|
8
|
+
# override helper delegates to point to jets controller
|
9
|
+
# TODO: params is weird
|
10
|
+
CONTROLLER_DELEGATES = %w[session response headers]
|
11
|
+
CONTROLLER_DELEGATES.each do |meth|
|
12
|
+
define_method meth do
|
13
|
+
@_jets[:controller].send(meth)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
class Jets::Router
|
2
|
+
class MethodCreator
|
3
|
+
include Util
|
4
|
+
|
5
|
+
def initialize(options, scope)
|
6
|
+
@options, @scope = options, scope
|
7
|
+
@controller, @action = get_controller_action(@options)
|
8
|
+
end
|
9
|
+
|
10
|
+
def define_url_helper!
|
11
|
+
return unless @options[:method] == :get
|
12
|
+
|
13
|
+
if %w[index new show edit].include?(@action)
|
14
|
+
create_method(@action)
|
15
|
+
else
|
16
|
+
create_method("generic")
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
# Examples:
|
21
|
+
#
|
22
|
+
# posts_path: path: 'posts'
|
23
|
+
# admin_posts_path: prefix: 'admin', path: 'posts'
|
24
|
+
# new_post_path
|
25
|
+
#
|
26
|
+
def create_method(action)
|
27
|
+
# Code eventually does this:
|
28
|
+
#
|
29
|
+
# code = Jets::Router::MethodCreator::Edit.new
|
30
|
+
# def_meth code.path_method
|
31
|
+
#
|
32
|
+
class_name = "Jets::Router::MethodCreator::#{action.camelize}"
|
33
|
+
klass = class_name.constantize # Index, Show, Edit, New
|
34
|
+
code = klass.new(@options, @scope, @controller)
|
35
|
+
|
36
|
+
# puts "define_#{action}_methods:".color(:yellow) if code.path_method
|
37
|
+
# puts code.path_method.color(:blue) if code.path_method
|
38
|
+
# puts code.url_method.color(:blue) if code.url_method
|
39
|
+
|
40
|
+
def_meth(code.path_method) if code.path_method
|
41
|
+
def_meth(code.url_method) if code.url_method
|
42
|
+
end
|
43
|
+
|
44
|
+
def create_root_helper
|
45
|
+
code = Jets::Router::MethodCreator::Root.new(@options, @scope, @controller)
|
46
|
+
def_meth(code.path_method)
|
47
|
+
def_meth(code.url_method)
|
48
|
+
end
|
49
|
+
|
50
|
+
def def_meth(str)
|
51
|
+
Jets::Router::Helpers::NamedRoutesHelper.class_eval(str)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,98 @@
|
|
1
|
+
class Jets::Router::MethodCreator
|
2
|
+
class Code
|
3
|
+
include Jets::Router::Util
|
4
|
+
|
5
|
+
def initialize(options, scope, controller, action=nil)
|
6
|
+
@options, @scope, @controller, @action = options, scope, controller, action
|
7
|
+
@path, @as = options[:path], options[:as]
|
8
|
+
end
|
9
|
+
|
10
|
+
def meth_args
|
11
|
+
params = full_path.split('/').select { |x| x.include?(':') }
|
12
|
+
items = params.map { |x| x.sub(':','') }
|
13
|
+
|
14
|
+
items.empty? ? nil : "("+items.join(', ')+")"
|
15
|
+
end
|
16
|
+
|
17
|
+
def meth_result
|
18
|
+
results = full_path.split('/').map do |x|
|
19
|
+
if x.include?(':')
|
20
|
+
variable = x.sub(':','')
|
21
|
+
"\#{#{variable}.to_param}"
|
22
|
+
else
|
23
|
+
x
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
'/' + results.join('/') unless results.empty?
|
28
|
+
end
|
29
|
+
|
30
|
+
def full_path
|
31
|
+
route = Jets::Router::Route.new(@options, @scope)
|
32
|
+
route.compute_path
|
33
|
+
end
|
34
|
+
|
35
|
+
def action
|
36
|
+
@action || self.class.name.split('::').last.downcase
|
37
|
+
end
|
38
|
+
|
39
|
+
def full_as
|
40
|
+
@scope&.full_as
|
41
|
+
end
|
42
|
+
|
43
|
+
# The method_name_leaf is used to generate method names.
|
44
|
+
# Can be nil because sometimes the name is fully acquired from the scope.
|
45
|
+
def method_name_leaf
|
46
|
+
unless %w[resource resources].include?(@scope.from.to_s) && @options[:from_scope]
|
47
|
+
@controller
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def full_meth_name(suffix=nil)
|
52
|
+
as = @as || meth_name
|
53
|
+
name = [as, suffix].compact.join('_')
|
54
|
+
underscore(name)
|
55
|
+
end
|
56
|
+
|
57
|
+
def path_method
|
58
|
+
<<~EOL
|
59
|
+
def #{full_meth_name(:path)}#{meth_args}
|
60
|
+
"#{meth_result}"
|
61
|
+
end
|
62
|
+
EOL
|
63
|
+
end
|
64
|
+
|
65
|
+
def url_method
|
66
|
+
path_method_call = "#{full_meth_name(:path)}#{meth_args}"
|
67
|
+
# Note: It is important lazily get the value of ENV['JETS_HOST'] within the method.
|
68
|
+
# Since it is not set until the requrest goes through the main middleware.
|
69
|
+
<<~EOL
|
70
|
+
def #{full_meth_name(:url)}#{meth_args}
|
71
|
+
"\#{ENV['JETS_HOST']}\#{#{path_method_call}}"
|
72
|
+
end
|
73
|
+
EOL
|
74
|
+
end
|
75
|
+
|
76
|
+
def param_name(name)
|
77
|
+
# split('/').last for case:
|
78
|
+
#
|
79
|
+
# resources :posts, prefix: "articles", only: :index do
|
80
|
+
# resources :comments, only: :new
|
81
|
+
# end
|
82
|
+
#
|
83
|
+
# Since the prefix at the scope level is added to the posts item, which results in:
|
84
|
+
#
|
85
|
+
# param_name("articles/posts")
|
86
|
+
#
|
87
|
+
# We drop the articles prefix portion. The resources items can only be words with no /.
|
88
|
+
#
|
89
|
+
name.to_s.split('/').last.singularize + "_id"
|
90
|
+
end
|
91
|
+
|
92
|
+
private
|
93
|
+
def singularize(s)
|
94
|
+
return unless s # nil
|
95
|
+
s.singularize
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
class Jets::Router::MethodCreator
|
2
|
+
class Index < Code
|
3
|
+
def meth_name
|
4
|
+
# Well this is pretty confusing and tough to follow. TODO: figure out how to improve this.
|
5
|
+
#
|
6
|
+
# Example 1:
|
7
|
+
#
|
8
|
+
# resources :users, only: [] do
|
9
|
+
# resources :posts, only: :index
|
10
|
+
# end
|
11
|
+
#
|
12
|
+
# Results in:
|
13
|
+
#
|
14
|
+
# full_as: user_posts
|
15
|
+
# method_name_leaf: nil
|
16
|
+
#
|
17
|
+
# Example 2:
|
18
|
+
#
|
19
|
+
# resources :users, only: [] do
|
20
|
+
# get "posts", to: "posts#index"
|
21
|
+
# end
|
22
|
+
#
|
23
|
+
# Results in:
|
24
|
+
#
|
25
|
+
# full_as: users
|
26
|
+
# method_name_leaf: posts
|
27
|
+
#
|
28
|
+
# This is because using resources contains all the info we need in parent scopes.
|
29
|
+
# The scope.full_as already has the desired meth_name.
|
30
|
+
#
|
31
|
+
# However, when using the simple create_route methods like get, the parent scope does not contain
|
32
|
+
# all the info we need. In this tricky case, the method_name_leaf is set.
|
33
|
+
# We then have to reconstruct the meth_name.
|
34
|
+
#
|
35
|
+
if method_name_leaf
|
36
|
+
join(singularize(full_as), method_name_leaf) # reconstruct
|
37
|
+
else
|
38
|
+
join(full_as) # construct entirely from scope info
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module Jets::Router::Resources
|
2
|
+
class Filter < Base
|
3
|
+
def yes?(action)
|
4
|
+
return true unless @options[:only] || @options[:except]
|
5
|
+
|
6
|
+
if @options[:only]
|
7
|
+
only = [@options[:only]].flatten.map(&:to_s)
|
8
|
+
only.include?(action.to_s)
|
9
|
+
else # except
|
10
|
+
except = [@options[:except]].flatten.map(&:to_s)
|
11
|
+
!except.include?(action.to_s)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
module Jets::Router::Resources
|
2
|
+
class Options < Base
|
3
|
+
def build(action)
|
4
|
+
controller = @options[:singular_resource] ? @name.to_s.pluralize : @name
|
5
|
+
options = @options.merge(to: "#{controller}##{action}") # important to create a copy of the options
|
6
|
+
# remove special options from getting to create_route. For some reason .slice! doesnt work
|
7
|
+
options.delete(:only)
|
8
|
+
options.delete(:except)
|
9
|
+
options[:from_scope] = true # flag to drop the prefix later in Route#compute_path
|
10
|
+
options
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,226 @@
|
|
1
|
+
# route = Jets::Router::Route.new(
|
2
|
+
# path: "posts",
|
3
|
+
# method: :get,
|
4
|
+
# to: "posts#index",
|
5
|
+
# )
|
6
|
+
class Jets::Router
|
7
|
+
class Route
|
8
|
+
include Util
|
9
|
+
|
10
|
+
CAPTURE_REGEX = "([^/]*)" # as string
|
11
|
+
|
12
|
+
attr_reader :to, :as
|
13
|
+
def initialize(options, scope=Scope.new)
|
14
|
+
@options, @scope = options, scope
|
15
|
+
@path = compute_path
|
16
|
+
@to = compute_to
|
17
|
+
@as = compute_as
|
18
|
+
end
|
19
|
+
|
20
|
+
def compute_path
|
21
|
+
# Note: The @options[:prefix] is missing prefix and is not support via direct create_route.
|
22
|
+
# This is because it can be added directly to the path. IE:
|
23
|
+
#
|
24
|
+
# get "myprefix/posts", to: "posts#index"
|
25
|
+
#
|
26
|
+
# Also, this helps to keep the method creator logic more simple.
|
27
|
+
#
|
28
|
+
prefix = @scope.full_prefix
|
29
|
+
prefix = account_scope(prefix)
|
30
|
+
prefix = account_on(prefix)
|
31
|
+
|
32
|
+
[prefix, @options[:path]].compact.join('/')
|
33
|
+
end
|
34
|
+
|
35
|
+
def account_scope(prefix)
|
36
|
+
return unless prefix
|
37
|
+
return prefix unless @options[:from_scope]
|
38
|
+
|
39
|
+
if @options[:singular_resource]
|
40
|
+
prefix.split('/')[0..-2].join('/')
|
41
|
+
else
|
42
|
+
prefix.split('/')[0..-3].join('/')
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def account_on(prefix)
|
47
|
+
# Tricky @scope.from == :resources since the account_scope already has accounted for it
|
48
|
+
if @options[:on] == :collection && @scope.from == :resources
|
49
|
+
prefix = prefix.split('/')[0..-2].join('/')
|
50
|
+
end
|
51
|
+
prefix == '' ? nil : prefix
|
52
|
+
end
|
53
|
+
|
54
|
+
def compute_to
|
55
|
+
controller, action = get_controller_action(@options)
|
56
|
+
mod = @options[:module] || @scope.full_module
|
57
|
+
controller = [mod, controller].compact.join('/') # add module
|
58
|
+
"#{controller}##{action}"
|
59
|
+
end
|
60
|
+
|
61
|
+
def compute_as
|
62
|
+
return unless @options[:method] == :get || @options[:root]
|
63
|
+
|
64
|
+
controller, action = get_controller_action(@options)
|
65
|
+
klass = if @options[:root]
|
66
|
+
Jets::Router::MethodCreator::Root
|
67
|
+
elsif %w[index edit show new].include?(action.to_s)
|
68
|
+
class_name = "Jets::Router::MethodCreator::#{action.camelize}"
|
69
|
+
class_name.constantize # Index, Show, Edit, New
|
70
|
+
else
|
71
|
+
Jets::Router::MethodCreator::Generic
|
72
|
+
end
|
73
|
+
|
74
|
+
klass.new(@options, @scope, controller).full_meth_name(nil)
|
75
|
+
end
|
76
|
+
|
77
|
+
# IE: standard: posts/:id/edit
|
78
|
+
# api_gateway: posts/{id}/edit
|
79
|
+
def path(format=:jets)
|
80
|
+
case format
|
81
|
+
when :api_gateway
|
82
|
+
api_gateway_format(@path)
|
83
|
+
when :raw
|
84
|
+
@path
|
85
|
+
else # jets format
|
86
|
+
ensure_jets_format(@path)
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
def method
|
91
|
+
@options[:method].to_s.upcase
|
92
|
+
end
|
93
|
+
|
94
|
+
def internal?
|
95
|
+
!!@options[:internal]
|
96
|
+
end
|
97
|
+
|
98
|
+
def homepage?
|
99
|
+
path == ''
|
100
|
+
end
|
101
|
+
|
102
|
+
# IE: PostsController
|
103
|
+
def controller_name
|
104
|
+
to.sub(/#.*/,'').camelize + "Controller"
|
105
|
+
end
|
106
|
+
|
107
|
+
# IE: index
|
108
|
+
def action_name
|
109
|
+
to.sub(/.*#/,'')
|
110
|
+
end
|
111
|
+
|
112
|
+
# Checks to see if the corresponding controller exists. Useful to validate routes
|
113
|
+
# before deploying to CloudFormation and then rolling back.
|
114
|
+
def valid?
|
115
|
+
controller_class = begin
|
116
|
+
controller_name.constantize
|
117
|
+
rescue NameError
|
118
|
+
return false
|
119
|
+
end
|
120
|
+
controller_class.lambda_functions.include?(action_name.to_sym)
|
121
|
+
end
|
122
|
+
|
123
|
+
# Extracts the path parameters from the actual path
|
124
|
+
# Only supports extracting 1 parameter. So:
|
125
|
+
#
|
126
|
+
# actual_path: posts/tung/edit
|
127
|
+
# route.path: posts/:id/edit
|
128
|
+
#
|
129
|
+
# Returns:
|
130
|
+
# { id: "tung" }
|
131
|
+
def extract_parameters(actual_path)
|
132
|
+
if path.include?(':')
|
133
|
+
extract_parameters_capture(actual_path)
|
134
|
+
elsif path.include?('*')
|
135
|
+
extract_parameters_proxy(actual_path)
|
136
|
+
else
|
137
|
+
# Lambda AWS_PROXY sets null to the input request when there are no path parmeters
|
138
|
+
nil
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
def extract_parameters_proxy(actual_path)
|
143
|
+
# changes path to a string used for a regexp
|
144
|
+
# others/*proxy => others\/(.*)
|
145
|
+
# nested/others/*proxy => nested/others\/(.*)
|
146
|
+
if path.include?('/')
|
147
|
+
leading_path = path.split('/')[0..-2].join('/') # drop last segment
|
148
|
+
# leading_path: nested/others
|
149
|
+
# capture everything after the leading_path as the value
|
150
|
+
regexp = Regexp.new("#{leading_path}/(.*)")
|
151
|
+
value = actual_path.match(regexp)[1]
|
152
|
+
else
|
153
|
+
value = actual_path
|
154
|
+
end
|
155
|
+
|
156
|
+
# the last segment without the '*' is the key
|
157
|
+
proxy_segment = path.split('/').last # last segment is the proxy segment
|
158
|
+
# proxy_segment: *proxy
|
159
|
+
key = proxy_segment.sub('*','')
|
160
|
+
|
161
|
+
{ key => value }
|
162
|
+
end
|
163
|
+
|
164
|
+
def extract_parameters_capture(actual_path)
|
165
|
+
# changes path to a string used for a regexp
|
166
|
+
# posts/:id/edit => posts\/(.*)\/edit
|
167
|
+
labels = []
|
168
|
+
regexp_string = path.split('/').map do |s|
|
169
|
+
if s.start_with?(':')
|
170
|
+
labels << s.delete_prefix(':')
|
171
|
+
CAPTURE_REGEX
|
172
|
+
else
|
173
|
+
s
|
174
|
+
end
|
175
|
+
end.join('\/')
|
176
|
+
# make sure beginning and end of the string matches
|
177
|
+
regexp_string = "^#{regexp_string}$"
|
178
|
+
regexp = Regexp.new(regexp_string)
|
179
|
+
|
180
|
+
values = regexp.match(actual_path).captures
|
181
|
+
labels.map do |next_label|
|
182
|
+
[next_label, values.delete_at(0)]
|
183
|
+
end.to_h
|
184
|
+
end
|
185
|
+
|
186
|
+
def authorization_type
|
187
|
+
@options[:authorization_type]
|
188
|
+
end
|
189
|
+
|
190
|
+
private
|
191
|
+
def ensure_jets_format(path)
|
192
|
+
path.split('/').map do |s|
|
193
|
+
if s =~ /^\{/ and s =~ /\+\}$/
|
194
|
+
s.sub(/^\{/, '*').sub(/\+\}$/,'') # {proxy+} => *proxy
|
195
|
+
elsif s =~ /^\{/ and s =~ /\}$/
|
196
|
+
s.sub('{',':').sub(/\}$/,'') # {id} => :id
|
197
|
+
else
|
198
|
+
s
|
199
|
+
end
|
200
|
+
end.join('/')
|
201
|
+
end
|
202
|
+
|
203
|
+
def api_gateway_format(path)
|
204
|
+
path.split('/')
|
205
|
+
.map {|s| transform_capture(s) }
|
206
|
+
.map {|s| transform_proxy(s) }
|
207
|
+
.join('/')
|
208
|
+
end
|
209
|
+
|
210
|
+
def transform_capture(text)
|
211
|
+
if text.starts_with?(':')
|
212
|
+
text = text.sub(':','')
|
213
|
+
text = "{#{text}}"
|
214
|
+
end
|
215
|
+
text
|
216
|
+
end
|
217
|
+
|
218
|
+
def transform_proxy(text)
|
219
|
+
if text.starts_with?('*')
|
220
|
+
text = text.sub('*','')
|
221
|
+
text = "{#{text}+}"
|
222
|
+
end
|
223
|
+
text
|
224
|
+
end
|
225
|
+
end
|
226
|
+
end
|