command_tower 0.3.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 +7 -0
- data/MIT-LICENSE +20 -0
- data/README.md +59 -0
- data/Rakefile +32 -0
- data/app/controllers/command_tower/admin_controller.rb +104 -0
- data/app/controllers/command_tower/application_controller.rb +81 -0
- data/app/controllers/command_tower/auth/plain_text_controller.rb +132 -0
- data/app/controllers/command_tower/inbox/message_blast_controller.rb +89 -0
- data/app/controllers/command_tower/inbox/message_controller.rb +79 -0
- data/app/controllers/command_tower/user_controller.rb +49 -0
- data/app/controllers/command_tower/username_controller.rb +26 -0
- data/app/helpers/command_tower/application_helper.rb +4 -0
- data/app/helpers/command_tower/schema_helper.rb +29 -0
- data/app/jobs/command_tower/application_job.rb +4 -0
- data/app/mailers/command_tower/application_mailer.rb +8 -0
- data/app/mailers/command_tower/email_verification_mailer.rb +12 -0
- data/app/models/command_tower/application_record.rb +45 -0
- data/app/models/message.rb +30 -0
- data/app/models/message_blast.rb +27 -0
- data/app/models/user.rb +61 -0
- data/app/models/user_secret.rb +72 -0
- data/app/services/command_tower/README.md +49 -0
- data/app/services/command_tower/argument_validation/README.md +192 -0
- data/app/services/command_tower/argument_validation/class_methods.rb +178 -0
- data/app/services/command_tower/argument_validation/instance_methods.rb +148 -0
- data/app/services/command_tower/argument_validation.rb +11 -0
- data/app/services/command_tower/authorize/validate.rb +49 -0
- data/app/services/command_tower/inbox_service/blast/delete.rb +23 -0
- data/app/services/command_tower/inbox_service/blast/metadata.rb +26 -0
- data/app/services/command_tower/inbox_service/blast/new_user_blaster.rb +24 -0
- data/app/services/command_tower/inbox_service/blast/retrieve.rb +30 -0
- data/app/services/command_tower/inbox_service/blast/upsert.rb +67 -0
- data/app/services/command_tower/inbox_service/message/metadata.rb +35 -0
- data/app/services/command_tower/inbox_service/message/modify.rb +44 -0
- data/app/services/command_tower/inbox_service/message/retrieve.rb +36 -0
- data/app/services/command_tower/inbox_service/message/send.rb +33 -0
- data/app/services/command_tower/jwt/authenticate_user.rb +86 -0
- data/app/services/command_tower/jwt/decode.rb +21 -0
- data/app/services/command_tower/jwt/encode.rb +15 -0
- data/app/services/command_tower/jwt/login_create.rb +21 -0
- data/app/services/command_tower/jwt/time_delay_token.rb +17 -0
- data/app/services/command_tower/login_strategy/plain_text/create.rb +43 -0
- data/app/services/command_tower/login_strategy/plain_text/email_verification/generate.rb +29 -0
- data/app/services/command_tower/login_strategy/plain_text/email_verification/required.rb +20 -0
- data/app/services/command_tower/login_strategy/plain_text/email_verification/send.rb +23 -0
- data/app/services/command_tower/login_strategy/plain_text/email_verification/verify.rb +24 -0
- data/app/services/command_tower/login_strategy/plain_text/login.rb +50 -0
- data/app/services/command_tower/secrets/cleanse.rb +14 -0
- data/app/services/command_tower/secrets/generate.rb +62 -0
- data/app/services/command_tower/secrets/verify.rb +27 -0
- data/app/services/command_tower/secrets.rb +15 -0
- data/app/services/command_tower/service_base.rb +89 -0
- data/app/services/command_tower/service_logging.rb +41 -0
- data/app/services/command_tower/user_attributes/modify.rb +68 -0
- data/app/services/command_tower/user_attributes/roles.rb +27 -0
- data/app/services/command_tower/username/available.rb +64 -0
- data/app/views/command_tower/email_verification_mailer/verify_email.html.erb +26 -0
- data/config/routes.rb +55 -0
- data/db/migrate/20241117043720_create_command_tower_users.rb +42 -0
- data/db/migrate/20241204065708_create_command_tower_user_secrets.rb +16 -0
- data/db/migrate/20250223023306_create_command_tower_messages.rb +12 -0
- data/db/migrate/20250223023313_create_command_tower_message_blasts.rb +14 -0
- data/lib/command_tower/authorization/default.yml +42 -0
- data/lib/command_tower/authorization/entity.rb +101 -0
- data/lib/command_tower/authorization/role.rb +101 -0
- data/lib/command_tower/authorization.rb +85 -0
- data/lib/command_tower/configuration/admin/config.rb +18 -0
- data/lib/command_tower/configuration/application/config.rb +40 -0
- data/lib/command_tower/configuration/authorization/config.rb +24 -0
- data/lib/command_tower/configuration/base.rb +11 -0
- data/lib/command_tower/configuration/config.rb +77 -0
- data/lib/command_tower/configuration/email/config.rb +87 -0
- data/lib/command_tower/configuration/jwt/config.rb +22 -0
- data/lib/command_tower/configuration/login/config.rb +18 -0
- data/lib/command_tower/configuration/login/strategy/plain_text/config.rb +57 -0
- data/lib/command_tower/configuration/login/strategy/plain_text/email_verify.rb +50 -0
- data/lib/command_tower/configuration/login/strategy/plain_text/lockable.rb +27 -0
- data/lib/command_tower/configuration/otp/config.rb +54 -0
- data/lib/command_tower/configuration/user/config.rb +56 -0
- data/lib/command_tower/configuration/username/check.rb +31 -0
- data/lib/command_tower/configuration/username/config.rb +41 -0
- data/lib/command_tower/engine.rb +53 -0
- data/lib/command_tower/error.rb +5 -0
- data/lib/command_tower/schema/admin/users.rb +15 -0
- data/lib/command_tower/schema/error/base.rb +15 -0
- data/lib/command_tower/schema/error/invalid_argument.rb +15 -0
- data/lib/command_tower/schema/error/invalid_argument_response.rb +17 -0
- data/lib/command_tower/schema/inbox/blast_request.rb +15 -0
- data/lib/command_tower/schema/inbox/blast_response.rb +16 -0
- data/lib/command_tower/schema/inbox/message_blast_entity.rb +16 -0
- data/lib/command_tower/schema/inbox/message_blast_metadata.rb +16 -0
- data/lib/command_tower/schema/inbox/message_entity.rb +14 -0
- data/lib/command_tower/schema/inbox/metadata.rb +18 -0
- data/lib/command_tower/schema/inbox/modified.rb +13 -0
- data/lib/command_tower/schema/page.rb +14 -0
- data/lib/command_tower/schema/plain_text/create_user_request.rb +18 -0
- data/lib/command_tower/schema/plain_text/create_user_response.rb +17 -0
- data/lib/command_tower/schema/plain_text/email_verify_request.rb +11 -0
- data/lib/command_tower/schema/plain_text/email_verify_response.rb +11 -0
- data/lib/command_tower/schema/plain_text/email_verify_send_request.rb +9 -0
- data/lib/command_tower/schema/plain_text/email_verify_send_response.rb +11 -0
- data/lib/command_tower/schema/plain_text/login_request.rb +15 -0
- data/lib/command_tower/schema/plain_text/login_response.rb +13 -0
- data/lib/command_tower/schema/user.rb +28 -0
- data/lib/command_tower/schema.rb +38 -0
- data/lib/command_tower/spec_helper.rb +19 -0
- data/lib/command_tower/version.rb +5 -0
- data/lib/command_tower.rb +33 -0
- data/lib/generators/api_engine_base/configure/USAGE +8 -0
- data/lib/generators/api_engine_base/configure/configure_generator.rb +12 -0
- data/lib/tasks/auto_annotate_models.rake +60 -0
- metadata +255 -0
@@ -0,0 +1,178 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module CommandTower::ArgumentValidation
|
4
|
+
module ClassMethods
|
5
|
+
ON_ARGUMENT_VALIDATION = [
|
6
|
+
DEFAULT_VALIDATION = :raise,
|
7
|
+
:fail_early,
|
8
|
+
:log,
|
9
|
+
]
|
10
|
+
|
11
|
+
def on_argument_validation_assigned
|
12
|
+
@on_argument_validation ||= DEFAULT_VALIDATION
|
13
|
+
end
|
14
|
+
|
15
|
+
def on_argument_validation(set)
|
16
|
+
raise "Must be one of #{ON_ARGUMENT_VALIDATION}" unless ON_ARGUMENT_VALIDATION.include?(set)
|
17
|
+
|
18
|
+
@on_argument_validation = set
|
19
|
+
end
|
20
|
+
|
21
|
+
def compose_exact(count, name, required:, delegation: false, &block)
|
22
|
+
raise CommandTower::ServiceBase::CompositionValidationError, "Count must be greater than 0" if count < 1
|
23
|
+
|
24
|
+
validation_proc = Proc.new do |input_count, keys|
|
25
|
+
language = (input_count > 0) ? "But #{input_count} keys were assigned" : "But no key was assigned"
|
26
|
+
{
|
27
|
+
message: "Expected [#{count}] of the keys to have a value assigned. #{language}",
|
28
|
+
is_valid: (input_count == count),
|
29
|
+
requirement: "Exactly #{count} key(s) of #{keys} must be provided.",
|
30
|
+
}
|
31
|
+
end
|
32
|
+
|
33
|
+
composition_validation_proc = Proc.new do
|
34
|
+
keys = compositions[__stacked_type.last[:name]][:keys]
|
35
|
+
if keys.length < count
|
36
|
+
raise CommandTower::ServiceBase::CompositionValidationError, "Composition configuration error. Key [#{name}] expects EXACTLY #{count} keys to create an instance. Only #{keys.length} is provided for the composition. Please add more keys or reduce the expectation"
|
37
|
+
end
|
38
|
+
end
|
39
|
+
composition(name:, type: :compose_exact, required:, delegation:, composition_validation_proc:, validation_proc:, &block)
|
40
|
+
end
|
41
|
+
|
42
|
+
def at_most(count, name, required:, &block)
|
43
|
+
raise CommandTower::ServiceBase::CompositionValidationError, "Count must be greater than 0" if count < 1
|
44
|
+
|
45
|
+
validation_proc = Proc.new do |input_count, keys|
|
46
|
+
{
|
47
|
+
message: "Expected at most #{count} keys assigned",
|
48
|
+
is_valid: (input_count <= count),
|
49
|
+
requirement: "Validation Error: At most #{count} key(s) of #{keys} can be provided.",
|
50
|
+
}
|
51
|
+
end
|
52
|
+
|
53
|
+
composition(name:, type: :at_most, required:, delegation: false, validation_proc:, &block)
|
54
|
+
end
|
55
|
+
|
56
|
+
def at_least(count, name, required:, &block)
|
57
|
+
raise CommandTower::ServiceBase::CompositionValidationError, "Count must be greater than 0" if count < 1
|
58
|
+
|
59
|
+
validation_proc = Proc.new do |input_count, keys|
|
60
|
+
{
|
61
|
+
message: "Expected at least #{count} keys assigned. Available keys",
|
62
|
+
is_valid: (input_count >= count),
|
63
|
+
requirement: "Validation Error: At least #{count} key(s) of #{keys} must be provided.",
|
64
|
+
}
|
65
|
+
end
|
66
|
+
|
67
|
+
composition_validation_proc = Proc.new do
|
68
|
+
keys = compositions[__stacked_type.last[:name]][:keys]
|
69
|
+
if keys.length < count
|
70
|
+
raise CommandTower::ServiceBase::CompositionValidationError, "Composition configuration error. Key [#{name}] expects AT LEAST #{count} keys to create an instance. Only #{keys.length} is provided for the composition. Please add more keys or reduce the expectation"
|
71
|
+
end
|
72
|
+
end
|
73
|
+
composition(name:, type: :at_least, required:, delegation: false, composition_validation_proc:, validation_proc:, &block)
|
74
|
+
end
|
75
|
+
|
76
|
+
def at_least_one(name, required:, &block)
|
77
|
+
at_least(1, name, required:, &block)
|
78
|
+
end
|
79
|
+
|
80
|
+
def one_of(name, required:, delegation: true, &block)
|
81
|
+
compose_exact(1, name, required:, delegation:, &block)
|
82
|
+
end
|
83
|
+
|
84
|
+
def composition(name:, type:, required:, delegation:, validation_proc:, composition_validation_proc: nil, &block)
|
85
|
+
compositions[name] ||= { type:, name:, keys: [], required:, delegation:, validation_proc: }
|
86
|
+
if __stacked_type.map { _1[:type] }.include?(type)
|
87
|
+
raise CommandTower::ServiceBase::NestedDuplicateTypeError, "Duplicate Nested type's are not allowed. #{type} composition was included more than once"
|
88
|
+
end
|
89
|
+
|
90
|
+
__stacked_type << { type:, name: }
|
91
|
+
|
92
|
+
yield
|
93
|
+
|
94
|
+
if composition_validation_proc
|
95
|
+
composition_validation_proc.()
|
96
|
+
end
|
97
|
+
|
98
|
+
if __existing_names.include?(name)
|
99
|
+
raise CommandTower::ServiceBase::NameConflictError, "Name conflict for #{name}. Duplicated as a key"
|
100
|
+
end
|
101
|
+
|
102
|
+
__existing_names << name
|
103
|
+
# returning from the yield...pop it from the stack.
|
104
|
+
# This allows us to know the current depth of where we are in the nested stack
|
105
|
+
__stacked_type.pop
|
106
|
+
|
107
|
+
if delegation
|
108
|
+
delegate name, to: :context
|
109
|
+
delegate :"#{name}_key", to: :context
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
def validate(name, default: nil, length: false, is_a: nil, is_one: nil, lt: nil, lte: nil, eq: nil, gt: nil, gte: nil, delegation: true, sensitive: false, required: false)
|
114
|
+
if __existing_names.include?(name)
|
115
|
+
raise CommandTower::ServiceBase::NameConflictError, "Duplicate key name found. [#{name}] can only be defined once"
|
116
|
+
end
|
117
|
+
|
118
|
+
__existing_names << name
|
119
|
+
|
120
|
+
if default
|
121
|
+
if is_a
|
122
|
+
if Array(is_a).none? { _1 === default }
|
123
|
+
raise CommandTower::ServiceBase::DefaultValueError, "Default value provided [#{default}] does not match any `is_a` value(s) of #{is_a}."
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
if is_one
|
128
|
+
if Array(is_one).none? { _1 == default }
|
129
|
+
raise CommandTower::ServiceBase::DefaultValueError, "Default value provided [#{default}] does not match any `is_one` value(s) of #{is_one}."
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
if __stacked_type.length > 0
|
135
|
+
compositions[__stacked_type.last[:name]][:keys] << name
|
136
|
+
end
|
137
|
+
|
138
|
+
validate_params << {
|
139
|
+
name:,
|
140
|
+
is_a:,
|
141
|
+
lt:,
|
142
|
+
lte:,
|
143
|
+
eq:,
|
144
|
+
gt:,
|
145
|
+
gte:,
|
146
|
+
length:,
|
147
|
+
required:,
|
148
|
+
is_one:,
|
149
|
+
default:,
|
150
|
+
}
|
151
|
+
sensitive_params << name if sensitive
|
152
|
+
|
153
|
+
if delegation
|
154
|
+
delegate name, to: :context
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
def sensitive_params
|
159
|
+
@sensitive_params ||= []
|
160
|
+
end
|
161
|
+
|
162
|
+
def validate_params
|
163
|
+
@validate_params ||= []
|
164
|
+
end
|
165
|
+
|
166
|
+
def compositions
|
167
|
+
@compositions ||= {}
|
168
|
+
end
|
169
|
+
|
170
|
+
def __stacked_type
|
171
|
+
@__stacked_type ||= []
|
172
|
+
end
|
173
|
+
|
174
|
+
def __existing_names
|
175
|
+
@__existing_names ||= []
|
176
|
+
end
|
177
|
+
end
|
178
|
+
end
|
@@ -0,0 +1,148 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module CommandTower::ArgumentValidation
|
4
|
+
module InstanceMethods
|
5
|
+
def run_validations!
|
6
|
+
context.valid_arguments = true
|
7
|
+
|
8
|
+
validate_param!
|
9
|
+
validate_compositions!
|
10
|
+
continue_with_logical_code!
|
11
|
+
end
|
12
|
+
|
13
|
+
def continue_with_logical_code!
|
14
|
+
return if @context_validation_failures.nil?
|
15
|
+
|
16
|
+
invalid_argument_keys = @context_validation_failures.keys
|
17
|
+
msg = @context_validation_failures.map { |_k, obj| obj[:msg] }.join(", ")
|
18
|
+
context.fail!(msg:, invalid_argument_hash: @context_validation_failures, invalid_argument_keys:, invalid_arguments: true)
|
19
|
+
end
|
20
|
+
|
21
|
+
def inline_argument_failure!(errors:)
|
22
|
+
errors = errors.to_hash
|
23
|
+
invalid_argument_keys = errors.keys
|
24
|
+
invalid_argument_hash = {}
|
25
|
+
human_readable = []
|
26
|
+
|
27
|
+
errors.each do |k, v|
|
28
|
+
error_message = Array(v).join(", ")
|
29
|
+
invalid_argument_hash[k] = { msg: error_message }
|
30
|
+
human_readable << "#{k}: #{error_message}"
|
31
|
+
end
|
32
|
+
|
33
|
+
msg = "Invalid arguments: #{human_readable.join(", ")}"
|
34
|
+
context.fail!(msg:, invalid_argument_hash:, invalid_argument_keys:, invalid_arguments: true)
|
35
|
+
end
|
36
|
+
|
37
|
+
def validate_param!
|
38
|
+
self.class.validate_params.each do |metadata|
|
39
|
+
value = context.public_send(metadata[:name])
|
40
|
+
use_length = metadata[:length]
|
41
|
+
if metadata[:required] && value.nil?
|
42
|
+
__failed_argument_validation(msg: "Parameter [#{metadata[:name]}] is required but not present", argument: metadata[:name], metadata:)
|
43
|
+
end
|
44
|
+
|
45
|
+
if value.nil? && metadata[:default]
|
46
|
+
context.public_send(:"#{metadata[:name]}=", metadata[:default])
|
47
|
+
next
|
48
|
+
elsif value.nil?
|
49
|
+
next
|
50
|
+
end
|
51
|
+
|
52
|
+
if is_a = metadata[:is_a]
|
53
|
+
direct_type = false
|
54
|
+
ancestor_type = false
|
55
|
+
|
56
|
+
# Check if direct type of `is_a` Integer === 5 => true
|
57
|
+
direct_type = Array(is_a).none? { _1 === value }
|
58
|
+
|
59
|
+
# If it is a direct type, we dont need to do any other type of checking
|
60
|
+
if direct_type == true
|
61
|
+
lineage = value.ancestors rescue []
|
62
|
+
# Check inclusion in ancestor list
|
63
|
+
ancestor_type = Array(is_a).none? { lineage.include?(_1) }
|
64
|
+
end
|
65
|
+
if direct_type && ancestor_type
|
66
|
+
__failed_argument_validation(msg: "Parameter [#{metadata[:name]}] must be of type #{is_a}. Given #{value.class} [#{value}]", argument: metadata[:name], metadata:)
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
if metadata[:is_one]
|
71
|
+
if Array(metadata[:is_one]).none? { _1 == value }
|
72
|
+
__failed_argument_validation(msg: "Parameter [#{metadata[:name]}] must be one of #{Array(metadata[:is_one])}. Given #{value}", argument: metadata[:name], metadata:)
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
validate_sign!(name: metadata[:name], value:, sign: "lt", validation: metadata[:lte], use_length:, metadata:) { (use_length ? value.length : value) <= _1 }
|
77
|
+
validate_sign!(name: metadata[:name], value:, sign: "lte", validation: metadata[:lt], use_length:, metadata:) { (use_length ? value.length : value) < _1 }
|
78
|
+
validate_sign!(name: metadata[:name], value:, sign: "eq", validation: metadata[:eq], use_length:, metadata:) { (use_length ? value.length : value) == _1 }
|
79
|
+
validate_sign!(name: metadata[:name], value:, sign: "gte", validation: metadata[:gte], use_length:, metadata:) { (use_length ? value.length : value) >= _1 }
|
80
|
+
validate_sign!(name: metadata[:name], value:, sign: "gt", validation: metadata[:gt], use_length:, metadata:) { (use_length ? value.length : value) > _1 }
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
def validate_compositions!
|
85
|
+
self.class.compositions.each do |type, metadata|
|
86
|
+
value_list = {}
|
87
|
+
|
88
|
+
metadata[:keys].each do |argument|
|
89
|
+
value = context.public_send(argument)
|
90
|
+
next if value.nil?
|
91
|
+
|
92
|
+
value_list[argument] = value
|
93
|
+
end
|
94
|
+
|
95
|
+
composition_result = metadata[:validation_proc].(value_list.count, value_list.keys)
|
96
|
+
|
97
|
+
next if value_list.count == 0 && !metadata[:required]
|
98
|
+
|
99
|
+
if !composition_result[:is_valid]
|
100
|
+
composition_result[:message]
|
101
|
+
context.client_composite_error = composition_result[:requirement]
|
102
|
+
msg = "Composite Key failure for #{type} [#{metadata[:name]}]. #{composition_result[:message]}. Provided values for the following keys: #{value_list.keys}. Available keys #{metadata[:keys]}"
|
103
|
+
__failed_argument_validation(msg:, argument: metadata[:name], metadata: ,error: CommandTower::ServiceBase::CompositionValidationError)
|
104
|
+
next
|
105
|
+
end
|
106
|
+
|
107
|
+
if metadata[:delegation]
|
108
|
+
context.public_send("#{metadata[:name]}=", value_list.first[1])
|
109
|
+
context.public_send("#{metadata[:name]}_key=", value_list.first[0])
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
def validate_sign!(name:, value:, sign:, validation:, use_length:, metadata:)
|
115
|
+
return if validation.nil?
|
116
|
+
return if yield(validation)
|
117
|
+
|
118
|
+
__failed_argument_validation(metadata:, msg: "Parameter [#{name}]#{ " lengths" if use_length} must be #{sign} to #{validation}. Given #{value}", argument: name)
|
119
|
+
end
|
120
|
+
|
121
|
+
def __failed_argument_validation(msg:, argument:, metadata:, error: CommandTower::ServiceBase::ArgumentValidationError)
|
122
|
+
case self.class.on_argument_validation_assigned
|
123
|
+
when :raise
|
124
|
+
raise error, msg
|
125
|
+
when :fail_early
|
126
|
+
@context_validation_failures ||= {}
|
127
|
+
@context_validation_failures[argument] = {
|
128
|
+
msg: msg,
|
129
|
+
required: metadata[:requirement],
|
130
|
+
is_a: metadata[:is_a],
|
131
|
+
}
|
132
|
+
# When gracefully failing, it will find all failures first before setting appropriate
|
133
|
+
# context variables -- Check out continue_with_logical_code!
|
134
|
+
else
|
135
|
+
context.invalid_arguments = true
|
136
|
+
log_warn(msg)
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
def sanitize_params
|
141
|
+
self.class.sensitive_params.each do |param|
|
142
|
+
next if context.send(param).nil?
|
143
|
+
|
144
|
+
context.send("#{param}=","[FILTERED]")
|
145
|
+
end
|
146
|
+
end
|
147
|
+
end
|
148
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module CommandTower::ArgumentValidation
|
4
|
+
class NameConflictError < CommandTower::ServiceBase::ConfigurationError; end
|
5
|
+
class NestedDuplicateTypeError < CommandTower::ServiceBase::ConfigurationError; end
|
6
|
+
|
7
|
+
def self.included(base)
|
8
|
+
base.extend(CommandTower::ArgumentValidation::ClassMethods)
|
9
|
+
base.include(CommandTower::ArgumentValidation::InstanceMethods)
|
10
|
+
end
|
11
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module CommandTower::Authorize
|
4
|
+
class Validate < CommandTower::ServiceBase
|
5
|
+
on_argument_validation :fail_early
|
6
|
+
|
7
|
+
validate :user, is_a: User, required: true
|
8
|
+
validate :controller, is_a: [ActionController::Base, ActionController::API], required: true
|
9
|
+
validate :method, is_a: String, required: true
|
10
|
+
|
11
|
+
def call
|
12
|
+
context.authorization_required = authorization_required?
|
13
|
+
unless context.authorization_required
|
14
|
+
log_info("controller:#{controller}; method:#{method} -- No Authorization required")
|
15
|
+
context.msg = "Authorization not required at this time"
|
16
|
+
return
|
17
|
+
end
|
18
|
+
|
19
|
+
# At this point we know authorization on the route is required
|
20
|
+
# Iterate through the users roles to find a matching role that allows authorization
|
21
|
+
# If at least 1 of the users roles passes validation, we can allow access to the path
|
22
|
+
log_info("User Roles: #{user.roles}")
|
23
|
+
auhorization_result = user_role_objects.any? do |_role_name, role_object|
|
24
|
+
result = role_object.authorized?(controller:, method:, user:)
|
25
|
+
log_info("Role:#{result[:role]};Authorized:[#{result[:authorized]}];Reason:[#{result[:reason]}]")
|
26
|
+
result[:authorized] == true
|
27
|
+
end
|
28
|
+
|
29
|
+
if auhorization_result
|
30
|
+
context.msg = "User is Authorized for action"
|
31
|
+
else
|
32
|
+
context.fail!(msg: "Unauthorized Access. Incorrect User Privileges")
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def authorization_required?
|
37
|
+
controller_mapping = CommandTower::Authorization.mapped_controllers[controller]
|
38
|
+
return false if controller_mapping.nil?
|
39
|
+
|
40
|
+
controller_mapping.include?(method.to_sym)
|
41
|
+
end
|
42
|
+
|
43
|
+
def user_role_objects
|
44
|
+
CommandTower::Authorization::Role.roles.select do |role_name, _|
|
45
|
+
user.roles.include?(role_name.to_s)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module CommandTower
|
4
|
+
module InboxService
|
5
|
+
module Blast
|
6
|
+
class Delete < CommandTower::ServiceBase
|
7
|
+
on_argument_validation :fail_early
|
8
|
+
|
9
|
+
validate :id, is_a: Integer, required: true
|
10
|
+
|
11
|
+
def call
|
12
|
+
message_blast = ::MessageBlast.where(id:).first
|
13
|
+
|
14
|
+
if message_blast.nil?
|
15
|
+
inline_argument_failure!(errors: { id: "MessageBlast ID not found" })
|
16
|
+
end
|
17
|
+
|
18
|
+
message_blast.destroy
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module CommandTower
|
4
|
+
module InboxService
|
5
|
+
module Blast
|
6
|
+
class Metadata < CommandTower::ServiceBase
|
7
|
+
def call
|
8
|
+
entities = ::MessageBlast.all.select(:id, :title, :existing_users, :new_users).map do |mb|
|
9
|
+
CommandTower::Schema::Inbox::MessageBlastEntity.new(
|
10
|
+
title: mb.title,
|
11
|
+
id: mb.id,
|
12
|
+
existing_users: mb.existing_users,
|
13
|
+
new_users: mb.new_users,
|
14
|
+
)
|
15
|
+
end
|
16
|
+
|
17
|
+
|
18
|
+
context.metadata = CommandTower::Schema::Inbox::MessageBlastMetadata.new(
|
19
|
+
entities: entities,
|
20
|
+
count: entities.length,
|
21
|
+
)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module CommandTower
|
4
|
+
module InboxService
|
5
|
+
module Blast
|
6
|
+
class NewUserBlaster < CommandTower::ServiceBase
|
7
|
+
on_argument_validation :fail_early
|
8
|
+
|
9
|
+
validate :user, is_a: User, required: true
|
10
|
+
|
11
|
+
def call
|
12
|
+
::MessageBlast.where(new_users: true).each do |message_blast|
|
13
|
+
InboxService::Message::Send.(
|
14
|
+
user:,
|
15
|
+
text: message_blast.text,
|
16
|
+
title: message_blast.title,
|
17
|
+
message_blast:,
|
18
|
+
)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module CommandTower
|
4
|
+
module InboxService
|
5
|
+
module Blast
|
6
|
+
class Retrieve < CommandTower::ServiceBase
|
7
|
+
on_argument_validation :fail_early
|
8
|
+
|
9
|
+
validate :id, is_a: Integer, required: true
|
10
|
+
|
11
|
+
def call
|
12
|
+
message_blast = ::MessageBlast.where(id:).first
|
13
|
+
|
14
|
+
if message_blast.nil?
|
15
|
+
inline_argument_failure!(errors: { id: "MessageBlast ID not found" })
|
16
|
+
end
|
17
|
+
|
18
|
+
context.message_blast = CommandTower::Schema::Inbox::MessageBlastEntity.new(
|
19
|
+
created_by: CommandTower::Schema::User.convert_user_object(user: message_blast.user),
|
20
|
+
title: message_blast.title,
|
21
|
+
text: message_blast.text,
|
22
|
+
id: message_blast.id,
|
23
|
+
existing_users: message_blast.existing_users,
|
24
|
+
new_users: message_blast.new_users,
|
25
|
+
)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module CommandTower
|
4
|
+
module InboxService
|
5
|
+
module Blast
|
6
|
+
class Upsert < CommandTower::ServiceBase
|
7
|
+
on_argument_validation :fail_early
|
8
|
+
|
9
|
+
validate :user, is_a: User, required: true
|
10
|
+
validate :existing_users, is_one: [true, false], required: true
|
11
|
+
validate :new_users, is_one: [true, false], required: true
|
12
|
+
validate :text, is_a: String, required: true
|
13
|
+
validate :title, is_a: String, required: true
|
14
|
+
validate :id, is_a: Integer, required: false
|
15
|
+
|
16
|
+
def call
|
17
|
+
ar = record
|
18
|
+
|
19
|
+
ar.existing_users = existing_users
|
20
|
+
ar.new_users = new_users
|
21
|
+
ar.text = text
|
22
|
+
ar.title = title
|
23
|
+
ar.user = user
|
24
|
+
|
25
|
+
# Probably should wrap this in a transaction block
|
26
|
+
ar.save!
|
27
|
+
blast_messages!(message_blast: ar)
|
28
|
+
|
29
|
+
context.message_blast = ar
|
30
|
+
context.blast = CommandTower::Schema::Inbox::BlastResponse.new(
|
31
|
+
existing_users:,
|
32
|
+
new_users:,
|
33
|
+
text:,
|
34
|
+
title:,
|
35
|
+
id: ar.id,
|
36
|
+
created_by: CommandTower::Schema::User.convert_user_object(user:)
|
37
|
+
)
|
38
|
+
end
|
39
|
+
|
40
|
+
def blast_messages!(message_blast:)
|
41
|
+
# Only blast messages to existing users that are brand new
|
42
|
+
return if @existing_record
|
43
|
+
return unless existing_users
|
44
|
+
|
45
|
+
User.all.each do |u|
|
46
|
+
InboxService::Message::Send.(
|
47
|
+
user: u,
|
48
|
+
text: text,
|
49
|
+
title: title,
|
50
|
+
message_blast: message_blast,
|
51
|
+
)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def record
|
56
|
+
return MessageBlast.new if id.nil?
|
57
|
+
|
58
|
+
record = MessageBlast.where(id:).first
|
59
|
+
inline_argument_failure!(errors: { id: "Message Blast ID does not exist" }) if record.nil?
|
60
|
+
|
61
|
+
@existing_record = true
|
62
|
+
record
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module CommandTower
|
4
|
+
module InboxService
|
5
|
+
module Message
|
6
|
+
class Metadata < CommandTower::ServiceBase
|
7
|
+
on_argument_validation :fail_early
|
8
|
+
|
9
|
+
validate :user, is_a: User, required: true
|
10
|
+
|
11
|
+
def call
|
12
|
+
entities = ::Message.where(user:).select(:id, :title, :viewed).map do |message|
|
13
|
+
CommandTower::Schema::Inbox::MessageEntity.new(
|
14
|
+
title: message.title,
|
15
|
+
id: message.id,
|
16
|
+
viewed: message.viewed,
|
17
|
+
)
|
18
|
+
end
|
19
|
+
|
20
|
+
params = {
|
21
|
+
entities: entities.nil? ? nil : entities,
|
22
|
+
count: entities.length,
|
23
|
+
}
|
24
|
+
|
25
|
+
context.metadata = CommandTower::Schema::Inbox::Metadata.new(**params.compact)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
|
33
|
+
|
34
|
+
|
35
|
+
|
@@ -0,0 +1,44 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module CommandTower
|
4
|
+
module InboxService
|
5
|
+
module Message
|
6
|
+
class Modify < CommandTower::ServiceBase
|
7
|
+
on_argument_validation :fail_early
|
8
|
+
|
9
|
+
IS_ONE = [
|
10
|
+
VIEWED = :viewed,
|
11
|
+
DELETE = :delete,
|
12
|
+
]
|
13
|
+
validate :user, is_a: User, required: true
|
14
|
+
validate :ids, is_a: Array, required: true
|
15
|
+
validate :type, is_one: IS_ONE, required: true
|
16
|
+
|
17
|
+
def call
|
18
|
+
messages = ::Message.where(user:, id: ids)
|
19
|
+
if messages.empty?
|
20
|
+
inline_argument_failure!(errors: { ids: "No ID's found for user" })
|
21
|
+
end
|
22
|
+
|
23
|
+
case type
|
24
|
+
when VIEWED
|
25
|
+
modified_ids = messages.update(viewed: true).pluck(:id)
|
26
|
+
when DELETE
|
27
|
+
modified_ids = messages.destroy_all.pluck(:id)
|
28
|
+
end
|
29
|
+
|
30
|
+
context.modified = CommandTower::Schema::Inbox::Modified.new(
|
31
|
+
ids: modified_ids,
|
32
|
+
type:,
|
33
|
+
count: modified_ids.length,
|
34
|
+
)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
|
42
|
+
|
43
|
+
|
44
|
+
|
@@ -0,0 +1,36 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module CommandTower
|
4
|
+
module InboxService
|
5
|
+
module Message
|
6
|
+
class Retrieve < CommandTower::ServiceBase
|
7
|
+
on_argument_validation :fail_early
|
8
|
+
|
9
|
+
validate :user, is_a: User, required: true
|
10
|
+
validate :id, is_a: Integer, required: true
|
11
|
+
|
12
|
+
def call
|
13
|
+
message = ::Message.where(user:, id:).first
|
14
|
+
|
15
|
+
if message.nil?
|
16
|
+
inline_argument_failure!(errors: { id: "Message ID not found for user" })
|
17
|
+
end
|
18
|
+
|
19
|
+
message.update!(viewed: true)
|
20
|
+
|
21
|
+
context.message = CommandTower::Schema::Inbox::MessageEntity.new(
|
22
|
+
title: message.title,
|
23
|
+
id: message.id,
|
24
|
+
text: message.text,
|
25
|
+
viewed: message.viewed,
|
26
|
+
)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
|
34
|
+
|
35
|
+
|
36
|
+
|