feat(housekeeping): deliver content vertical
This commit is contained in:
1 parent
bdab924bdf
commit
fd68819d9b
67 files changed
+5742
-1916
No files matched your search
@@ -0,0 +1,131 @@
|
||||
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<readonly ContentSearchCandidate[]>;
|
||||
|
||||
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);
|
||||
},
|
||||
});
|
||||
Reference in new issue
Block a user