Nitro's cached handler renders from a header-less copy of the request, so under swr route rules useJournalApi, /rss.xml and the sitemap source forwarded only the Nuxt peer and every cache miss shared one Journal API rate-limit bucket (deferred in Phase 16). A Nitro plugin records x-forwarded-for plus the peer in event.context.journalForwardedFor on the request hook; the three callers forward it. Same key as the host app's workaround, which keeps working. The acme SSR smoke's x-forwarded-for check on the now route-cached playground is the test.
41 lines
1.8 KiB
TypeScript
41 lines
1.8 KiB
TypeScript
// Sitemap source for Journal content (sitemap.sources in the layer config).
|
|
// It pages GET /posts at per_page=30 until meta.last_page, fetches the
|
|
// category tree with include_empty=1, and builds every loc from
|
|
// journal.routes. The API base is server runtime config, never request input.
|
|
|
|
import { createJournalClient, type JournalFetcher } from '../../../app/utils/client.ts'
|
|
import { buildJournalSitemapEntries } from '../../../app/utils/sitemap.ts'
|
|
import type { JournalPost, JournalRoutes } from '../../../app/utils/types.ts'
|
|
|
|
/** Hard stop for a runaway last_page (30 000 posts). */
|
|
const MAX_PAGES = 1000
|
|
|
|
export default defineSitemapEventHandler(async (event) => {
|
|
const config = useRuntimeConfig(event)
|
|
const base = (config.journal as { apiBase: string }).apiBase
|
|
const routes = (config.public.journal as { routes: JournalRoutes }).routes
|
|
|
|
// Recorded by server/plugins/journal-client-address.ts before any route
|
|
// cache hides the request headers.
|
|
const forwarded =
|
|
(event.context.journalForwardedFor as string | undefined) ||
|
|
[getRequestHeader(event, 'x-forwarded-for'), getRequestIP(event)]
|
|
.filter((v): v is string => typeof v === 'string' && v !== '')
|
|
.join(', ')
|
|
const headers: Record<string, string> = forwarded ? { 'x-forwarded-for': forwarded } : {}
|
|
const fetcher: JournalFetcher = (path, query) => $fetch(path, { baseURL: base, query, headers })
|
|
const api = createJournalClient(fetcher)
|
|
|
|
const posts: JournalPost[] = []
|
|
if (routes.post !== false) {
|
|
for (let page = 1; page <= MAX_PAGES; page++) {
|
|
const res = await api.listPosts({ page, perPage: 30 })
|
|
posts.push(...res.data)
|
|
if (page >= res.meta.last_page) break
|
|
}
|
|
}
|
|
const categories = routes.category !== false ? await api.listCategories() : []
|
|
|
|
return buildJournalSitemapEntries(posts, categories, routes)
|
|
})
|