slack-ruby-bot-server 2.2.1 → 2.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f7080b4c298a9246ee55a74395a69e185657e7999e7acd19c55792f3a5cc2fd4
4
- data.tar.gz: 4a5495629509d5b4352d42b0290ead69db7ffd8a157841eb7a85dd7d1053b928
3
+ metadata.gz: 8190478be418eff0f5da4e0cfede6b2203664c4e0812783575e9e00519b42b1c
4
+ data.tar.gz: 8a06246c7bd7052c5adf79b3aedfd95493408d909faca16eaca538f638b0aa9f
5
5
  SHA512:
6
- metadata.gz: 769de3df16397c503ed1075acda16a9b6f67a8a6dba7d2acad8a6d28c6a06dade0f7f2046f6f6cd560883dbee849dd17bdf913a0ee6b8eedc5928b9db58f40c1
7
- data.tar.gz: cabe33848ffe7d6895d1b659e28ff43922740062964d29c6ae7d7f152e33f38820b0849f5fdfe90d28f924904dc0b61496a4da723462aa6c1670688251843f78
6
+ metadata.gz: 26e3f8e38c8196c079ae1eaa8c5a6f7ec32cb71e963cb8e3f61f1f017dde997a739d0208d25f5a6dc1d84d38c9ba90b50072a16536b38a4b1444cbf9fbbc0e3b
7
+ data.tar.gz: 371ab36d34805242886ed780d01784535a133156fe633349389281effde66d62d607422ec6539fa892a430390eada2f5c6e66f0bd54be7591f17097db6b9ee94
@@ -21,12 +21,7 @@ jobs:
21
21
  entry:
22
22
  # These Active Support versions pass quirks_mode, removed in JSON 3.
23
23
  - { ruby: '3.2', mongoid: '6.4.8', mongodb: '4.4', json: '~> 2.0' }
24
- - { ruby: '3.2', mongoid: '7.2.3', mongodb: '4.4', json: '~> 2.0' }
25
- - { ruby: '3.2', mongoid: '7.3.0', mongodb: '4.4', json: '~> 2.0' }
26
- - { ruby: '3.3', mongoid: '7.3.0', mongodb: '5.0', json: '~> 2.0' }
27
24
  - { ruby: '3.4', mongoid: '7.5.2', mongodb: '5.0', json: '~> 2.0' }
28
- - { ruby: '3.4', mongoid: '8.1.11', mongodb: '6.0', json: '~> 2.0' }
29
- - { ruby: '3.4', mongoid: '8.1.11', mongodb: '7.0', json: '~> 2.0' }
30
25
  - { ruby: '3.4', mongoid: '8.1.11', mongodb: '8.0', json: '~> 2.0' }
31
26
  - { ruby: '3.3', mongoid: '~> 9.1', mongodb: '8.0', grape: '~> 3.2', json: '~> 3.0' }
32
27
  - { ruby: '3.4', mongoid: '~> 9.1', mongodb: '8.0', grape: '~> 4.0', json: '~> 3.0' }
@@ -20,14 +20,9 @@ jobs:
20
20
  fail-fast: false
21
21
  matrix:
22
22
  entry:
23
- # Active Support 6.x, 7.0, and 8.0 pass quirks_mode, removed in JSON 3.
24
- - { ruby: '3.2', postgresql: '11', active_record: '~> 6.0.0', json: '~> 2.0' }
23
+ # Active Support 6.x and 8.0 pass quirks_mode, removed in JSON 3.
25
24
  - { ruby: '3.2', postgresql: '11', active_record: '~> 6.1.0', json: '~> 2.0' }
26
- - { ruby: '3.2', postgresql: '14', active_record: '~> 7.0.0', json: '~> 2.0' }
27
25
  - { ruby: '3.3', postgresql: '14', active_record: '~> 7.2.0', json: '~> 3.0' }
28
- - { ruby: '3.4', postgresql: '14', active_record: '~> 7.2.0', json: '~> 3.0' }
29
- - { ruby: '3.4', postgresql: '15', active_record: '~> 8.0.2', json: '~> 2.0' }
30
- - { ruby: '3.4', postgresql: '16', active_record: '~> 8.0.2', json: '~> 2.0' }
31
26
  - { ruby: '3.4', postgresql: '17', active_record: '~> 8.0.2', json: '~> 2.0' }
32
27
  - { ruby: '3.3', postgresql: '18', active_record: '~> 8.1', grape: '~> 3.2', json: '~> 3.0' }
33
28
  - { ruby: '3.4', postgresql: '18', active_record: '~> 8.1', grape: '~> 4.0', json: '~> 3.0' }
data/AGENTS.md CHANGED
@@ -44,7 +44,7 @@ Browser tests require Firefox. Selenium Manager provisions geckodriver when need
44
44
 
45
45
  ## Changes and Compatibility
46
46
 
