Merge branch 'feat/section-based-tool-urls' into chore/consolidate-v2.0.0

This commit is contained in:
SnapOtter
2026-06-21 02:09:14 +08:00
522 changed files with 4650 additions and 3866 deletions
+175 -169
View File
File diff suppressed because it is too large Load Diff
+13 -6
View File
@@ -1,7 +1,7 @@
/**
* Batch processing route.
*
* POST /api/v1/tools/:toolId/batch
* POST /api/v1/tools/:section/:toolId/batch
*
* Accepts multipart with multiple files + settings JSON.
* Each file is enqueued as a batch-child BullMQ job; a batch-finalize
@@ -12,7 +12,7 @@ import { randomUUID } from "node:crypto";
import { mkdir } from "node:fs/promises";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { getBundleForTool, TOOL_BUNDLE_MAP, TOOLS } from "@snapotter/shared";
import { getBundleForTool, TOOL_BUNDLE_MAP, TOOLS, toolSection } from "@snapotter/shared";
import archiver from "archiver";
import type { FlowJob } from "bullmq";
import { eq } from "drizzle-orm";
@@ -56,10 +56,17 @@ function injectTraceContextIntoFlow(node: FlowJob): void {
export async function registerBatchRoutes(app: FastifyInstance): Promise<void> {
app.post(
"/api/v1/tools/:toolId/batch",
"/api/v1/tools/:section/:toolId/batch",
{ config: { rateLimit: { max: 20, timeWindow: "1 minute" } } },
async (request: FastifyRequest<{ Params: { toolId: string } }>, reply: FastifyReply) => {
const { toolId } = request.params;
async (
request: FastifyRequest<{ Params: { section: string; toolId: string } }>,
reply: FastifyReply,
) => {
const { section, toolId } = request.params;
const tool = TOOLS.find((t) => t.id === toolId);
if (!tool || toolSection(tool) !== section) {
return reply.status(404).send({ error: "Not found", code: "NOT_FOUND" });
}
// Batch processing (especially with AI) can take tens of minutes.
// Disable the Node.js HTTP socket timeout so the connection is not
@@ -182,7 +189,7 @@ export async function registerBatchRoutes(app: FastifyInstance): Promise<void> {
// Resolve the tool's modality so non-image files (audio/video/document)
// validate through their own handler instead of the image validator.
const modality = TOOLS.find((t) => t.id === toolId)?.modality ?? "image";
const modality = tool.modality;
const batchScratch = join(tmpdir(), "snapotter-scratch", `batch-${parentId}`);
await mkdir(batchScratch, { recursive: true });
+18 -3
View File
@@ -2,7 +2,14 @@ import { randomUUID } from "node:crypto";
import { mkdir, rm } from "node:fs/promises";
import { tmpdir } from "node:os";
import { extname, join } from "node:path";
import { ANALYTICS_EVENTS, getBundleForTool, TOOL_BUNDLE_MAP, TOOLS } from "@snapotter/shared";
import {
ANALYTICS_EVENTS,
apiToolPath,
getBundleForTool,
type Section,
TOOL_BUNDLE_MAP,
TOOLS,
} from "@snapotter/shared";
import { and, inArray, sql } from "drizzle-orm";
import type { FastifyInstance, FastifyReply, FastifyRequest } from "fastify";
import type { z } from "zod";
@@ -67,6 +74,12 @@ export type ToolProcessV2 = (ctx: ToolProcessCtxV2) => Promise<ToolProcessResult
export interface ToolRouteConfig<T> {
/** Unique tool identifier, used as the URL path segment. */
toolId: string;
/**
* Override the URL section for non-catalog alias routes kept for
* backwards-compatible URLs (e.g. adjust-colors' brightness-contrast).
* Catalog tools omit this; their section is derived via apiToolPath.
*/
section?: Section;
/**
* How many file parts the route accepts (default 1). Inputs beyond the
* first are validated by the same modality handler and appended to
@@ -186,7 +199,7 @@ export function registerToolProcessFn(config: AnyToolRouteConfig): void {
}
/**
* Factory that registers a POST /api/v1/tools/:toolId route.
* Factory that registers a POST /api/v1/tools/:section/:toolId route.
*
* The route accepts multipart with:
* - A file part (the image to process)
@@ -211,7 +224,9 @@ export function createToolRoute<T>(app: FastifyInstance, config: ToolRouteConfig
toolRegistry.set(config.toolId, resolved);
app.post(
`/api/v1/tools/${config.toolId}`,
config.section
? `/api/v1/tools/${config.section}/${config.toolId}`
: apiToolPath(config.toolId),
{ config: { rateLimit: { max: 60, timeWindow: "1 minute" } } },
async (request: FastifyRequest, reply: FastifyReply) => {
// Check per-tool access before processing uploads
+15 -9
View File
@@ -133,14 +133,20 @@ async function processColorAdjustments(
}
export function registerColorAdjustments(app: FastifyInstance) {
const allIds = [
"adjust-colors",
"brightness-contrast",
"saturation",
"color-channels",
"color-effects",
];
for (const toolId of allIds) {
createToolRoute(app, { toolId, settingsSchema, process: processColorAdjustments });
createToolRoute(app, {
toolId: "adjust-colors",
settingsSchema,
process: processColorAdjustments,
});
// Backwards-compatible alias routes consolidated into adjust-colors. These
// ids are not catalog TOOLS, so pass the section explicitly (all image).
const aliasIds = ["brightness-contrast", "saturation", "color-channels", "color-effects"];
for (const toolId of aliasIds) {
createToolRoute(app, {
toolId,
section: "image",
settingsSchema,
process: processColorAdjustments,
});
}
}
@@ -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";