Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
### Changelog

### 2.2.2 (Next)
### 2.3.0 (Next)

* [#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).
* Your contribution here.

### 2.2.1 (2026/10/24)
Expand Down
20 changes: 20 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ Build a complete Slack bot service with Slack button integration, in Ruby.
- [Extensions](#extensions)
- [Service Class](#service-class)
- [HTML Templates](#html-templates)
- [Page Middleware Configuration](#page-middleware-configuration)
- [Access Tokens](#access-tokens)
- [Sample Bots Using Slack Ruby Bot Server](#sample-bots-using-slack-ruby-bot-server)
- [Copyright & License](#copyright--license)
Expand Down Expand Up @@ -307,6 +308,25 @@ SlackRubyBotServer.configure do |config|
end
```

#### Page Middleware Configuration

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.

For example, with `rack-server-pages` 0.3.0 or newer:

```ruby
SlackRubyBotServer.configure do |config|
config.server_pages do |pages|
pages.cache_control = 'private, no-cache'
pages.static_cache_control = 'public, no-cache'
end
end
```

`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.

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.

### Access Tokens

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.
Expand Down
1 change: 1 addition & 0 deletions lib/slack-ruby-bot-server/api/middleware.rb
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ def self.instance

use Rack::ServerPages do |config|
config.view_path = SlackRubyBotServer::Config.view_paths
SlackRubyBotServer::Config.server_pages&.call(config)
end

run Middleware.new
Expand Down
6 changes: 6 additions & 0 deletions lib/slack-ruby-bot-server/config.rb
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ def reset!
self.service_class = SlackRubyBotServer::Service
self.oauth_scope = nil
self.oauth_version = :v2
@server_pages = nil

self.view_paths = [
'views',
Expand Down Expand Up @@ -56,6 +57,11 @@ def oauth_scope_s
oauth_scope&.join(',')
end

def server_pages(&block)
@server_pages = block if block
@server_pages
end

def activerecord?
database_adapter == :activerecord
end
Expand Down
2 changes: 1 addition & 1 deletion lib/slack-ruby-bot-server/version.rb
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
module SlackRubyBotServer
VERSION = '2.2.2'.freeze
VERSION = '2.3.0'.freeze
end
64 changes: 63 additions & 1 deletion spec/api/middleware_spec.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
require 'spec_helper'
require 'tmpdir'
require 'fileutils'

describe SlackRubyBotServer::Api::Middleware do
before { described_class.reset! }

after { described_class.reset! }

def middleware_classes(app)
r = [app]

Expand All @@ -13,11 +19,11 @@ def middleware_classes(app)

context 'overriding view_paths' do
before do
SlackRubyBotServer::Api::Middleware.reset!
SlackRubyBotServer.configure do |config|
config.view_paths << 'custom'
end
end

it 'uses custom view paths' do
server_pages = middleware_classes(SlackRubyBotServer::Api::Middleware.instance)[-2]
expect(server_pages).to be_a Rack::ServerPages
Expand All @@ -26,4 +32,60 @@ def middleware_classes(app)
expect(config[:view_path][-1]).to eq 'custom'
end
end

context 'configuring server pages' do
let(:directory) { Dir.mktmpdir('bot-server-pages') }
let(:request) { Rack::MockRequest.new(described_class.instance) }

before do
File.write(File.join(directory, 'index.html.erb'), '<%= "Rendered" %>')
SlackRubyBotServer.config.view_paths = [directory]
end

after { FileUtils.remove_entry(directory) }

it 'preserves default template caching when no block is configured' do
response = request.get('/')
expect(response.body).to eq 'Rendered'
expect(response['Cache-Control']).to be_nil
end

it 'applies the block after the default view paths and only when building the stack' do
calls = []
SlackRubyBotServer.configure do |config|
config.server_pages do |pages|
calls << pages.view_path
pages.cache_control = 'private, no-cache'
end
end
expect(request.get('/')['Cache-Control']).to eq 'private, no-cache'
request.get('/')
expect(calls).to eq [[directory]]
end

it 'allows the block to override the framework view paths' do
SlackRubyBotServer.config.view_paths = []
path = directory
SlackRubyBotServer.config.server_pages { |pages| pages.view_path = path }
expect(request.get('/').body).to eq 'Rendered'
end

it 'replaces a previously registered block' do
SlackRubyBotServer.config.server_pages { |pages| pages.cache_control = 'public' }
SlackRubyBotServer.config.server_pages { |pages| pages.cache_control = 'private, no-store' }
expect(request.get('/')['Cache-Control']).to eq 'private, no-store'
end

it 'clears the block when configuration is reset' do
SlackRubyBotServer.config.server_pages { |pages| pages.cache_control = 'private, no-store' }
SlackRubyBotServer.config.reset!
SlackRubyBotServer.config.view_paths = [directory]
expect(request.get('/')['Cache-Control']).to be_nil
end

it 'does not hide configuration errors' do
SlackRubyBotServer.config.server_pages { raise ArgumentError, 'Invalid page configuration' }
expect { described_class.instance }.to raise_error ArgumentError, 'Invalid page configuration'
end
end
end
Loading