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
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: d10e7cc64ff7f99aa912731ce7200ebfbff18e90b0cc05b948a8f6034d15e54f
|
4
|
+
data.tar.gz: 07baec4ba6dc487ab145d68e87e489181153550b3e22f1db594d56e1fcc86e3d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 93d925277dac2e33eff40c8f3e4669f4ea162051ca6f9ec0d9d66791ed9e937384262fd983a04d4895c7891d1ec763d41f092a3242c4e685f9237f6b8af463ba
|
7
|
+
data.tar.gz: 7e7bd57ff6c8a991fe251c6afb01d4d3a8900a002973dc8c661313fd94ceac1f81e17f7dc80ec96f175bc110f4c0ec74638c75b9eb32b4a871815dff2c20e21f
|
data/README.md
CHANGED
@@ -1,8 +1,7 @@
|
|
1
1
|
# ApiEngineBase
|
2
|
-
|
2
|
+
This is an API only base engine to build on top of. This Engine takes care of all Authentication, Token Refresh, and RBAC Roles so that you do not have to! For all applications, you can get right to work on implementing the code directly related to your project rather than dealing with the administrative overhead.
|
3
3
|
|
4
|
-
|
5
|
-
How to use my plugin.
|
4
|
+
While this gem is heavily opinionated, everything can be configured to your liking.
|
6
5
|
|
7
6
|
## Installation
|
8
7
|
Add this line to your application's Gemfile:
|
@@ -21,8 +20,40 @@ Or install it yourself as:
|
|
21
20
|
$ gem install api_engine_base
|
22
21
|
```
|
23
22
|
|
24
|
-
##
|
25
|
-
|
23
|
+
## Initializing ApiEngineBase
|
24
|
+
Please follow all steps in [Initializing ApiEngineBase](docs/initializing.md)
|
25
|
+
|
26
|
+
|
27
|
+
## Available Routes
|
28
|
+
|
29
|
+
For more info, check out [Controllers ReadMe](docs/controllers.md)
|
30
|
+
|
31
|
+
Additionally, You can check out [RSpec Integration Testing](/spec/integration_test)
|
32
|
+
|
33
|
+
## Available Models
|
34
|
+
|
35
|
+
ApiEngineBase provides several Models at the in the root namespace. Core Models like `User` and `UserSecret` are readily available. Don't forget! You can add additional methods to these classes by opening them back up.
|
36
|
+
|
37
|
+
For more info, check out [Models ReadMe](doc/models.md)
|
38
|
+
|
39
|
+
## Authentication (JWT BearerToken)
|
40
|
+
Authentication ensures that we know which user is requesting the action. When the Engine is unable to authenticate, a `401` status code is returned.
|
41
|
+
|
42
|
+
For more info, check out [Authentication ReadMe](docs/authentication.md)
|
43
|
+
|
44
|
+
## Authorization (RBAC)
|
45
|
+
Authorization is only done after authentication. This is the act of ensuring that the user can perform the action it is requesting. Put differently, I know who you are, but I need to validate you have permissions to complete the action. When the engine is unable to authorize the user, a `403` status code is returned.
|
46
|
+
|
47
|
+
For more info, check out [Authentication ReadMe](docs/authorization.md)
|
48
|
+
|
49
|
+
## Sensitive Changes
|
50
|
+
|
51
|
+
For more info, check out [Sensitive Routes](docs/sensitive_routes.md)
|
52
|
+
|
53
|
+
## ServiceBase
|
54
|
+
ServiceBase is built on top of Interactor. The ServiceBase is the heart of all logic for ApiEngineBase. It includes Logging and enhanced ArgumentValidation that can directly return back to the API request.
|
55
|
+
|
56
|
+
For more info, check out [ServiceBase ReadMe](app/services/api_engine_base/README.md)
|
26
57
|
|
27
58
|
## License
|
28
|
-
The
|
59
|
+
The engine is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
@@ -0,0 +1,104 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ApiEngineBase
|
4
|
+
class AdminController < ::ApiEngineBase::ApplicationController
|
5
|
+
include ApiEngineBase::SchemaHelper
|
6
|
+
|
7
|
+
before_action :authenticate_user!
|
8
|
+
before_action :authorize_user!
|
9
|
+
before_action :user!, only: [:modify, :modify_role]
|
10
|
+
|
11
|
+
# Pagination is needed here
|
12
|
+
def show
|
13
|
+
schemafied_users = User.all.map { ApiEngineBase::Schema::User.convert_user_object(user: _1) }
|
14
|
+
schema = ApiEngineBase::Schema::Admin::Users.new(users: schemafied_users)
|
15
|
+
schema_succesful!(status: 200, schema:)
|
16
|
+
end
|
17
|
+
|
18
|
+
def modify
|
19
|
+
result = ApiEngineBase::UserAttributes::Modify.(user:, admin_user:, **modify_params)
|
20
|
+
if result.success?
|
21
|
+
schema = ApiEngineBase::Schema::User.convert_user_object(user: user.reload)
|
22
|
+
status = 201
|
23
|
+
schema_succesful!(status:, schema:)
|
24
|
+
else
|
25
|
+
if result.invalid_arguments
|
26
|
+
invalid_arguments!(
|
27
|
+
status: 400,
|
28
|
+
message: result.msg,
|
29
|
+
argument_object: result.invalid_argument_hash,
|
30
|
+
schema: ApiEngineBase::Schema::PlainText::LoginRequest
|
31
|
+
)
|
32
|
+
else
|
33
|
+
server_error!(result:)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def modify_role
|
39
|
+
result = ApiEngineBase::UserAttributes::Roles.(user:, admin_user:, roles: params[:roles] || [])
|
40
|
+
if result.success?
|
41
|
+
schema = ApiEngineBase::Schema::User.convert_user_object(user: user.reload)
|
42
|
+
status = 201
|
43
|
+
schema_succesful!(status:, schema:)
|
44
|
+
else
|
45
|
+
if result.invalid_arguments
|
46
|
+
invalid_arguments!(
|
47
|
+
status: 400,
|
48
|
+
message: result.msg,
|
49
|
+
argument_object: result.invalid_argument_hash,
|
50
|
+
schema: ApiEngineBase::Schema::PlainText::LoginRequest
|
51
|
+
)
|
52
|
+
else
|
53
|
+
server_error!(result:)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def impersonate
|
59
|
+
# TODO: @matt-taylor
|
60
|
+
end
|
61
|
+
|
62
|
+
private
|
63
|
+
|
64
|
+
def server_error!(result:)
|
65
|
+
status = 500
|
66
|
+
schema = ApiEngineBase::Schema::Error::Base.new(status:, message: result.msg)
|
67
|
+
render(json: schema.to_h, status:)
|
68
|
+
end
|
69
|
+
|
70
|
+
def modify_params
|
71
|
+
{
|
72
|
+
email: params[:email],
|
73
|
+
email_validated: safe_boolean(value: params[:email_validated]),
|
74
|
+
first_name: params[:first_name],
|
75
|
+
last_name: params[:last_name],
|
76
|
+
username: params[:username],
|
77
|
+
verifier_token: safe_boolean(value: params[:verifier_token]),
|
78
|
+
}.compact
|
79
|
+
end
|
80
|
+
|
81
|
+
def admin_user
|
82
|
+
# current_user is defined via authenticate_user! before action
|
83
|
+
current_user
|
84
|
+
end
|
85
|
+
|
86
|
+
def user!
|
87
|
+
_user = User.where(id: params[:user_id]).first
|
88
|
+
if _user
|
89
|
+
@user = _user
|
90
|
+
return true
|
91
|
+
end
|
92
|
+
|
93
|
+
status = 400
|
94
|
+
schema = ApiEngineBase::Schema::Error::Base.new(status:, message: "Invalid user")
|
95
|
+
render(json: schema.to_h, status:)
|
96
|
+
# Must return false so callbacks know to halt propagation
|
97
|
+
false
|
98
|
+
end
|
99
|
+
|
100
|
+
def user
|
101
|
+
@user ||= nil
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
@@ -2,12 +2,21 @@
|
|
2
2
|
|
3
3
|
module ApiEngineBase
|
4
4
|
class ApplicationController < ActionController::API
|
5
|
-
|
5
|
+
AUTHENTICATION_HEADER = "Authentication"
|
6
|
+
AUTHENTICATION_EXPIRE_HEADER = "X-Authentication-Expire"
|
7
|
+
AUTHENTICATION_WITH_RESET = "X-Authentication-Reset"
|
8
|
+
|
9
|
+
def safe_boolean(value:)
|
10
|
+
return nil unless [true, false, "true", "false", "0", "1", 0, 1].include?(value)
|
11
|
+
|
12
|
+
ActiveModel::Type::Boolean.new.cast(value)
|
13
|
+
end
|
6
14
|
|
7
15
|
###
|
8
|
-
#
|
16
|
+
# Authenticate user via the passed in header
|
17
|
+
# AUTHENTICATION_HEADER="Bearer: {token value}"
|
9
18
|
def authenticate_user!(bypass_email_validation: false)
|
10
|
-
raw_token = request.headers[
|
19
|
+
raw_token = request.headers[AUTHENTICATION_HEADER]
|
11
20
|
if raw_token.nil?
|
12
21
|
status = 401
|
13
22
|
schema = ApiEngineBase::Schema::Error::Base.new(status:, message: "Bearer token missing")
|
@@ -16,9 +25,14 @@ module ApiEngineBase
|
|
16
25
|
end
|
17
26
|
|
18
27
|
token = raw_token.split("Bearer:")[1].strip
|
19
|
-
|
28
|
+
with_reset = safe_boolean(value: request.headers[AUTHENTICATION_WITH_RESET])
|
29
|
+
result = ApiEngineBase::Jwt::AuthenticateUser.(token:, bypass_email_validation:, with_reset:)
|
20
30
|
if result.success?
|
21
31
|
@current_user = result.user
|
32
|
+
response.set_header(AUTHENTICATION_EXPIRE_HEADER, result.expires_at)
|
33
|
+
if with_reset
|
34
|
+
response.set_header(AUTHENTICATION_WITH_RESET, result.generated_token)
|
35
|
+
end
|
22
36
|
true
|
23
37
|
else
|
24
38
|
status = 401
|
@@ -29,19 +43,39 @@ module ApiEngineBase
|
|
29
43
|
end
|
30
44
|
end
|
31
45
|
|
46
|
+
###
|
47
|
+
# Authenticate user via the passed in header without validating email
|
32
48
|
def authenticate_user_without_email_verification!
|
33
49
|
authenticate_user!(bypass_email_validation: true)
|
34
50
|
end
|
35
51
|
|
36
|
-
|
37
|
-
|
52
|
+
###
|
53
|
+
# After Authenticating user, see if the user needs authorization on the route
|
54
|
+
def authorize_user!
|
55
|
+
if current_user.nil?
|
56
|
+
Rails.logger.error { "Current User is not defined. This means that authenticate_user! was not called" }
|
57
|
+
status = 401
|
58
|
+
schema = ApiEngineBase::Schema::Error::Base.new(status:, message: "Bearer token missing")
|
59
|
+
render(json: schema.to_h, status:)
|
60
|
+
return false
|
61
|
+
end
|
62
|
+
result = ApiEngineBase::Authorize::Validate.(user: current_user, controller: self.class, method: params[:action])
|
63
|
+
|
64
|
+
if result.success?
|
65
|
+
@current_user = result.user
|
66
|
+
true
|
67
|
+
else
|
68
|
+
# Current user is not authorized for the current Controller#action
|
69
|
+
status = 403
|
70
|
+
schema = ApiEngineBase::Schema::Error::Base.new(status:, message: result.msg)
|
71
|
+
render(json: schema.to_h, status:)
|
72
|
+
# Must return false so callbacks know to halt propagation
|
73
|
+
false
|
74
|
+
end
|
38
75
|
end
|
39
76
|
|
40
|
-
def
|
41
|
-
|
42
|
-
# token_valid_till:,
|
43
|
-
# needs_email_verification:,
|
44
|
-
# }
|
77
|
+
def current_user
|
78
|
+
@current_user ||= nil
|
45
79
|
end
|
46
80
|
end
|
47
81
|
end
|
@@ -12,7 +12,7 @@ module ApiEngineBase
|
|
12
12
|
if result.success?
|
13
13
|
schema = ApiEngineBase::Schema::PlainText::LoginResponse.new(
|
14
14
|
token: result.token,
|
15
|
-
header_name:
|
15
|
+
header_name: AUTHENTICATION_HEADER,
|
16
16
|
message: "Successfully logged user in"
|
17
17
|
)
|
18
18
|
status = 201
|
@@ -0,0 +1,89 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ApiEngineBase
|
4
|
+
module Inbox
|
5
|
+
class MessageBlastController < ::ApiEngineBase::ApplicationController
|
6
|
+
include ApiEngineBase::SchemaHelper
|
7
|
+
|
8
|
+
before_action :authenticate_user!
|
9
|
+
before_action :authorize_user!
|
10
|
+
|
11
|
+
# GET /inbox/blast
|
12
|
+
def metadata
|
13
|
+
result = ApiEngineBase::InboxService::Blast::Metadata.(id: params[:id].to_i)
|
14
|
+
schema_succesful!(status: 200, schema: result.metadata)
|
15
|
+
end
|
16
|
+
|
17
|
+
# GET /inbox/blast/:id
|
18
|
+
def blast
|
19
|
+
result = ApiEngineBase::InboxService::Blast::Retrieve.(id: params[:id].to_i)
|
20
|
+
if result.success?
|
21
|
+
schema = result.message_blast
|
22
|
+
status = 200
|
23
|
+
schema_succesful!(status:, schema:)
|
24
|
+
else
|
25
|
+
invalid_arguments!(
|
26
|
+
status: 400,
|
27
|
+
message: result.msg,
|
28
|
+
argument_object: result.invalid_argument_hash,
|
29
|
+
schema: ApiEngineBase::Schema::PlainText::LoginRequest,
|
30
|
+
)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
# POST /inbox/blast
|
35
|
+
def create
|
36
|
+
upsert
|
37
|
+
end
|
38
|
+
|
39
|
+
# PATCH /inbox/blast/:id
|
40
|
+
def modify
|
41
|
+
upsert(id: params[:id].to_i)
|
42
|
+
end
|
43
|
+
|
44
|
+
# DELETE /inbox/blast/:id
|
45
|
+
def delete
|
46
|
+
result = ApiEngineBase::InboxService::Blast::Delete.(id: params[:id].to_i)
|
47
|
+
if result.success?
|
48
|
+
schema = result.message
|
49
|
+
status = 200
|
50
|
+
render :json, { id: params[:id], msg: "Message Blast message deleted" }
|
51
|
+
else
|
52
|
+
invalid_arguments!(
|
53
|
+
status: 400,
|
54
|
+
message: result.msg,
|
55
|
+
argument_object: result.invalid_argument_hash,
|
56
|
+
schema: ApiEngineBase::Schema::PlainText::LoginRequest,
|
57
|
+
)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
private
|
62
|
+
|
63
|
+
def upsert(id: nil)
|
64
|
+
upsert_params = {
|
65
|
+
user: current_user,
|
66
|
+
existing_users: safe_boolean(value: params[:existing_users]),
|
67
|
+
new_users: safe_boolean(value: params[:new_users]),
|
68
|
+
text: params[:text],
|
69
|
+
title: params[:title],
|
70
|
+
id:,
|
71
|
+
}.compact
|
72
|
+
result = ApiEngineBase::InboxService::Blast::Upsert.(**upsert_params)
|
73
|
+
|
74
|
+
if result.success?
|
75
|
+
schema = result.blast
|
76
|
+
status = 200
|
77
|
+
schema_succesful!(status:, schema:)
|
78
|
+
else
|
79
|
+
invalid_arguments!(
|
80
|
+
status: 400,
|
81
|
+
message: result.msg,
|
82
|
+
argument_object: result.invalid_argument_hash,
|
83
|
+
schema: ApiEngineBase::Schema::Inbox::BlastRequest
|
84
|
+
)
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
@@ -0,0 +1,79 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ApiEngineBase
|
4
|
+
module Inbox
|
5
|
+
class MessageController < ::ApiEngineBase::ApplicationController
|
6
|
+
include ApiEngineBase::SchemaHelper
|
7
|
+
|
8
|
+
before_action :authenticate_user!
|
9
|
+
|
10
|
+
# GET: /inbox/messages
|
11
|
+
def metadata
|
12
|
+
result = ApiEngineBase::InboxService::Message::Metadata.(user: current_user)
|
13
|
+
if result.success?
|
14
|
+
schema = result.metadata
|
15
|
+
status = 200
|
16
|
+
schema_succesful!(status:, schema:)
|
17
|
+
else
|
18
|
+
invalid_arguments!(
|
19
|
+
status: 400,
|
20
|
+
message: result.msg,
|
21
|
+
argument_object: result.invalid_argument_hash,
|
22
|
+
schema: ApiEngineBase::Schema::PlainText::LoginRequest
|
23
|
+
)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
# GET: /inbox/messages/:id
|
28
|
+
def message
|
29
|
+
result = ApiEngineBase::InboxService::Message::Retrieve.(user: current_user, id: params[:id].to_i)
|
30
|
+
if result.success?
|
31
|
+
schema = result.message
|
32
|
+
status = 200
|
33
|
+
schema_succesful!(status:, schema:)
|
34
|
+
else
|
35
|
+
invalid_arguments!(
|
36
|
+
status: 400,
|
37
|
+
message: result.msg,
|
38
|
+
argument_object: result.invalid_argument_hash,
|
39
|
+
schema: ApiEngineBase::Schema::PlainText::LoginRequest
|
40
|
+
)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
# POST: /inbox/messages/ack
|
45
|
+
# Body { ids: [<list of ids to ack>] }
|
46
|
+
def ack
|
47
|
+
modify(type: ApiEngineBase::InboxService::Message::Modify::VIEWED)
|
48
|
+
end
|
49
|
+
|
50
|
+
# POST: /inbox/messages/delete
|
51
|
+
# Body { ids: [<list of ids to delete>] }
|
52
|
+
def delete
|
53
|
+
modify(type: ApiEngineBase::InboxService::Message::Modify::DELETE)
|
54
|
+
end
|
55
|
+
|
56
|
+
private
|
57
|
+
|
58
|
+
def modify(type:)
|
59
|
+
result = ApiEngineBase::InboxService::Message::Modify.(
|
60
|
+
user: current_user,
|
61
|
+
ids: params[:ids],
|
62
|
+
type:,
|
63
|
+
)
|
64
|
+
if result.success?
|
65
|
+
schema = result.modified
|
66
|
+
status = 200
|
67
|
+
schema_succesful!(status:, schema:)
|
68
|
+
else
|
69
|
+
invalid_arguments!(
|
70
|
+
status: 400,
|
71
|
+
message: result.msg,
|
72
|
+
argument_object: result.invalid_argument_hash,
|
73
|
+
schema: ApiEngineBase::Schema::PlainText::LoginRequest
|
74
|
+
)
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ApiEngineBase
|
4
|
+
class UserController < ::ApiEngineBase::ApplicationController
|
5
|
+
include ApiEngineBase::SchemaHelper
|
6
|
+
|
7
|
+
before_action :authenticate_user!
|
8
|
+
|
9
|
+
def show
|
10
|
+
schema = ApiEngineBase::Schema::User.convert_user_object(user: current_user)
|
11
|
+
schema_succesful!(status: 200, schema:)
|
12
|
+
end
|
13
|
+
|
14
|
+
def modify
|
15
|
+
result = ApiEngineBase::UserAttributes::Modify.(user: current_user, **modify_params)
|
16
|
+
if result.success?
|
17
|
+
schema = ApiEngineBase::Schema::User.convert_user_object(user: current_user.reload)
|
18
|
+
status = 201
|
19
|
+
schema_succesful!(status:, schema:)
|
20
|
+
else
|
21
|
+
if result.invalid_arguments
|
22
|
+
invalid_arguments!(
|
23
|
+
status: 400,
|
24
|
+
message: result.msg,
|
25
|
+
argument_object: result.invalid_argument_hash,
|
26
|
+
schema: ApiEngineBase::Schema::PlainText::LoginRequest
|
27
|
+
)
|
28
|
+
else
|
29
|
+
status = 500
|
30
|
+
schema = ApiEngineBase::Schema::Error::Base.new(status:, message: result.msg)
|
31
|
+
render(json: schema.to_h, status:)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
private
|
37
|
+
|
38
|
+
def modify_params
|
39
|
+
{
|
40
|
+
email: params[:email],
|
41
|
+
email_validated: safe_boolean(value: params[:email_validated]),
|
42
|
+
first_name: params[:first_name],
|
43
|
+
last_name: params[:last_name],
|
44
|
+
username: params[:username],
|
45
|
+
verifier_token: safe_boolean(value: params[:verifier_token]),
|
46
|
+
}.compact
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -3,5 +3,43 @@
|
|
3
3
|
module ApiEngineBase
|
4
4
|
class ApplicationRecord < ActiveRecord::Base
|
5
5
|
self.abstract_class = true
|
6
|
+
|
7
|
+
def self.attribute_to_type_mapping
|
8
|
+
@attribute_to_type_mapping ||= begin
|
9
|
+
mapping = ActiveSupport::HashWithIndifferentAccess.new
|
10
|
+
columns_hash.each do |attribute_name, metadata|
|
11
|
+
base = nil
|
12
|
+
ruby_type = nil
|
13
|
+
allowed_types = nil
|
14
|
+
serialized_type = nil
|
15
|
+
case metadata.type
|
16
|
+
when :string, :text
|
17
|
+
base = ruby_type = String
|
18
|
+
when :integer, :bigint
|
19
|
+
base = ruby_type = Integer
|
20
|
+
when :datetime, :time, :date
|
21
|
+
base = String
|
22
|
+
ruby_type = [DateTime, Time]
|
23
|
+
when :float, :decimal
|
24
|
+
base = ruby_type = Float
|
25
|
+
when :boolean
|
26
|
+
base = "Boolean"
|
27
|
+
ruby_type = [TrueClass, FalseClass]
|
28
|
+
allowed_types = [true, false]
|
29
|
+
else
|
30
|
+
# All else fails convert to String and continue
|
31
|
+
base = ruby_type = String
|
32
|
+
end
|
33
|
+
|
34
|
+
attribute_type = attribute_types[attribute_name]
|
35
|
+
if attribute_type.is_a?(ActiveRecord::Type::Serialized)
|
36
|
+
serialized_type = attribute_type.coder.object_class
|
37
|
+
end
|
38
|
+
mapping[attribute_name] = { serialized_type:, base:, ruby_type:, allowed_types: }.compact
|
39
|
+
end
|
40
|
+
|
41
|
+
mapping
|
42
|
+
end
|
43
|
+
end
|
6
44
|
end
|
7
45
|
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# == Schema Information
|
4
|
+
#
|
5
|
+
# Table name: messages
|
6
|
+
#
|
7
|
+
# id :bigint not null, primary key
|
8
|
+
# pushed :boolean default(FALSE)
|
9
|
+
# text :text(65535)
|
10
|
+
# title :string(255)
|
11
|
+
# viewed :boolean default(FALSE)
|
12
|
+
# created_at :datetime not null
|
13
|
+
# updated_at :datetime not null
|
14
|
+
# message_blast_id :bigint
|
15
|
+
# user_id :bigint not null
|
16
|
+
#
|
17
|
+
# Indexes
|
18
|
+
#
|
19
|
+
# index_messages_on_message_blast_id (message_blast_id)
|
20
|
+
# index_messages_on_user_id (user_id)
|
21
|
+
#
|
22
|
+
# Foreign Keys
|
23
|
+
#
|
24
|
+
# fk_rails_... (message_blast_id => message_blasts.id)
|
25
|
+
# fk_rails_... (user_id => users.id)
|
26
|
+
#
|
27
|
+
class Message < ApplicationRecord
|
28
|
+
belongs_to :user
|
29
|
+
belongs_to :message_blast, optional: true
|
30
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# == Schema Information
|
4
|
+
#
|
5
|
+
# Table name: message_blasts
|
6
|
+
#
|
7
|
+
# id :bigint not null, primary key
|
8
|
+
# existing_users :boolean default(FALSE)
|
9
|
+
# new_users :boolean default(FALSE)
|
10
|
+
# text :text(65535)
|
11
|
+
# title :string(255)
|
12
|
+
# created_at :datetime not null
|
13
|
+
# updated_at :datetime not null
|
14
|
+
# user_id :bigint not null
|
15
|
+
#
|
16
|
+
# Indexes
|
17
|
+
#
|
18
|
+
# index_message_blasts_on_user_id (user_id)
|
19
|
+
#
|
20
|
+
# Foreign Keys
|
21
|
+
#
|
22
|
+
# fk_rails_... (user_id => users.id)
|
23
|
+
#
|
24
|
+
class MessageBlast < ApplicationRecord
|
25
|
+
has_many :messages
|
26
|
+
belongs_to :user
|
27
|
+
end
|
data/app/models/user.rb
CHANGED
@@ -16,6 +16,7 @@
|
|
16
16
|
# password_consecutive_fail :integer default(0)
|
17
17
|
# password_digest :string(255) default(""), not null
|
18
18
|
# recovery_password_digest :string(255) default(""), not null
|
19
|
+
# roles :string(255) default([])
|
19
20
|
# successful_login :integer default(0)
|
20
21
|
# username :string(255)
|
21
22
|
# verifier_token :string(255)
|
@@ -35,16 +36,26 @@ class User < ApiEngineBase::ApplicationRecord
|
|
35
36
|
validates :username, uniqueness: true
|
36
37
|
validates :email, uniqueness: true
|
37
38
|
|
39
|
+
###
|
40
|
+
# Serialize the roles column to check for inclusion easily
|
41
|
+
serialize :roles, coder: JSON, type: Array
|
42
|
+
|
43
|
+
has_many :messages
|
44
|
+
|
38
45
|
def full_name
|
39
46
|
"#{first_name} #{last_name}"
|
40
47
|
end
|
41
48
|
|
42
|
-
def
|
43
|
-
return verifier_token if verifier_token
|
44
|
-
|
49
|
+
def reset_verifier_token!
|
45
50
|
value = SecureRandom.alphanumeric(32)
|
46
|
-
update!(verifier_token: value)
|
51
|
+
update!(verifier_token: value, verifier_token_last_reset: Time.now)
|
47
52
|
|
48
53
|
value
|
49
54
|
end
|
55
|
+
|
56
|
+
def retreive_verifier_token!
|
57
|
+
return verifier_token if verifier_token
|
58
|
+
|
59
|
+
reset_verifier_token!
|
60
|
+
end
|
50
61
|
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
# ApiEngineBase Service
|
2
|
+
|
3
|
+
`ApiEngineBase::ServiceBase` an abstraction around the Ruby Gem Interactor. It dds custom functionality to the base Service and is intended to be an inherited Class to create Application logic code. All Services in `ApiEngineBase` utilize this base Service class for convenience and DRYness.
|
4
|
+
|
5
|
+
## What does ServiceBase offer
|
6
|
+
|
7
|
+
### Logging
|
8
|
+
`ServiceBase` offers a convenient way to tag logs. It keeps track of:
|
9
|
+
- The start of the the Logic call
|
10
|
+
- The time it took to complete the logic
|
11
|
+
- The status of the logic
|
12
|
+
|
13
|
+
Additionally, it provides some convenience methods for logging
|
14
|
+
- `log_debug`
|
15
|
+
- `log_info`
|
16
|
+
- `log_warn`
|
17
|
+
- `log_error`
|
18
|
+
|
19
|
+
### Argument Validation
|
20
|
+
Argument Validation is the powerhouse behind ServiceBase
|
21
|
+
|
22
|
+
Customized argument validation can be created by adding the method `validate!`
|
23
|
+
```ruby
|
24
|
+
class MyServiceClass < ApiEngineBase::ServiceBase
|
25
|
+
|
26
|
+
def call
|
27
|
+
end
|
28
|
+
|
29
|
+
def validate!
|
30
|
+
# run custom validations before executing call
|
31
|
+
end
|
32
|
+
end
|
33
|
+
```
|
34
|
+
|
35
|
+
Other more complex Argument validation includes:
|
36
|
+
- Validating Presence of Argument
|
37
|
+
- Validating Type of argument
|
38
|
+
- Validating a composition of argument values (At least, At Most, Exactly)
|
39
|
+
- Delegate context variable to the class for simplicity
|
40
|
+
- Validating Argument length or size is `<` `≤` `==` `>` `≥`
|
41
|
+
|
42
|
+
For More information, Check out the [ArgumentValidation ReadMe](argument_validation/README.md)
|
43
|
+
|
44
|
+
|
45
|
+
## Basic Examples:
|
46
|
+
Check out the examples used in this directory!
|
47
|
+
|
48
|
+
|
49
|
+
|