feat: API sync and documentation audit - 100% endpoint coverage (#94)

Code quality:
- Add Zod validation to 14 route handlers that used raw JSON.parse
  (favicon, find-duplicates, barcode-read, upscale, blur-faces,
  erase-object, colorize, enhance-faces, red-eye-removal,
  remove-background/effects, auth, api-keys, roles, teams,
  analytics, settings, user-files)
- Standardize error responses to safeParse + formatZodErrors pattern
- Replace unsafe `as` type casts with schema validation

OpenAPI spec (89 -> 115 operations):
- Add 14 missing tool endpoints (adjust-colors, sharpening,
  optimize-for-web, image-enhancement, noise-removal, red-eye-removal,
  restore-photo, passport-photo, colorize, enhance-faces, image-to-base64)
- Add 12 missing non-tool endpoints (analytics, features, audit-log,
  roles, admin-health)
- Add typed error schemas for 401/403/409 responses
- Add descriptions to all path parameters
- Bump version from 0.9.0 to 1.15.9

Documentation:
- Fix 8 incorrect env var defaults in configuration guide
- Add 15 undocumented env vars to configuration guide
- Fix tool ID mismatch (color-adjustments -> adjust-colors)
- Add 4 new API sections (Roles, Audit Log, Analytics, Features)
- Add image-enhancement to AI engine reference
- Update AI tool count from 13 to 14 across all docs
- Add 6 missing doc links to README
This commit is contained in:
Ashim
2026-04-23 20:26:58 +08:00
committed by GitHub
parent 136a4dd641
commit 97938bdc47
28 changed files with 2814 additions and 191 deletions
+32
View File
@@ -3,9 +3,14 @@ import { basename, extname } from "node:path";
import archiver from "archiver";
import type { FastifyInstance } from "fastify";
import sharp from "sharp";
import { z } from "zod";
import { autoOrient } from "../../lib/auto-orient.js";
import { formatZodErrors } from "../../lib/errors.js";
import { validateImageBuffer } from "../../lib/file-validation.js";
import { ensureSharpCompat } from "../../lib/heic-converter.js";
const settingsSchema = z.object({}).passthrough();
const FAVICON_SIZES = [
{ name: "favicon-16x16.png", size: 16, format: "png" as const },
{ name: "favicon-32x32.png", size: 32, format: "png" as const },
@@ -23,6 +28,7 @@ interface UploadedFile {
export function registerFavicon(app: FastifyInstance) {
app.post("/api/v1/tools/favicon", async (request, reply) => {
const uploadedFiles: UploadedFile[] = [];
let settingsRaw: string | null = null;
try {
const parts = request.parts();
@@ -35,6 +41,8 @@ export function registerFavicon(app: FastifyInstance) {
const buffer = Buffer.concat(chunks);
const filename = basename(part.filename ?? `image-${uploadedFiles.length + 1}`);
uploadedFiles.push({ buffer, filename });
} else if (part.fieldname === "settings") {
settingsRaw = part.value as string;
}
}
} catch (err) {
@@ -48,6 +56,30 @@ export function registerFavicon(app: FastifyInstance) {
return reply.status(400).send({ error: "No image file provided" });
}
// Validate all uploaded files
for (const file of uploadedFiles) {
const validation = await validateImageBuffer(file.buffer, file.filename);
if (!validation.valid) {
return reply
.status(400)
.send({ error: `Invalid file "${file.filename}": ${validation.reason}` });
}
}
if (settingsRaw) {
try {
const parsed = JSON.parse(settingsRaw);
const result = settingsSchema.safeParse(parsed);
if (!result.success) {
return reply
.status(400)
.send({ error: "Invalid settings", details: formatZodErrors(result.error.issues) });
}
} catch {
return reply.status(400).send({ error: "Settings must be valid JSON" });
}
}
try {
const jobId = randomUUID();
const isSingleFile = uploadedFiles.length === 1;