import type { FileListObject } from "media/storage/Storage"; import { type BaseModuleApiOptions, type FetchPromise, type ResponseObject, ModuleApi, type PrimaryFieldType, type TInput, } from "modules/ModuleApi"; import type { ApiFetcher } from "Api"; import type { DB, FileUploadedEventData } from "bknd"; export type MediaApiOptions = BaseModuleApiOptions & { upload_fetcher: ApiFetcher; init?: RequestInit; }; export class MediaApi extends ModuleApi { protected override getDefaultOptions(): Partial { return { basepath: "/api/media", upload_fetcher: fetch, init: {}, }; } listFiles() { return this.get(["files"]); } getFile(filename: string) { return this.get>(["file", filename], undefined, { headers: { Accept: "*/*", }, }); } async getFileStream(filename: string): Promise> { const { res } = await this.getFile(filename); if (!res.ok || !res.body) { throw new Error("Failed to fetch file"); } return res.body; } async download(filename: string): Promise { const { res } = await this.getFile(filename); if (!res.ok || !res.body) { throw new Error("Failed to fetch file"); } return (await res.blob()) as File; } getFileUploadUrl(file?: { path: string }): string { if (!file) return this.getUrl("/upload"); return this.getUrl(`/upload/${file.path}`); } getEntityUploadUrl(entity: string, id: PrimaryFieldType, field: string) { return this.getUrl(`/entity/${entity}/${id}/${field}`); } getUploadHeaders(): Headers { if (this.options.token_transport === "header" && this.options.token) { return new Headers({ Authorization: `Bearer ${this.options.token}`, }); } return new Headers(); } protected uploadFile( body: BodyInit, opts?: { filename?: string; path?: TInput; _init?: Omit; query?: Record; }, ): FetchPromise> { const headers = { "Content-Type": "application/octet-stream", ...(opts?._init?.headers || {}), }; let name: string = opts?.filename || ""; try { if (typeof (body as File).type !== "undefined") { headers["Content-Type"] = (body as File).type; } if (!opts?.filename) { name = (body as File).name; } } catch (e) {} if (name && name.length > 0 && name.includes("/")) { name = name.split("/").pop() || ""; } const init = { ...this.options.init, ...(opts?._init || {}), headers, }; if (opts?.path) { return this.request(opts.path, opts?.query, { ...init, body, method: "POST", }); } if (!name || name.length === 0) { throw new Error("Invalid filename"); } return this.request(opts?.path ?? ["upload", name], opts?.query, { ...init, body, method: "POST", }); } async upload( item: Request | Response | string | File | Blob | ReadableStream | Buffer, opts: { filename?: string; _init?: Omit; path?: TInput; fetcher?: ApiFetcher; query?: Record; } = {}, ) { if (item instanceof Request || typeof item === "string") { const fetcher = opts.fetcher ?? this.options.upload_fetcher; const res = await fetcher(item); if (!res.ok || !res.body) { throw new Error("Failed to fetch file"); } return this.uploadFile(res.body, opts); } else if (item instanceof Response) { if (!item.body) { throw new Error("Invalid response"); } return this.uploadFile(item.body, { ...(opts ?? {}), _init: { ...(opts._init ?? {}), headers: { ...(opts._init?.headers ?? {}), "Content-Type": item.headers.get("Content-Type") || "application/octet-stream", }, }, }); } return this.uploadFile(item as BodyInit, opts); } async uploadToEntity( entity: string, id: PrimaryFieldType, field: string, item: Request | Response | string | File | ReadableStream | Buffer, opts?: { _init?: Omit; fetcher?: typeof fetch; overwrite?: boolean; }, ): Promise> { const query = opts?.overwrite !== undefined ? { overwrite: opts.overwrite } : undefined; return this.upload(item, { ...opts, path: ["entity", entity, id, field], query, }); } deleteFile(filename: string) { return this.delete(["file", filename]); } }