Skip to content

Commit ac4e4b9

Browse files
committed
.
1 parent e3d76ed commit ac4e4b9

File tree

21 files changed

+133
-572
lines changed

21 files changed

+133
-572
lines changed

.devcontainer/Dockerfile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
FROM mcr.microsoft.com/devcontainers/ruby:3.4-bullseye
22

33
# Install Node.js 20
4-
RUN curl -fsSL https://deb.nodesource.com/setup_20.x | bash - \
4+
RUN curl -fsSL https://deb.nodesource.com/setup_22.x | bash - \
55
&& apt-get install -y nodejs
66

77
# Install system dependencies

.devcontainer/scripts/setup.sh

Lines changed: 0 additions & 29 deletions
This file was deleted.

Dockerfile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ ENV PORT=3000 \
4646
EXPOSE $PORT
4747

4848
HEALTHCHECK --interval=30m --timeout=60s --start-period=5s \
49-
CMD curl -f -H "Authorization: Bearer ${HEALTH_CHECK_TOKEN}" http://localhost:${PORT}/health_check.txt || exit 1
49+
CMD curl -f -H "Authorization: Bearer ${HEALTH_CHECK_TOKEN}" http://localhost:${PORT}/api/v1/health || exit 1
5050

5151
ARG USER=html2rss
5252
ARG UID=991

Rakefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ task :test do
6060
sh <<~COMMAND
6161
docker exec html2rss-web-test curl -f \
6262
-H "Authorization: Bearer health-check-token-xyz789" \
63-
http://127.0.0.1:3000/health_check.txt || exit 1
63+
http://127.0.0.1:3000/api/v1/health || exit 1
6464
COMMAND
6565

6666
# skipped as html2rss is used in development version

app.rb

Lines changed: 1 addition & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@
1010
require_relative 'app/auth'
1111
require_relative 'app/auto_source'
1212
require_relative 'app/feeds'
13-
require_relative 'app/health_check'
1413
require_relative 'app/local_config'
1514
require_relative 'app/exceptions'
1615
require_relative 'app/xml_builder'
@@ -120,21 +119,12 @@ def development? = self.class.development?
120119
r.response['Content-Type'] = 'application/json'
121120

122121
r.on 'health' do
123-
r.get 'ready' do
124-
JSON.generate(Api::V1::Health.ready(r))
125-
end
126-
r.get 'live' do
127-
JSON.generate(Api::V1::Health.live(r))
128-
end
129122
r.get do
130123
JSON.generate(Api::V1::Health.show(r))
131124
end
132125
end
133126

134127
r.on 'strategies' do
135-
r.get String do |strategy_id|
136-
JSON.generate(Api::V1::Strategies.show(r, strategy_id))
137-
end
138128
r.get do
139129
JSON.generate(Api::V1::Strategies.index(r))
140130
end
@@ -148,9 +138,6 @@ def development? = self.class.development?
148138
r.post do
149139
JSON.generate(Api::V1::Feeds.create(r))
150140
end
151-
r.get do
152-
JSON.generate(Api::V1::Feeds.index(r))
153-
end
154141
end
155142

156143
r.get 'docs' do
@@ -171,16 +158,12 @@ def development? = self.class.development?
171158
end
172159
end
173160

174-
# Backward compatibility: /{feed_name} (no auth required)
175161
r.get String do |feed_name|
176-
# Skip static file requests
177162
next if feed_name.include?('.') && !feed_name.end_with?('.xml', '.rss')
178163

179164
handle_feed_generation(r, feed_name)
180165
end
181-
r.get 'health_check.txt' do
182-
handle_health_check(r)
183-
end
166+
184167
r.root do
185168
index_path = 'public/frontend/index.html'
186169
response['Content-Type'] = 'text/html'
@@ -198,43 +181,6 @@ def handle_feed_generation(router, feed_name)
198181
rss_content
199182
end
200183

