Skip to content

Commit 363d10d

Browse files
committed
support for all pages
1 parent 81defb8 commit 363d10d

File tree

2 files changed

+251
-8
lines changed

2 files changed

+251
-8
lines changed

@api/page-rss-feed.get.ts

Lines changed: 50 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,17 @@
11
import type { ApiFunctionsContext } from '@redocly/config';
22
import { escapeXml, formatRssDate } from '../@theme/utils/rss';
33
import crypto from 'crypto';
4+
// @ts-ignore
5+
import pagesConfig from './page-rss-pages.yaml';
46

5-
const TARGET_PAGE_SLUG = '/docs/end-user/interact-with-pages';
7+
interface PageConfig {
8+
slug: string;
9+
title?: string;
10+
}
11+
12+
interface PagesConfig {
13+
pages: PageConfig[];
14+
}
615

716
interface PageData {
817
props?: {
@@ -172,11 +181,14 @@ function buildRssFeed(
172181
pageTitle: string,
173182
pageUrl: string,
174183
baseUrl: string,
175-
feedUrl: string,
184+
apiPath: string,
185+
pageSlug: string,
176186
records: PageChangeRecord[]
177187
): string {
178188
const rssItems = records.map((record) => buildRssItem(record, baseUrl)).join('');
179189

190+
const feedUrl = `${baseUrl}${apiPath}?page=${encodeURIComponent(pageSlug)}`;
191+
180192
return `<?xml version="1.0" encoding="UTF-8"?>
181193
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
182194
<channel>
@@ -191,27 +203,49 @@ function buildRssFeed(
191203
</rss>`;
192204
}
193205

206+
function getPageSlug(context: ApiFunctionsContext, config: PagesConfig): string {
207+
const pageSlug = context.query.page as string | undefined;
208+
209+
if (pageSlug) {
210+
const isValidPage = config.pages.some((p) => p.slug === pageSlug);
211+
if (!isValidPage) {
212+
throw new Error(`Page '${pageSlug}' is not in the allowed pages list`);
213+
}
214+
return pageSlug;
215+
}
216+
217+
if (config.pages.length === 0) {
218+
throw new Error('No pages configured in page-rss-pages.yaml');
219+
}
220+
221+
return config.pages[0].slug;
222+
}
223+
194224
export default async function pageRssFeedHandler(
195225
request: Request,
196226
context: ApiFunctionsContext
197227
) {
198228
try {
229+
const config = pagesConfig as PagesConfig;
230+
const pageSlug = getPageSlug(context, config);
231+
199232
// @ts-ignore - getKv may not be in type definitions yet but exists at runtime
200233
const kv = await context.getKv();
201234
const url = new URL(request.url);
202235
const baseUrl = `${url.protocol}//${url.host}`;
236+
const apiPath = url.pathname;
203237

204-
const pageData = await fetchPageData(baseUrl, TARGET_PAGE_SLUG);
205-
const pageDataPath = TARGET_PAGE_SLUG.replace(/^\//, '').replace(/\/$/, '');
238+
const pageData = await fetchPageData(baseUrl, pageSlug);
239+
const pageDataPath = pageSlug.replace(/^\//, '').replace(/\/$/, '');
206240

207241
const currentHash = await calculateHash(pageData);
208-
const pageTitle = getPageTitle(pageData, TARGET_PAGE_SLUG);
242+
const pageTitle = getPageTitle(pageData, pageSlug);
209243
const pageLastModified = pageData?.props?.lastModified;
210244

211-
await updatePageChanges(kv, pageDataPath, currentHash, pageTitle, TARGET_PAGE_SLUG, pageLastModified);
212-
const records = await getChangeRecords(kv, pageDataPath, currentHash, pageTitle, TARGET_PAGE_SLUG);
245+
await updatePageChanges(kv, pageDataPath, currentHash, pageTitle, pageSlug, pageLastModified);
246+
const records = await getChangeRecords(kv, pageDataPath, currentHash, pageTitle, pageSlug);
213247

214-
const rssXml = buildRssFeed(pageTitle, TARGET_PAGE_SLUG, baseUrl, request.url, records);
248+
const rssXml = buildRssFeed(pageTitle, pageSlug, baseUrl, apiPath, pageSlug, records);
215249

216250
return new Response(rssXml, {
217251
status: 200,
@@ -230,6 +264,14 @@ export default async function pageRssFeedHandler(
230264
});
231265
}
232266

267+
if (error?.message?.includes('not in the allowed pages list')) {
268+
return context.status(400).json({
269+
error: 'Invalid page',
270+
message: error.message,
271+
availablePages: (pagesConfig as PagesConfig).pages.map((p) => p.slug),
272+
});
273+
}
274+
233275
return context.status(500).json({
234276
error: 'Internal server error',
235277
message: error?.message || 'Failed to generate RSS feed',

@api/page-rss-pages.yaml

Lines changed: 201 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,201 @@
1+
pages:
2+
- slug: '/docs'
3+
title: 'docs'
4+
- slug: '/billing/signup'
5+
title: 'signup'
6+
- slug: '/billing/w-9'
7+
title: 'w-9'
8+
- slug: '/code-of-conduct'
9+
title: 'code-of-conduct'
10+
- slug: '/gsod-casestudy'
11+
title: 'gsod-casestudy'
12+
- slug: '/gsod-2022'
13+
title: 'gsod-2022'
14+
- slug: '/gsod'
15+
title: 'gsod'
16+
- slug: '/cookie-notice'
17+
title: 'cookie-notice'
18+
- slug: '/policies/pandemic-plan'
19+
title: 'pandemic-plan'
20+
- slug: '/dpa'
21+
title: 'dpa'
22+
- slug: '/legal/privacy-notice-2020-07-18'
23+
title: 'privacy-notice-2020-07-18'
24+
- slug: '/privacy-notice'
25+
title: 'privacy-notice'
26+
- slug: '/sla-2021-07-18'
27+
title: 'sla-2021-07-18'
28+
- slug: '/sla'
29+
title: 'sla'
30+
- slug: '/legal/subscription-agreement-2021-11-11'
31+
title: 'subscription-agreement-2021-11-11'
32+
- slug: '/subscription-agreement-2024-01-26'
33+
title: 'subscription-agreement-2024-01-26'
34+
- slug: '/subscription-agreement'
35+
title: 'subscription-agreement'
36+
- slug: '/vulnerability-disclosure-policy'
37+
title: 'vulnerability-disclosure-policy'
38+
- slug: '/product-timelines'
39+
title: 'product-timelines'
40+
- slug: '/products'
41+
title: 'products'
42+
- slug: '/resources'
43+
title: 'resources'
44+
- slug: '/roadmap'
45+
title: 'roadmap'
46+
- slug: '/sub-processors'
47+
title: 'sub-processors'
48+
- slug: '/learn/ai-for-docs/ai-modern-api-docs'
49+
title: 'ai-modern-api-docs'
50+
- slug: '/learn/ai-for-docs/ai-reviews'
51+
title: 'ai-reviews'
52+
- slug: '/learn/ai-for-docs/ai-usability-testing'
53+
title: 'ai-usability-testing'
54+
- slug: '/learn/arazzo/arazzo-basics'
55+
title: 'arazzo-basics'
56+
- slug: '/learn/arazzo/arazzo-walkthrough'
57+
title: 'arazzo-walkthrough'
58+
- slug: '/learn/arazzo/documenting-api-workflows'
59+
title: 'documenting-api-workflows'
60+
- slug: '/learn/arazzo/documenting-multiple-apis-using-arazzo'
61+
title: 'documenting-multiple-apis-using-arazzo'
62+
- slug: '/learn/arazzo/linting-arazzo-workflows'
63+
title: 'linting-arazzo-workflows'
64+
- slug: '/learn/arazzo/source-descriptions-and-refs'
65+
title: 'source-descriptions-and-refs'
66+
- slug: '/learn/arazzo/success-criteria-and-failure-handling'
67+
title: 'success-criteria-and-failure-handling'
68+
- slug: '/learn/arazzo/testing-arazzo-workflows'
69+
title: 'testing-arazzo-workflows'
70+
- slug: '/learn/arazzo/understanding-workflows-and-steps'
71+
title: 'understanding-workflows-and-steps'
72+
- slug: '/learn/arazzo/what-is-arazzo'
73+
title: 'what-is-arazzo'
74+
- slug: '/learn/arazzo/why-arazzo-matters'
75+
title: 'why-arazzo-matters'
76+
- slug: '/learn/markdoc/debug-markdoc-variables'
77+
title: 'debug-markdoc-variables'
78+
- slug: '/learn/markdoc/evaluating-markdoc'
79+
title: 'evaluating-markdoc'
80+
- slug: '/learn/markdoc'
81+
title: 'markdoc'
82+
- slug: '/learn/markdoc/write-with-markdoc'
83+
title: 'write-with-markdoc'
84+
- slug: '/learn/openapi/all-of'
85+
title: 'all-of'
86+
- slug: '/learn/openapi/any-of-one-of'
87+
title: 'any-of-one-of'
88+
- slug: '/learn/openapi/discriminator'
89+
title: 'discriminator'
90+
- slug: '/learn/openapi/learning-openapi'
91+
title: 'learning-openapi'
92+
- slug: '/learn/openapi/multi-file-definitions'
93+
title: 'multi-file-definitions'
94+
- slug: '/learn/openapi/openapi-decisions'
95+
title: 'openapi-decisions'
96+
- slug: '/learn/openapi/ref-guide'
97+
title: 'ref-guide'
98+
- slug: '/learn/security/api-input-validation-injection-prevention'
99+
title: 'api-input-validation-injection-prevention'
100+
- slug: '/learn/security/api-rate-limiting-abuse-prevention'
101+
title: 'api-rate-limiting-abuse-prevention'
102+
- slug: '/learn/security/api-tls-encryption-https-best-practices'
103+
title: 'api-tls-encryption-https-best-practices'
104+
- slug: '/learn/security/authentication-authorization-openapi'
105+
title: 'authentication-authorization-openapi'
106+
- slug: '/learn/security'
107+
title: 'security'
108+
- slug: '/learn/testing/contract-testing-101'
109+
title: 'contract-testing-101'
110+
- slug: '/learn/testing'
111+
title: 'testing'
112+
- slug: '/learn/testing/tools-for-api-testing-in-2025'
113+
title: 'tools-for-api-testing-in-2025'
114+
- slug: '/learn/yaml/blocks-and-flows'
115+
title: 'blocks-and-flows'
116+
- slug: '/learn/yaml/documents-comments'
117+
title: 'documents-comments'
118+
- slug: '/learn/yaml'
119+
title: 'yaml'
120+
- slug: '/learn/yaml/maps'
121+
title: 'maps'
122+
- slug: '/learn/yaml/scalars'
123+
title: 'scalars'
124+
- slug: '/learn/yaml/sequences'
125+
title: 'sequences'
126+
- slug: '/learn/yaml/troubleshooting'
127+
title: 'troubleshooting'
128+
- slug: '/learn/yaml/yaml-or-json'
129+
title: 'yaml-or-json'
130+
- slug: '/learn/openapi/openapi-visual-reference/contributing'
131+
title: 'contributing'
132+
- slug: '/learn/openapi/openapi-visual-reference/array'
133+
title: 'array'
134+
- slug: '/learn/openapi/openapi-visual-reference/boolean'
135+
title: 'boolean'
136+
- slug: '/learn/openapi/openapi-visual-reference/callbacks'
137+
title: 'callbacks'
138+
- slug: '/learn/openapi/openapi-visual-reference/components'
139+
title: 'components'
140+
- slug: '/learn/openapi/openapi-visual-reference/contact'
141+
title: 'contact'
142+
- slug: '/learn/openapi/openapi-visual-reference/discriminator'
143+
title: 'discriminator'
144+
- slug: '/learn/openapi/openapi-visual-reference/encoding'
145+
title: 'encoding'
146+
- slug: '/learn/openapi/openapi-visual-reference/example'
147+
title: 'example'
148+
- slug: '/learn/openapi/openapi-visual-reference/examples'
149+
title: 'examples'
150+
- slug: '/learn/openapi/openapi-visual-reference/external-docs'
151+
title: 'external-docs'
152+
- slug: '/learn/openapi/openapi-visual-reference/header'
153+
title: 'header'
154+
- slug: '/learn/openapi/openapi-visual-reference'
155+
title: 'openapi-visual-reference'
156+
- slug: '/learn/openapi/openapi-visual-reference/info'
157+
title: 'info'
158+
- slug: '/learn/openapi/openapi-visual-reference/integer'
159+
title: 'integer'
160+
- slug: '/learn/openapi/openapi-visual-reference/license'
161+
title: 'license'
162+
- slug: '/learn/openapi/openapi-visual-reference/links'
163+
title: 'links'
164+
- slug: '/learn/openapi/openapi-visual-reference/media-type'
165+
title: 'media-type'
166+
- slug: '/learn/openapi/openapi-visual-reference/named-path-items'
167+
title: 'named-path-items'
168+
- slug: '/learn/openapi/openapi-visual-reference/named-request-bodies'
169+
title: 'named-request-bodies'
170+
- slug: '/learn/openapi/openapi-visual-reference/named-responses'
171+
title: 'named-responses'
172+
- slug: '/learn/openapi/openapi-visual-reference/null'
173+
title: 'null'
174+
- slug: '/learn/openapi/openapi-visual-reference/number'
175+
title: 'number'
176+
- slug: '/learn/openapi/openapi-visual-reference/oauth-flows'
177+
title: 'oauth-flows'
178+
- slug: '/learn/openapi/openapi-visual-reference/object'
179+
title: 'object'
180+
- slug: '/learn/openapi/openapi-visual-reference/openapi-node-types'
181+
title: 'openapi-node-types'
182+
- slug: '/learn/openapi/openapi-visual-reference/openapi-1'
183+
title: 'openapi-1'
184+
- slug: '/learn/openapi/openapi-visual-reference/operation'
185+
title: 'operation'
186+
- slug: '/learn/openapi/openapi-visual-reference/parameter'
187+
title: 'parameter'
188+
- slug: '/learn/openapi/openapi-visual-reference/parameters'
189+
title: 'parameters'
190+
- slug: '/learn/openapi/openapi-visual-reference/path-item'
191+
title: 'path-item'
192+
- slug: '/learn/openapi/openapi-visual-reference/paths'
193+
title: 'paths'
194+
- slug: '/learn/openapi/openapi-visual-reference/reference'
195+
title: 'reference'
196+
- slug: '/learn/openapi/openapi-visual-reference/request-body'
197+
title: 'request-body'
198+
- slug: '/learn/openapi/openapi-visual-reference/response'
199+
title: 'response'
200+
- slug: '/learn/openapi/openapi-visual-reference/responses'
201+
title: 'responses'

0 commit comments

Comments
 (0)