feat(housekeeping): cut over administration to ase
This commit is contained in:
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,
|
||||
|
||||
Reference in new issue
Block a user