Skip to content

Commit e450ab9

Browse files
committed
refactor: roda app structure / routes
1 parent 2af420d commit e450ab9

File tree

5 files changed

+283
-163
lines changed

5 files changed

+283
-163
lines changed

app.rb

Lines changed: 12 additions & 148 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@
1414
require_relative 'app/response_helpers'
1515
require_relative 'app/static_file_helpers'
1616
require_relative 'app/xml_builder'
17+
require_relative 'app/auto_source_routes'
18+
require_relative 'app/health_check_routes'
1719

1820
module Html2rss
1921
module Web
@@ -25,6 +27,8 @@ class App < Roda
2527
include ApiRoutes
2628
include ResponseHelpers
2729
include StaticFileHelpers
30+
include AutoSourceRoutes
31+
include HealthCheckRoutes
2832

2933
CONTENT_TYPE_RSS = 'application/xml'
3034

@@ -108,27 +112,28 @@ def self.production_error_message
108112

109113
plugin :exception_page
110114
plugin :error_handler do |error|
111-
next exception_page(error) if ENV['RACK_ENV'] == 'development'
115+
next exception_page(error) if development?
112116

113117
response.status = 500
114-
'Internal Server Error'
118+
response['Content-Type'] = CONTENT_TYPE_RSS
119+
XmlBuilder.build_error_feed(message: error.message)
115120
end
116121

117122
plugin :public
118123
plugin :hash_branches
119124

120-
@show_backtrace = !ENV['CI'].to_s.empty? || (ENV['RACK_ENV'] == 'development')
125+
@show_backtrace = !ENV['CI'].to_s.empty? || development?
121126

122127
# API routes
123128
hash_branch 'api' do |r|
129+
response['Content-Type'] = 'application/json'
130+
124131
r.on 'feeds.json' do
125-
response['Content-Type'] = 'application/json'
126132
response['Cache-Control'] = 'public, max-age=300'
127133
JSON.generate(Feeds.list_feeds)
128134
end
129135

130136
r.on 'strategies.json' do
131-
response['Content-Type'] = 'application/json'
132137
response['Cache-Control'] = 'public, max-age=3600'
133138
JSON.generate(ApiRoutes.list_available_strategies)
134139
end
@@ -147,160 +152,19 @@ def self.production_error_message
147152

148153
# Auto source routes
149154
hash_branch 'auto_source' do |r|
150-
return auto_source_disabled_response unless AutoSource.enabled?
151-
152-
# New stable feed creation and management
153-
r.on 'create' do
154-
handle_create_feed(r)
155-
end
156-
157-
r.on 'feeds' do
158-
handle_list_feeds(r)
159-
end
160-
161-
# Legacy encoded URL route (for backward compatibility)
162-
r.on String do |encoded_url|
163-
handle_legacy_auto_source_feed(r, encoded_url)
164-
end
155+
handle_auto_source_routes(r)
165156
end
166157

167158
# Health check route
168159
hash_branch 'health_check.txt' do |r|
169-
handle_health_check(r)
160+
handle_health_check_routes(r)
170161
end
171162

