Revert "Merge pull request 'Complete Housekeeping migration and /ase cutover' (#52) from codex/housekeeping-complete into main"
This reverts commit488b6e57c4, reversing changes made tob506b4499a.
This commit is contained in:
1 parent
488b6e57c4
commit
b1ddda66ff
802 files changed
+61370
-76659
No files matched your search
+91
-66
@@ -1,12 +1,15 @@
|
||||
"use server";
|
||||
|
||||
import { mkdir, unlink, writeFile } from "node:fs/promises";
|
||||
import path from "node:path";
|
||||
import { eq } from "drizzle-orm";
|
||||
import { revalidatePath } from "next/cache";
|
||||
import type { ContentMutationSnapshot } from "@/features/housekeeping/domains/content/services/mutations";
|
||||
import { createCorrelationId } from "@/features/housekeeping/foundation/contracts";
|
||||
import { requirePermission, type StaffUser } from "@/lib/admin/guard";
|
||||
import { PERMS } from "@/lib/permissions";
|
||||
import { db, WebsiteSetting } from "@/lib/db";
|
||||
import { resolveMediaPath } from "@/lib/media-storage";
|
||||
import { siteSettings } from "@/lib/services/site-settings";
|
||||
|
||||
const MAX_SIZE = 2 * 1024 * 1024;
|
||||
const FAVICON_DIR = resolveMediaPath("favicon");
|
||||
const MAX_SIZE = 2 * 1024 * 1024; // 2MB
|
||||
const ALLOWED = [
|
||||
"image/png",
|
||||
"image/jpeg",
|
||||
@@ -15,37 +18,10 @@ const ALLOWED = [
|
||||
"image/x-icon",
|
||||
"image/svg+xml",
|
||||
];
|
||||
const PARTIAL_ERROR =
|
||||
"Favicon change completed partially; verify storage and audit state";
|
||||
|
||||
async function executeAuditedFaviconMutation(
|
||||
staff: StaffUser,
|
||||
operation: "favicon.save" | "favicon.delete",
|
||||
input: unknown,
|
||||
): Promise<ContentMutationSnapshot> {
|
||||
const [
|
||||
{ contentProductionMutationAdapter },
|
||||
{ getHousekeepingCapabilityContext },
|
||||
] = await Promise.all([
|
||||
import(
|
||||
"@/features/housekeeping/domains/content/services/mutations-production"
|
||||
),
|
||||
import("@/features/housekeeping/foundation/server-capability-context"),
|
||||
]);
|
||||
const capability = await getHousekeepingCapabilityContext();
|
||||
if (capability.actor.id !== staff.id)
|
||||
throw new Error("Authenticated staff changed during favicon mutation");
|
||||
return contentProductionMutationAdapter.execute(operation, input, {
|
||||
capability,
|
||||
correlationId: createCorrelationId(),
|
||||
legacy: true,
|
||||
});
|
||||
}
|
||||
|
||||
export async function saveFavicon(
|
||||
formData: FormData,
|
||||
): Promise<{ success: boolean; url?: string; error?: string }> {
|
||||
const staff = await requirePermission(PERMS.SETTINGS_VIEW);
|
||||
try {
|
||||
const file = formData.get("file") as File | null;
|
||||
if (!file || file.size === 0)
|
||||
@@ -57,27 +33,62 @@ export async function saveFavicon(
|
||||
success: false,
|
||||
error: "Invalid file type. Allowed: PNG, JPEG, GIF, WebP, ICO, SVG",
|
||||
};
|
||||
const result = await executeAuditedFaviconMutation(staff, "favicon.save", {
|
||||
file,
|
||||
});
|
||||
siteRevalidate();
|
||||
const url =
|
||||
typeof result.output?.url === "string" ? result.output.url : undefined;
|
||||
if (result.completion?.status === "partial") {
|
||||
return {
|
||||
success: false,
|
||||
...(url ? { url } : {}),
|
||||
error: PARTIAL_ERROR,
|
||||
};
|
||||
}
|
||||
return {
|
||||
success: true,
|
||||
...(url ? { url } : {}),
|
||||
|
||||
const mimeExt: Record<string, string> = {
|
||||
"image/png": "png",
|
||||
"image/jpeg": "jpg",
|
||||
"image/gif": "gif",
|
||||
"image/webp": "webp",
|
||||
"image/x-icon": "ico",
|
||||
"image/svg+xml": "svg",
|
||||
};
|
||||
} catch (error) {
|
||||
const ext = mimeExt[file.type] ?? "png";
|
||||
const filename = `favicon-${Date.now()}.${ext}`;
|
||||
const baseDir = FAVICON_DIR;
|
||||
const filePath = path.resolve(baseDir, filename);
|
||||
if (!filePath.startsWith(baseDir + path.sep)) {
|
||||
return { success: false, error: "Invalid path" };
|
||||
}
|
||||
|
||||
const buffer = Buffer.from(await file.arrayBuffer());
|
||||
// eslint-disable-next-line security/detect-non-literal-fs-filename
|
||||
await mkdir(baseDir, { recursive: true });
|
||||
// eslint-disable-next-line security/detect-non-literal-fs-filename
|
||||
await writeFile(filePath, buffer);
|
||||
|
||||
const url = `/api/media/favicon/${filename}`;
|
||||
|
||||
// Remove old favicon file if it exists
|
||||
const oldUrl = await siteSettings.get("cms_favicon");
|
||||
if (oldUrl?.startsWith("/api/media/favicon/")) {
|
||||
const oldName = oldUrl.replace("/api/media/favicon/", "");
|
||||
if (!oldName.includes("..") && !oldName.includes("/")) {
|
||||
const oldPath = path.resolve(baseDir, oldName);
|
||||
if (oldPath.startsWith(baseDir + path.sep)) {
|
||||
try {
|
||||
// eslint-disable-next-line security/detect-non-literal-fs-filename
|
||||
await unlink(oldPath);
|
||||
} catch {
|
||||
/* ignore if file doesn't exist */
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
await db
|
||||
.insert(WebsiteSetting)
|
||||
.values({ key: "cms_favicon", value: url, comment: "Favicon URL" })
|
||||
.onDuplicateKeyUpdate({ set: { value: url } });
|
||||
|
||||
siteSettings.reload();
|
||||
revalidatePath("/", "layout");
|
||||
revalidatePath("/admin/favicon");
|
||||
|
||||
return { success: true, url };
|
||||
} catch (e) {
|
||||
return {
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : "Unknown error",
|
||||
error: e instanceof Error ? e.message : "Unknown error",
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -86,26 +97,40 @@ export async function deleteFavicon(): Promise<{
|
||||
success: boolean;
|
||||
error?: string;
|
||||
}> {
|
||||
const staff = await requirePermission(PERMS.SETTINGS_VIEW);
|
||||
try {
|
||||
const result = await executeAuditedFaviconMutation(
|
||||
staff,
|
||||
"favicon.delete",
|
||||
{},
|
||||
);
|
||||
siteRevalidate();
|
||||
if (result.completion?.status === "partial")
|
||||
return { success: false, error: PARTIAL_ERROR };
|
||||
const oldUrl = await siteSettings.get("cms_favicon");
|
||||
if (oldUrl?.startsWith("/api/media/favicon/")) {
|
||||
const baseDir = FAVICON_DIR;
|
||||
const oldName = oldUrl.replace("/api/media/favicon/", "");
|
||||
if (!oldName.includes("..") && !oldName.includes("/")) {
|
||||
const oldPath = path.resolve(baseDir, oldName);
|
||||
if (oldPath.startsWith(baseDir + path.sep)) {
|
||||
try {
|
||||
// eslint-disable-next-line security/detect-non-literal-fs-filename
|
||||
await unlink(oldPath);
|
||||
} catch {
|
||||
/* ignore */
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
await db
|
||||
.delete(WebsiteSetting)
|
||||
.where(eq(WebsiteSetting.key, "cms_favicon"));
|
||||
} catch {
|
||||
/* ignore missing row */
|
||||
}
|
||||
siteSettings.reload();
|
||||
revalidatePath("/", "layout");
|
||||
revalidatePath("/admin/favicon");
|
||||
|
||||
return { success: true };
|
||||
} catch (error) {
|
||||
} catch (e) {
|
||||
return {
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : "Unknown error",
|
||||
error: e instanceof Error ? e.message : "Unknown error",
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
function siteRevalidate(): void {
|
||||
revalidatePath("/", "layout");
|
||||
revalidatePath("/ase/content/brand/favicon");
|
||||
}
|
||||
Reference in new issue
Block a user