feat(housekeeping): cut over administration to ase

This commit is contained in:
Simo committed 2026-08-30 20:35:22 +02:00
1 parent c9e35cf602
commit 2b8f73a91d
457 files changed
+589 -55936

No files matched your search

@@ -32,11 +32,6 @@ const expected = [
["content.media.photo.delete", "photo.delete", PERMS.PAGES_EDIT],
["content.media.asset.upload", "media.upload", PERMS.PAGES_EDIT],
["content.media.asset.delete", "media.delete", PERMS.PAGES_EDIT],
[
"content.editorial.navigation.update",
"navigation.update",
PERMS.SETTINGS_EDIT,
],
["content.editorial.tag.change", "tag.change", PERMS.PAGES_EDIT],
["content.engagement.prefix.change", "prefix.change", PERMS.PREFIXES_EDIT],
[
@@ -39,11 +39,6 @@ const CONTENT_COMMAND_DEFINITIONS = [
["content.media.photo.delete", "photo.delete", PERMS.PAGES_EDIT],
["content.media.asset.upload", "media.upload", PERMS.PAGES_EDIT],
["content.media.asset.delete", "media.delete", PERMS.PAGES_EDIT],
[
"content.editorial.navigation.update",
"navigation.update",
PERMS.SETTINGS_EDIT,
],
["content.editorial.tag.change", "tag.change", PERMS.PAGES_EDIT],
["content.engagement.prefix.change", "prefix.change", PERMS.PREFIXES_EDIT],
[
@@ -15,7 +15,6 @@ export function ContentEditorialPage({
}: ContentPageProps) {
const canNews = context.has(PERMS.NEWS_EDIT);
const canPages = context.has(PERMS.PAGES_EDIT);
const canSettings = context.has(PERMS.SETTINGS_EDIT);
return (
<ContentPageFrame
title="Editorial content"
@@ -49,22 +48,6 @@ export function ContentEditorialPage({
]}
/>
) : null}
{canSettings && routeId === "content.editorial.articles" ? (
<ContentCommandForm
commandId="content.editorial.navigation.update"
buttonLabel="Save navigation"
input={{}}
fields={[
{
name: "items",
label: "Navigation JSON",
type: "json",
required: true,
maxLength: 20_000,
},
]}
/>
) : null}
{canPages && routeId === "content.editorial.tags" ? (
<ContentCommandForm
commandId="content.editorial.tag.change"
@@ -5,11 +5,6 @@ import path from "node:path";
import { eq } from "drizzle-orm";
import * as JSONC from "jsonc-parser";
import { tryRemoveLocalPhotoFile } from "@/lib/admin/photo-files";
import {
ADMIN_NAV_CONFIG_KEY,
type AdminNavConfig,
serializeAdminNavConfig,
} from "@/lib/admin-nav-config";
import { getClientTranslationFile } from "@/lib/client-translation-files";
import { CameraWeb, db, EmulatorSettings, WebsiteSetting } from "@/lib/db";
import { patchJson5 } from "@/lib/json5-patch";
@@ -300,27 +295,6 @@ async function photoDelete(
return snapshot;
}
async function navigationUpdate(
input: unknown,
): Promise<ContentMutationSnapshot> {
const data = record(input);
const raw = data.items ?? data.config ?? data;
const config = jsonRecord(raw) as AdminNavConfig;
const value = serializeAdminNavConfig(config);
const beforeValue = await siteSettings.get(ADMIN_NAV_CONFIG_KEY, null);
await siteSettings.update(ADMIN_NAV_CONFIG_KEY, value);
return {
before: { configured: Boolean(beforeValue) },
after: {
configured: true,
groups: config.groupOrder?.length ?? 0,
hiddenGroups: config.hiddenGroups?.length ?? 0,
hiddenItems: config.hiddenItems?.length ?? 0,
},
output: { saved: true },
};
}
async function themeUpdate(
input: unknown,
context: ContentMutationContext,
@@ -802,7 +776,6 @@ const EXTERNAL_HANDLERS: Partial<
"media.upload": (input) => mediaUpload(input),
"media.delete": (input) => mediaDelete(input),
"photo.delete": photoDelete,
"navigation.update": (input) => navigationUpdate(input),
"theme.update": themeUpdate,
"theme.apply-preset": themeApplyPreset,
"theme.custom-change": themeCustomChange,
@@ -34,7 +34,6 @@ export const CONTENT_EXTERNAL_OPERATIONS = [
export const CONTENT_MIXED_OPERATIONS = [
"photo.delete",
"navigation.update",
"theme.update",
"theme.apply-preset",
"theme.custom-change",
@@ -23,7 +23,6 @@ export const CONTENT_MUTATION_OPERATIONS = [
"photo.delete",
"media.upload",
"media.delete",
"navigation.update",
"tag.change",
"prefix.change",
"prefix-blacklist.change",
@@ -105,7 +104,6 @@ const OPERATION_PERMISSION = Object.freeze({
"photo.delete": PERMS.PAGES_EDIT,
"media.upload": PERMS.PAGES_EDIT,
"media.delete": PERMS.PAGES_EDIT,
"navigation.update": PERMS.SETTINGS_EDIT,
"tag.change": PERMS.PAGES_EDIT,
"prefix.change": PERMS.PREFIXES_EDIT,
"prefix-blacklist.change": PERMS.PREFIXES_EDIT,
@@ -2,8 +2,8 @@ import "server-only";
import { asc, eq } from "drizzle-orm";
import type { ReactNode } from "react";
import { CatalogDetailTabs } from "@/app/admin/catalog/[id]/catalog-detail-tabs";
import { BcPageDetail } from "@/app/admin/catalog/builder-club/bc-manager";
import { BcPageDetail } from "@/components/admin/catalog/builder-club/bc-manager";
import { CatalogDetailTabs } from "@/components/admin/catalog/detail/catalog-detail-tabs";
import { CatalogItemsBc, CatalogPages, CatalogPagesBc, db } from "@/lib/db";
import { PERMS } from "@/lib/permission-slugs";
import { loadCatalogItemsData } from "@/lib/services/catalog-items-loader";
@@ -1,9 +1,6 @@
"use client";
import type {
HousekeepingRouteDefinition,
HousekeepingSurface,
} from "../../../foundation/contracts";
import type { HousekeepingRouteDefinition } from "../../../foundation/contracts";
import type { HousekeepingPreferences } from "../../../foundation/preferences/schema";
import type { HousekeepingWidgetOption } from "../../../foundation/preferences/widget-options";
import type { HousekeepingWidgetLoadResult } from "../../../foundation/registry";
@@ -47,7 +44,6 @@ export function OperationsPersonalization({
routes,
widgets,
widgetResult,
surface,
labels,
routeTitles,
widgetTitles,
@@ -57,7 +53,6 @@ export function OperationsPersonalization({
readonly routes: readonly HousekeepingRouteDefinition[];
readonly widgets: readonly HousekeepingWidgetOption[];
readonly widgetResult: HousekeepingWidgetLoadResult;
readonly surface: HousekeepingSurface;
readonly labels: OperationsPersonalizationLabels;
readonly routeTitles: Readonly<Record<string, string>>;
readonly widgetTitles: Readonly<Record<string, string>>;
@@ -69,7 +64,6 @@ export function OperationsPersonalization({
<Favorites
preferences={preferences}
routes={routes}
surface={surface}
savePreferences={savePreferences}
labels={labels.favorites}
titles={routeTitles}
@@ -123,7 +123,6 @@ describe("OperationsWorkspacePage", () => {
result={result}
routes={routes}
widgetDefinitions={widgets}
surface="preview"
labels={labels}
routeTitles={routeTitles}
widgetTitles={widgetTitles}
@@ -138,7 +137,7 @@ describe("OperationsWorkspacePage", () => {
expect(html).toContain("Some workspace sections are unavailable");
expect(html).not.toContain("system.alerts");
expect(html).not.toContain("system.health");
expect(html).toContain('href="/ase-next/people/users"');
expect(html).toContain('href="/ase/people/users"');
});
it("renders an explicit state when no operational domain is accessible", () => {
@@ -147,7 +146,6 @@ describe("OperationsWorkspacePage", () => {
result={result}
routes={[]}
widgetDefinitions={[]}
surface="preview"
labels={labels}
routeTitles={{}}
widgetTitles={{}}
@@ -1,7 +1,4 @@
import type {
HousekeepingRouteDefinition,
HousekeepingSurface,
} from "../../../foundation/contracts";
import type { HousekeepingRouteDefinition } from "../../../foundation/contracts";
import { HousekeepingPageShell } from "../../../foundation/page/housekeeping-page-shell";
import type { HousekeepingWidgetOption } from "../../../foundation/preferences/widget-options";
import { OperationalInbox } from "../../../foundation/shell/operational-inbox";
@@ -41,7 +38,6 @@ export function OperationsWorkspacePage({
result,
routes,
widgetDefinitions,
surface,
labels,
routeTitles,
widgetTitles,
@@ -51,7 +47,6 @@ export function OperationsWorkspacePage({
readonly result: OperationsWorkspaceLoadResult;
readonly routes: readonly HousekeepingRouteDefinition[];
readonly widgetDefinitions: readonly HousekeepingWidgetOption[];
readonly surface: HousekeepingSurface;
readonly labels: OperationsWorkspaceLabels;
readonly routeTitles: Readonly<Record<string, string>>;
readonly widgetTitles: Readonly<Record<string, string>>;
@@ -93,13 +88,11 @@ export function OperationsWorkspacePage({
<div className="grid gap-6 xl:grid-cols-2">
<OperationalInbox
response={result.model.inbox}
surface={surface}
labels={labels.inbox}
titles={inboxTitles}
/>
<RecentWork
items={result.model.recent}
surface={surface}
labels={labels.recent}
titles={routeTitles}
/>
@@ -110,7 +103,6 @@ export function OperationsWorkspacePage({
routes={routes}
widgets={widgetDefinitions}
widgetResult={result.model.widgets}
surface={surface}
labels={labels}
routeTitles={routeTitles}
widgetTitles={widgetTitles}
@@ -140,7 +140,6 @@ export async function renderOperationsWorkspacePage(
result: workspaceResult,
routes,
widgetDefinitions: toHousekeepingWidgetOptions(widgetDefinitions),
surface: input.surface ?? "preview",
labels,
routeTitles,
widgetTitles,
@@ -516,7 +516,7 @@ describe("People actionable form contract", () => {
it("provides a real Next loading boundary", () => {
const source = readFileSync(
"src/app/ase-next/[domain]/[[...segments]]/loading.tsx",
"src/app/ase/[domain]/[[...segments]]/loading.tsx",
"utf8",
);
expect(source).toContain('data-housekeeping-state="loading"');
@@ -2,7 +2,7 @@ import "server-only";
import { and, count, eq, inArray, sql } from "drizzle-orm";
import type { ResultSetHeader } from "mysql2";
import { MANAGED_SETTING_KEYS } from "@/app/admin/settings/cms-settings-config";
import { MANAGED_SETTING_KEYS } from "@/lib/admin/cms-settings-config";
import {
AclModelPermission,
AclModelRole,