command_tower 0.3.0 → 0.4.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: cf20c74b952fc6ca7dc2c7a67212ce9e5e8eb96a93bcb6173bd40564d28ceecc
4
- data.tar.gz: 1e6d03692209feecdad59b05f8296792ab5ccc0b62f301dc80bc5d88afe4a091
3
+ metadata.gz: 634d9c131508812e791849829e9cfe172b41c2508d25d3eefbad69a61ecb4228
4
+ data.tar.gz: c2c38369dd2c60e1656e0f607bda06518d32590baba669c05c1072e6cf6ac97b
5
5
  SHA512:
6
- metadata.gz: a6aeb3aa6c47f3b253b26bc0778c8862e80b78601bd495acea69709a4b0f39f830d3b236c1ed3385b0ad8457598dde34203a200ff809aa86e839c02706c10abd
7
- data.tar.gz: a3d9687e594904270b83b2c033cb1c83abe38c9ebceeef3b4c8c3b482e95b86b5423cb05f5d6598aa3659f1cca7d3996514a39d9fdbe4b67f975e2910f996b60
6
+ metadata.gz: 12c681eeaeefcb8ed919cbb2b0eaf77849c74eccec569d99ead80e1cafecd0b69c106ed12c8902b2e294f1886cd8b148a1ab798a46695e059bd6f92eaa9d2ff0
7
+ data.tar.gz: a28884515dc4d80c789e62a1b0971c8fadbb2dcdd3d5ad3c3df6d2b6740bbea5c2c378418b604b171eb42e2ba7c4adc8a89087e6ffc53981b8ee7771e7f8495a
data/README.md CHANGED
@@ -34,7 +34,7 @@ Additionally, You can check out [RSpec Integration Testing](/spec/integration_te
34
34
 
35
35
  CommandTower 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
36
 
37
- For more info, check out [Models ReadMe](doc/models.md)
37
+ For more info, check out [Models ReadMe](docs/models.md)
38
38
 
39
39
  ## Authentication (JWT BearerToken)
40
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.
@@ -55,5 +55,8 @@ ServiceBase is built on top of Interactor. The ServiceBase is the heart of all l
55
55
 
56
56
  For more info, check out [ServiceBase ReadMe](app/services/command_tower/README.md)
57
57
 
58
+ ## Pagination
59
+ Pagination is available on routes when explicitly set. There are a subset of routes available as part of this engine. Pagination is available to be used in downstream services as well. For more info, check out [Pagination ReadMe](docs/pagination.md)
60
+
58
61
  ## License
59
62
  The engine is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
@@ -3,18 +3,23 @@
3
3
  module CommandTower
4
4
  class AdminController < ::CommandTower::ApplicationController
5
5
  include CommandTower::SchemaHelper
6
+ include CommandTower::PaginationHelper
6
7
 
7
8
  before_action :authenticate_user!
8
9
  before_action :authorize_user!
9
10
  before_action :user!, only: [:modify, :modify_role]
10
11
 
11
- # Pagination is needed here
12
+ # GET: /admin
13
+ # Pagination Available
12
14
  def show
13
- schemafied_users = User.all.map { CommandTower::Schema::User.convert_user_object(user: _1) }
14
- schema = CommandTower::Schema::Admin::Users.new(users: schemafied_users)
15
+ schema = CommandTower::AdminService::Users.(
16
+ user: admin_user,
17
+ pagination: pagination_values,
18
+ ).schema
15
19
  schema_succesful!(status: 200, schema:)
16
20
  end
17
21
 
22
+ # POST: /modify
18
23
  def modify
19
24
  result = CommandTower::UserAttributes::Modify.(user:, admin_user:, **modify_params)
20
25
  if result.success?
@@ -35,6 +40,7 @@ module CommandTower
35
40
  end
36
41
  end
37
42
 
43
+ # POST: /modify/role
38
44
  def modify_role
39
45
  result = CommandTower::UserAttributes::Roles.(user:, admin_user:, roles: params[:roles] || [])
40
46
  if result.success?
@@ -13,6 +13,7 @@ module CommandTower
13
13
  schema = CommandTower::Schema::PlainText::LoginResponse.new(
14
14
  token: result.token,
15
15
  header_name: AUTHENTICATION_HEADER,
16
+ user: CommandTower::Schema::User.convert_user_object(user: result.user),
16
17
  message: "Successfully logged user in"
17
18
  )
18
19
  status = 201
@@ -4,12 +4,14 @@ module CommandTower
4
4
  module Inbox
5
5
  class MessageController < ::CommandTower::ApplicationController
6
6
  include CommandTower::SchemaHelper
7
+ include CommandTower::PaginationHelper
7
8
 
8
9
  before_action :authenticate_user!
9
10
 
10
11
  # GET: /inbox/messages
12
+ # Pagination Available
11
13
  def metadata
12
- result = CommandTower::InboxService::Message::Metadata.(user: current_user)
14
+ result = CommandTower::InboxService::Message::Metadata.(user: current_user, pagination: pagination_values)
13
15
  if result.success?
14
16
  schema = result.metadata
15
17
  status = 200
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ module CommandTower
4
+ module PaginationHelper
5
+ def pagination_values
6
+ (pagination_from_body || pagination_from_query || {}).compact
7
+ end
8
+
9
+ def pagination_from_body
10
+ pagination = params[:pagination]
11
+ return nil unless pagination.is_a?(ActionController::Parameters) || pagination.is_a?(Hash)
12
+
13
+ {
14
+ page: pagination_safe_integer_convert(pagination[:page]),
15
+ limit: pagination_safe_integer_convert(pagination[:limit]),
16
+ cursor: pagination_safe_integer_convert(pagination[:cursor]),
17
+ }
18
+ end
19
+
20
+ def pagination_from_query
21
+ return nil unless safe_boolean(value: params[:pagination]) == true
22
+
23
+ {
24
+ page: pagination_safe_integer_convert(params[:page]),
25
+ limit: pagination_safe_integer_convert(params[:limit]),
26
+ cursor: pagination_safe_integer_convert(params[:cursor]),
27
+ }
28
+ end
29
+
30
+ def pagination_safe_integer_convert(value, type = Integer)
31
+ return if value.presence.nil?
32
+
33
+ if value.to_i.to_s == value
34
+ value.to_i
35
+ else
36
+ nil
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ module CommandTower
4
+ module AdminService
5
+ class Users < CommandTower::ServiceBase
6
+ include CommandTower::PaginationServiceHelper
7
+
8
+ on_argument_validation :fail_early
9
+
10
+ # to be used for auditing later maybe?
11
+ validate :user, is_a: User, required: true
12
+
13
+ def call
14
+ schemafied_users = query.map { CommandTower::Schema::User.convert_user_object(user: _1) }
15
+ context.schema = CommandTower::Schema::Admin::Users.new(
16
+ users: schemafied_users,
17
+ count: schemafied_users.count,
18
+ pagination: pagination_schema,
19
+ )
20
+ end
21
+
22
+ def default_query
23
+ ::User.all.select(*CommandTower.config.user.default_attributes)
24
+ end
25
+ end
26
+ end
27
+ end
@@ -4,25 +4,31 @@ module CommandTower
4
4
  module InboxService
5
5
  module Message
6
6
  class Metadata < CommandTower::ServiceBase
7
+ include CommandTower::PaginationServiceHelper
8
+
7
9
  on_argument_validation :fail_early
8
10
 
9
11
  validate :user, is_a: User, required: true
10
12
 
11
13
  def call
12
- entities = ::Message.where(user:).select(:id, :title, :viewed).map do |message|
14
+ entities = query.map do |message|
13
15
  CommandTower::Schema::Inbox::MessageEntity.new(
14
- title: message.title,
15
- id: message.id,
16
- viewed: message.viewed,
16
+ id: message.id,
17
+ title: message.title,
18
+ viewed: message.viewed,
17
19
  )
18
20
  end
19
21
 
20
22
  params = {
21
- entities: entities.nil? ? nil : entities,
22
23
  count: entities.length,
23
- }
24
+ entities: entities.nil? ? nil : entities,
25
+ pagination: pagination_schema,
26
+ }.compact
27
+ context.metadata = CommandTower::Schema::Inbox::Metadata.new(**params)
28
+ end
24
29
 
