teqo-backup/src/utilities/jingleReads.ts
Francisco Solla c7eeb5dbd8
Some checks are pending
Deploy / preflight (push) Waiting to run
Deploy / verify (push) Blocked by required conditions
Deploy / deploy staging (teqo-staging) (push) Blocked by required conditions
Deploy / requeue when main advanced (push) Blocked by required conditions
Deploy / deploy production (teqo-1313) (push) Blocked by required conditions
feat(S21): jingles de Jorge Solla no site público — ouvir e baixar (Closes #1222)
2026-09-20 01:48:20 -03:00

80 lines
2.7 KiB
TypeScript

import 'server-only'
import type { Jingle, Media } from '@/payload-types'
import { toJingleViewModel, type JingleViewModel } from '@/lib/jingle'
import { getCachedDocumentById, isNotFoundError } from '@/utilities/documentReads'
import { getCollectionListingTag } from '@/utilities/documents'
import configPromise from '@payload-config'
import { unstable_cache } from 'next/cache'
import { getPayload } from 'payload'
/**
* S21 — public reads of the jingles page and of the footer discovery link.
* The listing is cached by the `jingles` collection listing tag (busted by
* `revalidateJinglesListing` on Jingle afterChange/afterDelete) and stores
* depth-0 docs; each cover/audio is resolved through `getCachedDocumentById`,
* so replacing the FILE in the media doc (Media.afterChange busts
* `document_media:<id>`) also refreshes the card without editing the jingle.
*/
const findPublishedJingles = () =>
getPayload({ config: configPromise }).then((payload) =>
payload.find({
collection: 'jingle',
where: { published: { equals: true } },
sort: ['order', 'title'],
depth: 0,
limit: 0,
}),
)
const getCachedPublishedJingles = () =>
unstable_cache(findPublishedJingles, ['jingles'], {
tags: [getCollectionListingTag('jingle')],
})
const mediaIdOf = (value: Jingle['coverImage']): number | null =>
typeof value === 'number' ? value : (value?.id ?? null)
/**
* A media row deleted while still referenced is blocked by the FK; a direct
* write that left the id dangling resolves to "no media" and the card fails
* closed instead of rendering a broken player. Real read failures stay loud.
*/
const readMedia = async (id: number | null): Promise<Media | null> => {
if (id === null) return null
try {
return await getCachedDocumentById('media', id)()
} catch (error) {
if (isNotFoundError(error)) return null
throw error
}
}
/** Published jingles as serializable view models, missing media skipped. */
export const getPublishedJingleItems = async (): Promise<JingleViewModel[]> => {
const { docs } = await getCachedPublishedJingles()()
const items = await Promise.all(
docs.map(async (jingle) => {
const [cover, audio] = await Promise.all([
readMedia(mediaIdOf(jingle.coverImage)),
readMedia(mediaIdOf(jingle.audio)),
])
return toJingleViewModel({ jingle, cover, audio })
}),
)
return items.filter((item): item is JingleViewModel => item !== null)
}
/**
* Discovery flag for the footer link: derived from the SAME resolved list the
* page renders, so a published row that cannot render (dangling media) never
* advertises a link to an empty page.
*/
export const hasPublishedJingles = async (): Promise<boolean> =>
(await getPublishedJingleItems()).length > 0