47
- Preserve both database adapters and the existing unique CI matrix combinations, including explicit Grape 3/4 coverage. Quote version strings in workflow YAML and validate workflow changes with `actionlint`.
47
+ Preserve both database adapters, representative legacy ORM major versions, and the modern Ruby/Grape 3/4 combinations in the six-job-per-adapter CI matrices. Avoid multiplying equivalent jobs across database server versions. Quote version strings in workflow YAML and validate workflow changes with `actionlint`.
48
48
 
49
49
  Keep JSON 2 constraints scoped to affected legacy matrix jobs through `JSON_VERSION`; modern jobs explicitly test JSON 3. Do not add a blanket JSON upper bound to the Gemfile or gemspec.
50
50
 
data/CHANGELOG.md CHANGED
@@ -1,5 +1,13 @@
1
1
  ### Changelog
2
2
 
3
+ ### 2.3.0 (2026/10/25)
4
+
5
+ * [#188](https://github.com/slack-ruby/slack-ruby-bot-server/pull/188): Reduce the database CI matrices from 22 to 12 jobs while retaining representative legacy ORM majors and modern Ruby/Grape 3/4 coverage - [@dblock](https://github.com/dblock), [@Copilot](https://github.com/apps/copilot-swe-agent).
6
+ * [#189](https://github.com/slack-ruby/slack-ruby-bot-server/pull/189): Add a prefixed Team fabricator entry point and preserve application-defined `:team` fabricators in the legacy RSpec helper - [@dblock](https://github.com/dblock), [@Copilot](https://github.com/apps/copilot-swe-agent).
7
+ * [#186](https://github.com/slack-ruby/slack-ruby-bot-server/pull/186): Document persisted custom Team attributes for both adapters and Rails concern loading/reloading - [@dblock](https://github.com/dblock), [@Copilot](https://github.com/apps/copilot-swe-agent).
8
+ * [#183](https://github.com/slack-ruby/slack-ruby-bot-server/pull/183): Add a `config.server_pages` block to configure page middleware options centrally without changing the installed library's defaults - [@dblock](https://github.com/dblock), [@Copilot](https://github.com/apps/copilot-swe-agent).
9
+ * [#184](https://github.com/slack-ruby/slack-ruby-bot-server/pull/184): Add a configurable API application and separately mountable team read/registration endpoints, preserving the default API while supporting custom routes and registration-only applications - [@dblock](https://github.com/dblock), [@Copilot](https://github.com/apps/copilot-swe-agent).
10
+ * [#185](https://github.com/slack-ruby/slack-ruby-bot-server/pull/185): Skip inactive installations in status checks and deactivate revoked credentials through service callbacks, returning an explicit failed ping instead of failing the whole status response - [@dblock](https://github.com/dblock), [@Copilot](https://github.com/apps/copilot-swe-agent).
3
11
  ### 2.2.1 (2026/10/24)
4
12
 
5
13
  * [#182](https://github.com/slack-ruby/slack-ruby-bot-server/pull/182): Modernize CI with current actions, native database services, dependency caching, and explicit Ruby 4, Mongoid 9, Active Record 8.1, PostgreSQL 18, Grape 3/4, and JSON 2/3 coverage while retaining existing compatibility combinations - [@dblock](https://github.com/dblock), [@Copilot](https://github.com/apps/copilot-swe-agent).
data/CONTRIBUTING.md CHANGED
@@ -39,7 +39,9 @@ bundle exec rake
39
39
 
40
40
  For PostgreSQL, set `DATABASE_ADAPTER=activerecord`, `ACTIVERECORD_VERSION`, and `DATABASE_URL` instead. `GRAPE_VERSION` optionally constrains the Grape version for either adapter; without it, Bundler selects a compatible version. CI preserves the older compatibility combinations and adds explicit Grape 3 and 4 jobs with current ORM versions, including Ruby 4. Each database runs in a health-checked service container, while Firefox runs under Xvfb. Selenium Manager provisions the matching driver.
41
41
 
42
- `JSON_VERSION` optionally constrains the development/test JSON dependency; otherwise it is unrestricted. CI uses JSON 2 for Mongoid 6-8 and Active Record 6.x, 7.0, and 8.0 because their Active Support versions pass `quirks_mode`, removed in JSON 3. Mongoid 9, Active Record 7.2, and Active Record 8.1 jobs explicitly test JSON 3. These selectors do not restrict the published gem's runtime dependencies.
42
+ CI runs six jobs per adapter: three representative legacy ORM/database stacks and three modern combinations (Ruby 3.3/Grape 3, Ruby 3.4/Grape 4, and Ruby 4/Grape 4). Mongoid retains 6.4, 7.5, 8.1, and 9.1; Active Record retains 6.1, 7.2, 8.0, and 8.1. Redundant database-server variants and older ORM minor combinations are omitted. A combination no longer appearing in CI is not by itself a change to the gem's supported runtime dependencies.
43
+
44
+ `JSON_VERSION` optionally constrains the development/test JSON dependency; otherwise it is unrestricted. CI uses JSON 2 for Mongoid 6-8 and Active Record 6.1 and 8.0 because their Active Support versions pass `quirks_mode`, removed in JSON 3. Mongoid 9, Active Record 7.2, and Active Record 8.1 jobs explicitly test JSON 3. These selectors do not restrict the published gem's runtime dependencies.
43
45
 
44
46
  On Linux, install [Firefox](https://www.mozilla.org/firefox/) and `Xvfb`, then run:
45
47
 
data/README.md CHANGED
@@ -17,9 +17,15 @@ Build a complete Slack bot service with Slack button integration, in Ruby.
17
17
  - [Storage](#storage)
18
18
  - [MongoDB](#mongodb)
19
19
  - [ActiveRecord](#activerecord)
20
+ - [Extending Team](#extending-team)
21
+ - [Mongoid Attributes](#mongoid-attributes)
22
+ - [Active Record Attributes](#active-record-attributes)
23
+ - [Rails Loading and Reloading](#rails-loading-and-reloading)
20
24
  - [OAuth Version and Scopes](#oauth-version-and-scopes)
21
25
  - [Slack App](#slack-app)
22
26
  - [API](#api)
27
+ - [Custom Endpoints](#custom-endpoints)
28
+ - [Team Endpoint Visibility](#team-endpoint-visibility)
23
29
  - [App](#app)
24
30
  - [Service Manager](#service-manager)
25
31
  - [Lifecycle Callbacks](#lifecycle-callbacks)
@@ -27,7 +33,9 @@ Build a complete Slack bot service with Slack button integration, in Ruby.
27
33
  - [Extensions](#extensions)
28
34
  - [Service Class](#service-class)
29
35
  - [HTML Templates](#html-templates)
36
+ - [Page Middleware Configuration](#page-middleware-configuration)
30
37
  - [Access Tokens](#access-tokens)
38
+ - [Testing](#testing)
31
39
  - [Sample Bots Using Slack Ruby Bot Server](#sample-bots-using-slack-ruby-bot-server)
32
40
  - [Copyright & License](#copyright--license)
33
41
 
@@ -37,7 +45,7 @@ A library that contains a web server and a RESTful [Grape](http://github.com/rub
37
45
 
38
46
  ## Stable Release
39
47
 
40
- You're reading the documentation for the **stable** release of slack-ruby-bot-server, 2.2.1. See [UPGRADING](UPGRADING.md) when upgrading from an older version. See [MIGRATING](MIGRATING.md) for help with migrating Legacy Slack Apps to Granular Scopes.
48
+ You're reading the documentation for the **stable** release of slack-ruby-bot-server, 2.3.0. See [UPGRADING](UPGRADING.md) when upgrading from an older version. See [MIGRATING](MIGRATING.md) for help with migrating Legacy Slack Apps to Granular Scopes.
41
49
 
42
50
  ## Make Your Own
43
51
 
@@ -107,6 +115,76 @@ db_config = if Gem::Version.new(Psych::VERSION) >= Gem::Version.new('3.1.0.pre1'
107
115
  ActiveRecord::Base.establish_connection(db_config)
108
116
  ```
109
117
 
118
+ #### Extending Team
119
+
120
+ The installation model is the top-level `::Team` class for both adapters, not `SlackRubyBotServer::Models::Team`. Load `slack-ruby-bot-server` before extending it. The model already includes its database adapter and timestamps; do not include them again or declare a different superclass.
121
+
122
+ ##### Mongoid Attributes
123
+
124
+ Add fields by reopening the existing class in an application file, for example `lib/team_extensions.rb`:
125
+
126
+ ```ruby
127
+ require 'slack-ruby-bot-server'
128
+
129
+ class Team
130
+ field :subscription_plan, type: String, default: 'free'
131
+
132
+ def paid_plan?
133
+ subscription_plan == 'paid'
134
+ end
135
+ end
136
+ ```
137
+
138
+ Require that file from your startup code after loading the framework and before serving requests or starting the service. Mongoid does not require a schema migration for the new field.
139
+
140
+ ##### Active Record Attributes
141
+
142
+ Add a column to the existing `teams` table with an application migration. Use your application's migration version; this example is also compatible with older supported Active Record versions:
143
+
144
+ ```ruby
145
+ class AddSubscriptionPlanToTeams < ActiveRecord::Migration[6.0]
146
+ def change
147
+ add_column :teams, :subscription_plan, :string, default: 'free', null: false
148
+ end
149
+ end
150
+ ```
151
+
152
+ Run the migration before code uses the attribute. Active Record provides its reader and writer from the column; do not use `attr_accessor`, which would bypass persistence. Add methods by reopening `class Team` after loading the framework, as above, but omit the Mongoid-only `field` declaration.
153
+
154
+ ##### Rails Loading and Reloading
155
+
156
+ Putting another `app/models/team.rb` in a Rails app is not a reliable way to extend a class already loaded by a gem. Put reloadable behavior in a concern instead:
157
+
158
+ ```ruby
159
+ # app/models/concerns/team_extensions.rb
160
+ module TeamExtensions
161
+ extend ActiveSupport::Concern
162
+
163
+ def paid_plan?
164
+ subscription_plan == 'paid'
165
+ end
166
+ end
167
+ ```
168
+
169
+ Include it in `::Team` from a reloader callback, rather than autoloading the concern directly during initializer execution:
170
+
171
+ ```ruby
172
+ # config/initializers/team_extensions.rb
173
+ require 'slack-ruby-bot-server'
174
+
175
+ if SlackRubyBotServer.config.mongoid?
176
+ ::Team.field :subscription_plan, type: String, default: 'free'
177
+ end
178
+
179
+ Rails.application.config.to_prepare do
180
+ ::Team.include(TeamExtensions)
181
+ end
182
+ ```
183
+
184
+ With Active Record, use the migration above instead of declaring a field in the initializer. The callback runs at boot and after development reloads; including the same concern more than once is safe. Keep one-time field declarations outside the callback. Do not query the database or swallow database errors just to attach methods: the extension can be loaded before the `teams` table exists.
185
+
186
+ The framework currently owns the global `Team` constant. An unrelated application model with the same name is a separate [name-collision limitation](https://github.com/slack-ruby/slack-ruby-bot-server/pull/83), not an extension mechanism.
187
+
110
188
  ### OAuth Version and Scopes
111
189
 
112
190
  Configure your app's [OAuth version](https://api.slack.com/authentication/oauth-v2) and [scopes](https://api.slack.com/legacy/oauth-scopes) as needed by your application.
@@ -151,6 +229,58 @@ Navigate to either [localhost:9292](http://localhost:9292) or the ngrok URL abov
151
229
 
152
230
  This library implements an app, [SlackRubyBotServer::App](lib/slack-ruby-bot-server/app.rb) and a service manager, [SlackRubyBotServer::Service](lib/slack-ruby-bot-server/service.rb). It also provides [default HTML templates and JS scripts](public) for Slack integration.
153
231
 
232
+ #### Custom Endpoints
233
+
234
+ Set `config.api` to a Rack application, typically a `Grape::API` class, to customize the API without reopening the framework's classes. The default is `nil`, which uses `SlackRubyBotServer::Api::Endpoints::RootEndpoint`, including extensions mounted there by other gems. `SlackRubyBotServer::Config.reset!` restores this default.
235
+
236
+ To add an endpoint alongside the default API:
237
+
238
+ ```ruby
239
+ class ExtraApi < Grape::API
240
+ prefix 'api'
241
+ format :json
242
+
243
+ get :foobar do
244
+ { ok: true }
245
+ end
246
+ end
247
+
248
+ class ApplicationApi < Grape::API
249
+ mount SlackRubyBotServer::Api::Endpoints::RootEndpoint
250
+ mount ExtraApi
251
+ end
252
+
253
+ SlackRubyBotServer.configure do |config|
254
+ config.api = ApplicationApi
255
+ end
256
+ ```
257
+
258
+ Configure this before starting the application. In `config.ru`, continue running `SlackRubyBotServer::Api::Middleware.instance`; both `/api/foobar` and the existing `/api` routes then share the framework's CORS, URL-rewrite, and page middleware. A separate `Rack::Cascade` stack is unnecessary.
259
+
260
+ In Rails, require the file defining `ApplicationApi` from your initializer before assigning `config.api`. For reloadable classes, make the assignment inside `Rails.application.config.to_prepare` so it references the current class. Mount `SlackRubyBotServer::Api::Middleware.instance` in your routes, for example `mount SlackRubyBotServer::Api::Middleware.instance => '/'`, rather than bypassing that middleware stack.
261
+
262
+ #### Team Endpoint Visibility
263
+
264
+ The default API exposes `GET /api/teams` and `GET /api/teams/:id` publicly. To retain OAuth registration without those read endpoints, use the separately mountable `TeamsEndpoint::Create`. `TeamsEndpoint::Read` contains the list and detail routes; `TeamsEndpoint` continues to mount both for backward compatibility.
265
+
266
+ ```ruby
267
+ class RegistrationApi < Grape::API
268
+ include SlackRubyBotServer::Api::Helpers::ErrorHelpers
269
+
270
+ prefix 'api'
271
+ format :json
272
+ formatter :json, Grape::Formatter::Roar
273
+
274
+ mount SlackRubyBotServer::Api::Endpoints::TeamsEndpoint::Create
275
+ end
276
+
277
+ SlackRubyBotServer.configure do |config|
278
+ config.api = RegistrationApi
279
+ end
280
+ ```
281
+
282
+ This replaces the default root API: it does not expose its status, root discovery, Swagger, or team-read routes. Do not also mount `RootEndpoint` or `TeamsEndpoint`, since either would restore the public team reads. If you use the Events extension or other custom endpoints, explicitly mount their routes with their required signature verification too. Add your own authentication before mounting `TeamsEndpoint::Read` if you need protected team queries. OAuth registration still requires a valid Slack authorization code; this configuration does not add a general authentication layer.
283
+
154
284
  #### App
155
285
 
156
286
  The app instance checks for a working database connection, ensures indexes, performs migrations, sets up bot aliases and log levels. You can introduce custom behavior into the app lifecycle by subclassing `SlackRubyBotServer::App` and creating an instance of the child class in `config.ru`.
@@ -307,10 +437,51 @@ SlackRubyBotServer.configure do |config|
307
437
  end
308
438
  ```
309
439
 
440
+ #### Page Middleware Configuration
441
+
442
+ Use `config.server_pages` to configure the underlying [`Rack::ServerPages`](https://github.com/migrs/rack-server-pages) middleware in an initializer, before `SlackRubyBotServer::Api::Middleware.instance` is first accessed. The block receives its configuration object after the framework applies `view_paths`, so it can customize any option supported by your installed `rack-server-pages` version.
443
+
444
+ For example, with `rack-server-pages` 0.3.0 or newer:
445
+
446
+ ```ruby
447
+ SlackRubyBotServer.configure do |config|
448
+ config.server_pages do |pages|
449
+ pages.cache_control = 'private, no-cache'
450
+ pages.static_cache_control = 'public, no-cache'
451
+ end
452
+ end
453
+ ```
454
+
455
+ `cache_control` sets the default `Cache-Control` header for rendered templates, including generated CSS. `static_cache_control` sets it for static files. `no-cache` allows storage but requires revalidation before reuse; use `private, no-store` for sensitive pages. You can also configure `static_etags` and `static_timestamps` with version 0.3.0 or newer. These settings do not change downstream API responses.
456
+
457
+ Without a block, the framework preserves the installed library's defaults and existing `view_paths` behavior. Registering another block replaces the previous one, and `SlackRubyBotServer::Config.reset!` clears it. The block runs when the middleware stack is built, not on each request. If you change configuration after initialization, rebuild the stack with `SlackRubyBotServer::Api::Middleware.reset!`; configure it before startup in production.
458
+
310
459
  ### Access Tokens
311
460
 
312
461
  By default the implementation of [Team](lib/slack-ruby-bot-server/models/team) stores the value of the token with all the requested OAuth scopes in both `token` and `activated_user_access_token` (for backwards compatibility), along with `oauth_version` and `oauth_scope`. If a legacy Slack bot integration `bot_access_token` is present, it is stored as `token`, and `activated_user_access_token` is the token that has all the requested OAuth scopes.
313
462
 
463
+ ## Testing
464
+
465
+ The optional test helpers use [Fabrication](https://fabricationgem.org/) and Faker. Add `fabrication` and `faker` to your application's test dependencies; neither is a runtime dependency of this gem.
466
+
467
+ To use the framework's installation defaults without reserving your application's `:team` fabricator name, load the prefixed-only helper:
468
+
469
+ ```ruby
470
+ require 'slack-ruby-bot-server/rspec/fabricators'
471
+
472
+ team = Fabricate(:slack_ruby_bot_server_team)
473
+ ```
474
+
475
+ This fabricator creates the existing `::Team` model with unique token/team ID sequences and a generated name. Applications can register their own `:team` before or after this helper, or inherit the framework defaults:
476
+
477
+ ```ruby
478
+ Fabricator(:team, from: :slack_ruby_bot_server_team) do
479
+ name { 'My application team' }
480
+ end
481
+ ```
482
+
483
+ The existing `require 'slack-ruby-bot-server/rspec'` remains supported. It loads the prefixed fabricator and registers the legacy `:team` alias only when no `:team` definition already exists. If your application defines `:team` later, replace the legacy require with `slack-ruby-bot-server/rspec/fabricators`; loading both entry points still opts into the legacy alias.
484
+
314
485
  ## Sample Bots Using Slack Ruby Bot Server
315
486
 
316
487
  * [slack-ruby-bot-server-events-sample](https://github.com/slack-ruby/slack-ruby-bot-server-events-sample), a generic sample
data/UPGRADING.md CHANGED
@@ -1,6 +1,30 @@
1
1
  Upgrading Slack-Ruby-Bot-Server
2
2
  ===============================
3
3
 
4
+ ### Upgrading to >= 2.3.0
5
+
6
+ #### Recommended Test Helper Migration
7
+
8
+ If your application defines its own `:team` fabricator, replace `require 'slack-ruby-bot-server/rspec'` with `require 'slack-ruby-bot-server/rspec/fabricators'`. This is recommended, not required: the new entry point registers only `:slack_ruby_bot_server_team`, leaving `:team` available regardless of whether your application's fabricator is loaded before or after the helper.
9
+
10
+ Use `Fabricate(:slack_ruby_bot_server_team)` for the framework defaults, or inherit them in your application's fabricator:
11
+
12
+ ```ruby
13
+ require 'slack-ruby-bot-server/rspec/fabricators'
14
+
15
+ Fabricator(:team, from: :slack_ruby_bot_server_team) do
16
+ name { 'My application team' }
17
+ end
18
+ ```
19
+
20
+ The legacy helper remains supported and registers `:team` only if no definition already exists. Replace the old require rather than adding the new one alongside it; loading both still opts into the legacy alias and can conflict with a `:team` definition loaded later.
21
+
22
+ #### Status Checks and Inactive Installations
23
+
24
+ `GET /api/status` now pings only active installations. When Slack returns `account_inactive` or `invalid_auth`, the installation is deactivated through `Service#deactivate!`, and the endpoint returns HTTP `200` with `ping: { "ok": false, "error": "account_inactive" }` (or `"invalid_auth"`). The active-team count reflects that deactivation. Monitoring should inspect the failed `ping` as well as the HTTP status.
25
+
26
+ When no active installations remain, no Slack ping is attempted. Other Slack errors retain their existing error response and do not deactivate installations.
27
+
4
28
  ### Upgrading to >= 2.2.0
5
29
 
6
30
  #### Cursor Helper Iterator Changes
@@ -2,118 +2,129 @@ module SlackRubyBotServer
2
2
  module Api
3
3
  module Endpoints
4
4
  class TeamsEndpoint < Grape::API
5
- format :json
6
- helpers Helpers::CursorHelpers
7
- helpers Helpers::SortHelpers
8
- helpers Helpers::PaginationParameters
9
-
10
- namespace :teams do
11
- desc 'Get a team.'
12
- params do
13
- requires :id, type: String, desc: 'Team ID.'
14
- end
15
- get ':id' do
16
- team = Team.find(params[:id]) || error!('Not Found', 404)
17
- present team, with: Presenters::TeamPresenter
18
- end
5
+ class Read < Grape::API
6
+ format :json
7
+ helpers Helpers::CursorHelpers
8
+ helpers Helpers::SortHelpers
9
+ helpers Helpers::PaginationParameters
19
10
 
20
- desc 'Get all the teams.'
21
- params do
22
- optional :active, type: ::Grape::API::Boolean, desc: 'Return active teams only.'
23
- use :pagination
24
- end
25
- sort Team::SORT_ORDERS
26
- get do
27
- teams = Team.all
28
- teams = teams.active if params[:active]
29
- teams = paginate_and_sort_by_cursor(teams, default_sort_order: '-id')
30
- present teams, with: Presenters::TeamsPresenter
31
- end
11
+ namespace :teams do
12
+ desc 'Get a team.'
13
+ params do
14
+ requires :id, type: String, desc: 'Team ID.'
15
+ end
16
+ get ':id' do
17
+ team = Team.find(params[:id]) || error!('Not Found', 404)
18
+ present team, with: Presenters::TeamPresenter
19
+ end
32
20
 
33
- desc 'Create a team using an OAuth token.'
34
- params do
35
- requires :code, type: String
36
- optional :state, type: String
37
- end
38
- post do
39
- client = Slack::Web::Client.new
40
-
41
- raise 'Missing SLACK_CLIENT_ID or SLACK_CLIENT_SECRET.' unless ENV.key?('SLACK_CLIENT_ID') && ENV.key?('SLACK_CLIENT_SECRET')
42
-
43
- options = {
44
- client_id: ENV.fetch('SLACK_CLIENT_ID', nil),
45
- client_secret: ENV.fetch('SLACK_CLIENT_SECRET', nil),
46
- code: params[:code]
47
- }
48
-
49
- rc = client.send(SlackRubyBotServer.config.oauth_access_method, options)
50
-
51
- token = nil
52
- access_token = nil
53
- user_id = nil
54
- bot_user_id = nil
55
- team_id = nil
56
- team_name = nil
57
- oauth_scope = nil
58
- oauth_version = SlackRubyBotServer::Config.oauth_version
59
-
60
- case oauth_version
61
- when :v2
62
- access_token = rc.access_token
63
- token = rc.access_token
64
- user_id = rc.authed_user&.id
65
- bot_user_id = rc.bot_user_id
66
- team_id = rc.team&.id
67
- team_name = rc.team&.name
68
- oauth_scope = rc.scope
69
- when :v1
70
- access_token = rc.access_token
71
- bot = rc.bot if rc.key?(:bot)
72
- token = bot ? bot.bot_access_token : access_token
73
- user_id = rc.user_id
74
- bot_user_id = bot ? bot.bot_user_id : nil
75
- team_id = rc.team_id
76
- team_name = rc.team_name
77
- oauth_scope = rc.scope
21
+ desc 'Get all the teams.'
22
+ params do
23
+ optional :active, type: ::Grape::API::Boolean, desc: 'Return active teams only.'
24
+ use :pagination
25
+ end
26
+ sort Team::SORT_ORDERS
27
+ get do
28
+ teams = Team.all
29
+ teams = teams.active if params[:active]
30
+ teams = paginate_and_sort_by_cursor(teams, default_sort_order: '-id')
31
+ present teams, with: Presenters::TeamsPresenter
78
32
  end
33
+ end
34
+ end
35
+
36
+ class Create < Grape::API
37
+ format :json
79
38
 
80
- team = Team.where(token: token).first
81
- team ||= Team.where(team_id: team_id, oauth_version: oauth_version).first
82
- team ||= Team.where(team_id: team_id).first
83
-
84
- if team
85
- team.ping_if_active!
86
-
87
- team.update!(
88
- oauth_version: oauth_version,
89
- oauth_scope: oauth_scope,
90
- activated_user_id: user_id,
91
- activated_user_access_token: access_token,
92
- bot_user_id: bot_user_id
93
- )
94
-
95
- raise "Team #{team.name} is already registered." if team.active?
96
-
97
- team.activate!(token)
98
- else
99
- team = Team.create!(
100
- token: token,
101
- oauth_version: oauth_version,
102
- oauth_scope: oauth_scope,
103
- team_id: team_id,
104
- name: team_name,
105
- activated_user_id: user_id,
106
- activated_user_access_token: access_token,
107
- bot_user_id: bot_user_id
108
- )
39
+ namespace :teams do
40
+ desc 'Create a team using an OAuth token.'
41
+ params do
42
+ requires :code, type: String
43
+ optional :state, type: String
109
44
  end
45
+ post do
46
+ client = Slack::Web::Client.new
47
+
48
+ raise 'Missing SLACK_CLIENT_ID or SLACK_CLIENT_SECRET.' unless ENV.key?('SLACK_CLIENT_ID') && ENV.key?('SLACK_CLIENT_SECRET')
110
49
 
111
- options = params.slice(:state)
50
+ options = {
51
+ client_id: ENV.fetch('SLACK_CLIENT_ID', nil),
52
+ client_secret: ENV.fetch('SLACK_CLIENT_SECRET', nil),
53
+ code: params[:code]
54
+ }
112
55
 
113
- Service.instance.create!(team, options)
114
- present team, with: Presenters::TeamPresenter
56
+ rc = client.send(SlackRubyBotServer.config.oauth_access_method, options)
57
+
58
+ token = nil
59
+ access_token = nil
60
+ user_id = nil
61
+ bot_user_id = nil
62
+ team_id = nil
63
+ team_name = nil
64
+ oauth_scope = nil
65
+ oauth_version = SlackRubyBotServer::Config.oauth_version
66
+
67
+ case oauth_version
68
+ when :v2
69
+ access_token = rc.access_token
70
+ token = rc.access_token
71
+ user_id = rc.authed_user&.id
72
+ bot_user_id = rc.bot_user_id
73
+ team_id = rc.team&.id
74
+ team_name = rc.team&.name
75
+ oauth_scope = rc.scope
76
+ when :v1
77
+ access_token = rc.access_token
78
+ bot = rc.bot if rc.key?(:bot)
79
+ token = bot ? bot.bot_access_token : access_token
80
+ user_id = rc.user_id
81
+ bot_user_id = bot ? bot.bot_user_id : nil
82
+ team_id = rc.team_id
83
+ team_name = rc.team_name
84
+ oauth_scope = rc.scope
85
+ end
86
+
87
+ team = Team.where(token: token).first
88
+ team ||= Team.where(team_id: team_id, oauth_version: oauth_version).first
89
+ team ||= Team.where(team_id: team_id).first
90
+
91
+ if team
92
+ team.ping_if_active!
93
+
94
+ team.update!(
95
+ oauth_version: oauth_version,
96
+ oauth_scope: oauth_scope,
97
+ activated_user_id: user_id,
98
+ activated_user_access_token: access_token,
99
+ bot_user_id: bot_user_id
100
+ )
101
+
102
+ raise "Team #{team.name} is already registered." if team.active?
103
+
104
+ team.activate!(token)
105
+ else
106
+ team = Team.create!(
107
+ token: token,
108
+ oauth_version: oauth_version,
109
+ oauth_scope: oauth_scope,
110
+ team_id: team_id,
111
+ name: team_name,
112
+ activated_user_id: user_id,
113
+ activated_user_access_token: access_token,
114
+ bot_user_id: bot_user_id
115
+ )
116
+ end
117
+
118
+ options = params.slice(:state)
119
+
120
+ Service.instance.create!(team, options)
121
+ present team, with: Presenters::TeamPresenter
122
+ end
115
123
  end
116
124
  end
125
+
126
+ mount Read
127
+ mount Create
117
128
  end
118
129
  end
119
130
  end
@@ -30,6 +30,7 @@ module SlackRubyBotServer
30
30
 
31
31
  use Rack::ServerPages do |config|
32
32
  config.view_path = SlackRubyBotServer::Config.view_paths
33
+ SlackRubyBotServer::Config.server_pages&.call(config)
33
34
  end
34
35
 
35
36
  run Middleware.new
@@ -37,7 +38,7 @@ module SlackRubyBotServer
37
38
  end
38
39
 
39
40
  def call(env)
40
- Endpoints::RootEndpoint.call(env)
41
+ (SlackRubyBotServer::Config.api || Endpoints::RootEndpoint).call(env)
41
42
  end
42
43
  end
43
44
  end
@@ -10,21 +10,27 @@ module SlackRubyBotServer
10
10
  "#{base_url(opts)}/status"
11
11
  end
12
12
 
13
+ property :ping
13
14
  property :teams_count
14
15
  property :active_teams_count
15
- property :ping
16
16
 
17
17
  def ping
18
18
  if SlackRubyBotServer::Config.mongoid?
19
- team = Team.asc(:_id).first
19
+ team = Team.active.asc(:_id).first
20
20
  elsif SlackRubyBotServer::Config.activerecord?
21
- team = Team.last
21
+ team = Team.active.last
22
22
  else
23
23
  raise 'Unsupported database driver.'
24
24
  end
25
25
  return unless team
26
26
 
27
27
  team.ping!
28
+ rescue Slack::Web::Api::Errors::SlackError => e
29
+ raise unless %w[account_inactive invalid_auth].include?(e.message)
30
+
31
+ Middleware.logger.warn "Status ping failed for #{team}: #{e.message}; deactivating installation."
32
+ Service.instance.deactivate!(team)
33
+ { ok: false, error: e.message }
28
34
  end
29
35
 
30
36
  def teams_count
@@ -8,12 +8,15 @@ module SlackRubyBotServer
8
8
  attr_accessor :view_paths
9
9
  attr_accessor :oauth_scope
10
10
  attr_accessor :oauth_version
11
+ attr_accessor :api
11
12
 
12
13
  def reset!
13
14
  self.logger = nil
14
15
  self.service_class = SlackRubyBotServer::Service
15
16
  self.oauth_scope = nil
16
17
  self.oauth_version = :v2
18
+ @server_pages = nil
19
+ self.api = nil
17
20
 
18
21
  self.view_paths = [
19
22
  'views',
@@ -56,6 +59,11 @@ module SlackRubyBotServer
56
59
  oauth_scope&.join(',')
57
60
  end
58
61
 
62
+ def server_pages(&block)
63
+ @server_pages = block if block
64
+ @server_pages
65
+ end
66
+
59
67
  def activerecord?
60
68
  database_adapter == :activerecord
61
69
  end
@@ -0,0 +1,5 @@
1
+ Fabricator(:slack_ruby_bot_server_team, class_name: 'Team') do
2
+ token { Fabricate.sequence(:team_token) { |i| "abc-#{i}" } }
3
+ team_id { Fabricate.sequence(:team_id) { |i| "T#{i}" } }
4
+ name { Faker::Lorem.word }
5
+ end
@@ -1,5 +1,3 @@
1
- Fabricator(:team) do
2
- token { Fabricate.sequence(:team_token) { |i| "abc-#{i}" } }
3
- team_id { Fabricate.sequence(:team_id) { |i| "T#{i}" } }
4
- name { Faker::Lorem.word }
5
- end
1
+ require_relative 'slack_ruby_bot_server_team'
2
+
3
+ Fabricator(:team, from: :slack_ruby_bot_server_team) unless Fabrication.manager[:team]
@@ -0,0 +1,4 @@
1
+ require 'slack-ruby-bot-server'
2
+ require 'fabrication'
3
+ require 'faker'
4
+ require_relative 'fabricators/slack_ruby_bot_server_team'
@@ -1,4 +1,2 @@
1
- require 'slack-ruby-bot-server'
2
- require 'fabrication'
3
- require 'faker'
1
+ require_relative 'rspec/fabricators'
4
2
  require_relative 'rspec/fabricators/team'
@@ -1,3 +1,3 @@
1
1
  module SlackRubyBotServer
2
- VERSION = '2.2.1'.freeze
2
+ VERSION = '2.3.0'.freeze
3
3
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: slack-ruby-bot-server
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.2.1
4
+ version: 2.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Daniel Doubrovkine
@@ -225,6 +225,8 @@ files:
225
225
  - lib/slack-ruby-bot-server/models/team/methods.rb
226
226
  - lib/slack-ruby-bot-server/models/team/mongoid.rb
227
227
  - lib/slack-ruby-bot-server/rspec.rb
228
+ - lib/slack-ruby-bot-server/rspec/fabricators.rb
229
+ - lib/slack-ruby-bot-server/rspec/fabricators/slack_ruby_bot_server_team.rb
228
230
  - lib/slack-ruby-bot-server/rspec/fabricators/team.rb
229
231
  - lib/slack-ruby-bot-server/service.rb
230
232
  - lib/slack-ruby-bot-server/version.rb