import { PERMS } from "@/lib/permission-slugs"; import { authorizeHousekeeping } from "../../foundation/authorization"; import { satisfiesCapability } from "../../foundation/capability-context"; import { anyCapability, type CapabilityRequirement, fail, type HousekeepingCapabilityContext, type HousekeepingSearchProvider, ok, } from "../../foundation/contracts"; import { isSafeHousekeepingHref } from "../../foundation/housekeeping-href"; export const CONTENT_SEARCH_PROVIDER_IDS = [ "content.articles", "content.events", "content.media", "content.help", ] as const; export interface ContentSearchCandidate { readonly id: string; readonly title: string; readonly description?: string; readonly href: string; readonly capability: CapabilityRequirement; } type ContentSearchLoader = ( context: HousekeepingCapabilityContext, term: string, limit: number, ) => Promise; export interface ContentSearchAdapters { readonly articles: ContentSearchLoader; readonly events: ContentSearchLoader; readonly media: ContentSearchLoader; readonly help: ContentSearchLoader; } function createProvider( id: (typeof CONTENT_SEARCH_PROVIDER_IDS)[number], capability: CapabilityRequirement, load: ContentSearchLoader, ): HousekeepingSearchProvider { return { id, owner: "content", capability, async search(context, input) { const authorization = authorizeHousekeeping(context, capability); if (!authorization.ok) return authorization; const limit = Number.isFinite(input.limit) ? Math.min(25, Math.max(1, Math.trunc(input.limit))) : 25; const term = input.term.normalize("NFC").trim().slice(0, 128); try { const candidates = await load(context, term, limit); return ok( candidates .filter( (item) => isSafeHousekeepingHref(item.href) && satisfiesCapability(context, item.capability), ) .slice(0, limit) .map((item) => ({ ...item, domain: "content" as const, type: "entity" as const, href: item.href as `/ase/${string}`, })), authorization.correlationId, ); } catch { return fail( "DEPENDENCY_UNAVAILABLE", "errors.housekeeping.dependencyUnavailable", authorization.correlationId, ); } }, }; } export function createContentSearchProviders( adapters: ContentSearchAdapters, ): readonly HousekeepingSearchProvider[] { return [ createProvider( "content.articles", anyCapability(PERMS.NEWS_VIEW), adapters.articles, ), createProvider( "content.events", anyCapability(PERMS.EVENTS_VIEW), adapters.events, ), createProvider( "content.media", anyCapability(PERMS.PAGES_VIEW, PERMS.BANNERS_VIEW), adapters.media, ), createProvider( "content.help", anyCapability(PERMS.PAGES_VIEW), adapters.help, ), ]; } export const CONTENT_SEARCH_PROVIDERS = createContentSearchProviders({ async articles(context, term, limit) { const { loadContentSearchCandidates } = await import("./search-production"); return loadContentSearchCandidates("articles", context, term, limit); }, async events(context, term, limit) { const { loadContentSearchCandidates } = await import("./search-production"); return loadContentSearchCandidates("events", context, term, limit); }, async media(context, term, limit) { const { loadContentSearchCandidates } = await import("./search-production"); return loadContentSearchCandidates("media", context, term, limit); }, async help(context, term, limit) { const { loadContentSearchCandidates } = await import("./search-production"); return loadContentSearchCandidates("help", context, term, limit); }, });