Skip to content

Commit 929ae67

Browse files
authored
refactor: improve structure to follow more Roda conventions (#675)
1 parent 59831cd commit 929ae67

25 files changed

+409
-318
lines changed

.rubocop.yml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ require:
22
- rubocop-performance
33
- rubocop-rspec
44
- rubocop-rake
5+
- rubocop-thread_safety
56

67
AllCops:
78
DisplayCopNames: true
@@ -18,3 +19,12 @@ Naming/RescuedExceptionsVariableName:
1819

1920
Layout/ClassStructure:
2021
Enabled: true
22+
23+
Style/Documentation:
24+
AllowedConstants:
25+
- App
26+
27+
RSpec/SpecFilePathFormat:
28+
Exclude:
29+
- 'spec/html2rss/web/app/*_spec.rb'
30+
- 'spec/html2rss/web/app/health_check/auth_spec.rb'

Gemfile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ group :development do
2828
gem 'rubocop-performance', require: false
2929
gem 'rubocop-rake', require: false
3030
gem 'rubocop-rspec', require: false
31+
gem 'rubocop-thread_safety', require: false
3132
gem 'yard', require: false
3233
end
3334

Gemfile.lock

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,8 @@ GEM
119119
rubocop (~> 1.0)
120120
rubocop-rspec (3.0.4)
121121
rubocop (~> 1.61)
122+
rubocop-thread_safety (0.5.1)
123+
rubocop (>= 0.90.0)
122124
ruby-progressbar (1.13.0)
123125
sanitize (6.1.3)
124126
crass (~> 1.0.2)
@@ -165,6 +167,7 @@ DEPENDENCIES
165167
rubocop-performance
166168
rubocop-rake
167169
rubocop-rspec
170+
rubocop-thread_safety
168171
simplecov
169172
tilt
170173
vcr

Procfile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
web: bundle exec puma -C config/puma.rb
1+
web: bundle exec puma -C config/puma.rb -p 3000

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -180,7 +180,7 @@ If you're comfortable with installing Ruby directly on your machine, follow thes
180180
3. `bundle`
181181
4. `foreman start`
182182

183-
_html2rss-web_ now listens on port **5000** for requests.
183+
_html2rss-web_ now listens on port **3000** for requests.
184184

185185
## Contribute
186186

app.rb

Lines changed: 73 additions & 102 deletions
Original file line numberDiff line numberDiff line change
@@ -2,126 +2,97 @@
22

33
require 'roda'
44
require 'rack/cache'
5-
require 'rack-timeout'
6-
require_relative 'app/health_check'
7-
require_relative 'app/local_config'
8-
require_relative 'app/html2rss_facade'
95

106
require_relative 'roda/roda_plugins/basic_auth'
117

12-
module App
13-
##
14-
# This app uses html2rss and serves the feeds via HTTP.
15-
#
16-
# It is built with [Roda](https://roda.jeremyevans.net/).
17-
class App < Roda
18-
opts[:check_dynamic_arity] = false
19-
opts[:check_arity] = :warn
20-
21-
use Rack::Timeout
22-
23-
use Rack::Cache,
24-
metastore: 'file:./tmp/rack-cache-meta',
25-
entitystore: 'file:./tmp/rack-cache-body',
26-
verbose: (ENV.fetch('RACK_ENV', nil) == 'development')
27-
28-
plugin :content_security_policy do |csp|
29-
csp.default_src :none
30-
csp.style_src :self
31-
csp.script_src :self
32-
csp.connect_src :self
33-
csp.img_src :self
34-
csp.font_src :self
35-
csp.form_action :self
36-
csp.base_uri :none
37-
csp.frame_ancestors :none
38-
csp.block_all_mixed_content
39-
end
40-
41-
plugin :default_headers,
42-
'Content-Type' => 'text/html',
43-
'X-Frame-Options' => 'deny',
44-
'X-Content-Type-Options' => 'nosniff',
45-
'X-XSS-Protection' => '1; mode=block'
46-
47-
plugin :error_handler do |error|
48-
handle_error(error)
49-
end
50-
51-
plugin :public
52-
plugin :render, escape: true, layout: 'layout'
53-
plugin :typecast_params
54-
plugin :basic_auth
55-
56-
route do |r|
57-
path = RequestPath.new(request)
8+
module Html2rss
9+
module Web
10+
##
11+
# This app uses html2rss and serves the feeds via HTTP.
12+
#
13+
# It is built with [Roda](https://roda.jeremyevans.net/).
14+
class App < Roda
15+
# TODO: move to helper
16+
def self.development?
17+
ENV['RACK_ENV'] == 'development'
18+
end
5819

59-
r.root { view 'index' }
20+
def development? = self.class.development?
21+
22+
opts[:check_dynamic_arity] = false
23+
opts[:check_arity] = :warn
24+
25+
use Rack::Cache,
26+
metastore: 'file:./tmp/rack-cache-meta',
27+
entitystore: 'file:./tmp/rack-cache-body',
28+
verbose: development?
29+
30+
plugin :content_security_policy do |csp|
31+
csp.default_src :none
32+
csp.style_src :self
33+
csp.script_src :self
34+
csp.connect_src :self
35+
csp.img_src :self
36+
csp.font_src :self
37+
csp.form_action :self
38+
csp.base_uri :none
39+
csp.frame_ancestors :none
40+
csp.block_all_mixed_content
41+
end
6042

61-
r.public
43+
plugin :default_headers,
44+
'Content-Type' => 'text/html',
45+
'X-Frame-Options' => 'deny',
46+
'X-Content-Type-Options' => 'nosniff',
47+
'X-XSS-Protection' => '1; mode=block'
6248

63-
r.get 'health_check.txt' do
64-
handle_health_check
65-
end
49+
plugin :exception_page
50+
plugin :error_handler do |error|
51+
next exception_page(error) if development?
6652

67-
r.on String, String do |folder_name, config_name_with_ext|
68-
handle_html2rss_configs(path.full_config_name, folder_name, config_name_with_ext)
53+
handle_error(error)
6954
end
7055

71-
r.on String do |config_name_with_ext|
72-
handle_local_config_feeds(path.full_config_name, config_name_with_ext)
56+
plugin :hash_branches
57+
plugin :public
58+
plugin :render, escape: true, layout: 'layout'
59+
plugin :typecast_params
60+
plugin :basic_auth
61+
62+
Dir['routes/**/*.rb'].each do |f|
63+
if development?
64+
Unreloader.require f
65+
else
66+
require_relative f
67+
end
7368
end
74-
end
7569

76-
private
77-
78-
def handle_error(error) # rubocop:disable Metrics/MethodLength
79-
case error
80-
when Html2rss::Config::ParamsMissing,
81-
Roda::RodaPlugins::TypecastParams::Error
82-
set_error_response('Parameters missing or invalid', 422)
83-
when Html2rss::AttributePostProcessors::UnknownPostProcessorName,
84-
Html2rss::ItemExtractors::UnknownExtractorName,
85-
Html2rss::Config::ChannelMissing
86-
set_error_response('Invalid feed config', 422)
87-
when ::App::LocalConfig::NotFound,
88-
Html2rss::Configs::ConfigNotFound
89-
set_error_response('Feed config not found', 404)
90-
else
91-
set_error_response('Internal Server Error', 500)
92-
end
70+
route do |r|
71+
r.public
9372

94-
@show_backtrace = ENV.fetch('RACK_ENV', nil) == 'development'
95-
@error = error
96-
view 'error'
97-
end
73+
r.hash_branches('')
9874

99-
def set_error_response(page_title, status)
100-
@page_title = page_title
101-
response.status = status
102-
end
75+
r.root { view 'index' }
10376

104-
def handle_health_check
105-
HttpCache.expires_now(response)
77+
r.get 'health_check.txt' do
78+
handle_health_check
79+
end
10680

107-
with_basic_auth(realm: HealthCheck,
108-
username: HealthCheck::Auth.username,
109-
password: HealthCheck::Auth.password) do
110-
HealthCheck.run
111-
end
112-
end
81+
r.on String, String do |folder_name, config_name_with_ext|
82+
handle_html2rss_configs(request, folder_name, config_name_with_ext)
83+
end
11384

114-
def handle_local_config_feeds(full_config_name, _config_name_with_ext)
115-
Html2rssFacade.from_local_config(full_config_name, typecast_params) do |config|
116-
response['Content-Type'] = 'text/xml'
117-
HttpCache.expires(response, config.ttl * 60, cache_control: 'public')
85+
r.on String do |config_name_with_ext|
86+
handle_local_config_feeds(request, config_name_with_ext)
87+
end
11888
end
119-
end
12089

121-
def handle_html2rss_configs(full_config_name, _folder_name, _config_name_with_ext)
122-
Html2rssFacade.from_config(full_config_name, typecast_params) do |config|
123-
response['Content-Type'] = 'text/xml'
124-
HttpCache.expires(response, config.ttl * 60, cache_control: 'public')
90+
Dir['helpers/*.rb'].each do |f|
91+
if development?
92+
Unreloader.require f
93+
else
94+
require_relative f
95+
end
12596
end
12697
end
12798
end

app/health_check.rb

Lines changed: 34 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,21 @@
33
require 'parallel'
44
require_relative 'local_config'
55
require 'securerandom'
6+
require 'singleton'
67

7-
module App
8-
##
9-
# Checks if the local configs are generatable.
10-
module HealthCheck
8+
module Html2rss
9+
module Web
1110
##
12-
# Contains logic to obtain username and password to be used with HealthCheck endpoint.
13-
class Auth
14-
class << self
11+
# Checks if the local configs are generatable.
12+
module HealthCheck
13+
##
14+
# Contains logic to obtain username and password to be used with HealthCheck endpoint.
15+
class Auth
16+
include Singleton
17+
18+
def self.username = instance.username
19+
def self.password = instance.password
20+
1521
def username
1622
@username ||= fetch_credential('HEALTH_CHECK_USERNAME')
1723
end
@@ -30,33 +36,33 @@ def fetch_credential(env_var)
3036
end
3137
end
3238
end
33-
end
3439

35-
module_function
40+
module_function
3641

37-
##
38-
# @return [String] "success" when all checks passed.
39-
def run
40-
broken_feeds = errors
41-
broken_feeds.any? ? broken_feeds.join("\n") : 'success'
42-
end
42+
##
43+
# @return [String] "success" when all checks passed.
44+
def run
45+
broken_feeds = errors
46+
broken_feeds.any? ? broken_feeds.join("\n") : 'success'
47+
end
4348

44-
##
45-
# @return [Array<String>]
46-
def errors
47-
[].tap do |errors|
48-
Parallel.each(LocalConfig.feed_names, in_threads: 4) do |feed_name|
49-
Html2rss.feed_from_yaml_config(LocalConfig::CONFIG_FILE, feed_name.to_s).to_s
50-
rescue StandardError => error
51-
errors << "[#{feed_name}] #{error.class}: #{error.message}"
49+
##
50+
# @return [Array<String>]
51+
def errors
52+
[].tap do |errors|
53+
Parallel.each(LocalConfig.feed_names) do |feed_name|
54+
Html2rss.feed_from_yaml_config(LocalConfig::CONFIG_FILE, feed_name.to_s)
55+
rescue StandardError => error
56+
errors << "[#{feed_name}] #{error.class}: #{error.message}"
57+
end
5258
end
5359
end
54-
end
5560

56-
def format_error(feed_name, error)
57-
"[#{feed_name}] #{error.class}: #{error.message}"
58-
end
61+
def format_error(feed_name, error)
62+
"[#{feed_name}] #{error.class}: #{error.message}"
63+
end
5964

60-
private_class_method :format_error
65+
private_class_method :format_error
66+
end
6167
end
6268
end

0 commit comments

Comments
 (0)