Implement portfolio admin management

This commit is contained in:
MOH
2026-03-07 16:06:20 +01:00
parent f983ce2203
commit ea64373853
35 changed files with 5126 additions and 178 deletions
+15
View File
@@ -0,0 +1,15 @@
import { describe, expect, it } from "vitest";
import { moveArrayItem } from "../lib/array";
describe("moveArrayItem", () => {
it("moves an item to a new position", () => {
expect(moveArrayItem(["a", "b", "c"], 0, 2)).toEqual(["b", "c", "a"]);
});
it("returns the original order when the move is invalid", () => {
expect(moveArrayItem(["a", "b", "c"], -1, 2)).toEqual(["a", "b", "c"]);
expect(moveArrayItem(["a", "b", "c"], 1, 1)).toEqual(["a", "b", "c"]);
expect(moveArrayItem(["a", "b", "c"], 1, 5)).toEqual(["a", "b", "c"]);
});
});
+54
View File
@@ -0,0 +1,54 @@
import { mkdir, stat, writeFile } from "fs/promises";
import path from "path";
import { afterEach, describe, expect, it } from "vitest";
import {
MEDIA_UPLOAD_ROOT,
isManagedMediaFilePath,
removeManagedMediaFile,
resolveMediaUploadPath,
sanitizeBaseName,
} from "../lib/media-storage";
const createdFiles: string[] = [];
afterEach(async () => {
await Promise.all(
createdFiles.splice(0).map(async (filePath) => {
await removeManagedMediaFile(filePath);
}),
);
});
describe("media storage helpers", () => {
it("sanitizes upload names safely", () => {
expect(sanitizeBaseName("Brand Redesign 2026!.svg")).toBe("brand-redesign-2026-svg");
});
it("detects managed media upload paths", () => {
expect(isManagedMediaFilePath("/uploads/media/covers/test.svg")).toBe(true);
expect(isManagedMediaFilePath("https://example.com/test.svg")).toBe(false);
expect(isManagedMediaFilePath("../test.svg")).toBe(false);
});
it("resolves managed paths inside the upload root", () => {
const resolvedPath = resolveMediaUploadPath("/uploads/media/assets/test.svg");
expect(resolvedPath.startsWith(MEDIA_UPLOAD_ROOT)).toBe(true);
expect(resolvedPath.endsWith(path.join("assets", "test.svg"))).toBe(true);
});
it("removes a managed file from disk", async () => {
const relativePath = `/uploads/media/tests/${Date.now()}-temp.txt`;
const absolutePath = resolveMediaUploadPath(relativePath);
await mkdir(path.dirname(absolutePath), { recursive: true });
await writeFile(absolutePath, "temporary-test-file", "utf8");
createdFiles.push(relativePath);
await expect(stat(absolutePath)).resolves.toBeDefined();
await expect(removeManagedMediaFile(relativePath)).resolves.toBe(true);
await expect(stat(absolutePath)).rejects.toThrow();
});
});
+124
View File
@@ -0,0 +1,124 @@
import { describe, expect, it } from "vitest";
import {
assetInputSchema,
categoryInputSchema,
projectInputSchema,
sectionInputSchema,
} from "../lib/portfolio-validation";
describe("portfolio validation", () => {
it("accepts a valid category payload", () => {
expect(
categoryInputSchema.parse({
slug: "branding",
nameAr: "الهوية",
nameEn: "Branding",
nameDe: "Branding",
descriptionAr: "وصف",
descriptionEn: "Description",
descriptionDe: "Beschreibung",
sortOrder: 1,
isActive: true,
}).slug,
).toBe("branding");
});
it("rejects invalid project slugs", () => {
expect(() =>
projectInputSchema.parse({
categoryId: "cat_1",
slug: "Invalid Slug",
titleAr: "عنوان",
titleEn: "Title",
titleDe: "Titel",
summaryAr: "ملخص",
summaryEn: "Summary",
summaryDe: "Zusammenfassung",
clientName: "Client",
projectYear: 2025,
serviceLabelAr: "خدمة",
serviceLabelEn: "Service",
serviceLabelDe: "Service",
previewUrl: "https://example.com",
currentCoverImagePath: "",
coverMedia: {
mode: "external",
assetId: "",
url: "https://example.com/cover.jpg",
label: "Cover",
kind: "IMAGE",
},
sortOrder: 1,
isFeatured: false,
isPublished: true,
sections: [],
assets: [],
}),
).toThrow(/slug/i);
});
it("accepts valid section and asset payloads", () => {
expect(
sectionInputSchema.parse({
type: "RICH_TEXT",
titleAr: "العنوان",
titleEn: "Title",
titleDe: "Titel",
bodyAr: "النص",
bodyEn: "Body",
bodyDe: "Text",
imagePath: "/uploads/media/covers/example.svg",
media: {
mode: "library",
assetId: "asset_1",
url: "",
label: "Section Image",
kind: "IMAGE",
},
linkUrl: "https://example.com",
sortOrder: 0,
}).type,
).toBe("RICH_TEXT");
expect(
assetInputSchema.parse({
kind: "IMAGE",
filePath: "/uploads/media/assets/example.svg",
fileFieldName: "",
media: {
mode: "external",
assetId: "",
url: "https://example.com/example.svg",
label: "Example",
kind: "IMAGE",
},
altAr: "بديل",
altEn: "Alt",
altDe: "Alt",
sortOrder: 0,
}).kind,
).toBe("IMAGE");
});
it("rejects invalid media payloads", () => {
expect(() =>
assetInputSchema.parse({
kind: "IMAGE",
filePath: "",
fileFieldName: "",
media: {
mode: "external",
assetId: "",
url: "not-a-url",
label: "Broken",
kind: "IMAGE",
},
altAr: "بديل",
altEn: "Alt",
altDe: "Alt",
sortOrder: 0,
}),
).toThrow(/url/i);
});
});