rhino_project_core 0.20.0.alpha.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/LICENSE +21 -0
- data/README.md +28 -0
- data/Rakefile +35 -0
- data/app/assets/stripe_flow.png +0 -0
- data/app/controllers/concerns/rhino/authenticated.rb +18 -0
- data/app/controllers/concerns/rhino/error_handling.rb +60 -0
- data/app/controllers/concerns/rhino/paper_trail_whodunnit.rb +11 -0
- data/app/controllers/concerns/rhino/permit.rb +38 -0
- data/app/controllers/concerns/rhino/set_current_user.rb +13 -0
- data/app/controllers/rhino/account_controller.rb +34 -0
- data/app/controllers/rhino/active_model_extension_controller.rb +52 -0
- data/app/controllers/rhino/base_controller.rb +23 -0
- data/app/controllers/rhino/crud_controller.rb +57 -0
- data/app/controllers/rhino/simple_controller.rb +13 -0
- data/app/controllers/rhino/simple_stream_controller.rb +12 -0
- data/app/helpers/rhino/omniauth_helper.rb +67 -0
- data/app/helpers/rhino/policy_helper.rb +42 -0
- data/app/helpers/rhino/segment_helper.rb +62 -0
- data/app/models/rhino/account.rb +13 -0
- data/app/models/rhino/current.rb +7 -0
- data/app/models/rhino/user.rb +44 -0
- data/app/overrides/active_record/autosave_association_override.rb +18 -0
- data/app/overrides/active_record/delegated_type_override.rb +14 -0
- data/app/overrides/activestorage/direct_uploads_controller_override.rb +23 -0
- data/app/overrides/activestorage/redirect_controller_override.rb +21 -0
- data/app/overrides/activestorage/redirect_representation_controller_override.rb +21 -0
- data/app/overrides/devise_token_auth/confirmations_controller_override.rb +14 -0
- data/app/overrides/devise_token_auth/omniauth_callbacks_controller_override.rb +45 -0
- data/app/overrides/devise_token_auth/passwords_controller_override.rb +9 -0
- data/app/overrides/devise_token_auth/registrations_controller_override.rb +20 -0
- data/app/overrides/devise_token_auth/sessions_controller_override.rb +26 -0
- data/app/overrides/devise_token_auth/token_validations_controller_override.rb +18 -0
- data/app/policies/rhino/account_policy.rb +27 -0
- data/app/policies/rhino/active_storage_attachment_policy.rb +16 -0
- data/app/policies/rhino/admin_policy.rb +20 -0
- data/app/policies/rhino/base_policy.rb +72 -0
- data/app/policies/rhino/crud_policy.rb +109 -0
- data/app/policies/rhino/editor_policy.rb +12 -0
- data/app/policies/rhino/global_policy.rb +8 -0
- data/app/policies/rhino/resource_info_policy.rb +9 -0
- data/app/policies/rhino/user_policy.rb +20 -0
- data/app/policies/rhino/viewer_policy.rb +19 -0
- data/app/resources/rhino/info_graph.rb +41 -0
- data/app/resources/rhino/open_api_info.rb +108 -0
- data/config/routes.rb +19 -0
- data/db/migrate/20180101000000_devise_token_auth_create_users.rb +54 -0
- data/db/migrate/20180622142754_add_allow_change_password_to_users.rb +5 -0
- data/db/migrate/20191217010224_add_approved_to_users.rb +7 -0
- data/db/migrate/20200503182019_change_tokens_to_json_b.rb +9 -0
- data/lib/commands/rhino/module/coverage_command.rb +44 -0
- data/lib/commands/rhino/module/dummy_command.rb +43 -0
- data/lib/commands/rhino/module/new_command.rb +34 -0
- data/lib/commands/rhino/module/rails_command.rb +43 -0
- data/lib/commands/rhino/module/test_command.rb +43 -0
- data/lib/generators/rhino/dev/setup/setup_generator.rb +199 -0
- data/lib/generators/rhino/dev/setup/templates/env.client.tt +4 -0
- data/lib/generators/rhino/dev/setup/templates/env.root.tt +1 -0
- data/lib/generators/rhino/dev/setup/templates/env.server.tt +35 -0
- data/lib/generators/rhino/dev/setup/templates/prepare-commit-msg +17 -0
- data/lib/generators/rhino/install/install_generator.rb +24 -0
- data/lib/generators/rhino/install/templates/account.rb +4 -0
- data/lib/generators/rhino/install/templates/rhino.rb +24 -0
- data/lib/generators/rhino/install/templates/user.rb +4 -0
- data/lib/generators/rhino/model/model_generator.rb +96 -0
- data/lib/generators/rhino/module/USAGE +6 -0
- data/lib/generators/rhino/module/module_generator.rb +92 -0
- data/lib/generators/rhino/module/templates/%name%.gemspec.tt +24 -0
- data/lib/generators/rhino/module/templates/lib/%namespaced_name%/engine.rb.tt +18 -0
- data/lib/generators/rhino/module/templates/lib/generators/%namespaced_name%/install/install_generator.rb.tt +12 -0
- data/lib/generators/rhino/module/templates/lib/tasks/%namespaced_name%_tasks.rake.tt +13 -0
- data/lib/generators/rhino/module/templates/test/dummy/app/models/user.rb +4 -0
- data/lib/generators/rhino/module/templates/test/dummy/config/database.yml +25 -0
- data/lib/generators/rhino/module/templates/test/dummy/config/initializers/devise.rb +311 -0
- data/lib/generators/rhino/module/templates/test/dummy/config/initializers/devise_token_auth.rb +71 -0
- data/lib/generators/rhino/module/templates/test/test_helper.rb +54 -0
- data/lib/generators/rhino/policy/policy_generator.rb +33 -0
- data/lib/generators/rhino/policy/templates/policy.rb.tt +46 -0
- data/lib/generators/test_unit/rhino_policy_generator.rb +13 -0
- data/lib/generators/test_unit/templates/policy_test.rb.tt +57 -0
- data/lib/rhino/engine.rb +166 -0
- data/lib/rhino/omniauth/strategies/azure_o_auth2.rb +16 -0
- data/lib/rhino/resource/active_model_extension/backing_store/google_sheet.rb +89 -0
- data/lib/rhino/resource/active_model_extension/backing_store.rb +33 -0
- data/lib/rhino/resource/active_model_extension/describe.rb +38 -0
- data/lib/rhino/resource/active_model_extension/params.rb +70 -0
- data/lib/rhino/resource/active_model_extension/properties.rb +231 -0
- data/lib/rhino/resource/active_model_extension/reference.rb +50 -0
- data/lib/rhino/resource/active_model_extension/routing.rb +15 -0
- data/lib/rhino/resource/active_model_extension/serialization.rb +16 -0
- data/lib/rhino/resource/active_model_extension.rb +38 -0
- data/lib/rhino/resource/active_record_extension/describe.rb +44 -0
- data/lib/rhino/resource/active_record_extension/params.rb +213 -0
- data/lib/rhino/resource/active_record_extension/properties.rb +85 -0
- data/lib/rhino/resource/active_record_extension/properties_describe.rb +228 -0
- data/lib/rhino/resource/active_record_extension/reference.rb +50 -0
- data/lib/rhino/resource/active_record_extension/routing.rb +21 -0
- data/lib/rhino/resource/active_record_extension/search.rb +23 -0
- data/lib/rhino/resource/active_record_extension/serialization.rb +16 -0
- data/lib/rhino/resource/active_record_extension/super_admin.rb +25 -0
- data/lib/rhino/resource/active_record_extension.rb +32 -0
- data/lib/rhino/resource/active_record_tree.rb +50 -0
- data/lib/rhino/resource/active_storage_extension.rb +41 -0
- data/lib/rhino/resource/describe.rb +19 -0
- data/lib/rhino/resource/owner.rb +172 -0
- data/lib/rhino/resource/params.rb +31 -0
- data/lib/rhino/resource/properties.rb +192 -0
- data/lib/rhino/resource/reference.rb +29 -0
- data/lib/rhino/resource/routing.rb +107 -0
- data/lib/rhino/resource/serialization.rb +13 -0
- data/lib/rhino/resource/sieves.rb +36 -0
- data/lib/rhino/resource.rb +55 -0
- data/lib/rhino/sieve/filter.rb +149 -0
- data/lib/rhino/sieve/geospatial.rb +45 -0
- data/lib/rhino/sieve/helpers.rb +11 -0
- data/lib/rhino/sieve/limit.rb +20 -0
- data/lib/rhino/sieve/offset.rb +16 -0
- data/lib/rhino/sieve/order.rb +143 -0
- data/lib/rhino/sieve/search.rb +20 -0
- data/lib/rhino/sieve.rb +159 -0
- data/lib/rhino/test_case/controller.rb +145 -0
- data/lib/rhino/test_case/model.rb +86 -0
- data/lib/rhino/test_case/override.rb +19 -0
- data/lib/rhino/test_case/policy.rb +76 -0
- data/lib/rhino/test_case.rb +11 -0
- data/lib/rhino/version.rb +17 -0
- data/lib/rhino_project_core.rb +131 -0
- data/lib/tasks/rhino.rake +24 -0
- data/lib/tasks/rhino_dev.rake +17 -0
- data/lib/validators/country_validator.rb +11 -0
- data/lib/validators/email_validator.rb +8 -0
- data/lib/validators/ipv4_validator.rb +10 -0
- data/lib/validators/mac_address_validator.rb +9 -0
- metadata +531 -0
@@ -0,0 +1,145 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Rhino
|
4
|
+
module TestCase
|
5
|
+
# rubocop:disable Metrics/ClassLength
|
6
|
+
class ControllerTest < ActionDispatch::IntegrationTest
|
7
|
+
setup :set_cookie_attributes
|
8
|
+
|
9
|
+
protected
|
10
|
+
def users_index(params: {}, headers: {})
|
11
|
+
get "/api/users", params:, headers:
|
12
|
+
end
|
13
|
+
|
14
|
+
def validate_session(params: {}, headers: {})
|
15
|
+
get "/api/auth/validate_token", params:, headers:
|
16
|
+
end
|
17
|
+
|
18
|
+
def sign_out_with_empty_auth_cookie
|
19
|
+
params = {}
|
20
|
+
headers = { "Cookie" => "#{DeviseTokenAuth.cookie_name}=;" }
|
21
|
+
delete "/api/auth/sign_out", params:, headers:
|
22
|
+
end
|
23
|
+
|
24
|
+
def sign_out(params: {}, headers: {})
|
25
|
+
delete "/api/auth/sign_out", params:, headers:
|
26
|
+
end
|
27
|
+
|
28
|
+
def get_api(path, params: {}, headers: {})
|
29
|
+
get path, params:, headers:, xhr: true, as: :json
|
30
|
+
end
|
31
|
+
|
32
|
+
def post_api(path, params: {})
|
33
|
+
post path, params:, xhr: true, as: :json
|
34
|
+
end
|
35
|
+
|
36
|
+
def patch_api(path, params: {}, headers: {})
|
37
|
+
patch path, params:, xhr: true, as: :json, headers:
|
38
|
+
end
|
39
|
+
|
40
|
+
def put_api(path, params: {})
|
41
|
+
put path, params:, xhr: true, as: :json
|
42
|
+
end
|
43
|
+
|
44
|
+
def delete_api(path)
|
45
|
+
delete path, xhr: true, as: :json
|
46
|
+
end
|
47
|
+
|
48
|
+
def head_api(path)
|
49
|
+
head path, xhr: true, as: :json
|
50
|
+
end
|
51
|
+
|
52
|
+
def assert_response_ok
|
53
|
+
assert_equal 200, response.status
|
54
|
+
end
|
55
|
+
|
56
|
+
def assert_response_bad_request
|
57
|
+
assert_equal 400, response.status
|
58
|
+
end
|
59
|
+
|
60
|
+
def assert_response_unauthorized
|
61
|
+
assert_equal 401, response.status
|
62
|
+
end
|
63
|
+
|
64
|
+
def assert_response_forbidden
|
65
|
+
assert_equal 403, response.status
|
66
|
+
end
|
67
|
+
|
68
|
+
def assert_response_not_found
|
69
|
+
assert_equal 404, response.status
|
70
|
+
end
|
71
|
+
|
72
|
+
def assert_response_unprocessable
|
73
|
+
assert_equal 422, response.status
|
74
|
+
end
|
75
|
+
|
76
|
+
def assert_deleted_cookie(cookie_name)
|
77
|
+
assert response.cookies.key?(cookie_name)
|
78
|
+
assert_nil response.cookies[cookie_name]
|
79
|
+
end
|
80
|
+
|
81
|
+
def assert_not_deleted_cookie(cookie_name)
|
82
|
+
has_cookie = response.cookies.key?(cookie_name)
|
83
|
+
cookie_not_blank = response.cookies[cookie_name].present?
|
84
|
+
assert !has_cookie || cookie_not_blank, "Response should either not have the auth cookie present or it should be set to something. Current value is #{response.cookies[cookie_name]}" # rubocop:disable Layout/LineLength
|
85
|
+
end
|
86
|
+
|
87
|
+
def sign_in(user = nil)
|
88
|
+
@current_user = user || create(:user)
|
89
|
+
post "/api/auth/sign_in", params: {
|
90
|
+
email: @current_user.email,
|
91
|
+
password: @current_user.password
|
92
|
+
}, as: :json
|
93
|
+
assert_response_ok
|
94
|
+
headers = response.headers
|
95
|
+
# Use the configured header names
|
96
|
+
assert headers[DeviseTokenAuth.headers_names[:"access-token"]]
|
97
|
+
end
|
98
|
+
|
99
|
+
def parsed_response_ids
|
100
|
+
parsed_response.fetch("results", []).map { |result| result["id"] }
|
101
|
+
end
|
102
|
+
|
103
|
+
def parsed_response
|
104
|
+
JSON.parse response.body
|
105
|
+
end
|
106
|
+
|
107
|
+
def parsed_auth_cookie_header
|
108
|
+
JSON.parse(cookies[DeviseTokenAuth.cookie_name])
|
109
|
+
end
|
110
|
+
|
111
|
+
def empty_auth_cookie_header
|
112
|
+
{ "Cookie" => "#{DeviseTokenAuth.cookie_name}=;" }
|
113
|
+
end
|
114
|
+
|
115
|
+
def current_access_token
|
116
|
+
parsed_auth_cookie["access-token"]
|
117
|
+
end
|
118
|
+
|
119
|
+
def delete_user_tokens(user = nil)
|
120
|
+
(user || @current_user).reload.update tokens: {}
|
121
|
+
end
|
122
|
+
|
123
|
+
def serialized_user(user = @current_user)
|
124
|
+
{
|
125
|
+
"id" => user.id,
|
126
|
+
"provider" => "email",
|
127
|
+
"uid" => user.email,
|
128
|
+
"name" => user.name,
|
129
|
+
"nickname" => user.nickname,
|
130
|
+
"image" => user.image,
|
131
|
+
"email" => user.email,
|
132
|
+
"allow_password_change" => false,
|
133
|
+
"approved" => true
|
134
|
+
}
|
135
|
+
end
|
136
|
+
|
137
|
+
private
|
138
|
+
def set_cookie_attributes
|
139
|
+
DeviseTokenAuth.cookie_attributes[:secure] = false
|
140
|
+
DeviseTokenAuth.cookie_attributes[:domain] = :all
|
141
|
+
end
|
142
|
+
end
|
143
|
+
# rubocop:enable Metrics/ClassLength
|
144
|
+
end
|
145
|
+
end
|
@@ -0,0 +1,86 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Rhino
|
4
|
+
module TestCase
|
5
|
+
class ModelTest < ActiveSupport::TestCase
|
6
|
+
protected
|
7
|
+
def assert_required(attribute)
|
8
|
+
instance = @model.new
|
9
|
+
instance[attribute] = nil
|
10
|
+
assert instance.invalid?
|
11
|
+
assert_includes instance.errors.messages[attribute], "can't be blank", ":#{attribute} should be required"
|
12
|
+
end
|
13
|
+
|
14
|
+
def assert_not_required(attribute)
|
15
|
+
instance = build factory
|
16
|
+
instance[attribute] = nil
|
17
|
+
assert instance.valid?, ":#{attribute} should not be required, got errors: #{instance.errors.full_messages}"
|
18
|
+
end
|
19
|
+
|
20
|
+
def assert_association_required(association)
|
21
|
+
instance = @model.new
|
22
|
+
instance.send("#{association}_id=", nil)
|
23
|
+
assert instance.invalid?
|
24
|
+
assert_includes instance.errors.messages[association], "must exist", ":#{association} should be required"
|
25
|
+
end
|
26
|
+
|
27
|
+
def assert_uniqueness(attribute, scope: nil)
|
28
|
+
instance = create factory
|
29
|
+
duplicate = build factory, attribute => instance[attribute]
|
30
|
+
duplicate.send("#{scope}=", instance.send(scope)) if scope.present?
|
31
|
+
assert duplicate.invalid?, "Should be invalid with duplicate #{attribute}"
|
32
|
+
assert_includes duplicate.errors.messages[attribute], "has already been taken", ":#{attribute} should be unique"
|
33
|
+
end
|
34
|
+
|
35
|
+
# association_model is a symbol
|
36
|
+
def assert_accepts_nested_attributes_for(association_model, association_factory: association_model) # rubocop:disable Metrics/AbcSize, Metrics/MethodLength, Metrics/PerceivedComplexity
|
37
|
+
association = @model.reflect_on_association(association_model)
|
38
|
+
|
39
|
+
assert association.present?, "association :#{association_model} not found"
|
40
|
+
|
41
|
+
key = if association.macro == :belongs_to || association.macro == :has_one
|
42
|
+
"#{association_model}_attributes"
|
43
|
+
else
|
44
|
+
"#{association_model.to_s.pluralize}_attributes"
|
45
|
+
end
|
46
|
+
|
47
|
+
success_checker = if association.macro == :belongs_to || association.macro == :has_one
|
48
|
+
->(instance) { instance.reload.send(association_model).present? }
|
49
|
+
else
|
50
|
+
->(instance) { instance.reload.send(association_model).any? }
|
51
|
+
end
|
52
|
+
|
53
|
+
assert @model.new.respond_to?("#{key}="),
|
54
|
+
"#{@model} doesn't have a #{key} setter method. Probably there's no `accepts_nested_attributes_for :#{association_model}` in #{@model}" # rubocop:disable Layout/LineLength
|
55
|
+
object = if association_factory.is_a?(Proc)
|
56
|
+
model_instance = build factory
|
57
|
+
association_instance = association_factory.call model_instance
|
58
|
+
model_instance.send("#{key}=", association_instance.attributes)
|
59
|
+
model_instance
|
60
|
+
else
|
61
|
+
build factory, { key => build(association_factory).attributes }
|
62
|
+
end
|
63
|
+
assert object.valid?, "Should be valid after assigning nested model attributes, but got errors: #{object.errors.full_messages}"
|
64
|
+
assert object.save, "Should be able to save after assigning nested model attributes, but got errors: #{object.errors.full_messages}"
|
65
|
+
assert success_checker.call(object), "Should have a #{association_model} after assigning nested model attributes"
|
66
|
+
end
|
67
|
+
|
68
|
+
def assert_destroy_association_cascade(association_model, association_factory: association_model) # rubocop:disable Metrics/AbcSize
|
69
|
+
instance = create factory
|
70
|
+
association_instance = if association_factory.is_a?(Proc)
|
71
|
+
association_factory.call(instance)
|
72
|
+
else
|
73
|
+
create association, { @model.to_s.underscore => instance }
|
74
|
+
end
|
75
|
+
assert instance.reload.send(association_model).any?, "Should have a #{association_model} after creating #{@model}"
|
76
|
+
assert instance.destroy, "Should be able to destroy #{@model}"
|
77
|
+
assert_not association_instance.class.exists?(association_instance.id), "Should destroy #{association_model} when destroying #{@model}"
|
78
|
+
end
|
79
|
+
|
80
|
+
private
|
81
|
+
def factory
|
82
|
+
@factory ||= @model.to_s.underscore.to_sym
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Rhino
|
4
|
+
module TestHelperOverride
|
5
|
+
def assert_overridden(overriding, klass, *methods)
|
6
|
+
methods.each do |method|
|
7
|
+
assert_equal File.realpath(overriding), klass.new.method(method).source_location[0]
|
8
|
+
end
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
module Rhino
|
14
|
+
module TestCase
|
15
|
+
class Override < ActiveSupport::TestCase
|
16
|
+
include Rhino::TestHelperOverride
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,76 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Based on https://github.com/varvet/pundit/issues/204#issuecomment-60166450 and
|
4
|
+
# https://github.com/varvet/pundit/issues/204#issuecomment-192256023
|
5
|
+
|
6
|
+
module Rhino
|
7
|
+
module TestHelperPolicy
|
8
|
+
def assert_permit(user, record, action)
|
9
|
+
msg = "User #{user.inspect} should be permitted to #{action} #{record}, but isn't permitted"
|
10
|
+
assert permit(user, record, action), msg
|
11
|
+
end
|
12
|
+
|
13
|
+
def assert_not_permit(user, record, action)
|
14
|
+
msg = "User #{user.inspect} should NOT be permitted to #{action} #{record}, but is permitted"
|
15
|
+
assert_not permit(user, record, action), msg
|
16
|
+
end
|
17
|
+
|
18
|
+
def assert_scope_only(user, scope, expected)
|
19
|
+
resolved = scope(user, scope)
|
20
|
+
msg = "User #{user.inspect} should be scoped to #{expected.inspect}, but receives #{resolved.inspect}"
|
21
|
+
assert_empty resolved.to_a.difference(expected), msg
|
22
|
+
assert_empty expected.difference(resolved.to_a), msg
|
23
|
+
end
|
24
|
+
|
25
|
+
def assert_params(user, record, action, expected)
|
26
|
+
permitted_params = params(user, record, action)
|
27
|
+
msg = "User #{user.inspect} should be permitted #{action} params for #{record}, but isn't"
|
28
|
+
assert_empty permitted_params.to_a.difference(expected), msg
|
29
|
+
assert_empty expected.difference(permitted_params.to_a), msg
|
30
|
+
end
|
31
|
+
|
32
|
+
def assert_scope_empty(user, scope)
|
33
|
+
resolved = scope(user, scope)
|
34
|
+
msg = "User #{user.inspect} should have no scope, but receives #{resolved.inspect}"
|
35
|
+
assert_equal 0, resolved.count, msg
|
36
|
+
end
|
37
|
+
|
38
|
+
def policy_test_name
|
39
|
+
self.class.ancestors.find { |a| a.to_s.end_with?("PolicyTest") }
|
40
|
+
end
|
41
|
+
|
42
|
+
def policy_instance(user, record)
|
43
|
+
klass = policy_test_name.to_s.gsub(/Test/, "")
|
44
|
+
klass.constantize.new(user, record)
|
45
|
+
end
|
46
|
+
|
47
|
+
def policy_scope_instance(user, scope)
|
48
|
+
klass = policy_test_name.to_s.gsub(/Test/, "::Scope")
|
49
|
+
klass.constantize.new(user, scope)
|
50
|
+
end
|
51
|
+
|
52
|
+
def scope(user, scope)
|
53
|
+
policy_scope_instance(user, scope).resolve
|
54
|
+
end
|
55
|
+
|
56
|
+
def permit(user, record, action)
|
57
|
+
policy_instance(user, record).public_send("#{action}?")
|
58
|
+
end
|
59
|
+
|
60
|
+
def params(user, record, action)
|
61
|
+
policy_instance(user, record).public_send("permitted_attributes_for_#{action}")
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
module Rhino
|
67
|
+
module TestCase
|
68
|
+
class Policy < ActiveSupport::TestCase
|
69
|
+
include Rhino::TestHelperPolicy
|
70
|
+
|
71
|
+
def self.testing_policy
|
72
|
+
name.gsub(/Test/, "::Scope")
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Rhino
|
4
|
+
# Returns the currently loaded version of Rhino core as a +Gem::Version+.
|
5
|
+
def self.gem_version
|
6
|
+
Gem::Version.new VERSION::STRING
|
7
|
+
end
|
8
|
+
|
9
|
+
module VERSION
|
10
|
+
MAJOR = 0
|
11
|
+
MINOR = 20
|
12
|
+
TINY = 0
|
13
|
+
PRE = "alpha.0"
|
14
|
+
|
15
|
+
STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".")
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,131 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'rhino/engine'
|
4
|
+
require 'validators/country_validator'
|
5
|
+
require 'validators/email_validator'
|
6
|
+
require 'validators/ipv4_validator'
|
7
|
+
require 'validators/mac_address_validator'
|
8
|
+
require 'active_support'
|
9
|
+
|
10
|
+
module Rhino
|
11
|
+
extend ActiveSupport::Autoload
|
12
|
+
extend ActiveSupport::Concern
|
13
|
+
|
14
|
+
autoload :SieveStack, 'rhino/sieve'
|
15
|
+
|
16
|
+
# The root path for the api ie '/api'
|
17
|
+
mattr_accessor :namespace, default: :api
|
18
|
+
|
19
|
+
# Include Rhino::Resource::ActiveRecordExtension by default
|
20
|
+
mattr_accessor :auto_include_active_record, default: true
|
21
|
+
|
22
|
+
mattr_accessor :resources, default: if Rails.env.production?
|
23
|
+
['ActiveStorage::Attachment']
|
24
|
+
else
|
25
|
+
['ActiveStorage::Attachment', 'Rhino::OpenApiInfo', 'Rhino::InfoGraph']
|
26
|
+
end
|
27
|
+
|
28
|
+
mattr_accessor :resource_classes
|
29
|
+
|
30
|
+
mattr_accessor :registered_modules, default: {}
|
31
|
+
|
32
|
+
# Whether to allow signup or not
|
33
|
+
mattr_accessor :allow_signup, default: true
|
34
|
+
|
35
|
+
# sieves
|
36
|
+
mattr_accessor :sieves
|
37
|
+
|
38
|
+
##
|
39
|
+
# Stolen from devise
|
40
|
+
#
|
41
|
+
# https://github.com/heartcombo/devise/blob/main/lib/devise.rb#L310
|
42
|
+
class Getter
|
43
|
+
def initialize(name)
|
44
|
+
@name = name
|
45
|
+
end
|
46
|
+
|
47
|
+
def get
|
48
|
+
@name.constantize
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def self.ref(arg)
|
53
|
+
Getter.new(arg)
|
54
|
+
end
|
55
|
+
#
|
56
|
+
##
|
57
|
+
|
58
|
+
# Get the resource classes from the resource reference object.
|
59
|
+
def self.resource_classes
|
60
|
+
resource_classes ||= resources.map(&:constantize)
|
61
|
+
|
62
|
+
resource_classes
|
63
|
+
end
|
64
|
+
|
65
|
+
self.sieves = Rhino::SieveStack.new do |sieve|
|
66
|
+
sieve.use Rhino::Sieve::Filter
|
67
|
+
|
68
|
+
sieve.use Rhino::Sieve::Geospatial
|
69
|
+
|
70
|
+
sieve.use Rhino::Sieve::Search
|
71
|
+
|
72
|
+
sieve.use Rhino::Sieve::Order
|
73
|
+
|
74
|
+
sieve.use Rhino::Sieve::Offset
|
75
|
+
|
76
|
+
sieve.use Rhino::Sieve::Limit, default_limit: 20
|
77
|
+
end
|
78
|
+
|
79
|
+
def self.auth_owner
|
80
|
+
@@auth_owner_ref.get
|
81
|
+
end
|
82
|
+
|
83
|
+
# Set the auth owner reference object to access the mailer.
|
84
|
+
def self.auth_owner=(class_name)
|
85
|
+
@@auth_owner_ref = ref(class_name) # rubocop:disable Style/ClassVars
|
86
|
+
end
|
87
|
+
self.auth_owner = 'User'
|
88
|
+
|
89
|
+
def self.auth_owner_sym
|
90
|
+
auth_owner.to_s.underscore.pluralize.to_sym
|
91
|
+
end
|
92
|
+
|
93
|
+
def self.base_owner
|
94
|
+
@@base_owner_ref.get
|
95
|
+
end
|
96
|
+
|
97
|
+
# Set the mailer reference object to access the mailer.
|
98
|
+
def self.base_owner=(class_name)
|
99
|
+
@@base_owner_ref = ref(class_name) # rubocop:disable Style/ClassVars
|
100
|
+
end
|
101
|
+
self.base_owner = 'User'
|
102
|
+
|
103
|
+
def self.base_owner_sym
|
104
|
+
base_owner.to_s.underscore.pluralize.to_sym
|
105
|
+
end
|
106
|
+
|
107
|
+
def self.same_owner?
|
108
|
+
base_owner == auth_owner
|
109
|
+
end
|
110
|
+
|
111
|
+
def self.base_to_auth
|
112
|
+
return auth_owner.model_name.i18n_key if same_owner?
|
113
|
+
|
114
|
+
return auth_owner_sym if base_owner.reflections.key?(auth_owner_sym.to_s)
|
115
|
+
|
116
|
+
nil
|
117
|
+
end
|
118
|
+
|
119
|
+
def self.auth_to_base
|
120
|
+
return auth_owner.model_name.i18n_key if same_owner?
|
121
|
+
|
122
|
+
return base_owner_sym if auth_owner.reflections.key?(base_owner_sym.to_s)
|
123
|
+
|
124
|
+
nil
|
125
|
+
end
|
126
|
+
|
127
|
+
# Default way to set up Rhino
|
128
|
+
def self.setup
|
129
|
+
yield self
|
130
|
+
end
|
131
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
namespace :rhino do
|
4
|
+
# Prevent migration installation task from showing up twice.
|
5
|
+
Rake::Task['rhino_engine:install:migrations'].clear_comments
|
6
|
+
|
7
|
+
desc 'Install Rhino'
|
8
|
+
task install: %w[environment run_installer]
|
9
|
+
|
10
|
+
desc 'Export Rhino Open API information for client'
|
11
|
+
task open_api_export: :environment do
|
12
|
+
static_file = Rails.root.parent.join('client', 'src', 'models', 'static.js')
|
13
|
+
File.open(static_file, 'w') do |f|
|
14
|
+
f.write "const api = #{Rhino::OpenApiInfo.index};\n\n"
|
15
|
+
f.write("export default api;\n")
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
task run_installer: :environment do
|
20
|
+
Rake::Task['rhino_engine:install:migrations'].invoke if Rake::Task.task_defined?('rhino_engine:install:migrations')
|
21
|
+
|
22
|
+
Rails::Command.invoke :generate, ['rhino:install']
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
namespace :rhino do
|
4
|
+
namespace :dev do
|
5
|
+
desc "Setup Rhino development environment"
|
6
|
+
task setup: :environment do
|
7
|
+
# Extract extra arguments from ARGV
|
8
|
+
extra_args = ARGV.drop_while { |arg| arg != "--" }.drop(1)
|
9
|
+
|
10
|
+
# Remove extra arguments from ARGV to prevent interference with other tasks
|
11
|
+
extra_args.each { |arg| ARGV.delete(arg) }
|
12
|
+
ARGV.delete("--")
|
13
|
+
|
14
|
+
Rails::Command.invoke :generate, ["rhino:dev:setup", *extra_args]
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class CountryValidator < ActiveModel::EachValidator
|
4
|
+
def validate_each(record, attribute, value)
|
5
|
+
if options[:alpha3]
|
6
|
+
record.errors.add(attribute, "must be 3 characters (ISO 3166-1).") unless ISO3166::Country.find_country_by_alpha3(value)
|
7
|
+
else
|
8
|
+
record.errors.add(attribute, "must be 2 characters (ISO 3166-1).") unless ISO3166::Country[value]
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
@@ -0,0 +1,8 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class EmailValidator < ActiveModel::EachValidator
|
4
|
+
def validate_each(record, attribute, value)
|
5
|
+
# https://stackoverflow.com/questions/38611405/email-validation-in-ruby-on-rails
|
6
|
+
record.errors.add(attribute, "must be a valid email address") unless value&.match?(Devise.email_regexp)
|
7
|
+
end
|
8
|
+
end
|
@@ -0,0 +1,10 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "resolv"
|
4
|
+
|
5
|
+
class Ipv4Validator < ActiveModel::EachValidator
|
6
|
+
def validate_each(record, attribute, value)
|
7
|
+
# https://gist.github.com/mozamimy/52c0004c8370f78df2c2
|
8
|
+
record.errors.add(attribute, "not a valid IPv4 address") unless Resolv::IPv4::Regex.match?(value)
|
9
|
+
end
|
10
|
+
end
|
@@ -0,0 +1,9 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "resolv"
|
4
|
+
|
5
|
+
class MacAddressValidator < ActiveModel::EachValidator
|
6
|
+
def validate_each(record, attribute, value)
|
7
|
+
record.errors.add(attribute, "not a valid Mac address") unless /\A([0-9A-Fa-f]{2}[-:]){5}([0-9A-Fa-f]{2})\z/.match?(value)
|
8
|
+
end
|
9
|
+
end
|