import type { ArticleSummary, Category, Paginated } from '#shared/types/blog' import type { Locale } from '#shared/utils/locale' interface RawCategory extends Category { nameEn?: string | null } interface RawArticleSummary { id: string title: string titleEn: string | null slug: string publishedAt: string cover: string coverAlt: string | null coverAltEn: string | null expand?: { category?: RawCategory } } function toSummary(raw: RawArticleSummary, lang: Locale): ArticleSummary { const title = lang === 'en' ? raw.titleEn || raw.title : raw.title const coverAlt = lang === 'en' ? raw.coverAltEn || raw.coverAlt : raw.coverAlt const category = raw.expand?.category ? { name: lang === 'en' ? raw.expand.category.nameEn || raw.expand.category.name : raw.expand.category.name, slug: raw.expand.category.slug, } : null return { title, slug: raw.slug, publishedAt: raw.publishedAt, cover: toMediaImage('articles', raw.id, raw.cover, coverAlt), category, } } export default defineEventHandler(async (event): Promise> => { const query = getQuery(event) const page = pageParam(query.page) const lang = langParam(query.lang) const category = typeof query.category === 'string' ? query.category : null const filters = [] if (category) filters.push(`category.slug = ${quote(category)}`) // An English listing only ever shows articles that have actually been // translated — never a partial/fallback entry, see docs/content-model.md. if (lang === 'en') filters.push(`titleEn != ''`) const filter = filters.length ? `&filter=${encodeURIComponent(filters.join(' && '))}` : '' const response = await pbFetch>( `/api/collections/articles/records?fields=${ARTICLE_SUMMARY_FIELDS}&expand=category&sort=-publishedAt&${pagination(page)}${filter}` ) return { items: response.items.map((raw) => toSummary(raw, lang)), page: response.page, pageCount: response.totalPages, total: response.totalItems, } })