201-
def handle_health_check(router)
202-
router.response['Content-Type'] = 'text/plain'
203-
204-
verify_health_check_authorization!(router)
205-
verify_health_status!
206-
207-
'success'
208-
rescue UnauthorizedError => error
209-
router.response.status = 401
210-
error.message
211-
rescue StandardError => error
212-
router.response.status = 500
213-
214-
"health check error: #{error.message}"
215-
end
216-
217-
def verify_health_check_authorization!(router)
218-
health_account = HealthCheck.find_health_check_account
219-
account = Auth.authenticate(router)
220-
221-
return if authorized_health_check_account?(account, health_account)
222-
223-
raise UnauthorizedError, 'health check requires bearer token'
224-
end
225-
226-
def authorized_health_check_account?(account, health_account)
227-
account && health_account &&
228-
account[:username] == health_account[:username] &&
229-
account[:token] == health_account[:token]
230-
end
231-
232-
def verify_health_status!
233-
return if HealthCheck.run == 'success'
234-
235-
raise 'unhealthy'
236-
end
237-
238184
def fallback_html
239185
<<~HTML
240186
<!DOCTYPE html>

app/account_manager.rb

Lines changed: 8 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -29,19 +29,6 @@ def build_token_index
2929

3030
# @return [Array<Hash>]
3131
def accounts
32-
load_accounts
33-
end
34-
35-
# @param username [String]
36-
# @return [Hash, nil]
37-
def get_account_by_username(username)
38-
return nil unless username
39-
40-
accounts.find { |account| account[:username] == username }
41-
end
42-
43-
# @return [Array<Hash>]
44-
def load_accounts
4532
auth_config = LocalConfig.global[:auth]
4633
return [] unless auth_config&.dig(:accounts)
4734

@@ -53,6 +40,14 @@ def load_accounts
5340
}
5441
end
5542
end
43+
44+
# @param username [String]
45+
# @return [Hash, nil]
46+
def get_account_by_username(username)
47+
return nil unless username
48+
49+
accounts.find { |account| account[:username] == username }
50+
end
5651
end
5752
end
5853
end

app/api/v1/feeds.rb

Lines changed: 19 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,15 @@
11
# frozen_string_literal: true
22

33
require 'time'
4+
require 'html2rss/url'
45

6+
require_relative '../../account_manager'
57
require_relative '../../auth'
68
require_relative '../../auto_source'
79
require_relative '../../feeds'
810
require_relative '../../exceptions'
911
require_relative '../../feed_token'
12+
require_relative '../../url_validator'
1013

1114
module Html2rss
1215
module Web
@@ -16,11 +19,6 @@ module V1
1619
module Feeds
1720
module_function
1821

19-
def index(_request)
20-
feeds = Html2rss::Web::Feeds.list_feeds
21-
{ success: true, data: { feeds: feeds }, meta: { total: feeds.count } }
22-
end
23-
2422
def show(request, token)
2523
handle_token_based_feed(request, token)
2624
end
@@ -48,10 +46,6 @@ def handle_token_based_feed(request, token)
4846
generate_feed_response(request, feed_token.url)
4947
end
5048

51-
def extract_site_title(url)
52-
AutoSource.extract_site_title(url)
53-
end
54-
5549
def validate_feed_token(token)
5650
feed_token = FeedToken.decode(token)
5751
raise UnauthorizedError, 'Invalid token' unless feed_token
@@ -63,21 +57,23 @@ def validate_feed_token(token)
6357
end
6458

6559
def get_account_for_token(feed_token)
66-
account = Auth.get_account_by_username(feed_token.username)
60+
account = AccountManager.get_account_by_username(feed_token.username)
6761
raise UnauthorizedError, 'Account not found' unless account
6862

6963
account
7064
end
7165

7266
def validate_account_access(account, url)
73-
raise ForbiddenError, 'Access Denied' unless Auth.url_allowed?(account, url)
67+
raise ForbiddenError, 'Access Denied' unless UrlValidator.url_allowed?(account, url)
7468
end
7569

7670
def generate_feed_response(request, url)
7771
strategy = select_strategy(request.params['strategy'])
7872
rss_content = AutoSource.generate_feed_content(url, strategy)
7973

8074
request.response['Content-Type'] = 'application/xml'
75+
76+
# TODO: get ttl from feed
8177
HttpCache.expires(request.response, 600, cache_control: 'public')
8278

