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.
Files changed (64) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +37 -6
  3. data/app/controllers/api_engine_base/admin_controller.rb +104 -0
  4. data/app/controllers/api_engine_base/application_controller.rb +45 -11
  5. data/app/controllers/api_engine_base/auth/plain_text_controller.rb +1 -1
  6. data/app/controllers/api_engine_base/inbox/message_blast_controller.rb +89 -0
  7. data/app/controllers/api_engine_base/inbox/message_controller.rb +79 -0
  8. data/app/controllers/api_engine_base/user_controller.rb +49 -0
  9. data/app/models/api_engine_base/application_record.rb +38 -0
  10. data/app/models/message.rb +30 -0
  11. data/app/models/message_blast.rb +27 -0
  12. data/app/models/user.rb +15 -4
  13. data/app/services/api_engine_base/README.md +49 -0
  14. data/app/services/api_engine_base/argument_validation/README.md +192 -0
  15. data/app/services/api_engine_base/argument_validation/class_methods.rb +2 -3
  16. data/app/services/api_engine_base/argument_validation/instance_methods.rb +13 -1
  17. data/app/services/api_engine_base/authorize/validate.rb +49 -0
  18. data/app/services/api_engine_base/inbox_service/blast/delete.rb +23 -0
  19. data/app/services/api_engine_base/inbox_service/blast/metadata.rb +26 -0
  20. data/app/services/api_engine_base/inbox_service/blast/new_user_blaster.rb +24 -0
  21. data/app/services/api_engine_base/inbox_service/blast/retrieve.rb +30 -0
  22. data/app/services/api_engine_base/inbox_service/blast/upsert.rb +67 -0
  23. data/app/services/api_engine_base/inbox_service/message/metadata.rb +35 -0
  24. data/app/services/api_engine_base/inbox_service/message/modify.rb +44 -0
  25. data/app/services/api_engine_base/inbox_service/message/retrieve.rb +36 -0
  26. data/app/services/api_engine_base/inbox_service/message/send.rb +33 -0
  27. data/app/services/api_engine_base/jwt/authenticate_user.rb +22 -7
  28. data/app/services/api_engine_base/jwt/login_create.rb +1 -1
  29. data/app/services/api_engine_base/login_strategy/plain_text/create.rb +1 -0
  30. data/app/services/api_engine_base/service_base.rb +4 -5
  31. data/app/services/api_engine_base/user_attributes/modify.rb +68 -0
  32. data/app/services/api_engine_base/user_attributes/roles.rb +27 -0
  33. data/config/routes.rb +32 -0
  34. data/db/migrate/20241117043720_create_api_engine_base_users.rb +2 -0
  35. data/db/migrate/20250223023306_create_api_engine_base_messages.rb +12 -0
  36. data/db/migrate/20250223023313_create_api_engine_base_message_blasts.rb +14 -0
  37. data/lib/api_engine_base/authorization/default.yml +42 -0
  38. data/lib/api_engine_base/authorization/entity.rb +101 -0
  39. data/lib/api_engine_base/authorization/role.rb +101 -0
  40. data/lib/api_engine_base/authorization.rb +85 -0
  41. data/lib/api_engine_base/configuration/admin/config.rb +18 -0
  42. data/lib/api_engine_base/configuration/application/config.rb +2 -2
  43. data/lib/api_engine_base/configuration/authorization/config.rb +24 -0
  44. data/lib/api_engine_base/configuration/config.rb +19 -1
  45. data/lib/api_engine_base/configuration/user/config.rb +56 -0
  46. data/lib/api_engine_base/engine.rb +38 -6
  47. data/lib/api_engine_base/error.rb +5 -0
  48. data/lib/api_engine_base/schema/admin/users.rb +15 -0
  49. data/lib/api_engine_base/schema/error/invalid_argument_response.rb +1 -1
  50. data/lib/api_engine_base/schema/inbox/blast_request.rb +15 -0
  51. data/lib/api_engine_base/schema/inbox/blast_response.rb +16 -0
  52. data/lib/api_engine_base/schema/inbox/message_blast_entity.rb +16 -0
  53. data/lib/api_engine_base/schema/inbox/message_blast_metadata.rb +16 -0
  54. data/lib/api_engine_base/schema/inbox/message_entity.rb +14 -0
  55. data/lib/api_engine_base/schema/inbox/metadata.rb +18 -0
  56. data/lib/api_engine_base/schema/inbox/modified.rb +13 -0
  57. data/lib/api_engine_base/schema/page.rb +14 -0
  58. data/lib/api_engine_base/schema/user.rb +28 -0
  59. data/lib/api_engine_base/schema.rb +13 -0
  60. data/lib/api_engine_base/spec_helper.rb +4 -3
  61. data/lib/api_engine_base/version.rb +1 -1
  62. data/lib/api_engine_base.rb +2 -2
  63. metadata +44 -5
  64. 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, matches: nil, is_a: nil, is_one: nil, lt: nil, lte: nil, eq: nil, gt: nil, gte: nil, delegation: true, sensitive: false, required: 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
- if Array(is_a).none? { _1 === value }
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
- validate_expires_at!(expires_at: payload[:expires_at])
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 validate_expires_at!(expires_at:)
35
- if expires_at.nil?
36
- log_warn("expires_at payload is missing from the JWT token. Cannot continue")
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 = Time.at(expires_at) rescue nil
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("expires_at payload cannot be parsed. Cannot continue")
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("expires_at is no longer valid. Must request new token")
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:)
@@ -12,7 +12,7 @@ module ApiEngineBase::Jwt
12
12
 
13
13
  def payload
14
14
  {
15
- expires_at: ApiEngineBase.config.jwt.ttl.from_now.to_i,
15
+ generated_at: Time.now.to_i,
16
16
  user_id: user.id,
17
17
  verifier_token: user.retreive_verifier_token!,
18
18
  }