feat(api): section-prefix all custom tool route literals

This commit is contained in:
SnapOtter
2026-06-20 11:47:59 +08:00
parent 68dd7dabaa
commit 10bb73ffc1
46 changed files with 54 additions and 54 deletions
@@ -131,7 +131,7 @@ registerAiJobHandler("ai-canvas-expand", async (input, data, ctx) => {
export function registerAiCanvasExpand(app: FastifyInstance) {
app.post(
"/api/v1/tools/ai-canvas-expand",
"/api/v1/tools/image/ai-canvas-expand",
async (request: FastifyRequest, reply: FastifyReply) => {
const toolId = "ai-canvas-expand";
if (!isToolInstalled(toolId)) {
+1 -1
View File
@@ -90,7 +90,7 @@ registerAiJobHandler("auto-subtitles", async (input, data, ctx) => {
});
export function registerAutoSubtitles(app: FastifyInstance) {
app.post("/api/v1/tools/auto-subtitles", async (request: FastifyRequest, reply: FastifyReply) => {
app.post("/api/v1/tools/video/auto-subtitles", async (request: FastifyRequest, reply: FastifyReply) => {
const toolId = "auto-subtitles";
if (!isToolInstalled(toolId)) {
const bundle = getBundleForTool(toolId);
@@ -110,7 +110,7 @@ registerAiJobHandler("background-replace", async (input, data, ctx) => {
export function registerBackgroundReplace(app: FastifyInstance) {
app.post(
"/api/v1/tools/background-replace",
"/api/v1/tools/image/background-replace",
async (request: FastifyRequest, reply: FastifyReply) => {
const toolId = "background-replace";
if (!isToolInstalled(toolId)) {
@@ -19,7 +19,7 @@ const settingsSchema = z.object({
*/
export function registerBarcodeGenerate(app: FastifyInstance) {
app.post(
"/api/v1/tools/barcode-generate",
"/api/v1/tools/image/barcode-generate",
async (request: FastifyRequest, reply: FastifyReply) => {
let body: unknown;
try {
+1 -1
View File
@@ -79,7 +79,7 @@ function buildOverlaySvg(
* Read barcodes (all 1D + 2D types) from uploaded images using zxing-wasm.
*/
export function registerBarcodeRead(app: FastifyInstance) {
app.post("/api/v1/tools/barcode-read", async (request: FastifyRequest, reply: FastifyReply) => {
app.post("/api/v1/tools/image/barcode-read", async (request: FastifyRequest, reply: FastifyReply) => {
let fileBuffer: Buffer | null = null;
let filename = "image";
let settingsRaw: string | null = null;
+1 -1
View File
@@ -245,7 +245,7 @@ export async function processBeautify(
export function registerBeautify(app: FastifyInstance) {
// Custom HTTP route (multi-file upload: main image + optional background image)
app.post("/api/v1/tools/beautify", async (request, reply) => {
app.post("/api/v1/tools/image/beautify", async (request, reply) => {
let mainBuffer: Buffer | null = null;
let bgImageBuffer: Buffer | null = null;
let filename = "image";
+1 -1
View File
@@ -80,7 +80,7 @@ registerAiJobHandler("blur-background", async (input, data, ctx) => {
export function registerBlurBackground(app: FastifyInstance) {
app.post(
"/api/v1/tools/blur-background",
"/api/v1/tools/image/blur-background",
async (request: FastifyRequest, reply: FastifyReply) => {
const toolId = "blur-background";
if (!isToolInstalled(toolId)) {
+1 -1
View File
@@ -65,7 +65,7 @@ registerAiJobHandler("blur-faces", async (input, data, ctx) => {
/** Face detection and blurring route. */
export function registerBlurFaces(app: FastifyInstance) {
app.post("/api/v1/tools/blur-faces", async (request: FastifyRequest, reply: FastifyReply) => {
app.post("/api/v1/tools/image/blur-faces", async (request: FastifyRequest, reply: FastifyReply) => {
const toolId = "blur-faces";
if (!isToolInstalled(toolId)) {
const bundle = getBundleForTool(toolId);
+1 -1
View File
@@ -17,7 +17,7 @@ const settingsSchema = z.object({
* No image processing - just renames.
*/
export function registerBulkRename(app: FastifyInstance) {
app.post("/api/v1/tools/bulk-rename", async (request, reply) => {
app.post("/api/v1/tools/image/bulk-rename", async (request, reply) => {
const files: Array<{ buffer: Buffer; filename: string }> = [];
let settingsRaw: string | null = null;
+1 -1
View File
@@ -421,7 +421,7 @@ function getAspectMultiplier(ar: string): number | null {
// ── Route registration ─────────────────────────────────────────────
export function registerCollage(app: FastifyInstance) {
app.post("/api/v1/tools/collage", async (request, reply) => {
app.post("/api/v1/tools/image/collage", async (request, reply) => {
const files: Array<{ buffer: Buffer; filename: string }> = [];
let settingsRaw: string | null = null;
+1 -1
View File
@@ -151,7 +151,7 @@ function extractColors(
}
export function registerColorPalette(app: FastifyInstance) {
app.post("/api/v1/tools/color-palette", async (request, reply) => {
app.post("/api/v1/tools/image/color-palette", async (request, reply) => {
let fileBuffer: Buffer | null = null;
let filename = "image";
let rawSettings: string | undefined;
+1 -1
View File
@@ -66,7 +66,7 @@ registerAiJobHandler("colorize", async (input, data, ctx) => {
* with OpenCV DNN fallback.
*/
export function registerColorize(app: FastifyInstance) {
app.post("/api/v1/tools/colorize", async (request: FastifyRequest, reply: FastifyReply) => {
app.post("/api/v1/tools/image/colorize", async (request: FastifyRequest, reply: FastifyReply) => {
const toolId = "colorize";
if (!isToolInstalled(toolId)) {
const bundle = getBundleForTool(toolId);
+1 -1
View File
@@ -12,7 +12,7 @@ import { decompressSvgz, sanitizeSvg } from "../../lib/svg-sanitize.js";
* Compare two images: compute a pixel-level diff and similarity score.
*/
export function registerCompare(app: FastifyInstance) {
app.post("/api/v1/tools/compare", async (request, reply) => {
app.post("/api/v1/tools/image/compare", async (request, reply) => {
let bufferA: Buffer | null = null;
let bufferB: Buffer | null = null;
+1 -1
View File
@@ -52,7 +52,7 @@ const settingsSchema = z.object({
});
export function registerCompose(app: FastifyInstance) {
app.post("/api/v1/tools/compose", async (request, reply) => {
app.post("/api/v1/tools/image/compose", async (request, reply) => {
let baseBuffer: Buffer | null = null;
let overlayBuffer: Buffer | null = null;
let filename = "image";
@@ -28,7 +28,7 @@ type Settings = z.infer<typeof settingsSchema>;
/** Content-aware resize (seam carving via caire) route. */
export function registerContentAwareResize(app: FastifyInstance) {
app.post(
"/api/v1/tools/content-aware-resize",
"/api/v1/tools/image/content-aware-resize",
async (request: FastifyRequest, reply: FastifyReply) => {
let fileBuffer: Buffer | null = null;
let filename = "image";
+2 -2
View File
@@ -68,7 +68,7 @@ const BROWSER_PREVIEWABLE = new Set([
export function registerEditMetadata(app: FastifyInstance) {
// Inspect endpoint - returns parsed metadata via ExifTool
app.post(
"/api/v1/tools/edit-metadata/inspect",
"/api/v1/tools/image/edit-metadata/inspect",
async (request: FastifyRequest, reply: FastifyReply) => {
let fileBuffer: Buffer | null = null;
let filename = "image";
@@ -109,7 +109,7 @@ export function registerEditMetadata(app: FastifyInstance) {
);
// Edit endpoint - writes metadata in-place using ExifTool (no pixel re-encoding)
app.post("/api/v1/tools/edit-metadata", async (request: FastifyRequest, reply: FastifyReply) => {
app.post("/api/v1/tools/image/edit-metadata", async (request: FastifyRequest, reply: FastifyReply) => {
let fileBuffer: Buffer | null = null;
let filename = "image";
let settingsRaw: string | null = null;
+1 -1
View File
@@ -58,7 +58,7 @@ registerAiJobHandler("enhance-faces", async (input, data, ctx) => {
/** Face enhancement route using GFPGAN/CodeFormer. */
export function registerEnhanceFaces(app: FastifyInstance) {
app.post("/api/v1/tools/enhance-faces", async (request: FastifyRequest, reply: FastifyReply) => {
app.post("/api/v1/tools/image/enhance-faces", async (request: FastifyRequest, reply: FastifyReply) => {
const toolId = "enhance-faces";
if (!isToolInstalled(toolId)) {
const bundle = getBundleForTool(toolId);
+1 -1
View File
@@ -33,7 +33,7 @@ const settingsSchema = z.object({
* via getObjectBuffer(data.inputRefs[1]) inside the handler.
*/
export function registerEraseObject(app: FastifyInstance) {
app.post("/api/v1/tools/erase-object", async (request: FastifyRequest, reply: FastifyReply) => {
app.post("/api/v1/tools/image/erase-object", async (request: FastifyRequest, reply: FastifyReply) => {
const toolId = "erase-object";
if (!isToolInstalled(toolId)) {
const bundle = getBundleForTool(toolId);
+1 -1
View File
@@ -79,7 +79,7 @@ interface UploadedFile {
}
export function registerFavicon(app: FastifyInstance) {
app.post("/api/v1/tools/favicon", async (request, reply) => {
app.post("/api/v1/tools/image/favicon", async (request, reply) => {
const uploadedFiles: UploadedFile[] = [];
let settingsRaw: string | null = null;
+1 -1
View File
@@ -95,7 +95,7 @@ async function extractFileInfo(file: FileData): Promise<FileInfo> {
}
export function registerFindDuplicates(app: FastifyInstance) {
app.post("/api/v1/tools/find-duplicates", async (request, reply) => {
app.post("/api/v1/tools/image/find-duplicates", async (request, reply) => {
const files: FileData[] = [];
let settingsRaw: string | null = null;
+1 -1
View File
@@ -98,7 +98,7 @@ const settingsSchema = z.object({
export function registerGifTools(app: FastifyInstance) {
// ── Metadata endpoint ───────────────────────────────────────────
app.post("/api/v1/tools/gif-tools/info", async (request: FastifyRequest, reply: FastifyReply) => {
app.post("/api/v1/tools/image/gif-tools/info", async (request: FastifyRequest, reply: FastifyReply) => {
let fileBuffer: Buffer | null = null;
try {
+1 -1
View File
@@ -34,7 +34,7 @@ const settingsSchema = z
export function registerHtmlToImage(app: FastifyInstance) {
app.post(
"/api/v1/tools/html-to-image",
"/api/v1/tools/image/html-to-image",
{
config: {
rateLimit: { max: 120, timeWindow: "1 hour" },
@@ -110,7 +110,7 @@ export function registerImageEnhancement(app: FastifyInstance) {
});
app.post(
"/api/v1/tools/image-enhancement/analyze",
"/api/v1/tools/image/image-enhancement/analyze",
async (request: FastifyRequest, reply: FastifyReply) => {
let fileBuffer: Buffer | null = null;
let filename = "image";
+1 -1
View File
@@ -54,7 +54,7 @@ function detectMimeType(format: string): string {
export function registerImageToBase64(app: FastifyInstance) {
app.post(
"/api/v1/tools/image-to-base64",
"/api/v1/tools/image/image-to-base64",
async (request: FastifyRequest, reply: FastifyReply) => {
const files: Array<{ buffer: Buffer; filename: string }> = [];
let settings = {};
+1 -1
View File
@@ -97,7 +97,7 @@ async function flattenAlpha(buf: Buffer): Promise<Buffer> {
}
export function registerImageToPdf(app: FastifyInstance) {
app.post("/api/v1/tools/image-to-pdf", async (request, reply) => {
app.post("/api/v1/tools/image/image-to-pdf", async (request, reply) => {
const files: Array<{ buffer: Buffer; filename: string }> = [];
let settingsRaw: string | null = null;
+1 -1
View File
@@ -18,7 +18,7 @@ const execFileAsync = promisify(execFile);
* Does NOT use createToolRoute since it doesn't produce a processed file.
*/
export function registerInfo(app: FastifyInstance) {
app.post("/api/v1/tools/info", async (request: FastifyRequest, reply: FastifyReply) => {
app.post("/api/v1/tools/image/info", async (request: FastifyRequest, reply: FastifyReply) => {
let fileBuffer: Buffer | null = null;
let filename = "image";
+1 -1
View File
@@ -217,7 +217,7 @@ export function registerMemeGenerator(app: FastifyInstance) {
},
});
app.post("/api/v1/tools/meme-generator", async (request, reply) => {
app.post("/api/v1/tools/image/meme-generator", async (request, reply) => {
const contentTypeHeader = request.headers["content-type"] ?? "";
const isMultipart = contentTypeHeader.includes("multipart/form-data");
+1 -1
View File
@@ -69,7 +69,7 @@ registerAiJobHandler("noise-removal", async (input, data, ctx) => {
* Uses the Python sidecar for multi-tier denoising.
*/
export function registerNoiseRemoval(app: FastifyInstance) {
app.post("/api/v1/tools/noise-removal", async (request: FastifyRequest, reply: FastifyReply) => {
app.post("/api/v1/tools/image/noise-removal", async (request: FastifyRequest, reply: FastifyReply) => {
const toolId = "noise-removal";
if (!isToolInstalled(toolId)) {
const bundle = getBundleForTool(toolId);
+1 -1
View File
@@ -57,7 +57,7 @@ registerAiJobHandler("ocr-pdf", async (input, data, ctx) => {
});
export function registerOcrPdf(app: FastifyInstance) {
app.post("/api/v1/tools/ocr-pdf", async (request: FastifyRequest, reply: FastifyReply) => {
app.post("/api/v1/tools/pdf/ocr-pdf", async (request: FastifyRequest, reply: FastifyReply) => {
const toolId = "ocr-pdf";
if (!isToolInstalled(toolId)) {
const bundle = getBundleForTool(toolId);
+1 -1
View File
@@ -28,7 +28,7 @@ const settingsSchema = z.object({
* Returns JSON with extracted text rather than an image.
*/
export function registerOcr(app: FastifyInstance) {
app.post("/api/v1/tools/ocr", async (request: FastifyRequest, reply: FastifyReply) => {
app.post("/api/v1/tools/image/ocr", async (request: FastifyRequest, reply: FastifyReply) => {
const toolId = "ocr";
if (!isToolInstalled(toolId)) {
const bundle = getBundleForTool(toolId);
@@ -63,7 +63,7 @@ async function processImage(inputBuffer: Buffer, settings: Settings, filename: s
export function registerOptimizeForWeb(app: FastifyInstance) {
// Lightweight preview route for live parameter tuning
app.post(
"/api/v1/tools/optimize-for-web/preview",
"/api/v1/tools/image/optimize-for-web/preview",
async (request: FastifyRequest, reply: FastifyReply) => {
let fileBuffer: Buffer | null = null;
let filename = "image";
+4 -4
View File
@@ -131,7 +131,7 @@ async function generatePrintSheet(
export function registerPassportPhoto(app: FastifyInstance) {
// ── Phase 1: Analyze (face landmarks + bg removal) ────────────────
app.post(
"/api/v1/tools/passport-photo/analyze",
"/api/v1/tools/image/passport-photo/analyze",
async (request: FastifyRequest, reply: FastifyReply) => {
const toolId = "passport-photo";
if (!isToolInstalled(toolId)) {
@@ -310,7 +310,7 @@ export function registerPassportPhoto(app: FastifyInstance) {
// ── Base route: return 501 so generic callers don't get 404 ──────
app.post(
"/api/v1/tools/passport-photo",
"/api/v1/tools/image/passport-photo",
async (_request: FastifyRequest, reply: FastifyReply) => {
const toolId = "passport-photo";
if (!isToolInstalled(toolId)) {
@@ -324,14 +324,14 @@ export function registerPassportPhoto(app: FastifyInstance) {
});
}
return reply.status(400).send({
error: "Use /api/v1/tools/passport-photo/analyze or /generate",
error: "Use /api/v1/tools/image/passport-photo/analyze or /generate",
});
},
);
// ── Phase 2: Generate (crop + resize + tile) ─────────────────────
app.post(
"/api/v1/tools/passport-photo/generate",
"/api/v1/tools/image/passport-photo/generate",
async (request: FastifyRequest, reply: FastifyReply) => {
const parseResult = generateSettingsSchema.safeParse(request.body);
if (!parseResult.success) {
+3 -3
View File
@@ -181,7 +181,7 @@ function isPdfBuffer(buf: Buffer): boolean {
// ── Route registration ───────────────────────────────────────────
export function registerPdfToImage(app: FastifyInstance) {
// ── Info endpoint ────────────────────────────────────────────
app.post("/api/v1/tools/pdf-to-image/info", async (request, reply) => {
app.post("/api/v1/tools/pdf/pdf-to-image/info", async (request, reply) => {
let fileBuffer: Buffer | null = null;
try {
const result = await readPdfFromParts(request);
@@ -223,7 +223,7 @@ export function registerPdfToImage(app: FastifyInstance) {
});
// ── Preview endpoint (thumbnails) ─────────────────────────────
app.post("/api/v1/tools/pdf-to-image/preview", async (request, reply) => {
app.post("/api/v1/tools/pdf/pdf-to-image/preview", async (request, reply) => {
let fileBuffer: Buffer | null = null;
try {
const result = await readPdfFromParts(request);
@@ -288,7 +288,7 @@ export function registerPdfToImage(app: FastifyInstance) {
});
// ── Main processing endpoint ─────────────────────────────────
app.post("/api/v1/tools/pdf-to-image", async (request, reply) => {
app.post("/api/v1/tools/pdf/pdf-to-image", async (request, reply) => {
let fileBuffer: Buffer | null = null;
let settingsRaw: string | null = null;
+1 -1
View File
@@ -30,7 +30,7 @@ const settingsSchema = z.object({
* images from text input, not from uploaded files.
*/
export function registerQrGenerate(app: FastifyInstance) {
app.post("/api/v1/tools/qr-generate", async (request: FastifyRequest, reply: FastifyReply) => {
app.post("/api/v1/tools/image/qr-generate", async (request: FastifyRequest, reply: FastifyReply) => {
let body: unknown;
try {
body = request.body;
+1 -1
View File
@@ -58,7 +58,7 @@ registerAiJobHandler("red-eye-removal", async (input, data, ctx) => {
/** Red eye detection and removal route. */
export function registerRedEyeRemoval(app: FastifyInstance) {
app.post(
"/api/v1/tools/red-eye-removal",
"/api/v1/tools/image/red-eye-removal",
async (request: FastifyRequest, reply: FastifyReply) => {
const toolId = "red-eye-removal";
if (!isToolInstalled(toolId)) {
@@ -92,7 +92,7 @@ registerAiJobHandler("remove-background", async (input, data, ctx) => {
export function registerRemoveBackground(app: FastifyInstance) {
// ── Phase 1: Background removal ──────────────────────────────────
app.post(
"/api/v1/tools/remove-background",
"/api/v1/tools/image/remove-background",
async (request: FastifyRequest, reply: FastifyReply) => {
const toolId = "remove-background";
if (!isToolInstalled(toolId)) {
@@ -229,7 +229,7 @@ export function registerRemoveBackground(app: FastifyInstance) {
// ── Phase 2: Effects-only (no AI re-run) ─────────────────────────
app.post(
"/api/v1/tools/remove-background/effects",
"/api/v1/tools/image/remove-background/effects",
async (request: FastifyRequest, reply: FastifyReply) => {
let settingsRaw: string | null = null;
let bgImageBuffer: Buffer | null = null;
+1 -1
View File
@@ -83,7 +83,7 @@ registerAiJobHandler("restore-photo", async (input, data, ctx) => {
* optional colorization.
*/
export function registerRestorePhoto(app: FastifyInstance) {
app.post("/api/v1/tools/restore-photo", async (request: FastifyRequest, reply: FastifyReply) => {
app.post("/api/v1/tools/image/restore-photo", async (request: FastifyRequest, reply: FastifyReply) => {
if (!isToolInstalled("restore-photo")) {
const bundle = getBundleForTool("restore-photo");
return reply.status(501).send({
+1 -1
View File
@@ -44,7 +44,7 @@ function resolveOutputFormat(
}
export function registerSplit(app: FastifyInstance) {
app.post("/api/v1/tools/split", async (request, reply) => {
app.post("/api/v1/tools/image/split", async (request, reply) => {
let fileBuffer: Buffer | null = null;
let filename = "image";
let settingsRaw: string | null = null;
+1 -1
View File
@@ -44,7 +44,7 @@ interface PreparedImage {
}
export function registerStitch(app: FastifyInstance) {
app.post("/api/v1/tools/stitch", async (request, reply) => {
app.post("/api/v1/tools/image/stitch", async (request, reply) => {
const files: Array<{ buffer: Buffer; filename: string }> = [];
let settingsRaw: string | null = null;
+1 -1
View File
@@ -96,7 +96,7 @@ function parseIccProfile(iccBuffer: Buffer): Record<string, string> {
export function registerStripMetadata(app: FastifyInstance) {
// Inspect endpoint — returns parsed metadata as JSON
app.post(
"/api/v1/tools/strip-metadata/inspect",
"/api/v1/tools/image/strip-metadata/inspect",
async (request: FastifyRequest, reply: FastifyReply) => {
let fileBuffer: Buffer | null = null;
let filename = "image";
+2 -2
View File
@@ -105,7 +105,7 @@ async function convertSvg(
*/
export function registerSvgToRaster(app: FastifyInstance) {
// --- Batch endpoint (registered first for route priority) ---
app.post("/api/v1/tools/svg-to-raster/batch", async (request, reply) => {
app.post("/api/v1/tools/image/svg-to-raster/batch", async (request, reply) => {
const files: ParsedSvgFile[] = [];
let settingsRaw: string | null = null;
let clientJobId: string | null = null;
@@ -347,7 +347,7 @@ export function registerSvgToRaster(app: FastifyInstance) {
});
// --- Single-file endpoint ---
app.post("/api/v1/tools/svg-to-raster", async (request, reply) => {
app.post("/api/v1/tools/image/svg-to-raster", async (request, reply) => {
let fileBuffer: Buffer | null = null;
let filename = "output";
let settingsRaw: string | null = null;
@@ -79,7 +79,7 @@ registerAiJobHandler("transcribe-audio", async (input, data, ctx) => {
export function registerTranscribeAudio(app: FastifyInstance) {
app.post(
"/api/v1/tools/transcribe-audio",
"/api/v1/tools/audio/transcribe-audio",
async (request: FastifyRequest, reply: FastifyReply) => {
const toolId = "transcribe-audio";
if (!isToolInstalled(toolId)) {
@@ -152,7 +152,7 @@ registerAiJobHandler("transparency-fixer", async (input, data, ctx) => {
export function registerTransparencyFixer(app: FastifyInstance) {
app.post(
"/api/v1/tools/transparency-fixer",
"/api/v1/tools/image/transparency-fixer",
async (request: FastifyRequest, reply: FastifyReply) => {
if (!isToolInstalled(TOOL_ID)) {
const bundle = getBundleForTool(TOOL_ID);
+1 -1
View File
@@ -116,7 +116,7 @@ registerAiJobHandler("upscale", async (input, data, ctx) => {
* Uses Real-ESRGAN when available, falls back to Lanczos.
*/
export function registerUpscale(app: FastifyInstance) {
app.post("/api/v1/tools/upscale", async (request: FastifyRequest, reply: FastifyReply) => {
app.post("/api/v1/tools/image/upscale", async (request: FastifyRequest, reply: FastifyReply) => {
const toolId = "upscale";
if (!isToolInstalled(toolId)) {
const bundle = getBundleForTool(toolId);
+1 -1
View File
@@ -98,7 +98,7 @@ async function vectorizeBuffer(
}
export function registerVectorize(app: FastifyInstance) {
app.post("/api/v1/tools/vectorize", async (request, reply) => {
app.post("/api/v1/tools/image/vectorize", async (request, reply) => {
let fileBuffer: Buffer | null = null;
let filename = "output";
let settingsRaw: string | null = null;
+1 -1
View File
@@ -21,7 +21,7 @@ const settingsSchema = z.object({
export function registerWatermarkImage(app: FastifyInstance) {
// Custom route since we need two file uploads
app.post("/api/v1/tools/watermark-image", async (request, reply) => {
app.post("/api/v1/tools/image/watermark-image", async (request, reply) => {
let mainBuffer: Buffer | null = null;
let watermarkBuffer: Buffer | null = null;
let filename = "image";