@@ -15,6 +15,7 @@ import { RequestStatus } from './constants';
1515import {
1616 FetchFeedDto ,
1717 FetchFeedDetailsDto ,
18+ FetchFeedDeliveryPreviewDto ,
1819 GetFeedRequestsInputDto ,
1920 GetFeedRequestsOutputDto ,
2021} from './dto' ;
@@ -103,6 +104,117 @@ export class FeedFetcherController {
103104 return this . getLatestRequest ( data ) ;
104105 }
105106
107+ @Post ( 'feed-requests/delivery-preview' )
108+ @UseGuards ( ApiGuard )
109+ async fetchFeedDeliveryPreview (
110+ @Body ( ValidationPipe ) data : FetchFeedDeliveryPreviewDto ,
111+ ) : Promise < FetchFeedDetailsDto > {
112+ const lookupKey = data . lookupKey || data . url ;
113+
114+ // 1. Get latest request (any status, including errors)
115+ let latestRequest =
116+ await this . partitionedRequestsStoreService . getLatestRequestAnyStatus (
117+ lookupKey ,
118+ ) ;
119+
120+ // 2. Check staleness
121+ const threshold = data . stalenessThresholdSeconds ?? 1800 ;
122+ const isStale =
123+ ! latestRequest ||
124+ dayjs ( ) . diff ( latestRequest . createdAt , 'second' ) > threshold ;
125+
126+ // 3. If stale, fetch new
127+ if ( isStale ) {
128+ const { request } = await this . feedFetcherService . fetchAndSaveResponse (
129+ data . url ,
130+ {
131+ lookupDetails : data . lookupKey ? { key : data . lookupKey } : undefined ,
132+ source : undefined ,
133+ } ,
134+ ) ;
135+
136+ await this . partitionedRequestsStoreService . flushInserts ( [ request ] ) ;
137+
138+ // Re-fetch latest
139+ latestRequest =
140+ await this . partitionedRequestsStoreService . getLatestRequestAnyStatus (
141+ lookupKey ,
142+ ) ;
143+ }
144+
145+ // 4. Map to response
146+ if ( ! latestRequest ) {
147+ return { requestStatus : 'FETCH_ERROR' as const } ;
148+ }
149+
150+ const decodedBody = await this . feedFetcherService . decodeResponseContent (
151+ latestRequest . response ?. content ,
152+ ) ;
153+
154+ return this . mapStatusToResponse (
155+ latestRequest . status ,
156+ latestRequest . response ,
157+ decodedBody ,
158+ ) ;
159+ }
160+
161+ /**
162+ * Maps a request status and response to FetchFeedDetailsDto.
163+ * Shared between delivery preview and regular feed request endpoints.
164+ */
165+ private mapStatusToResponse (
166+ status : RequestStatus ,
167+ response : { textHash ?: string | null ; statusCode : number } | null ,
168+ body : string ,
169+ ) : FetchFeedDetailsDto {
170+ if ( status === RequestStatus . INVALID_SSL_CERTIFICATE ) {
171+ return { requestStatus : 'INVALID_SSL_CERTIFICATE' as const } ;
172+ }
173+
174+ if ( status === RequestStatus . REFUSED_LARGE_FEED ) {
175+ return { requestStatus : 'REFUSED_LARGE_FEED' as const } ;
176+ }
177+
178+ if ( status === RequestStatus . FETCH_TIMEOUT ) {
179+ return { requestStatus : 'FETCH_TIMEOUT' as const } ;
180+ }
181+
182+ if ( status === RequestStatus . FETCH_ERROR || ! response ) {
183+ return { requestStatus : 'FETCH_ERROR' as const } ;
184+ }
185+
186+ if ( status === RequestStatus . OK ) {
187+ return {
188+ requestStatus : 'SUCCESS' as const ,
189+ response : {
190+ hash : response . textHash ,
191+ body,
192+ statusCode : response . statusCode ,
193+ } ,
194+ } ;
195+ }
196+
197+ if ( status === RequestStatus . PARSE_ERROR ) {
198+ return {
199+ requestStatus : 'PARSE_ERROR' as const ,
200+ response : { statusCode : response . statusCode } ,
201+ } ;
202+ }
203+
204+ if ( status === RequestStatus . INTERNAL_ERROR ) {
205+ return { requestStatus : 'INTERNAL_ERROR' as const } ;
206+ }
207+
208+ if ( status === RequestStatus . BAD_STATUS_CODE ) {
209+ return {
210+ requestStatus : 'BAD_STATUS_CODE' as const ,
211+ response : { statusCode : response . statusCode } ,
212+ } ;
213+ }
214+
215+ throw new Error ( `Unhandled request status: ${ status } ` ) ;
216+ }
217+
106218 private async getLatestRequest (
107219 data : FetchFeedDto ,
108220 ) : Promise < FetchFeedDetailsDto > {
@@ -191,9 +303,7 @@ export class FeedFetcherController {
191303 } ;
192304 }
193305
194- const latestRequestStatus = latestRequest . request . status ;
195- const latestRequestResponse = latestRequest . request . response ;
196-
306+ // Check for hash match before mapping status
197307 if (
198308 data . hashToCompare &&
199309 latestRequest . request . response ?. textHash &&
@@ -204,68 +314,10 @@ export class FeedFetcherController {
204314 } ;
205315 }
206316
207- if ( latestRequestStatus === RequestStatus . INVALID_SSL_CERTIFICATE ) {
208- return {
209- requestStatus : 'INVALID_SSL_CERTIFICATE' as const ,
210- } ;
211- }
212-
213- if ( latestRequestStatus === RequestStatus . REFUSED_LARGE_FEED ) {
214- return {
215- requestStatus : 'REFUSED_LARGE_FEED' as const ,
216- } ;
217- }
218-
219- if ( latestRequestStatus === RequestStatus . FETCH_TIMEOUT ) {
220- return {
221- requestStatus : 'FETCH_TIMEOUT' as const ,
222- } ;
223- }
224-
225- if (
226- latestRequestStatus === RequestStatus . FETCH_ERROR ||
227- ! latestRequestResponse
228- ) {
229- return {
230- requestStatus : 'FETCH_ERROR' as const ,
231- } ;
232- }
233-
234- if ( latestRequestStatus === RequestStatus . OK ) {
235- return {
236- requestStatus : 'SUCCESS' as const ,
237- response : {
238- hash : latestRequestResponse . textHash ,
239- body : latestRequest . decodedResponseText as string ,
240- statusCode : latestRequestResponse . statusCode ,
241- } ,
242- } ;
243- }
244-
245- if ( latestRequestStatus === RequestStatus . PARSE_ERROR ) {
246- return {
247- requestStatus : 'PARSE_ERROR' as const ,
248- response : {
249- statusCode : latestRequestResponse . statusCode ,
250- } ,
251- } ;
252- }
253-
254- if ( latestRequestStatus === RequestStatus . INTERNAL_ERROR ) {
255- return {
256- requestStatus : 'INTERNAL_ERROR' as const ,
257- } ;
258- }
259-
260- if ( latestRequestStatus === RequestStatus . BAD_STATUS_CODE ) {
261- return {
262- requestStatus : 'BAD_STATUS_CODE' as const ,
263- response : {
264- statusCode : latestRequestResponse . statusCode ,
265- } ,
266- } ;
267- }
268-
269- throw new Error ( `Unhandled request status: ${ latestRequestStatus } ` ) ;
317+ return this . mapStatusToResponse (
318+ latestRequest . request . status ,
319+ latestRequest . request . response ,
320+ latestRequest . decodedResponseText ?? '' ,
321+ ) ;
270322 }
271323}
0 commit comments