feat(housekeeping): deliver content vertical

This commit is contained in:
Simo committed 2026-08-30 01:54:43 +02:00
1 parent bdab924bdf
commit fd68819d9b
67 files changed
+5742 -1916

No files matched your search

@@ -0,0 +1,348 @@
import "server-only";
import { type ContentRouteId, contentRouteGroup } from "../routes";
import type {
ContentQueryData,
ContentQueryItem,
NormalizedContentQueryInput,
} from "./content-queries";
type RawRow = Readonly<Record<string, unknown>>;
interface QueryDefinition {
readonly statement: string;
readonly href: string;
readonly appendId?: boolean;
}
export const CONTENT_QUERY_DEFINITIONS = {
"content.editorial.articles": {
statement:
"SELECT id, title, slug AS description, 'published' AS status, updated_at FROM website_articles ORDER BY created_at DESC LIMIT 500",
href: "/ase/content/editorial/articles/",
appendId: true,
},
"content.editorial.article-detail": {
statement:
"SELECT id, title, slug AS description, 'published' AS status, updated_at FROM website_articles ORDER BY created_at DESC LIMIT 500",
href: "/ase/content/editorial/articles/",
appendId: true,
},
"content.editorial.tags": {
statement:
"SELECT id, name AS title, background_color AS status, updated_at FROM tags ORDER BY name LIMIT 500",
href: "/ase/content/editorial/tags",
},
"content.editorial.writeable-boxes": {
statement:
"SELECT id, title, CASE WHEN is_active = 1 THEN 'active' ELSE 'hidden' END AS status, updated_at FROM website_writeable_boxes ORDER BY position, id LIMIT 500",
href: "/ase/content/editorial/writeable-boxes",
},
"content.media.photos": {
statement:
"SELECT id, url AS title, 'published' AS status, FROM_UNIXTIME(timestamp) AS updated_at FROM camera_web ORDER BY id DESC LIMIT 500",
href: "/ase/content/media/photos",
},
"content.media.banners": {
statement:
"SELECT id, title, CASE WHEN is_active = 1 THEN 'active' ELSE 'hidden' END AS status, NULL AS updated_at FROM website_banners ORDER BY sort_order, id LIMIT 500",
href: "/ase/content/media/banners",
},
"content.media.ads": {
statement:
"SELECT id, image AS title, 'active' AS status, updated_at FROM website_ads ORDER BY created_at DESC LIMIT 500",
href: "/ase/content/media/ads/",
appendId: true,
},
"content.media.ad-detail": {
statement:
"SELECT id, image AS title, 'active' AS status, updated_at FROM website_ads ORDER BY created_at DESC LIMIT 500",
href: "/ase/content/media/ads/",
appendId: true,
},
"content.engagement.events": {
statement:
"SELECT id, title, status, updated_at FROM website_events ORDER BY starts_at DESC LIMIT 500",
href: "/ase/content/engagement/events/",
appendId: true,
},
"content.engagement.event-detail": {
statement:
"SELECT id, title, status, updated_at FROM website_events ORDER BY starts_at DESC LIMIT 500",
href: "/ase/content/engagement/events/",
appendId: true,
},
"content.engagement.event-types": {
statement:
"SELECT id, name AS title, CASE WHEN is_active = 1 THEN 'active' ELSE 'inactive' END AS status, NULL AS updated_at FROM website_event_types ORDER BY name LIMIT 500",
href: "/ase/content/engagement/events/types",
},
"content.engagement.polls": {
statement:
"SELECT id, title, status, updated_at FROM website_polls ORDER BY created_at DESC LIMIT 500",
href: "/ase/content/engagement/polls/",
appendId: true,
},
"content.engagement.poll-detail": {
statement:
"SELECT id, title, status, updated_at FROM website_polls ORDER BY created_at DESC LIMIT 500",
href: "/ase/content/engagement/polls/",
appendId: true,
},
"content.engagement.prefixes": {
statement:
"SELECT id, text AS title, color AS description, CASE WHEN active = 1 THEN 'active' ELSE 'inactive' END AS status, NULL AS updated_at FROM custom_prefixes ORDER BY id DESC LIMIT 500",
href: "/ase/content/engagement/prefixes",
},
"content.help.questions": {
statement:
"SELECT id, name AS title, CONCAT('position-', position) AS status, NULL AS updated_at FROM website_help_center_categories ORDER BY position, id LIMIT 500",
href: "/ase/content/help/questions/",
appendId: true,
},
"content.help.question-detail": {
statement:
"SELECT id, name AS title, CONCAT('position-', position) AS status, NULL AS updated_at FROM website_help_center_categories ORDER BY position, id LIMIT 500",
href: "/ase/content/help/questions/",
appendId: true,
},
"content.help.email-templates": {
statement:
"SELECT id, name AS title, subject AS description, CASE WHEN is_active = 1 THEN 'active' ELSE 'inactive' END AS status, updated_at FROM email_templates ORDER BY name LIMIT 500",
href: "/ase/content/help/email-templates",
},
"content.localization.emulator": {
statement:
"SELECT emulator_settings.key AS id, emulator_settings.key AS title, LEFT(emulator_settings.value, 120) AS description, 'configured' AS status, NULL AS updated_at FROM emulator_settings ORDER BY emulator_settings.key LIMIT 500",
href: "/ase/content/localization/emulator",
},
} as const satisfies Partial<Record<ContentRouteId, QueryDefinition>>;
const EMPTY_ROUTES = new Set<ContentRouteId>([
"content.editorial.article-create",
"content.media.ad-create",
"content.engagement.event-create",
"content.engagement.poll-create",
"content.help.question-create",
]);
function rows(result: unknown): readonly RawRow[] {
if (!Array.isArray(result) || !Array.isArray(result[0])) {
throw new Error("invalid Content query result");
}
return result[0] as readonly RawRow[];
}
function value(value: unknown, fallback = ""): string {
return typeof value === "string" ? value : value == null ? fallback : String(value);
}
function updatedAt(value: unknown): string | null {
if (value == null) return null;
const parsed = value instanceof Date ? value : new Date(String(value));
return Number.isFinite(parsed.getTime()) ? parsed.toISOString() : null;
}
function response(
input: NormalizedContentQueryInput,
items: readonly ContentQueryItem[],
total = items.length,
): ContentQueryData {
const start = input.list.offset;
return {
kind: contentRouteGroup(input.routeId),
items: items.slice(start, start + input.list.pageSize),
total,
partialDependencies: [],
};
}
function matches(input: NormalizedContentQueryInput, item: ContentQueryItem): boolean {
const needle = input.list.search.toLocaleLowerCase();
return (
needle.length === 0 ||
item.title.toLocaleLowerCase().includes(needle) ||
item.description?.toLocaleLowerCase().includes(needle) === true
);
}
function mapRows(
input: NormalizedContentQueryInput,
definition: QueryDefinition,
rawRows: readonly RawRow[],
): readonly ContentQueryItem[] {
let items = rawRows.map((row) => {
const id = value(row.id);
if (!id) throw new Error("invalid Content identifier");
return {
id,
title: value(row.title, "Untitled content"),
description: value(row.description) || undefined,
status: value(row.status) || undefined,
updatedAt: updatedAt(row.updated_at),
href: definition.appendId ? definition.href + id : definition.href,
};
}).filter((item) => matches(input, item));
if (input.params.id) {
items = items.filter((item) => item.id === input.params.id);
}
return items;
}
async function databaseRoute(
input: NormalizedContentQueryInput,
definition: QueryDefinition,
): Promise<ContentQueryData> {
const [{ sql }, { db }] = await Promise.all([
import("drizzle-orm"),
import("@/lib/db"),
]);
const items = mapRows(
input,
definition,
rows(await db.execute(sql.raw(definition.statement))),
);
return response(input, items, items.length);
}
async function mediaLibrary(
input: NormalizedContentQueryInput,
): Promise<ContentQueryData> {
const [{ readdir, stat }, { resolve }, { MEDIA_ROOT }] = await Promise.all([
import("node:fs/promises"),
import("node:path"),
import("@/lib/media-storage"),
]);
let names: string[];
try {
names = (await readdir(MEDIA_ROOT)).filter((name) =>
/[.](png|jpe?g|gif|webp|svg|bmp)$/iu.test(name),
);
} catch (error) {
if ((error as NodeJS.ErrnoException).code === "ENOENT") return response(input, []);
throw error;
}
const entries = await Promise.all(
names.map(async (name) => ({ name, metadata: await stat(resolve(MEDIA_ROOT, name)) })),
);
entries.sort((left, right) => right.metadata.mtimeMs - left.metadata.mtimeMs);
const items = entries.map(({ name, metadata }) => ({
id: name,
title: name,
description: String(metadata.size) + " bytes",
status: "stored",
updatedAt: metadata.mtime.toISOString(),
href: "/ase/content/media/library",
})).filter((item) => matches(input, item));
return response(input, items, items.length);
}
async function brand(input: NormalizedContentQueryInput): Promise<ContentQueryData> {
const [{ siteSettings }, { listCustomThemes }] = await Promise.all([
import("@/lib/services/site-settings"),
import("@/lib/theme-custom-store"),
]);
if (input.routeId === "content.brand.favicon") {
const favicon = await siteSettings.get("cms_favicon", null);
return response(input, [{
id: "favicon",
title: favicon || "Default favicon",
status: favicon ? "custom" : "default",
href: "/ase/content/brand/favicon",
}]);
}
const [preset, customThemes] = await Promise.all([
siteSettings.get("theme_preset", "Atom (golden)"),
listCustomThemes(),
]);
return response(input, [{
id: "active-theme",
title: preset || "Atom (golden)",
status: "active",
href: "/ase/content/brand/theme",
}, ...customThemes.map((theme) => ({
id: theme.id,
title: theme.name,
status: "saved",
updatedAt: new Date(theme.createdAt).toISOString(),
href: "/ase/content/brand/theme",
}))]);
}
async function localizationFiles(
input: NormalizedContentQueryInput,
): Promise<ContentQueryData> {
if (input.routeId === "content.localization.client") {
const { CLIENT_TRANSLATION_FILES } = await import(
"@/lib/client-translation-files"
);
return response(input, CLIENT_TRANSLATION_FILES.map((file) => ({
id: file.id,
title: file.id,
description: file.relPath,
status: file.readOnly ? "read-only" : "editable",
href: "/ase/content/localization/client",
})));
}
const [{ readdir }, { join }] = await Promise.all([
import("node:fs/promises"),
import("node:path"),
]);
let locales: string[] = [];
try {
locales = (await readdir(join(process.cwd(), "src", "messages")))
.filter((name) => name.endsWith(".json"))
.map((name) => name.slice(0, -5))
.sort();
} catch (error) {
if ((error as NodeJS.ErrnoException).code !== "ENOENT") throw error;
}
if (input.routeId === "content.localization.overview") {
return response(input, [
{
id: "cms",
title: "CMS translations",
status: String(locales.length) + " locales",
href: "/ase/content/localization/cms",
},
{
id: "client",
title: "Client translations",
status: "configured sources",
href: "/ase/content/localization/client",
},
{
id: "emulator",
title: "Emulator translations",
status: "database store",
href: "/ase/content/localization/emulator",
},
]);
}
return response(input, locales.map((locale) => ({
id: locale,
title: locale,
status: "editable",
href: "/ase/content/localization/cms",
})));
}
export async function loadProductionContentQuery(
input: NormalizedContentQueryInput,
): Promise<ContentQueryData> {
if (EMPTY_ROUTES.has(input.routeId)) return response(input, []);
if (input.routeId === "content.media.library") return mediaLibrary(input);
if (input.routeId.startsWith("content.brand.")) return brand(input);
if (
input.routeId === "content.localization.overview" ||
input.routeId === "content.localization.client" ||
input.routeId === "content.localization.cms"
) {
return localizationFiles(input);
}
const definition = (CONTENT_QUERY_DEFINITIONS as Partial<
Record<ContentRouteId, QueryDefinition>
>)[input.routeId];
if (!definition) throw new Error("missing Content query adapter");
return databaseRoute(input, definition);
}
@@ -0,0 +1,138 @@
import { describe, expect, it, vi } from "vitest";
import { PERMS } from "@/lib/permission-slugs";
import type { HousekeepingCapabilityContext } from "../../../foundation/contracts";
import { CONTENT_ROUTE_IDS } from "../routes";
import {
type ContentQueryData,
type ContentQueryInput,
createContentQuery,
} from "./content-queries";
import { CONTENT_QUERY_DEFINITIONS } from "./content-queries-production";
function context(granted: readonly string[]): HousekeepingCapabilityContext {
const permissions = new Set(granted);
return {
actor: { id: 42, username: "operator", rank: 7 },
isSuperAdmin: false,
has: (slug) => permissions.has(slug),
hasAny: (...slugs) => slugs.some((slug) => permissions.has(slug)),
hasAll: (...slugs) => slugs.every((slug) => permissions.has(slug)),
};
}
const ready: ContentQueryData = {
kind: "editorial",
items: [
{
id: "18446744073709551615",
title: "Published article",
status: "published",
href: "/ase/content/editorial/articles/18446744073709551615",
},
],
total: 1,
partialDependencies: [],
};
describe("Content query", () => {
it("never selects email template bodies into summary query results", () => {
const emailTemplates =
CONTENT_QUERY_DEFINITIONS["content.help.email-templates"];
expect(emailTemplates.statement).not.toMatch(/\bbody\b|private_payload/iu);
});
it("fails closed before production adapters are invoked", async () => {
const load = vi.fn(async () => ready);
const query = createContentQuery({ load });
const result = await query.run(context([]), {
routeId: "content.editorial.articles",
list: { search: "", pageSize: 25, offset: 0 },
});
expect(result).toMatchObject({
ok: false,
error: { code: "FORBIDDEN" },
});
expect(load).not.toHaveBeenCalled();
});
it("bounds list input, preserves canonical BIGINT strings, and returns real adapter data", async () => {
const load = vi.fn(async (_input: ContentQueryInput) => ready);
const query = createContentQuery({ load });
const result = await query.run(context([PERMS.NEWS_VIEW]), {
routeId: "content.editorial.articles",
list: {
search: " launch ",
pageSize: 1000,
offset: 999_999,
},
});
expect(load).toHaveBeenCalledWith({
routeId: "content.editorial.articles",
params: {},
list: { search: "launch", pageSize: 100, offset: 100_000 },
});
expect(result).toMatchObject({
ok: true,
data: {
items: [{ id: "18446744073709551615" }],
total: 1,
partialDependencies: [],
},
});
});
it("rejects a fake partial result and maps complete adapter failure", async () => {
const malformed = createContentQuery({
load: async () =>
({
kind: "media",
items: [],
total: 0,
partialDependencies: ["imaginary"],
}) as ContentQueryData,
});
const unavailable = createContentQuery({
load: async () => {
throw new Error("database unavailable");
},
});
expect(
await malformed.run(context([PERMS.PAGES_VIEW]), {
routeId: "content.media.library",
}),
).toMatchObject({
ok: false,
error: { code: "DEPENDENCY_UNAVAILABLE" },
});
expect(
await unavailable.run(context([PERMS.PAGES_VIEW]), {
routeId: "content.media.library",
}),
).toMatchObject({
ok: false,
error: { code: "DEPENDENCY_UNAVAILABLE" },
});
});
it("has an authorized production query path for every Content route", async () => {
const load = vi.fn(async (input: ContentQueryInput) => ({
kind: input.routeId.split(".")[1] as ContentQueryData["kind"],
items: [],
total: 0,
partialDependencies: [],
}));
const query = createContentQuery({ load });
const all = context(Object.values(PERMS));
for (const routeId of CONTENT_ROUTE_IDS) {
const result = await query.run(all, { routeId });
expect(result.ok, routeId).toBe(true);
}
expect(load).toHaveBeenCalledTimes(CONTENT_ROUTE_IDS.length);
});
});
@@ -0,0 +1,149 @@
import { authorizeHousekeeping } from "../../../foundation/authorization";
import {
fail,
type HousekeepingQuery,
ok,
} from "../../../foundation/contracts";
import { isSafeHousekeepingHref } from "../../../foundation/housekeeping-href";
import {
type ContentRouteGroup,
type ContentRouteId,
contentRouteById,
contentRouteGroup,
} from "../routes";
export interface ContentQueryListInput {
readonly search?: string;
readonly pageSize?: number;
readonly offset?: number;
}
export interface ContentQueryInput {
readonly routeId: ContentRouteId;
readonly params?: Readonly<Record<string, string>>;
readonly list?: ContentQueryListInput;
}
export interface ContentQueryItem {
readonly id: string;
readonly title: string;
readonly status?: string;
readonly description?: string;
readonly href?: string;
readonly updatedAt?: string | null;
readonly privatePayload?: unknown;
}
export interface ContentQueryData {
readonly kind: ContentRouteGroup;
readonly items: readonly ContentQueryItem[];
readonly total: number;
readonly partialDependencies: readonly string[];
}
export interface NormalizedContentQueryInput {
readonly routeId: ContentRouteId;
readonly params: Readonly<Record<string, string>>;
readonly list: Readonly<{
search: string;
pageSize: number;
offset: number;
}>;
}
export interface ContentQueryAdapters {
load(input: NormalizedContentQueryInput): Promise<ContentQueryData>;
}
function boundedInteger(
value: unknown,
fallback: number,
minimum: number,
maximum: number,
): number {
const parsed = Number(value);
if (!Number.isSafeInteger(parsed)) return fallback;
return Math.min(maximum, Math.max(minimum, parsed));
}
function normalizeInput(input: ContentQueryInput): NormalizedContentQueryInput {
return {
routeId: input.routeId,
params: Object.fromEntries(
Object.entries(input.params ?? {}).map(([key, value]) => [
key.normalize("NFC").trim().slice(0, 128),
value.normalize("NFC").trim().slice(0, 128),
]),
),
list: {
search: String(input.list?.search ?? "")
.normalize("NFC")
.trim()
.slice(0, 128),
pageSize: boundedInteger(input.list?.pageSize, 25, 1, 100),
offset: boundedInteger(input.list?.offset, 0, 0, 100_000),
},
};
}
function isValidData(
data: ContentQueryData,
input: NormalizedContentQueryInput,
): boolean {
if (
data.kind !== contentRouteGroup(input.routeId) ||
!Array.isArray(data.items) ||
!Number.isSafeInteger(data.total) ||
data.total < 0 ||
!Array.isArray(data.partialDependencies) ||
data.partialDependencies.length !== 0
) {
return false;
}
return data.items.every(
(item) =>
typeof item.id === "string" &&
item.id.length > 0 &&
typeof item.title === "string" &&
item.title.length > 0 &&
(item.href === undefined || isSafeHousekeepingHref(item.href)),
);
}
export function createContentQuery(
adapters: ContentQueryAdapters,
): HousekeepingQuery<ContentQueryInput, ContentQueryData> {
return {
id: "content.query",
owner: "content",
capability: contentRouteById("content.editorial.articles").capability,
async run(context, input) {
const route = contentRouteById(input.routeId);
const authorization = authorizeHousekeeping(context, route.capability);
if (!authorization.ok) return authorization;
const normalized = normalizeInput(input);
try {
const data = await adapters.load(normalized);
if (!isValidData(data, normalized)) {
throw new Error("invalid Content query data");
}
return ok(data, authorization.correlationId);
} catch {
return fail(
"DEPENDENCY_UNAVAILABLE",
"errors.housekeeping.dependencyUnavailable",
authorization.correlationId,
);
}
},
};
}
export const contentQuery = createContentQuery({
async load(input) {
const { loadProductionContentQuery } = await import(
"./content-queries-production"
);
return loadProductionContentQuery(input);
},
});