Files
vue-faq-layer/app/composables/useFaqApi.ts
Jakub Zych 2d62924b78 feat: FaqBlock layer for the SummerCMS FAQ API
- typed client and useFaq/useFaqApi (SSR, faq:<slug>:<locale> key,
  server and public bases, x-forwarded-for forwarded also on cached renders)
- FaqBlock, FaqList and FaqItem: native details, exclusive and openFirst,
  server-sanitized description and answers, nothing or #fallback on 404,
  error or no items
- FAQ tokens and base CSS with the .faq-html rules, README
- node client tests and a playground SSR smoke against a fixture API
2026-10-12 18:22:03 +02:00

29 lines
1.4 KiB
TypeScript

// The FAQ API client. On the server it calls the internal base
// (runtimeConfig.faq.apiBase) and forwards the visitor's x-forwarded-for,
// plus the address of the peer that reached Nuxt, so the Go rate limiter
// keeps one bucket per visitor, also on route-cached renders. In the browser
// it calls the same-origin public base. The base is always runtime config,
// never request input.
import { createFaqClient, type FaqClient, type FaqFetcher } from '../utils/client.ts'
export function useFaqApi(): FaqClient {
const config = useRuntimeConfig()
if (import.meta.server) {
const base = (config.faq as { apiBase: string }).apiBase
const event = useRequestEvent()
const recorded = event?.context.faqForwardedFor as string | undefined
const incoming = useRequestHeaders(['x-forwarded-for'])['x-forwarded-for']
const peer = event?.node?.req?.socket?.remoteAddress
const forwarded = recorded || [incoming, peer].filter((v): v is string => typeof v === 'string' && v !== '').join(', ')
const headers: Record<string, string> = forwarded ? { 'x-forwarded-for': forwarded } : {}
const fetcher: FaqFetcher = (path, query) => $fetch(path, { baseURL: base, query, headers })
return createFaqClient(fetcher)
}
const base = (config.public.faq as { apiBase: string }).apiBase
const fetcher: FaqFetcher = (path, query) => $fetch(path, { baseURL: base, query })
return createFaqClient(fetcher)
}