25
- context.metadata = CommandTower::Schema::Inbox::Metadata.new(**params.compact)
30
+ def default_query
31
+ ::Message.where(user:).select(:id, :title, :viewed)
26
32
  end
27
33
  end
28
34
  end
@@ -0,0 +1,88 @@
1
+ # frozen_string_literal: true
2
+
3
+ module CommandTower
4
+ module PaginationServiceHelper
5
+ def query
6
+ return default_query if pagination_params.empty?
7
+
8
+ context.pagination_used = true
9
+
10
+ default_query.offset(pagination_params[:offset]).limit(pagination_params[:limit])
11
+ end
12
+
13
+ def default_query
14
+ raise NoMethodError, "Method must be defined on base class"
15
+ end
16
+
17
+ def pagination_schema
18
+ return nil unless context.pagination_used
19
+
20
+ base_params = {
21
+ limit: pagination_params[:limit],
22
+ cursor: pagination_params[:offset],
23
+ }
24
+ query = base_params.to_query
25
+ current = CommandTower::Schema::Page.new(query:, **base_params)
26
+
27
+ base_params[:cursor] += pagination_params[:limit]
28
+ query = base_params.to_query
29
+ next_page = CommandTower::Schema::Page.new(query:, **base_params)
30
+
31
+ count_available = default_query.size
32
+ total_pages = count_available / pagination_params[:limit]
33
+ # when offset is zero, it returns 0...Min page is 1
34
+ base_current_page = (pagination_params[:offset].to_f / pagination_params[:limit].to_f)
35
+ if pagination_params[:offset] % pagination_params[:limit] == 0
36
+ current_page = (base_current_page + 1).to_i
37
+ else
38
+ current_page = base_current_page.ceil
39
+ end
40
+ # current_page = [, 1].max
41
+ # Ensure we cannot go negative when no elements are returned
42
+ remaining_pages = [total_pages - current_page, 0].max
43
+
44
+ CommandTower::Schema::Pagination.new(
45
+ count_available:,
46
+ total_pages:,
47
+ current_page:,
48
+ remaining_pages:,
49
+ current:,
50
+ next: next_page,
51
+ )
52
+ end
53
+
54
+ def pagination_params
55
+ return {} if context.pagination.presence.nil?
56
+
57
+ __params = { limit: pagination_limit }
58
+ if pagination_cursor
59
+ # When cursor is provided, we take cursor and limit as injections
60
+ __params[:offset] = pagination_cursor
61
+ else
62
+ # When cursor is not provided, use default of page and limit
63
+ __params[:offset] = pagination_page * pagination_limit
64
+ end
65
+
66
+ __params.compact
67
+ end
68
+
69
+ def pagination_cursor
70
+ return if context.pagination[:cursor].nil?
71
+
72
+ # Cursor must be greater than or equal to 0
73
+ [context.pagination[:cursor].to_i, 0].max
74
+ end
75
+
76
+ def pagination_page
77
+ # Page must be greater than or equal to 0
78
+ # Incoming page is 1 indexed -- must convert to 0 indexed to work properly
79
+ [(context.pagination[:page] || 1).to_i, 1].max - 1
80
+ end
81
+
82
+ def pagination_limit
83
+ return CommandTower.config.pagination.limit if context.pagination[:limit].nil?
84
+
85
+ [context.pagination[:limit].to_i, 1].max
86
+ end
87
+ end
88
+ end
@@ -2,16 +2,17 @@
2
2
 
