mirror of
https://github.com/rgrgogu/new_starr.git
synced 2026-09-27 00:12:54 +08:00
add: more commits
Signed-off-by: Kenneth Obsequio <k80308392@gmail.com>
This commit is contained in:
+85
-5
@@ -1,13 +1,21 @@
|
||||
// utils/media.util.js
|
||||
//
|
||||
// Resolves a displayable <img> src for an asset payload that may carry a
|
||||
// short-lived stream_token (private/S3 — proxied through the backend) instead
|
||||
// of a raw file_url. Private assets must never expose their real storage URL
|
||||
// to the browser — see controllers/admin/assets.controller.js's redactS3Url
|
||||
// pattern, mirrored for advertisements in controllers/*/advertisements.controller.js.
|
||||
// Resolves a displayable <img>/<video>/<audio> src for an asset payload that
|
||||
// may carry a short-lived stream_token (private/S3 — proxied through the
|
||||
// backend) instead of a raw file_url. Private assets must never expose their
|
||||
// real storage URL to the browser — see controllers/admin/assets.controller.js's
|
||||
// redactS3Url pattern, mirrored for advertisements in
|
||||
// controllers/*/advertisements.controller.js.
|
||||
|
||||
import api from "@/utils/api.util";
|
||||
|
||||
const API_BASE = (import.meta.env.VITE_API_URL ?? "http://localhost:3024").replace(/\/$/, "");
|
||||
|
||||
const TOKEN_ENDPOINTS = {
|
||||
admin: "/admin/media/token",
|
||||
client: "/client/media/token",
|
||||
};
|
||||
|
||||
export function streamUrl(token) {
|
||||
return token ? `${API_BASE}/client/media/stream/${token}` : null;
|
||||
}
|
||||
@@ -20,3 +28,75 @@ export function resolveAssetSrc(asset) {
|
||||
if (asset.stream_token) return streamUrl(asset.stream_token);
|
||||
return asset.thumbnail_url || asset.file_url || null;
|
||||
}
|
||||
|
||||
// ─── fetchAssetPreviewSrc ──────────────────────────────────────────────────────
|
||||
//
|
||||
// Async fallback for when resolveAssetSrc() can't resolve synchronously (S3
|
||||
// asset whose stream_token wasn't embedded on the row — e.g. an expired cache
|
||||
// edge case). Mints a fresh token via the admin or client media-token endpoint.
|
||||
// Non-S3 assets (chibisafe/local) resolve straight to file_url — no request.
|
||||
//
|
||||
// Returns { src, thumbnailUrl }.
|
||||
export async function fetchAssetPreviewSrc(asset, { scope = "admin" } = {}) {
|
||||
if (!asset) return { src: null, thumbnailUrl: null };
|
||||
|
||||
if (asset.storage_provider !== "s3") {
|
||||
return { src: asset.file_url ?? null, thumbnailUrl: asset.thumbnail_url ?? null };
|
||||
}
|
||||
|
||||
if (asset.stream_token) {
|
||||
return { src: streamUrl(asset.stream_token), thumbnailUrl: asset.thumbnail_url ?? null };
|
||||
}
|
||||
|
||||
try {
|
||||
const { data } = await api.post(TOKEN_ENDPOINTS[scope] ?? TOKEN_ENDPOINTS.admin, {
|
||||
asset_id: asset.asset_id,
|
||||
});
|
||||
const { token, thumbnail_url } = data?.data ?? {};
|
||||
return { src: streamUrl(token), thumbnailUrl: thumbnail_url ?? null };
|
||||
} catch {
|
||||
return { src: null, thumbnailUrl: null };
|
||||
}
|
||||
}
|
||||
|
||||
// ─── saveBlob ───────────────────────────────────────────────────────────────────
|
||||
//
|
||||
// Triggers a browser "Save As" for an in-memory blob via a synthetic <a download>
|
||||
// click. Shared by downloadAsset() below and FilePreview.jsx's file-completion
|
||||
// download flow — kept in one place instead of being copy-pasted per consumer.
|
||||
export function saveBlob(blob, filename) {
|
||||
const url = URL.createObjectURL(blob);
|
||||
const a = document.createElement("a");
|
||||
a.href = url;
|
||||
a.download = filename ?? "download";
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
document.body.removeChild(a);
|
||||
URL.revokeObjectURL(url);
|
||||
}
|
||||
|
||||
// ─── downloadAsset ──────────────────────────────────────────────────────────────
|
||||
//
|
||||
// Fetches an asset's bytes (via the same proxy/file_url resolution as above)
|
||||
// and saves it locally, without ever mounting a preview element. Used for
|
||||
// "Public" assets in the Assets admin surfaces, which are download-only —
|
||||
// see AssetPickerSheet.jsx / the admin ViewXAsset pages.
|
||||
export async function downloadAsset(asset, { scope = "admin" } = {}) {
|
||||
if (!asset) return;
|
||||
|
||||
const filename = asset.display_name ?? asset.original_name ?? "download";
|
||||
let src = resolveAssetSrc(asset);
|
||||
if (!src) {
|
||||
({ src } = await fetchAssetPreviewSrc(asset, { scope }));
|
||||
}
|
||||
if (!src) return;
|
||||
|
||||
try {
|
||||
const { data: blob } = await api.get(src, { responseType: "blob" });
|
||||
saveBlob(blob, filename);
|
||||
} catch {
|
||||
// Cross-origin CDN (chibisafe/public file_url) may reject the blob
|
||||
// fetch — fall back to opening it directly.
|
||||
if (asset.file_url) window.open(asset.file_url, "_blank");
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user