Skip to content

Commit 60eb9d2

Browse files
committed
Fix legacy feed lookup and cache TTL
1 parent 0c2f47e commit 60eb9d2

5 files changed

Lines changed: 47 additions & 14 deletions

File tree

README.md

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,29 +5,35 @@
55
html2rss-web converts arbitrary websites into RSS 2.0 feeds with a slim Ruby backend and an Astro-powered frontend.
66

77
## Links
8+
89
- Docs & feed directory: https://html2rss.github.io
910
- Discussions: https://github.com/orgs/html2rss/discussions
1011
- Sponsor: https://github.com/sponsors/gildesmarais
1112

1213
## Highlights
14+
1315
- Responsive Astro interface with gallery and custom feed creation.
1416
- Automatic source discovery with token-scoped permissions.
1517
- Signed public feed URLs that work in standard RSS readers.
1618
- Built-in SSRF defences, input validation, and HMAC-protected tokens.
1719

1820
## Architecture
21+
1922
- **Backend:** Ruby + Roda, backed by the `html2rss` gem for extraction.
2023
- **Frontend:** Astro static site with progressive enhancement.
2124
- **Distribution:** Docker Compose by default; other deployments require manual wiring.
2225

2326
## Documentation
27+
2428
In-repo docs live under `frontend/src/content/docs/` and are published by Astro.
29+
2530
- [Configuration Guide](frontend/src/content/docs/configuration.md)
2631
- [Security Guide](frontend/src/content/docs/security.md)
2732
- [REST API v1](frontend/src/content/docs/api/v1.md)
2833
- [Testing Overview](frontend/src/content/docs/testing.md)
2934

3035
## REST API Snapshot
36+
3137
```bash
3238
# List available strategies
3339
curl -H "Authorization: Bearer <token>" \
@@ -41,13 +47,15 @@ curl -X POST "https://your-domain.com/api/v1/feeds" \
4147
```
4248

4349
## Deploy (Docker Compose)
50+
4451
1. Generate a key: `openssl rand -hex 32`.
4552
2. Set `HTML2RSS_SECRET_KEY` in `docker-compose.yml`.
4653
3. Start: `docker-compose up`.
4754

4855
UI + API run on `http://localhost:4000`. The app exits if the secret key is missing.
4956

5057
## Development (Dev Container)
58+
5159
Use the repository's [Dev Container](.devcontainer/README.md) for all local development and tests.
5260
Running the app directly on the host is not supported.
5361

@@ -82,12 +90,12 @@ Dev URLs: Ruby app at `http://localhost:4000`, Astro dev server at `http://local
8290

8391
## Frontend npm Scripts (inside Dev Container)
8492

85-
| Command | Purpose |
86-
| ----------------------- | --------------------------------- |
93+
| Command | Purpose |
94+
| ----------------------- | --------------------------------------------- |
8795
| `npm run dev` | Astro dev server with hot reload (port 4001). |
88-
| `npm run build` | Production build. |
89-
| `npm run test:run` | Unit tests (Vitest). |
90-
| `npm run test:contract` | Contract tests with MSW. |
96+
| `npm run build` | Production build. |
97+
| `npm run test:run` | Unit tests (Vitest). |
98+
| `npm run test:contract` | Contract tests with MSW. |
9199

92100
## Testing Strategy
93101

@@ -99,4 +107,5 @@ Dev URLs: Ruby app at `http://localhost:4000`, Astro dev server at `http://local
99107
| Docker smoke | RSpec (`:docker`) | Net::HTTP probes against the containerised service. |
100108

101109
## Contributing
110+
102111
See the [html2rss project guidelines](https://html2rss.github.io/get-involved/contributing).

app.rb

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -42,19 +42,15 @@ class App < Roda
4242
</body>
4343
</html>
4444
HTML
45-
4645
def self.development? = EnvironmentValidator.development?
4746
def development? = self.class.development?
48-
4947
EnvironmentValidator.validate_environment!
5048
EnvironmentValidator.validate_production_security!
5149

52-
# Inline Roda configuration
5350
Html2rss::RequestService.register_strategy(:ssrf_filter, SsrfFilterStrategy)
5451
Html2rss::RequestService.default_strategy_name = :ssrf_filter
5552
Html2rss::RequestService.unregister_strategy(:faraday)
56-
opts[:check_dynamic_arity] = false
57-
opts[:check_arity] = :warn
53+
opts.merge!(check_dynamic_arity: false, check_arity: :warn)
5854
use Rack::Cache, metastore: 'file:./tmp/rack-cache-meta', entitystore: 'file:./tmp/rack-cache-body',
5955
verbose: development?
6056

@@ -132,9 +128,10 @@ def development? = self.class.development?
132128

133129
def handle_feed_generation(router, feed_name)
134130
rss_content = Feeds.generate_feed(feed_name, router.params)
135-
ttl = LocalConfig.find(feed_name)&.dig(:channel, :ttl) || 3600
131+
ttl_minutes = LocalConfig.find(feed_name)&.dig(:channel, :ttl)
132+
ttl_seconds = ttl_minutes ? ttl_minutes * 60 : 3600
136133
router.response['Content-Type'] = 'application/xml'
137-
router.response['Cache-Control'] = "public, max-age=#{ttl}"
134+
HttpCache.expires(router.response, ttl_seconds, cache_control: 'public')
138135
rss_content
139136
end
140137

app/local_config.rb

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,10 @@ class << self
1818
# @param name [String, Symbol, #to_sym]
1919
# @return [Hash<Symbol, Any>]
2020
def find(name)
21-
config = feeds.fetch(name.to_sym) { raise NotFound, "Did not find local feed config at '#{name}'" }
21+
normalized_name = normalize_name(name)
22+
config = feeds.fetch(normalized_name.to_sym) do
23+
raise NotFound, "Did not find local feed config at '#{normalized_name}'"
24+
end
2225
config = deep_dup(config)
2326

2427
apply_global_defaults(config)
@@ -55,6 +58,10 @@ def apply_global_defaults(config)
5558
config
5659
end
5760

61+
def normalize_name(name)
62+
File.basename(name.to_s, '.*')
63+
end
64+
5865
def deep_dup(value)
5966
case value
6067
when Hash

spec/html2rss/web/app_spec.rb

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,9 @@ def app = described_class
2929

3030
expect(last_response.status).to eq(200)
3131
expect(last_response.headers['Content-Type']).to eq('application/xml')
32-
expect(last_response.headers['Cache-Control']).to eq('public, max-age=180')
32+
cache_control = last_response.headers['Cache-Control']
33+
expect(cache_control).to include('public')
34+
expect(cache_control).to include('max-age=10800')
3335
expect(last_response.body).to eq('<rss/>')
3436
end
3537

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
# frozen_string_literal: true
2+
3+
require 'spec_helper'
4+
5+
require_relative '../../../app/local_config'
6+
7+
RSpec.describe Html2rss::Web::LocalConfig do
8+
describe '.find' do
9+
it 'strips feed extensions before lookup', :aggregate_failures do
10+
allow(described_class).to receive(:yaml).and_return(
11+
{ feeds: { example: { title: 'Example' } } }
12+
)
13+
14+
expect(described_class.find('example.rss')[:title]).to eq('Example')
15+
expect(described_class.find('example.xml')[:title]).to eq('Example')
16+
end
17+
end
18+
end

0 commit comments

Comments
 (0)