3
3
  require "singleton"
4
4
  require "class_composer"
5
+ require "command_tower/configuration/admin/config"
6
+ require "command_tower/configuration/application/config"
7
+ require "command_tower/configuration/authorization/config"
5
8
  require "command_tower/configuration/base"
6
9
  require "command_tower/configuration/email/config"
7
10
  require "command_tower/configuration/jwt/config"
8
11
  require "command_tower/configuration/login/config"
9
12
  require "command_tower/configuration/otp/config"
10
- require "command_tower/configuration/username/config"
11
- require "command_tower/configuration/application/config"
12
- require "command_tower/configuration/admin/config"
13
- require "command_tower/configuration/authorization/config"
13
+ require "command_tower/configuration/pagination/config"
14
14
  require "command_tower/configuration/user/config"
15
+ require "command_tower/configuration/username/config"
15
16
 
16
17
  module CommandTower
17
18
  module Configuration
@@ -66,6 +67,11 @@ module CommandTower
66
67
  allowed: Configuration::Admin::Config,
67
68
  default: Configuration::Admin::Config.new
68
69
 
70
+ add_composer :pagination,
71
+ desc: "Pagination configuration for the app",
72
+ allowed: Configuration::Pagination::Config,
73
+ default: Configuration::Pagination::Config.new
74
+
69
75
  # To be Deleted
