api_engine_base 0.1.1 → 0.2.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/README.md +37 -6
- data/app/controllers/api_engine_base/admin_controller.rb +104 -0
- data/app/controllers/api_engine_base/application_controller.rb +45 -11
- data/app/controllers/api_engine_base/auth/plain_text_controller.rb +1 -1
- data/app/controllers/api_engine_base/inbox/message_blast_controller.rb +89 -0
- data/app/controllers/api_engine_base/inbox/message_controller.rb +79 -0
- data/app/controllers/api_engine_base/user_controller.rb +49 -0
- data/app/models/api_engine_base/application_record.rb +38 -0
- data/app/models/message.rb +30 -0
- data/app/models/message_blast.rb +27 -0
- data/app/models/user.rb +15 -4
- data/app/services/api_engine_base/README.md +49 -0
- data/app/services/api_engine_base/argument_validation/README.md +192 -0
- data/app/services/api_engine_base/argument_validation/class_methods.rb +2 -3
- data/app/services/api_engine_base/argument_validation/instance_methods.rb +13 -1
- data/app/services/api_engine_base/authorize/validate.rb +49 -0
- data/app/services/api_engine_base/inbox_service/blast/delete.rb +23 -0
- data/app/services/api_engine_base/inbox_service/blast/metadata.rb +26 -0
- data/app/services/api_engine_base/inbox_service/blast/new_user_blaster.rb +24 -0
- data/app/services/api_engine_base/inbox_service/blast/retrieve.rb +30 -0
- data/app/services/api_engine_base/inbox_service/blast/upsert.rb +67 -0
- data/app/services/api_engine_base/inbox_service/message/metadata.rb +35 -0
- data/app/services/api_engine_base/inbox_service/message/modify.rb +44 -0
- data/app/services/api_engine_base/inbox_service/message/retrieve.rb +36 -0
- data/app/services/api_engine_base/inbox_service/message/send.rb +33 -0
- data/app/services/api_engine_base/jwt/authenticate_user.rb +22 -7
- data/app/services/api_engine_base/jwt/login_create.rb +1 -1
- data/app/services/api_engine_base/login_strategy/plain_text/create.rb +1 -0
- data/app/services/api_engine_base/service_base.rb +4 -5
- data/app/services/api_engine_base/user_attributes/modify.rb +68 -0
- data/app/services/api_engine_base/user_attributes/roles.rb +27 -0
- data/config/routes.rb +32 -0
- data/db/migrate/20241117043720_create_api_engine_base_users.rb +2 -0
- data/db/migrate/20250223023306_create_api_engine_base_messages.rb +12 -0
- data/db/migrate/20250223023313_create_api_engine_base_message_blasts.rb +14 -0
- data/lib/api_engine_base/authorization/default.yml +42 -0
- data/lib/api_engine_base/authorization/entity.rb +101 -0
- data/lib/api_engine_base/authorization/role.rb +101 -0
- data/lib/api_engine_base/authorization.rb +85 -0
- data/lib/api_engine_base/configuration/admin/config.rb +18 -0
- data/lib/api_engine_base/configuration/application/config.rb +2 -2
- data/lib/api_engine_base/configuration/authorization/config.rb +24 -0
- data/lib/api_engine_base/configuration/config.rb +19 -1
- data/lib/api_engine_base/configuration/user/config.rb +56 -0
- data/lib/api_engine_base/engine.rb +38 -6
- data/lib/api_engine_base/error.rb +5 -0
- data/lib/api_engine_base/schema/admin/users.rb +15 -0
- data/lib/api_engine_base/schema/error/invalid_argument_response.rb +1 -1
- data/lib/api_engine_base/schema/inbox/blast_request.rb +15 -0
- data/lib/api_engine_base/schema/inbox/blast_response.rb +16 -0
- data/lib/api_engine_base/schema/inbox/message_blast_entity.rb +16 -0
- data/lib/api_engine_base/schema/inbox/message_blast_metadata.rb +16 -0
- data/lib/api_engine_base/schema/inbox/message_entity.rb +14 -0
- data/lib/api_engine_base/schema/inbox/metadata.rb +18 -0
- data/lib/api_engine_base/schema/inbox/modified.rb +13 -0
- data/lib/api_engine_base/schema/page.rb +14 -0
- data/lib/api_engine_base/schema/user.rb +28 -0
- data/lib/api_engine_base/schema.rb +13 -0
- data/lib/api_engine_base/spec_helper.rb +4 -3
- data/lib/api_engine_base/version.rb +1 -1
- data/lib/api_engine_base.rb +2 -2
- metadata +44 -5
- data/app/controllers/concerns/api_engine_base/schematizable.rb +0 -5
@@ -0,0 +1,192 @@
|
|
1
|
+
# Argument Validation
|
2
|
+
|
3
|
+
Argument validation provides a robust framework to ensure correctness of arguments before executing any application logic code. This was created because when in use with an API, this can help provide reusable messaging directly back to the API when the parameters are incorrect.
|
4
|
+
|
5
|
+
## Argument Validation
|
6
|
+
Argument Validation provides service object code assurances on what to expect for inputted arguments.
|
7
|
+
|
8
|
+
Available arguments:
|
9
|
+
- `default`: Default value to set the argument when not provided by user
|
10
|
+
- `is_a`: The allowed types of the passed in argument. Will also check if the type is in the ancestral tree
|
11
|
+
- `is_one`: Checks a direct comparison if the input is one of these values. Note: while not disallowed, `is_a` and `is_one` should not be used together
|
12
|
+
- `length`: (used with operators exclusively) When set to true, the operators will use the length of value rather than the exact value
|
13
|
+
- `lt`: When provided, argument must be less than this value
|
14
|
+
- `lte`: When provided, argument must be less than or equal to this value
|
15
|
+
- `eq`: When provided, argument must be equal to this value
|
16
|
+
- `gte`: When provided, argument must be greater than or equal to this value
|
17
|
+
- `gt`: When provided, argument must be greater than this value
|
18
|
+
- `delegation`: (Default set to true) - Sets the delegation on the object. This allows you to reference the argument name rather than the context.{argument_name}
|
19
|
+
- `sensitive`: This marks the argument as sensitive. It will scrub the value of the argument when returning the context to the caller
|
20
|
+
- `required`: When set, this marks the argument as required. If not provided, validations are not run. When provided, validations must pass
|
21
|
+
|
22
|
+
## Argument Composition
|
23
|
+
Argument Compositions are made up of 1 or more Argument Validations. The intention of compositions are to ensure `at_most`, `at_least`, or `exactly` X argument validations are provided by the user.
|
24
|
+
|
25
|
+
### Composition: At Most
|
26
|
+
At most composition expects at most X arguments to get passed into the instance.
|
27
|
+
|
28
|
+
```ruby
|
29
|
+
class ServiceExample < ApiEngineBase::ServiceBase
|
30
|
+
at_most 2, :name_of_composition, required: true do
|
31
|
+
validate :email, is_a: String
|
32
|
+
validate :phone, is_a: String
|
33
|
+
validate :username, is_a: String
|
34
|
+
end
|
35
|
+
|
36
|
+
def call; end
|
37
|
+
end
|
38
|
+
```
|
39
|
+
|
40
|
+
```ruby
|
41
|
+
rails-app(dev)> ServiceExample.(email: "email", phone: "phone", username: "username")
|
42
|
+
=> # Composite Key failure for name_of_composition [name_of_composition]. Expected at most 2 keys assigned. Provided values for the following keys: [:email, :phone, :username]. Available keys [:email, :phone, :username] (ApiEngineBase::ServiceBase::CompositionValidationError)
|
43
|
+
```
|
44
|
+
|
45
|
+
### Composition: At Least
|
46
|
+
At least composition expects at least X arguments to get passed into the instance.
|
47
|
+
|
48
|
+
```ruby
|
49
|
+
class ServiceExample < ApiEngineBase::ServiceBase
|
50
|
+
at_least 2, :name_of_composition, required: true do
|
51
|
+
validate :email, is_a: String
|
52
|
+
validate :phone, is_a: String
|
53
|
+
validate :username, is_a: String
|
54
|
+
end
|
55
|
+
|
56
|
+
def call; end
|
57
|
+
end
|
58
|
+
```
|
59
|
+
|
60
|
+
```ruby
|
61
|
+
rails-app(dev)> ServiceExample.(email: "email")
|
62
|
+
=> # Composite Key failure for name_of_composition [name_of_composition]. Expected at least 2 keys assigned. Available keys. Provided values for the following keys: [:email]. Available keys [:email, :phone, :username] (ApiEngineBase::ServiceBase::CompositionValidationError)
|
63
|
+
```
|
64
|
+
|
65
|
+
**Noteworthy**: `at_least` can take in any integer for its `count`. However, we found that most people just need one. For that reason, the convenience method of `at_least_one` was created. It can be used without the `count` argument in `at_least`
|
66
|
+
|
67
|
+
### Composition: Compose Exact
|
68
|
+
Compose Exact composition expects exactly X arguments to get passed into the instance. For this composition to be valid, there must be X or more validations.
|
69
|
+
|
70
|
+
```ruby
|
71
|
+
class ServiceExample < ApiEngineBase::ServiceBase
|
72
|
+
compose_exact 2, :name_of_composition, required: true do
|
73
|
+
validate :email, is_a: String
|
74
|
+
validate :phone, is_a: String
|
75
|
+
validate :username, is_a: String
|
76
|
+
end
|
77
|
+
|
78
|
+
def call; end
|
79
|
+
end
|
80
|
+
```
|
81
|
+
```ruby
|
82
|
+
rails-app(dev)> ServiceExample.(email: "email")
|
83
|
+
=> # Composite Key failure for name_of_composition [name_of_composition]. Expected [2] of the keys to have a value assigned. But 1 keys were assigned. Provided values for the following keys: [:email]. Available keys [:email, :phone, :username] (ApiEngineBase::ServiceBase::CompositionValidationError)
|
84
|
+
```
|
85
|
+
|
86
|
+
**Noteworthy**: `compose_exact` can take any `count` value to dynamically provision the exact component. However, we found that we almost only just needed count == 1. We have provided a convenience method of `one_of` without the `count` variable to simplify. There are quite a few examples of this already created
|
87
|
+
|
88
|
+
### Custom Compositions
|
89
|
+
All compositions are built on top of the same underlying function. This allows you to build additional compositions to add custom logic for validations and what not.
|
90
|
+
Check out the [ClassMethods Source Code](class_methods.rb) on what method arguments are required.
|
91
|
+
|
92
|
+
|
93
|
+
## Argument validation Failures
|
94
|
+
When an argument validation fails (whether that is a single `validate` or a composition), there are 3 options on what to do:
|
95
|
+
|
96
|
+
### Raise an error (Default)
|
97
|
+
As you can see in the examples above, the default for argument validation failures is to raise the following error
|
98
|
+
```ruby
|
99
|
+
ApiEngineBase::ServiceBase::CompositionValidationError
|
100
|
+
```
|
101
|
+
|
102
|
+
The expected behavior is:
|
103
|
+
- Downstream code catches the failure and handles it correctly
|
104
|
+
- Service Logic code is not executed
|
105
|
+
|
106
|
+
This failure mode can get explicitly set via:
|
107
|
+
```ruby
|
108
|
+
class ServiceExample < ApiEngineBase::ServiceBase
|
109
|
+
on_argument_validation :raise
|
110
|
+
|
111
|
+
one_of :name_of_composition, required: true do
|
112
|
+
validate :email, is_a: String
|
113
|
+
validate :phone, is_a: String
|
114
|
+
validate :username, is_a: String
|
115
|
+
end
|
116
|
+
|
117
|
+
def call; end
|
118
|
+
end
|
119
|
+
```
|
120
|
+
|
121
|
+
### Fail the context Early (Recommended)
|
122
|
+
Failing the context early is we recommend to do for your service objects. This mode provides an exceptionally amount of context into **HOW** the validation failed and what needs to get corrected.
|
123
|
+
|
124
|
+
|
125
|
+
The expected behavior is:
|
126
|
+
- Downstream code checks for `result.failure?` and continues accordingly
|
127
|
+
- Service Logic code is not executed
|
128
|
+
- Nothing is raised
|
129
|
+
|
130
|
+
```ruby
|
131
|
+
class ServiceExample < ApiEngineBase::ServiceBase
|
132
|
+
on_argument_validation :fail_early
|
133
|
+
|
134
|
+
one_of :name_of_composition, required: true do
|
135
|
+
validate :email, is_a: String
|
136
|
+
validate :phone, is_a: String
|
137
|
+
validate :username, is_a: String
|
138
|
+
end
|
139
|
+
|
140
|
+
def call; end
|
141
|
+
end
|
142
|
+
```
|
143
|
+
```ruby
|
144
|
+
result = ServiceExample.(email: :not_a_string)
|
145
|
+
if result.success?
|
146
|
+
else
|
147
|
+
if result.invalid_arguments
|
148
|
+
# context.fail! was called by argument validation
|
149
|
+
puts result.invalid_arguments
|
150
|
+
puts result.invalid_argument_hash
|
151
|
+
puts result.invalid_argument_keys
|
152
|
+
else
|
153
|
+
# context.fail! was called by user
|
154
|
+
end
|
155
|
+
end
|
156
|
+
=> true
|
157
|
+
=> {:email=>{:msg=>"Parameter [email] must be of type String. Given Symbol [not_a_string]", :required=>nil, :is_a=>String}}
|
158
|
+
=> [:email]
|
159
|
+
|
160
|
+
result = ServiceExample.()
|
161
|
+
result.invalid_arguments
|
162
|
+
=> true
|
163
|
+
result.invalid_argument_hash
|
164
|
+
=> {:name_of_composition=>{:msg=>"Composite Key failure for name_of_composition [name_of_composition]. Expected [1] of the keys to have a value assigned. But no key was assigned. Provided values for the following keys: []. Available keys [:email, :phone, :username]", :required=>nil, :is_a=>nil}}
|
165
|
+
result.invalid_argument_keys
|
166
|
+
=> [:name_of_composition]
|
167
|
+
|
168
|
+
result = ServiceExample.(email: 7, username: 8)
|
169
|
+
result.invalid_arguments
|
170
|
+
=> true
|
171
|
+
result.invalid_argument_hash
|
172
|
+
=> {:email=>{:msg=>"Parameter [email] must be of type String. Given Integer [7]", :required=>nil, :is_a=>String}, :username=>{:msg=>"Parameter [username] must be of type String. Given Integer [8]", :required=>nil, :is_a=>String}, :name_of_composition=>{:msg=>"Composite Key failure for name_of_composition [name_of_composition]. Expected [1] of the keys to have a value assigned. But 2 keys were assigned. Provided values for the following keys: [:email, :username]. Available keys [:email, :phone, :username]", :required=>nil, :is_a=>nil}}
|
173
|
+
=> [:email, :username, :name_of_composition]
|
174
|
+
```
|
175
|
+
|
176
|
+
### Log and Continue (Not Recommended)
|
177
|
+
This mode will allow you to log the validation failure and continue. We do not recommend this
|
178
|
+
|
179
|
+
|
180
|
+
```ruby
|
181
|
+
class ServiceExample < ApiEngineBase::ServiceBase
|
182
|
+
on_argument_validation :log
|
183
|
+
|
184
|
+
one_of :name_of_composition, required: true do
|
185
|
+
validate :email, is_a: String
|
186
|
+
validate :phone, is_a: String
|
187
|
+
validate :username, is_a: String
|
188
|
+
end
|
189
|
+
|
190
|
+
def call; end
|
191
|
+
end
|
192
|
+
```
|
@@ -22,8 +22,7 @@ module ApiEngineBase::ArgumentValidation
|
|
22
22
|
raise ApiEngineBase::ServiceBase::CompositionValidationError, "Count must be greater than 0" if count < 1
|
23
23
|
|
24
24
|
validation_proc = Proc.new do |input_count, keys|
|
25
|
-
|
26
|
-
language = (input_count > 1) ? "But more than #{count} did" : "But no key had a value"
|
25
|
+
language = (input_count > 0) ? "But #{input_count} keys were assigned" : "But no key was assigned"
|
27
26
|
{
|
28
27
|
message: "Expected [#{count}] of the keys to have a value assigned. #{language}",
|
29
28
|
is_valid: (input_count == count),
|
@@ -111,7 +110,7 @@ module ApiEngineBase::ArgumentValidation
|
|
111
110
|
end
|
112
111
|
end
|
113
112
|
|
114
|
-
def validate(name, default: nil, length: false,
|
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)
|
115
114
|
if __existing_names.include?(name)
|
116
115
|
raise ApiEngineBase::ServiceBase::NameConflictError, "Duplicate key name found. [#{name}] can only be defined once"
|
117
116
|
end
|
@@ -50,7 +50,19 @@ module ApiEngineBase::ArgumentValidation
|
|
50
50
|
end
|
51
51
|
|
52
52
|
if is_a = metadata[:is_a]
|
53
|
-
|
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
|
54
66
|
__failed_argument_validation(msg: "Parameter [#{metadata[:name]}] must be of type #{is_a}. Given #{value.class} [#{value}]", argument: metadata[:name], metadata:)
|
55
67
|
end
|
56
68
|
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ApiEngineBase::Authorize
|
4
|
+
class Validate < ApiEngineBase::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 = ApiEngineBase::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
|
+
ApiEngineBase::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 ApiEngineBase
|
4
|
+
module InboxService
|
5
|
+
module Blast
|
6
|
+
class Delete < ApiEngineBase::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 ApiEngineBase
|
4
|
+
module InboxService
|
5
|
+
module Blast
|
6
|
+
class Metadata < ApiEngineBase::ServiceBase
|
7
|
+
def call
|
8
|
+
entities = ::MessageBlast.all.select(:id, :title, :existing_users, :new_users).map do |mb|
|
9
|
+
ApiEngineBase::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 = ApiEngineBase::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 ApiEngineBase
|
4
|
+
module InboxService
|
5
|
+
module Blast
|
6
|
+
class NewUserBlaster < ApiEngineBase::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 ApiEngineBase
|
4
|
+
module InboxService
|
5
|
+
module Blast
|
6
|
+
class Retrieve < ApiEngineBase::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 = ApiEngineBase::Schema::Inbox::MessageBlastEntity.new(
|
19
|
+
created_by: ApiEngineBase::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 ApiEngineBase
|
4
|
+
module InboxService
|
5
|
+
module Blast
|
6
|
+
class Upsert < ApiEngineBase::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 = ApiEngineBase::Schema::Inbox::BlastResponse.new(
|
31
|
+
existing_users:,
|
32
|
+
new_users:,
|
33
|
+
text:,
|
34
|
+
title:,
|
35
|
+
id: ar.id,
|
36
|
+
created_by: ApiEngineBase::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 ApiEngineBase
|
4
|
+
module InboxService
|
5
|
+
module Message
|
6
|
+
class Metadata < ApiEngineBase::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
|
+
ApiEngineBase::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 = ApiEngineBase::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 ApiEngineBase
|
4
|
+
module InboxService
|
5
|
+
module Message
|
6
|
+
class Modify < ApiEngineBase::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 = ApiEngineBase::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 ApiEngineBase
|
4
|
+
module InboxService
|
5
|
+
module Message
|
6
|
+
class Retrieve < ApiEngineBase::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 = ApiEngineBase::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
|
+
|
@@ -0,0 +1,33 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ApiEngineBase
|
4
|
+
module InboxService
|
5
|
+
module Message
|
6
|
+
class Send < ApiEngineBase::ServiceBase
|
7
|
+
validate :user, is_a: User, required: true
|
8
|
+
validate :text, is_a: String, required: true
|
9
|
+
validate :title, is_a: String, required: true
|
10
|
+
validate :message_blast, is_a: ::MessageBlast, required: false
|
11
|
+
validate :pushed, is_one: [true, false], default: false
|
12
|
+
|
13
|
+
def call
|
14
|
+
message = create_message!
|
15
|
+
context.message = message
|
16
|
+
end
|
17
|
+
|
18
|
+
def push_notification!
|
19
|
+
# TODO: Push notifications
|
20
|
+
end
|
21
|
+
|
22
|
+
def create_message!
|
23
|
+
::Message.create!(
|
24
|
+
user:,
|
25
|
+
text: ,
|
26
|
+
title: ,
|
27
|
+
message_blast:,
|
28
|
+
)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -5,6 +5,7 @@ module ApiEngineBase::Jwt
|
|
5
5
|
|
6
6
|
validate :token, is_a: String, required: true, sensitive: true
|
7
7
|
validate :bypass_email_validation, is_one: [true, false], default: false
|
8
|
+
validate :with_reset, is_one: [true, false], default: false
|
8
9
|
|
9
10
|
def call
|
10
11
|
result = Decode.(token:)
|
@@ -14,7 +15,7 @@ module ApiEngineBase::Jwt
|
|
14
15
|
end
|
15
16
|
payload = result.payload
|
16
17
|
|
17
|
-
|
18
|
+
expires_at = validate_generated_at!(generated_at: payload[:generated_at])
|
18
19
|
|
19
20
|
user = User.find(payload[:user_id]) rescue nil
|
20
21
|
if user.nil?
|
@@ -29,25 +30,39 @@ module ApiEngineBase::Jwt
|
|
29
30
|
end
|
30
31
|
|
31
32
|
email_validation_required!(user:)
|
33
|
+
|
34
|
+
if with_reset
|
35
|
+
context.generated_token = ApiEngineBase::Jwt::LoginCreate.(user:).token
|
36
|
+
expires_at = ApiEngineBase.config.jwt.ttl.from_now.to_time
|
37
|
+
end
|
38
|
+
|
39
|
+
context.expires_at = expires_at.to_s
|
32
40
|
end
|
33
41
|
|
34
|
-
def
|
35
|
-
if
|
36
|
-
log_warn("
|
42
|
+
def validate_generated_at!(generated_at:)
|
43
|
+
if generated_at.nil?
|
44
|
+
log_warn("generated_at payload is missing from the JWT token. Cannot continue")
|
37
45
|
context.fail!(msg: "Unauthorized Access. Invalid Authorization token")
|
38
46
|
end
|
39
47
|
|
40
|
-
expires_time =
|
48
|
+
expires_time = begin
|
49
|
+
time = Time.at(generated_at)
|
50
|
+
time + ApiEngineBase.config.jwt.ttl
|
51
|
+
rescue
|
52
|
+
nil
|
53
|
+
end
|
41
54
|
|
42
55
|
if expires_time.nil?
|
43
|
-
log_warn("
|
56
|
+
log_warn("generated_at payload cannot be parsed. Cannot continue")
|
44
57
|
context.fail!(msg: "Unauthorized Access. Invalid Authorization token")
|
45
58
|
end
|
46
59
|
|
47
60
|
if expires_time < Time.now
|
48
|
-
log_warn("
|
61
|
+
log_warn("generated_at is no longer valid. Must request new token")
|
49
62
|
context.fail!(msg: "Unauthorized Access. Invalid Authorization token")
|
50
63
|
end
|
64
|
+
|
65
|
+
expires_time
|
51
66
|
end
|
52
67
|
|
53
68
|
def email_validation_required!(user:)
|