v3: fixed rich content extraction heuristics
All checks were successful
Build and Publish Docker Image / build-and-push (push) Successful in 3m15s

This commit is contained in:
khannurien
2026-04-11 13:13:43 +00:00
parent b822f861ed
commit 34933a3d4f
6 changed files with 280 additions and 32 deletions

View File

@@ -1,6 +1,7 @@
import { Router } from "@oak/oak";
import {
fetchRichContent,
fetchWithTimeout,
isValidHttpUrl,
} from "../services/rich-content-service.ts";
import { APIErrorCode } from "../model/interfaces.ts";
@@ -21,4 +22,44 @@ previewRouter.get("/api/preview", async (ctx) => {
ctx.response.body = { success: true, data: data ?? null };
});
/**
* Proxy an external image through the server so HTTP thumbnail URLs don't
* trigger mixed-content blocks when the frontend is served over HTTPS.
*/
previewRouter.get("/api/proxy-image", async (ctx) => {
const url = ctx.request.url.searchParams.get("url") ?? "";
if (!isValidHttpUrl(url)) {
ctx.response.status = 400;
return;
}
try {
const res = await fetchWithTimeout(url, 8000);
const contentType = res.headers.get("content-type") ?? "";
if (!contentType.startsWith("image/")) {
ctx.response.status = 400;
return;
}
const MAX_SIZE = 5 * 1024 * 1024; // 5 MB
const contentLength = Number(res.headers.get("content-length") ?? "0");
if (contentLength > MAX_SIZE) {
ctx.response.status = 400;
return;
}
const bytes = new Uint8Array(await res.arrayBuffer());
if (bytes.length > MAX_SIZE) {
ctx.response.status = 400;
return;
}
ctx.response.headers.set("Content-Type", contentType);
ctx.response.headers.set("Cache-Control", "public, max-age=86400");
ctx.response.body = bytes;
} catch {
ctx.response.status = 502;
}
});
export default previewRouter;