70
76
  add_composer :otp,
71
77
  desc: "One Time Password generation is used for ease in quickly validating a users actions. This is good for short term validation requirements as opposed to UserSecrets",
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ module CommandTower
4
+ module Configuration
5
+ module Pagination
6
+ class Config < ::CommandTower::Configuration::Base
7
+ include ClassComposer::Generator
8
+
9
+ add_composer :limit,
10
+ desc: "Default Limit for pagination return when not present on service or query/body. Negative values are treated like no limit",
11
+ allowed: Integer,
12
+ default: 10
13
+ end
14
+ end
15
+ end
16
+ end
@@ -1,14 +1,15 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "command_tower/schema/user"
4
- require "command_tower/schema/page"
4
+ require "command_tower/schema/pagination"
5
5
 
6
6
  module CommandTower
7
7
  module Schema
8
8
  module Admin
9
9
  class Users < JsonSchematize::Generator
10
10
  add_field name: :users, array_of_types: true, type: CommandTower::Schema::User
11
- add_field name: :pagination, type: CommandTower::Schema::Page, required: false
11
+ add_field name: :count, type: Integer, required: false
12
+ add_field name: :pagination, type: CommandTower::Schema::Pagination, required: false
12
13
  end
13
14
  end
14
15
  end
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "command_tower/schema/inbox/message_blast_entity"
4
- require "command_tower/schema/page"
4
+ require "command_tower/schema/pagination"
5
5
 
6
6
  module CommandTower
7
7
  module Schema
@@ -9,7 +9,7 @@ module CommandTower
9
9
  class MessageBlastMetadata < JsonSchematize::Generator
10
10
  add_field name: :entities, array_of_types: true, type: CommandTower::Schema::Inbox::MessageBlastEntity, required: false
11
11
  add_field name: :count, type: Integer
12
- add_field name: :pagination, type: CommandTower::Schema::Page, required: false
12
+ add_field name: :pagination, type: CommandTower::Schema::Pagination, required: false
13
13
  end
14
14
  end
15
15
  end
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "command_tower/schema/inbox/message_entity"
4
- require "command_tower/schema/page"
4
+ require "command_tower/schema/pagination"
5
5
 
6
6
  module CommandTower
7
7
  module Schema
@@ -11,7 +11,7 @@ module CommandTower
11
11
 
12
12
  add_field name: :entities, array_of_types: true, type: CommandTower::Schema::Inbox::MessageEntity, required: false
13
13
  add_field name: :count, type: Integer
14
- add_field name: :pagination, type: CommandTower::Schema::Page, required: false
14
+ add_field name: :pagination, type: CommandTower::Schema::Pagination, required: false
15
15
  end
16
16
  end
17
17
  end
@@ -3,12 +3,9 @@
3
3
  module CommandTower
4
4
  module Schema
5
5
  class Page < JsonSchematize::Generator
6
- schema_default option: :dig_type, value: :string
7
-
8
- add_field name: :count, type: Integer
9
6
  add_field name: :cursor, type: Integer
10
7
  add_field name: :limit, type: Integer
11
- add_field name: :next, type: String
8
+ add_field name: :query, type: String
12
9
  end
13
10
  end
14
11
  end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "command_tower/schema/page"
4
+
5
+ module CommandTower
6
+ module Schema
7
+ class Pagination < JsonSchematize::Generator
8
+ add_field name: :current, type: Page
9
+ add_field name: :next, type: Page, required: false
10
+ add_field name: :count_available, type: Integer, required: false
11
+ add_field name: :current_page, type: Integer, required: false
12
+ add_field name: :remaining_pages, type: Integer, required: false
13
+ add_field name: :total_pages, type: Integer, required: false
14
+ end
15
+ end
16
+ end
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "command_tower/schema/user"
4
+
3
5
  module CommandTower
4
6
  module Schema
5
7
  module PlainText
@@ -7,6 +9,7 @@ module CommandTower
7
9
  add_field name: :token, type: String
8
10
  add_field name: :header_name, type: String
9
11
  add_field name: :message, type: String
12
+ add_field name: :user, type: CommandTower::Schema::User
10
13
  end
