Files
emaui/libs/api/src/lib/file-upload/index.ts
Estifo77 3edd0587e5 document
2026-07-28 14:50:53 +03:00

106 lines
2.9 KiB
TypeScript

import { useCallback, useState } from "react";
import { baseApi } from "../base-api";
import { resolveTokenFromStorage } from "../session";
export interface UploadFileInfo {
bucket: string;
fileName: string;
contentType: string;
originalname: string;
size: number;
}
export interface UploadKeyResponse {
fileInfo: UploadFileInfo;
presigned: string;
}
const fileUploadApi = baseApi.injectEndpoints({
endpoints: (builder) => ({
getFileUploadKey: builder.mutation<
UploadKeyResponse,
{ endpoint: string; file: File }
>({
query: ({ endpoint, file }) => ({
url: endpoint,
method: "POST",
body: {
fileName: file.name,
contentType: file.type || "application/octet-stream",
size: file.size,
originalname: file.name,
},
}),
}),
}),
overrideExisting: false,
});
export const { useGetFileUploadKeyMutation } = fileUploadApi;
export const STORAGE_UPLOAD_ERROR = "STORAGE_UPLOAD_ERROR";
export const STORAGE_UPLOAD_TOO_LARGE = "STORAGE_UPLOAD_TOO_LARGE";
export function isStorageUploadError(error: unknown): error is Error & {
message: typeof STORAGE_UPLOAD_ERROR | typeof STORAGE_UPLOAD_TOO_LARGE;
} {
return (
error instanceof Error &&
(error.message === STORAGE_UPLOAD_ERROR ||
error.message === STORAGE_UPLOAD_TOO_LARGE)
);
}
// Bare fetch on purpose: this PUT targets the presigned storage URL directly, not
// VITE_BASE_API_URL, so it must not go through baseApi/fetchBaseQuery.
async function uploadToPresigned(
file: File,
presignedUrl: string,
): Promise<void> {
if (!presignedUrl) throw new Error(STORAGE_UPLOAD_ERROR);
const token = resolveTokenFromStorage();
const res = await fetch(presignedUrl, {
method: "PUT",
body: file,
headers: {
"Content-Type": file.type || "application/octet-stream",
...(token ? { Authorization: `Bearer ${token}` } : {}),
},
});
if (res.status === 413) throw new Error(STORAGE_UPLOAD_TOO_LARGE);
if (!res.ok) throw new Error(STORAGE_UPLOAD_ERROR);
}
export function useDocumentUpload() {
const [getFileUploadKey, mutationState] = useGetFileUploadKeyMutation();
// Spans both steps (key request + presigned PUT) — mutationState.isLoading alone
// would drop to false once the key request resolves, before the file PUT finishes.
const [isUploading, setIsUploading] = useState(false);
const upload = useCallback(
async (file: File, endpoint = "/documents/get-file-upload-key"): Promise<UploadKeyResponse> => {
setIsUploading(true);
try {
const { presigned, fileInfo } = await getFileUploadKey({
endpoint,
file,
}).unwrap();
await uploadToPresigned(file, presigned);
return { presigned, fileInfo };
} finally {
setIsUploading(false);
}
},
[getFileUploadKey],
);
return {
upload,
isUploading,
error: mutationState.error,
reset: mutationState.reset,
};
}