Skip to main content
This release is 12 versions behind 0.0.265 — the latest version of @bureaudouble/bureau. Jump to latest
It is unknown whether this package works with Cloudflare Workers, Node.js, Deno, Bun, Browsers
It is unknown whether this package works with Cloudflare Workers
It is unknown whether this package works with Node.js
It is unknown whether this package works with Deno
It is unknown whether this package works with Bun
It is unknown whether this package works with Browsers
JSR Score
0%
Published
a month ago (0.0.253)
"use server"; import type { S3ObjectMetadata } from "jsr:@bradenmacdonald/s3-lite-client@^0.7.6"; import { s3Client } from "../utils/storage.ts"; import { basename } from "jsr:/@std/path@^1.0.8/basename"; import { join } from "jsr:/@std/path@^1.0.8/join"; import { lookup } from "npm:mrmime@^2.0.0"; interface S3Object { type: "Object"; key: string; lastModified: Date; etag: string; size: number; "content-type"?: string; } export type MediaItem = S3Object & { name: string; alt?: string }; const sessionFolders: Set<string> = new Set(); export interface ListMedia { prefix?: string; breadcrumb?: { prefix: string; name: string }[]; folders: { prefix: string; name: string | undefined }[]; objects: MediaItem[]; } export async function listMedia( _state: ListMedia, options?: { prefix?: string; filter?: string } | undefined, ): Promise<ListMedia> { const objects: MediaItem[] = []; const basePrefix = join("medias", "/"); const folders = new Set<string>( [...sessionFolders] .filter((path) => { if (options?.prefix) { // Include folders that are direct children of the current prefix return ( path.startsWith(options.prefix) && path.slice(options.prefix.length).split("/").filter(Boolean) .length === 1 ); } // Include only top-level folders when no prefix is specified return path.split("/").filter(Boolean).length === 1; }) .map((path) => (path.endsWith("/") ? path : `${path}/`)), ); for await ( const result of s3Client.listObjectsGrouped({ prefix: join(basePrefix, options?.prefix ?? "", "/"), delimiter: "/", }) ) { if (result.type === "CommonPrefix") { if (!result.prefix.includes(options?.filter ?? "")) continue; folders.add(result.prefix.slice(basePrefix.length)); continue; } if (!result.key.includes(options?.filter ?? "")) continue; if (basename(result.key).toLowerCase() === ".ds_store") continue; objects.push({ ...result, name: basename(result.key), "content-type": lookup(result.key) ?? "application/octet-stream", }); } return { prefix: options?.prefix, folders: Array.from(folders) .filter((v) => v) .map((prefix) => ({ prefix, name: basename(prefix), })), breadcrumb: [ { prefix: "", name: "medias" }, ...(options?.prefix ?? "") .split("/") .filter((v) => v.length > 0) .map((name, index, array) => ({ name, prefix: join(...array.slice(0, index + 1), "/"), })), ], objects, }; } export const createFolder = async (prefix: string): Promise<string> => { await sessionFolders.add(prefix); return prefix; }; export async function getUploadUrl(key: string): Promise<string> { return await s3Client.getPresignedUrl("PUT", join("medias", key), { expirySeconds: 20, }); } export async function deleteMedia(keys: string[]): Promise<boolean> { for (const key of keys.filter((key) => key.startsWith("medias/"))) { await s3Client.deleteObject(key); } return true; } export type MediaActionResult<T> = | ({ success: true } & T) | { success: false; error: string; }; const formatAsyncResult = <T>( fn: () => Promise<T>, ): Promise<MediaActionResult<T>> => fn() .then((data) => ({ success: true as const, ...data })) .catch((error) => ({ success: false, error: error.message })); export const getMediaMetadata = ( sourceKey: string, ): Promise<MediaActionResult<{ data: S3ObjectMetadata }>> => formatAsyncResult(async () => { const objectStatus = await s3Client.statObject(sourceKey); return { data: objectStatus.metadata }; }); export const updateMediaMetadata = ( sourceKey: string, metadata: { name?: string; alt?: string }, ): Promise<MediaActionResult<{ etag: string }>> => formatAsyncResult(async () => { const objectStatus = await s3Client.statObject(sourceKey); const newMetadata = { ...objectStatus.metadata }; if (metadata.alt !== undefined) { newMetadata["x-amz-meta-alt"] = metadata.alt; } const result = await s3Client.copyObject({ sourceKey }, sourceKey, { metadata: newMetadata, }); return result; });