mirror of
https://github.com/snapotter-hq/SnapOtter.git
synced 2026-08-03 07:46:42 +02:00
feat(shared): add apiToolPath() and section-prefixed routes; drop MODALITY_URL_SLUG
This commit is contained in:
@@ -1,10 +1,5 @@
|
||||
import {
|
||||
AUDIO_INPUTS,
|
||||
IMAGE_INPUTS,
|
||||
MODALITY_URL_SLUG,
|
||||
SUBTITLE_INPUTS,
|
||||
VIDEO_INPUTS,
|
||||
} from "./modality.js";
|
||||
import { AUDIO_INPUTS, IMAGE_INPUTS, SUBTITLE_INPUTS, VIDEO_INPUTS } from "./modality.js";
|
||||
import { toolSection } from "./section.js";
|
||||
import type { CategoryInfo, SocialMediaPreset, Tool } from "./types.js";
|
||||
|
||||
export const CATEGORIES: CategoryInfo[] = [
|
||||
@@ -1790,7 +1785,18 @@ export const TOOLS: Tool[] = [
|
||||
];
|
||||
|
||||
for (const tool of TOOLS) {
|
||||
tool.route = `/${MODALITY_URL_SLUG[tool.modality]}${tool.route}`;
|
||||
const slug = `/${toolSection(tool)}`;
|
||||
if (!tool.route.startsWith(`${slug}/`)) {
|
||||
tool.route = `${slug}${tool.route}`;
|
||||
}
|
||||
}
|
||||
|
||||
export function apiToolPath(
|
||||
toolOrId: string | Pick<Tool, "id" | "modality" | "acceptedInputs">,
|
||||
): string {
|
||||
const tool = typeof toolOrId === "string" ? TOOLS.find((t) => t.id === toolOrId) : toolOrId;
|
||||
if (!tool) throw new Error(`apiToolPath: unknown tool "${String(toolOrId)}"`);
|
||||
return `/api/v1/tools/${toolSection(tool)}/${tool.id}`;
|
||||
}
|
||||
|
||||
export const SOCIAL_MEDIA_PRESETS: SocialMediaPreset[] = [
|
||||
|
||||
@@ -117,14 +117,6 @@ export const DOCUMENT_INPUTS = [
|
||||
];
|
||||
export const FILE_INPUTS = [".csv", ".json", ".xml", ".yaml", ".yml", ".zip"];
|
||||
|
||||
export const MODALITY_URL_SLUG: Record<Modality, string> = {
|
||||
image: "image",
|
||||
video: "video",
|
||||
audio: "audio",
|
||||
document: "document",
|
||||
file: "data",
|
||||
};
|
||||
|
||||
export function detectModalityFromMime(mime: string): Modality {
|
||||
if (mime.startsWith("image/")) return "image";
|
||||
if (mime.startsWith("video/")) return "video";
|
||||
|
||||
@@ -62,9 +62,9 @@ test.describe("Navigation", () => {
|
||||
});
|
||||
|
||||
test("clicking a tool on home page navigates to tool page", async ({ loggedInPage: page }) => {
|
||||
// Routes are /:modality/:toolId (App.tsx line 243).
|
||||
// Routes are /:section/:toolId (App.tsx line 243).
|
||||
// Resize route = /image/resize (constants.ts: route "/resize" + modality "image",
|
||||
// post-processed at line 1793 with MODALITY_URL_SLUG prefix).
|
||||
// section prefix added via toolSection() in the route post-processing loop).
|
||||
await page.goto("/");
|
||||
|
||||
// Click on Resize tool
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
import { apiToolPath, TOOLS } from "@snapotter/shared";
|
||||
import { describe, expect, it } from "vitest";
|
||||
|
||||
describe("apiToolPath", () => {
|
||||
it("builds /api/v1/tools/<section>/<id>", () => {
|
||||
expect(apiToolPath("crop")).toBe("/api/v1/tools/image/crop");
|
||||
expect(apiToolPath("merge-pdf")).toBe("/api/v1/tools/pdf/merge-pdf");
|
||||
expect(apiToolPath("word-to-pdf")).toBe("/api/v1/tools/files/word-to-pdf");
|
||||
expect(apiToolPath("csv-json")).toBe("/api/v1/tools/files/csv-json");
|
||||
});
|
||||
|
||||
it("throws on unknown tool", () => {
|
||||
expect(() => apiToolPath("nope")).toThrow(/unknown tool/);
|
||||
});
|
||||
});
|
||||
|
||||
describe("route values", () => {
|
||||
it("are section-prefixed and unique", () => {
|
||||
const routes = TOOLS.map((t) => t.route);
|
||||
expect(routes).toContain("/pdf/merge-pdf");
|
||||
expect(routes).toContain("/files/csv-json");
|
||||
expect(routes).toContain("/image/crop");
|
||||
expect(new Set(routes).size).toBe(TOOLS.length); // no collisions
|
||||
// every route is exactly /<section>/<id> (two segments)
|
||||
expect(routes.every((r) => r.split("/").length === 3)).toBe(true);
|
||||
});
|
||||
|
||||
it("has globally unique ids", () => {
|
||||
expect(new Set(TOOLS.map((t) => t.id)).size).toBe(TOOLS.length);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user