You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
105 lines
3.5 KiB
105 lines
3.5 KiB
import { fetchPopiserverGateway as fetch } from "@/app/api/_popiserverFetch";
|
|
import { NextRequest } from "next/server";
|
|
import { ApiError } from "./_errors";
|
|
import { getPopiartApiBaseUrl } from "./_popiartBaseUrl";
|
|
|
|
const POPI_MEDIA_UPLOAD_PATH = "/api_client/media/upload";
|
|
|
|
type PopiserverResponse<T> = {
|
|
status?: string;
|
|
message?: string;
|
|
data?: T;
|
|
};
|
|
|
|
type PopiMedia = {
|
|
path?: string;
|
|
url?: string;
|
|
originPath?: string;
|
|
originUrl?: string;
|
|
};
|
|
|
|
function getPopiBaseUrl(request: NextRequest): string {
|
|
void request;
|
|
return getPopiartApiBaseUrl();
|
|
}
|
|
|
|
function parseDataUrl(value: string): { mimeType: string; data: Buffer } | null {
|
|
const match = value.match(/^data:([^;,]+)?(?:;[^,]*)?;base64,(.*)$/);
|
|
if (!match) return null;
|
|
return {
|
|
mimeType: match[1] || "application/octet-stream",
|
|
data: Buffer.from(match[2], "base64"),
|
|
};
|
|
}
|
|
|
|
function extensionForMimeType(mimeType: string): string {
|
|
const normalized = mimeType.toLowerCase();
|
|
if (normalized === "image/jpeg") return "jpg";
|
|
if (normalized === "image/png") return "png";
|
|
if (normalized === "image/webp") return "webp";
|
|
if (normalized === "image/gif") return "gif";
|
|
if (normalized === "video/mp4") return "mp4";
|
|
if (normalized === "video/webm") return "webm";
|
|
if (normalized === "video/quicktime") return "mov";
|
|
if (normalized === "audio/mpeg" || normalized === "audio/mp3") return "mp3";
|
|
if (normalized === "audio/wav" || normalized === "audio/x-wav") return "wav";
|
|
if (normalized === "audio/ogg") return "ogg";
|
|
if (normalized === "audio/flac") return "flac";
|
|
if (normalized === "audio/aac") return "aac";
|
|
if (normalized === "model/gltf-binary") return "glb";
|
|
return normalized.split("/")[1]?.replace(/[^a-z0-9]/g, "") || "bin";
|
|
}
|
|
|
|
function mediaResourceUrl(media: PopiMedia): string | null {
|
|
return media.url || media.originUrl || media.path || media.originPath || null;
|
|
}
|
|
|
|
export function isDataUrl(value: string | null | undefined): value is string {
|
|
return typeof value === "string" && value.startsWith("data:");
|
|
}
|
|
|
|
export async function uploadDataUrlToPopiMedia(
|
|
request: NextRequest,
|
|
token: string,
|
|
dataUrl: string,
|
|
filePrefix: string
|
|
): Promise<string> {
|
|
const parsed = parseDataUrl(dataUrl);
|
|
if (!parsed) {
|
|
throw ApiError.badRequest("Invalid media data URL.");
|
|
}
|
|
|
|
const extension = extensionForMimeType(parsed.mimeType);
|
|
const arrayBuffer = parsed.data.buffer.slice(
|
|
parsed.data.byteOffset,
|
|
parsed.data.byteOffset + parsed.data.byteLength
|
|
) as ArrayBuffer;
|
|
const blob = new Blob([arrayBuffer], { type: parsed.mimeType });
|
|
const formData = new FormData();
|
|
formData.append("file", blob, `${filePrefix}-${Date.now()}.${extension}`);
|
|
|
|
const upstream = await fetch(getPopiBaseUrl(request) + POPI_MEDIA_UPLOAD_PATH, {
|
|
method: "POST",
|
|
headers: {
|
|
Accept: "application/json",
|
|
Authorization: `Bearer ${token}`,
|
|
token,
|
|
},
|
|
body: formData,
|
|
cache: "no-store",
|
|
});
|
|
|
|
const payload = (await upstream.json().catch(() => null)) as PopiserverResponse<PopiMedia> | null;
|
|
if (!upstream.ok) {
|
|
throw ApiError.provider(payload?.message || "Popiserver media upload failed.", upstream.status);
|
|
}
|
|
if (!payload || payload.status !== "0000" || !payload.data) {
|
|
throw ApiError.provider(payload?.message || "Popiserver media upload returned an error.", 502);
|
|
}
|
|
|
|
const url = mediaResourceUrl(payload.data);
|
|
if (!url) {
|
|
throw ApiError.provider("Popiserver media upload response is missing resource URL.", 502);
|
|
}
|
|
return url;
|
|
}
|
|
|