8379
rss_content.to_s
@@ -97,15 +93,16 @@ def extract_create_params(request)
9793
strategy = select_strategy(request.params['strategy'])
9894
{
9995
url: url,
100-
name: request.params['name'] || extract_site_title(url),
96+
name: extract_site_title(url),
10197
strategy: strategy
10298
}
10399
end
104100

105101
def validate_create_params(params, account)
106102
raise BadRequestError, 'URL parameter is required' if params[:url].nil? || params[:url].empty?
107-
raise BadRequestError, 'Invalid URL format' unless Auth.valid_url?(params[:url])
108-
raise ForbiddenError, 'URL not allowed for this account' unless Auth.url_allowed?(account, params[:url])
103+
raise BadRequestError, 'Invalid URL format' unless UrlValidator.valid_url?(params[:url])
104+
raise ForbiddenError, 'URL not allowed for this account' unless UrlValidator.url_allowed?(account,
105+
params[:url])
109106
end
110107

111108
def build_create_response(request, feed_data)
@@ -147,12 +144,18 @@ def feed_response_payload(feed_data)
147144
}
148145
end
149146

147+
def extract_site_title(url)
148+
Html2rss::Url.for_channel(url).channel_titleized
149+
rescue StandardError
150+
nil
151+
end
152+
150153
module_function :extract_create_params, :validate_create_params, :build_create_response,
151154
:authenticate_request, :select_strategy, :supported_strategies, :default_strategy,
152-
:feed_response_payload
155+
:feed_response_payload, :extract_site_title
153156
private_class_method :extract_create_params, :validate_create_params, :build_create_response,
154157
:authenticate_request, :select_strategy, :supported_strategies, :default_strategy,
155-
:feed_response_payload
158+
:feed_response_payload, :extract_site_title
156159
end
157160
end
158161
end

app/api/v1/health.rb

Lines changed: 15 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@
33
require 'time'
44

55
require_relative '../../auth'
6-
require_relative '../../health_check'
76
require_relative '../../exceptions'
7+
require_relative '../../local_config'
88

99
module Html2rss
1010
module Web
@@ -16,17 +16,9 @@ module V1
1616
module Health
1717
module_function
1818

19-
def show(request) # rubocop:disable Metrics/MethodLength
20-
health_check_account = HealthCheck.find_health_check_account
21-
account = Auth.authenticate(request)
22-
23-
unless account && health_check_account && account[:token] == health_check_account[:token]
24-
raise UnauthorizedError, 'Health check authentication required'
25-
end
26-
27-
health_result = HealthCheck.run
28-
29-
raise InternalServerError, 'Health check failed' unless health_result == 'success'
19+
def show(request)
20+
authorize_health_check!(request)
21+
verify_configuration!
3022

3123
{
3224
success: true,
@@ -35,29 +27,25 @@ def show(request) # rubocop:disable Metrics/MethodLength
3527
status: 'healthy',
3628
timestamp: Time.now.iso8601,
3729
version: '1.0.0',
38-
environment: ENV['RACK_ENV'] || 'development',
30+
environment: ENV.fetch('RACK_ENV', 'development'),
31+
uptime: Process.clock_gettime(Process::CLOCK_MONOTONIC),
3932
checks: {}
4033
}
4134
}
4235
}
4336
end
4437

45-
def ready(_request)
46-
# Simple readiness check - just verify the app can respond
47-
{ success: true, data: { readiness: {
48-
status: 'ready',
49-
timestamp: Time.now.iso8601,
50-
version: '1.0.0'
51-
} } }
38+
def authorize_health_check!(request)
39+
account = Auth.authenticate(request)
40+
return if account && account[:username] == 'health-check'
41+
42+
raise UnauthorizedError, 'Health check authentication required'
5243
end
5344

54-
def live(_request)
55-
# Simple liveness check - just verify the app is running
56-
{ success: true, data: { liveness: {
57-
status: 'alive',
58-
timestamp: Time.now.iso8601,
59-
uptime: Process.clock_gettime(Process::CLOCK_MONOTONIC)
60-
} } }
45+
def verify_configuration!
46+
LocalConfig.yaml
47+
rescue StandardError => error
48+
raise InternalServerError, "Health check failed: #{error.message}"
6149
end
6250
end
6351
end

0 commit comments

Comments
 (0)