172163
route do |r|
173164
r.public
174165
r.hash_branches
175166
handle_static_files(r)
176167
end
177-
178-
private
179-
180-
# Auto source route helpers
181-
def auto_source_disabled_response
182-
response.status = 400
183-
'The auto source feature is disabled.'
184-
end
185-
186-
def handle_stable_feed(router, feed_id)
187-
url = router.params['url']
188-
feed_token = router.params['token']
189-
190-
return bad_request_response('URL parameter required') unless url
191-
return bad_request_response('URL too long') if url.length > 2048
192-
return bad_request_response('Invalid URL format') unless Auth.valid_url?(url)
193-
194-
return handle_public_feed_access(router, feed_id, feed_token, url) if feed_token
195-
196-
handle_authenticated_feed_access(router, url)
197-
rescue StandardError => error
198-
handle_auto_source_error(error)
199-
end
200-
201-
def handle_authenticated_feed_access(router, url)
202-
token_data = Auth.authenticate(router)
203-
return unauthorized_response unless token_data
204-
205-
return access_denied_response(url) unless AutoSource.url_allowed_for_token?(token_data, url)
206-
207-
strategy = router.params['strategy'] || 'ssrf_filter'
208-
rss_content = AutoSource.generate_feed_content(url, strategy)
209-
210-
set_auto_source_headers
211-
rss_content.to_s
212-
end
213-
214-
def handle_public_feed_access(router, _feed_id, feed_token, url)
215-
# Validate feed token and URL
216-
return access_denied_response(url) unless Auth.feed_url_allowed?(feed_token, url)
217-
218-
strategy = router.params['strategy'] || 'ssrf_filter'
219-
rss_content = AutoSource.generate_feed_content(url, strategy)
220-
221-
set_auto_source_headers
222-
rss_content.to_s
223-
rescue StandardError => error
224-
handle_auto_source_error(error)
225-
end
226-
227-
def handle_create_feed(router)
228-
return method_not_allowed_response unless router.post?
229-
230-
token_data = Auth.authenticate(router)
231-
return unauthorized_response unless token_data
232-
233-
url = router.params['url']
234-
return bad_request_response('URL parameter required') unless url
235-
236-
return access_denied_response(url) unless AutoSource.url_allowed_for_token?(token_data, url)
237-
238-
create_feed_response(url, token_data, router.params)
239-
rescue StandardError => error
240-
handle_auto_source_error(error)
241-
end
242-
243-
def create_feed_response(url, token_data, params)
244-
name = params['name'] || "Auto-generated feed for #{url}"
245-
strategy = params['strategy'] || 'ssrf_filter'
246-
247-
feed_data = AutoSource.create_stable_feed(name, url, token_data, strategy)
248-
return internal_error_response unless feed_data
249-
250-
response['Content-Type'] = 'application/json'
251-
JSON.generate(feed_data)
252-
end
253-
254-
def handle_list_feeds(router)
255-
token_data = Auth.authenticate(router)
256-
return unauthorized_response unless token_data
257-
258-
# For stateless system, we can't list feeds without storage
259-
# Return empty array for now
260-
response['Content-Type'] = 'application/json'
261-
JSON.generate([])
262-
end
263-
264-
def handle_legacy_auto_source_feed(router, encoded_url)
265-
token_data = AutoSource.authenticate_with_token(router)
266-
return unauthorized_response unless token_data
267-
return forbidden_origin_response unless AutoSource.allowed_origin?(router)
268-
269-
process_legacy_auto_source_request(router, encoded_url, token_data)
270-
rescue StandardError => error
271-
handle_auto_source_error(error)
272-
end
273-
274-
def process_legacy_auto_source_request(router, encoded_url, token_data)
275-
decoded_url = validate_and_decode_base64(encoded_url)
276-
return bad_request_response('Invalid URL encoding') unless decoded_url
277-
return bad_request_response('Invalid URL format') unless Auth.valid_url?(decoded_url)
278-
return access_denied_response(decoded_url) unless AutoSource.url_allowed_for_token?(token_data, decoded_url)
279-
280-
strategy = router.params['strategy'] || 'ssrf_filter'
281-
rss_content = AutoSource.generate_feed(encoded_url, strategy)
282-
set_auto_source_headers
283-
rss_content.to_s
284-
end
285-
286-
def handle_auto_source_error(error)
287-
response.status = 500
288-
response['Content-Type'] = CONTENT_TYPE_RSS
289-
AutoSource.error_feed(error.message)
290-
end
291-
292-
# Health check route helpers
293-
def handle_health_check(router)
294-
token_data = Auth.authenticate(router)
295-
health_check_account = HealthCheck.find_health_check_account
296-
297-
if token_data && health_check_account && token_data[:token] == health_check_account[:token]
298-
response['Content-Type'] = 'text/plain'
299-
HealthCheck.run
300-
else
301-
health_check_unauthorized
302-
end
303-
end
304168
end
305169
end
306170
end

app/api_routes.rb

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,6 @@ def handle_feed_generation(router, feed_name)
3232
def rss_headers(router)
3333
router.response['Content-Type'] = 'application/xml'
3434
router.response['Cache-Control'] = 'public, max-age=3600'
35-
router.response['X-Content-Type-Options'] = 'nosniff'
36-
router.response['X-XSS-Protection'] = '1; mode=block'
3735
end
3836
end
3937
end

0 commit comments

Comments
 (0)