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 {
|
import { AUDIO_INPUTS, IMAGE_INPUTS, SUBTITLE_INPUTS, VIDEO_INPUTS } from "./modality.js";
|
||||||
AUDIO_INPUTS,
|
import { toolSection } from "./section.js";
|
||||||
IMAGE_INPUTS,
|
|
||||||
MODALITY_URL_SLUG,
|
|
||||||
SUBTITLE_INPUTS,
|
|
||||||
VIDEO_INPUTS,
|
|
||||||
} from "./modality.js";
|
|
||||||
import type { CategoryInfo, SocialMediaPreset, Tool } from "./types.js";
|
import type { CategoryInfo, SocialMediaPreset, Tool } from "./types.js";
|
||||||
|
|
||||||
export const CATEGORIES: CategoryInfo[] = [
|
export const CATEGORIES: CategoryInfo[] = [
|
||||||
@@ -1790,7 +1785,18 @@ export const TOOLS: Tool[] = [
|
|||||||
];
|
];
|
||||||
|
|
||||||
for (const tool of TOOLS) {
|
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[] = [
|
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 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 {
|
export function detectModalityFromMime(mime: string): Modality {
|
||||||
if (mime.startsWith("image/")) return "image";
|
if (mime.startsWith("image/")) return "image";
|
||||||
if (mime.startsWith("video/")) return "video";
|
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 }) => {
|
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",
|
// 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("/");
|
await page.goto("/");
|
||||||
|
|
||||||
// Click on Resize tool
|
// 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