22
33require 'time'
44require 'json'
5- require 'html2rss/url'
65
76require_relative '../../../auth'
87require_relative '../../../auto_source'
@@ -18,12 +17,11 @@ module Api
1817 module V1
1918 module Feeds
2019 ##
21- # Creates stable feed records from authenticated API requests.
22- #
23- # The implementation intentionally keeps parsing, authorization, and
24- # normalization in a single boundary object so callers can rely on one
25- # predictable contract instead of coordinating multiple services.
20+ # Creates stable feed records from authenticated API requests with one predictable boundary contract.
2621 module CreateFeed
22+ FEED_ATTRIBUTE_KEYS =
23+ %i[ id name url strategy feed_token public_url json_public_url created_at updated_at ] . freeze
24+
2725 class << self
2826 # Creates a feed and returns a normalized API success payload.
2927 #
@@ -41,41 +39,19 @@ def call(request)
4139 raise
4240 end
4341
44- # Extracts a best-effort human-readable title from the URL.
45- #
46- # @param url [String] target source URL.
47- # @return [String, nil] inferred title or nil when unavailable.
48- def extract_site_title ( url )
49- Html2rss ::Url . for_channel ( url ) . channel_titleized
50- rescue StandardError
51- nil
52- end
53-
5442 private
5543
56- # Enforces feature availability at the API edge to fail fast.
57- #
58- # @return [void]
5944 def ensure_auto_source_enabled!
6045 raise ForbiddenError , Contract ::MESSAGES [ :auto_source_disabled ] unless AutoSource . enabled?
6146 end
6247
63- # Resolves the authenticated account from the request.
64- #
65- # @param request [Rack::Request]
66- # @return [Hash{Symbol=>Object}] authenticated account attributes.
6748 def require_account ( request )
6849 account = Auth . authenticate ( request )
6950 raise UnauthorizedError , 'Authentication required' unless account
7051
7152 account
7253 end
7354
74- # Validates and normalizes feed creation parameters.
75- #
76- # @param params [Hash{String=>Object}] merged request parameters.
77- # @param account [Hash{Symbol=>Object}] authenticated account.
78- # @return [Html2rss::Web::BoundaryModels::FeedCreateParams]
7955 def build_create_params ( params , account )
8056 url = params [ 'url' ] . to_s . strip
8157 raise BadRequestError , 'URL parameter is required' if url . empty?
@@ -89,10 +65,6 @@ def build_create_params(params, account)
8965 )
9066 end
9167
92- # Normalizes a strategy value while preserving a default path.
93- #
94- # @param raw_strategy [String, nil]
95- # @return [String]
9668 def normalize_strategy ( raw_strategy )
9769 strategy = raw_strategy . to_s . strip
9870 strategy = default_strategy if strategy . empty?
@@ -112,24 +84,13 @@ def default_strategy
11284 Html2rss ::RequestService . default_strategy_name . to_s
11385 end
11486
115- # Shapes feed attributes into the stable API schema.
116- #
117- # @param feed_data [Html2rss::Web::BoundaryModels::FeedMetadata, Hash{Symbol=>Object}] feed record.
118- # @return [Hash{Symbol=>Object}] response-safe feed attributes.
11987 def feed_attributes ( feed_data )
120- typed_feed = feed_data . is_a? ( BoundaryModels ::FeedMetadata ) ? feed_data : BoundaryModels ::FeedMetadata . new ( **feed_data )
12188 timestamp = Time . now . iso8601
89+ typed_feed = feed_metadata ( feed_data )
12290
123- typed_feed . to_h . merge (
124- created_at : timestamp ,
125- updated_at : timestamp
126- ) . slice ( :id , :name , :url , :strategy , :feed_token , :public_url , :created_at , :updated_at )
91+ typed_feed_attributes ( typed_feed , timestamp ) . slice ( *FEED_ATTRIBUTE_KEYS )
12792 end
12893
129- # Parses params with optional JSON body override.
130- #
131- # @param request [Rack::Request]
132- # @return [Hash{String=>Object}] merged request params.
13394 def request_params ( request )
13495 return request . params unless json_request? ( request )
13596
@@ -145,15 +106,11 @@ def request_params(request)
145106 raise BadRequestError , 'Invalid JSON payload'
146107 end
147108
148- # @param request [Rack::Request]
149- # @return [Boolean] whether request body should be parsed as JSON.
150109 def json_request? ( request )
151110 content_type = request . env [ 'CONTENT_TYPE' ] . to_s
152111 content_type . include? ( 'application/json' )
153112 end
154113
155- # @param request [Rack::Request]
156- # @return [Array<(Html2rss::Web::BoundaryModels::FeedCreateParams, Object)>]
157114 def build_feed_from_request ( request )
158115 account = require_account ( request )
159116 ensure_auto_source_enabled!
@@ -165,8 +122,6 @@ def build_feed_from_request(request)
165122 [ params , feed_data ]
166123 end
167124
168- # @param params [Html2rss::Web::BoundaryModels::FeedCreateParams]
169- # @return [void]
170125 def emit_create_success ( params )
171126 Observability . emit (
172127 event_name : 'feed.create' ,
@@ -176,8 +131,6 @@ def emit_create_success(params)
176131 )
177132 end
178133
179- # @param error [StandardError]
180- # @return [void]
181134 def emit_create_failure ( error )
182135 Observability . emit (
183136 event_name : 'feed.create' ,
@@ -186,6 +139,16 @@ def emit_create_failure(error)
186139 level : :warn
187140 )
188141 end
142+
143+ def feed_metadata ( feed_data )
144+ return feed_data if feed_data . is_a? ( BoundaryModels ::FeedMetadata )
145+
146+ BoundaryModels ::FeedMetadata . new ( **feed_data )
147+ end
148+
149+ def typed_feed_attributes ( typed_feed , timestamp )
150+ typed_feed . to_h . merge ( created_at : timestamp , updated_at : timestamp )
151+ end
189152 end
190153 end
191154 end
0 commit comments