diff --git a/app/src/adapter/cloudflare/cloudflare-workers.adapter.ts b/app/src/adapter/cloudflare/cloudflare-workers.adapter.ts index d1b6712..427f8e4 100644 --- a/app/src/adapter/cloudflare/cloudflare-workers.adapter.ts +++ b/app/src/adapter/cloudflare/cloudflare-workers.adapter.ts @@ -33,6 +33,7 @@ export type CloudflareBkndConfig = RuntimeBkndConfig & keepAliveSeconds?: number; forceHttps?: boolean; manifest?: string; + registerMedia?: boolean | ((env: Env) => void); }; export type Context = { diff --git a/app/src/adapter/cloudflare/config.ts b/app/src/adapter/cloudflare/config.ts index 7f3eaee..568b09c 100644 --- a/app/src/adapter/cloudflare/config.ts +++ b/app/src/adapter/cloudflare/config.ts @@ -93,8 +93,12 @@ export function makeConfig( config: CloudflareBkndConfig, args?: CfMakeConfigArgs, ) { - if (!media_registered) { - registerMedia(args?.env as any); + if (!media_registered && config.registerMedia !== false) { + if (typeof config.registerMedia === "function") { + config.registerMedia(args?.env as any); + } else { + registerMedia(args?.env as any); + } media_registered = true; } diff --git a/app/src/adapter/cloudflare/index.ts b/app/src/adapter/cloudflare/index.ts index 26044ba..b8b3c4e 100644 --- a/app/src/adapter/cloudflare/index.ts +++ b/app/src/adapter/cloudflare/index.ts @@ -13,6 +13,8 @@ export { type BindingMap, } from "./bindings"; export { constants } from "./config"; +export { StorageR2Adapter } from "./storage/StorageR2Adapter"; +export { registries } from "bknd"; // for compatibility with old code export function d1( diff --git a/app/src/adapter/cloudflare/storage/StorageR2Adapter.ts b/app/src/adapter/cloudflare/storage/StorageR2Adapter.ts index 030855a..7716f02 100644 --- a/app/src/adapter/cloudflare/storage/StorageR2Adapter.ts +++ b/app/src/adapter/cloudflare/storage/StorageR2Adapter.ts @@ -1,5 +1,6 @@ import { registries } from "bknd"; import { isDebug } from "bknd/core"; +// @ts-ignore import { StringEnum } from "bknd/utils"; import { guessMimeType as guess, StorageAdapter, type FileBody } from "bknd/media"; import { getBindings } from "../bindings"; @@ -63,46 +64,49 @@ export class StorageR2Adapter extends StorageAdapter { async putObject(key: string, body: FileBody) { try { - const res = await this.bucket.put(key, body); + const res = await this.bucket.put(this.getKey(key), body); return res?.etag; } catch (e) { return undefined; } } - async listObjects( - prefix?: string, - ): Promise<{ key: string; last_modified: Date; size: number }[]> { - const list = await this.bucket.list({ limit: 50 }); + async listObjects(prefix = ""): Promise<{ key: string; last_modified: Date; size: number }[]> { + const list = await this.bucket.list({ limit: 50, prefix: this.getKey(prefix) }); return list.objects.map((item) => ({ - key: item.key, + key: item.key.replace(this.getKey(""), ""), size: item.size, last_modified: item.uploaded, })); } private async headObject(key: string): Promise { - return await this.bucket.head(key); + return await this.bucket.head(this.getKey(key)); } async objectExists(key: string): Promise { return (await this.headObject(key)) !== null; } - async getObject(key: string, headers: Headers): Promise { + async getObject(_key: string, headers: Headers): Promise { let object: R2ObjectBody | null; + const key = this.getKey(_key); + const responseHeaders = new Headers({ "Accept-Ranges": "bytes", "Content-Type": guess(key), }); + const range = headers.has("range"); + //console.log("getObject:headers", headersToObject(headers)); - if (headers.has("range")) { + if (range) { const options = isDebug() ? {} // miniflare doesn't support range requests : { range: headers, onlyIf: headers, }; + object = (await this.bucket.get(key, options)) as R2ObjectBody; if (!object) { @@ -130,13 +134,14 @@ export class StorageR2Adapter extends StorageAdapter { responseHeaders.set("Last-Modified", object.uploaded.toUTCString()); return new Response(object.body, { - status: object.range ? 206 : 200, + status: range ? 206 : 200, headers: responseHeaders, }); } private writeHttpMetadata(headers: Headers, object: R2Object | R2ObjectBody): void { let metadata = object.httpMetadata; + if (!metadata || Object.keys(metadata).length === 0) { // guessing is especially required for dev environment (miniflare) metadata = { @@ -163,13 +168,17 @@ export class StorageR2Adapter extends StorageAdapter { } async deleteObject(key: string): Promise { - await this.bucket.delete(key); + await this.bucket.delete(this.getKey(key)); } getObjectUrl(key: string): string { throw new Error("Method getObjectUrl not implemented."); } + protected getKey(key: string) { + return key; + } + toJSON(secrets?: boolean) { return { type: this.getName(), diff --git a/app/src/ui/routes/media/media.settings.tsx b/app/src/ui/routes/media/media.settings.tsx index 1635539..e8a7cd7 100644 --- a/app/src/ui/routes/media/media.settings.tsx +++ b/app/src/ui/routes/media/media.settings.tsx @@ -124,7 +124,8 @@ const Icons = { }; const AdapterIcon = ({ type }: { type: string }) => { - const Icon = Icons[type]; + // find icon whose name starts with type + const Icon = Object.entries(Icons).find(([key]) => type.startsWith(key))?.[1]; if (!Icon) return null; return ; };