11
14
  end
12
15
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module CommandTower
4
- VERSION = "0.3.0"
4
+ VERSION = "0.4.0"
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: command_tower
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.0
4
+ version: 0.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - matt-taylor
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2025-03-02 00:00:00.000000000 Z
11
+ date: 2025-05-08 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rotp
@@ -28,16 +28,22 @@ dependencies:
28
28
  name: rails
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
- - - "~>"
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '7.0'
34
+ - - "<"
32
35
  - !ruby/object:Gem::Version
33
- version: '7'
36
+ version: '9.0'
34
37
  type: :runtime
35
38
  prerelease: false
36
39
  version_requirements: !ruby/object:Gem::Requirement
37
40
  requirements:
38
- - - "~>"
41
+ - - ">="
42
+ - !ruby/object:Gem::Version
43
+ version: '7.0'
44
+ - - "<"
39
45
  - !ruby/object:Gem::Version
40
- version: '7'
46
+ version: '9.0'
41
47
  - !ruby/object:Gem::Dependency
42
48
  name: jwt
43
49
  requirement: !ruby/object:Gem::Requirement
@@ -127,6 +133,7 @@ files:
127
133
  - app/controllers/command_tower/user_controller.rb
128
134
  - app/controllers/command_tower/username_controller.rb
129
135
  - app/helpers/command_tower/application_helper.rb
136
+ - app/helpers/command_tower/pagination_helper.rb
130
137
  - app/helpers/command_tower/schema_helper.rb
131
138
  - app/jobs/command_tower/application_job.rb
132
139
  - app/mailers/command_tower/application_mailer.rb
@@ -137,6 +144,7 @@ files:
137
144
  - app/models/user.rb
138
145
  - app/models/user_secret.rb
139
146
  - app/services/command_tower/README.md
147
+ - app/services/command_tower/admin_service/users.rb
140
148
  - app/services/command_tower/argument_validation.rb
141
149
  - app/services/command_tower/argument_validation/README.md
142
150
  - app/services/command_tower/argument_validation/class_methods.rb
@@ -162,6 +170,7 @@ files:
162
170
  - app/services/command_tower/login_strategy/plain_text/email_verification/send.rb
163
171
  - app/services/command_tower/login_strategy/plain_text/email_verification/verify.rb
164
172
  - app/services/command_tower/login_strategy/plain_text/login.rb
173
+ - app/services/command_tower/pagination_service_helper.rb
165
174
  - app/services/command_tower/secrets.rb
166
175
  - app/services/command_tower/secrets/cleanse.rb
167
176
  - app/services/command_tower/secrets/generate.rb
@@ -194,6 +203,7 @@ files:
194
203
  - lib/command_tower/configuration/login/strategy/plain_text/email_verify.rb
195
204
  - lib/command_tower/configuration/login/strategy/plain_text/lockable.rb
196
205
  - lib/command_tower/configuration/otp/config.rb
206
+ - lib/command_tower/configuration/pagination/config.rb
197
207
  - lib/command_tower/configuration/user/config.rb
198
208
  - lib/command_tower/configuration/username/check.rb
199
209
  - lib/command_tower/configuration/username/config.rb
@@ -212,6 +222,7 @@ files:
212
222
  - lib/command_tower/schema/inbox/metadata.rb
213
223
  - lib/command_tower/schema/inbox/modified.rb
214
224
  - lib/command_tower/schema/page.rb
225
+ - lib/command_tower/schema/pagination.rb
215
226
  - lib/command_tower/schema/plain_text/create_user_request.rb
216
227
  - lib/command_tower/schema/plain_text/create_user_response.rb
217
228
  - lib/command_tower/schema/plain_text/email_verify_request.rb
@@ -223,8 +234,8 @@ files:
223
234
  - lib/command_tower/schema/user.rb
224
235
  - lib/command_tower/spec_helper.rb
225
236
  - lib/command_tower/version.rb
226
- - lib/generators/api_engine_base/configure/USAGE
227
- - lib/generators/api_engine_base/configure/configure_generator.rb
237
+ - lib/generators/command_tower/configure/USAGE
238
+ - lib/generators/command_tower/configure/configure_generator.rb
228
239
  - lib/tasks/auto_annotate_models.rake
229
240
  homepage: https://github.com/matt-taylor/command_tower
230
241
  licenses: