mirror of
https://github.com/multica-ai/multica.git
synced 2026-07-05 05:19:30 +02:00
fix: prefer local upload attachment URLs
Co-authored-by: multica-agent <github@multica.ai>
This commit is contained in:
@@ -262,6 +262,40 @@ describe("Attachment — image dispatch", () => {
|
||||
);
|
||||
});
|
||||
|
||||
it("prefers a local disk /uploads URL over API markdown in split-origin self-host", () => {
|
||||
getBaseUrlMock.mockReturnValue("https://api.example.test");
|
||||
const id = "11111111-2222-3333-4444-555555555555";
|
||||
const markdownUrl = `https://api.example.test/api/attachments/${id}/download`;
|
||||
const mediaUrl = "https://api.example.test/uploads/workspaces/ws-1/shot.png";
|
||||
const att = makeRecord({
|
||||
id,
|
||||
url: "/uploads/workspaces/ws-1/shot.png",
|
||||
markdown_url: markdownUrl,
|
||||
download_url: `/api/attachments/${id}/download`,
|
||||
});
|
||||
resolverState.attachments = [att];
|
||||
|
||||
renderWithQuery(
|
||||
<Attachment
|
||||
attachment={{
|
||||
kind: "url",
|
||||
url: markdownUrl,
|
||||
filename: "shot.png",
|
||||
forceKind: "image",
|
||||
}}
|
||||
/>,
|
||||
);
|
||||
|
||||
expect(document.querySelector("img")?.getAttribute("src")).toBe(mediaUrl);
|
||||
|
||||
fireEvent.click(screen.getByTitle("View"));
|
||||
|
||||
const imageSrcs = [...document.querySelectorAll("img")].map((img) =>
|
||||
img.getAttribute("src"),
|
||||
);
|
||||
expect(imageSrcs).toEqual([mediaUrl, mediaUrl]);
|
||||
});
|
||||
|
||||
it("opens preview with the same resolved media URL when a reopened draft record has no download_url", () => {
|
||||
configStore.setState({ cdnDomain: "cdn.example.test" });
|
||||
const id = "11111111-2222-3333-4444-555555555555";
|
||||
|
||||
@@ -237,12 +237,17 @@ function absolutizeMediaURL(rawUrl: string): string {
|
||||
// reports `cdn_signed` — in CloudFront signed-URL mode the same
|
||||
// domain serves PRIVATE content and a raw (unsigned) storage URL is
|
||||
// a guaranteed 403 (MUL-3254).
|
||||
// 3. `record.markdown_url` — the durable, server-policy-aligned URL.
|
||||
// 3. Local disk `record.url` — self-host LocalStorage without
|
||||
// LOCAL_UPLOAD_BASE_URL stores a site-relative `/uploads/...` path.
|
||||
// It is the direct static object URL and is loadable once
|
||||
// `absolutizeMediaURL` prefixes apiBaseUrl in split-origin clients.
|
||||
// 4. `record.markdown_url` — the durable, server-policy-aligned URL.
|
||||
// Beats raw `record.url` because it never points at a private
|
||||
// bucket (must-fix 2 from MUL-3192 review).
|
||||
// 4. `record.url` — legacy fallback for responses that omit
|
||||
// bucket (must-fix 2 from MUL-3192 review), except for the explicit
|
||||
// site-relative local upload path above.
|
||||
// 5. `record.url` — legacy fallback for responses that omit
|
||||
// `markdown_url` (a backend old enough to predate MUL-3192).
|
||||
// 5. The input URL — when there's no record at all.
|
||||
// 6. The input URL — when there's no record at all.
|
||||
function pickInlineMediaURL(
|
||||
record: AttachmentRecord,
|
||||
fallback: string,
|
||||
@@ -257,11 +262,18 @@ function pickInlineMediaURL(
|
||||
return dl;
|
||||
}
|
||||
if (!cdnSigned && storageURLMatchesCdnDomain(record.url, cdnDomain)) return record.url;
|
||||
if (isSiteRelativeLocalUploadURL(record.url)) return record.url;
|
||||
if (record.markdown_url) return record.markdown_url;
|
||||
if (record.url) return record.url;
|
||||
return fallback;
|
||||
}
|
||||
|
||||
function isSiteRelativeLocalUploadURL(rawURL: string): boolean {
|
||||
if (!rawURL || !rawURL.startsWith("/")) return false;
|
||||
const path = rawURL.split(/[?#]/, 1)[0] ?? "";
|
||||
return path === "/uploads" || path.startsWith("/uploads/");
|
||||
}
|
||||
|
||||
function storageURLMatchesCdnDomain(rawURL: string, cdnDomain: string): boolean {
|
||||
const expected = normalizeHost(cdnDomain);
|
||||
if (!rawURL || !expected) return false;
|
||||
|
||||
Reference in New Issue
Block a user