REFACTORED - Flatten portfolio category routes to /portfolio/[slug]

Merge the category and project routes under a single /portfolio/[slug]
segment via resolvePortfolioSlug (category wins over project on a slug
clash). Removes the /portfolio/category/... prefix from links, redirect,
and sitemap. Adds resolver integration tests.
This commit is contained in:
moh
2026-09-20 18:53:48 +02:00
parent 5b019052b1
commit 3f96abc60f
9 changed files with 133 additions and 136 deletions
+39
View File
@@ -8,6 +8,7 @@ import {
getAdminPortfolioProjects,
getPublishedPortfolioProjectBySlug,
getPublishedPortfolioProjects,
resolvePortfolioSlug,
} from "@/lib/portfolio";
import { eq } from "drizzle-orm";
@@ -124,6 +125,44 @@ describe("published projects", () => {
});
});
describe("resolvePortfolioSlug", () => {
it("resolves an active category slug to a category", async () => {
await createCategory({ slug: "web", isActive: true });
const resolved = await resolvePortfolioSlug("web");
expect(resolved?.kind).toBe("category");
expect(resolved?.kind === "category" && resolved.category.slug).toBe("web");
});
it("resolves a published project slug to a project", async () => {
const cat = await createCategory({ isActive: true });
await createProject({ categoryId: cat.id, slug: "my-project", isPublished: true });
const resolved = await resolvePortfolioSlug("my-project");
expect(resolved?.kind).toBe("project");
expect(resolved?.kind === "project" && resolved.project.slug).toBe("my-project");
});
it("prefers the category when a category and a project share a slug", async () => {
const cat = await createCategory({ slug: "shared", isActive: true });
await createProject({ categoryId: cat.id, slug: "shared", isPublished: true });
const resolved = await resolvePortfolioSlug("shared");
expect(resolved?.kind).toBe("category");
});
it("ignores an inactive category and falls back to a matching project", async () => {
// An inactive category named "hidden" must not shadow a published project "hidden".
await createCategory({ slug: "hidden", isActive: false });
const activeCat = await createCategory({ isActive: true });
await createProject({ categoryId: activeCat.id, slug: "hidden", isPublished: true });
const resolved = await resolvePortfolioSlug("hidden");
expect(resolved?.kind).toBe("project");
expect(resolved?.kind === "project" && resolved.project.slug).toBe("hidden");
});
it("returns null for an unknown slug", async () => {
expect(await resolvePortfolioSlug("does-not-exist")).toBeNull();
});
});
describe("referential integrity", () => {
it("restricts deleting a category that still has projects", async () => {
const cat = await createCategory();