From 94c40de011bf566588565390914bd9fd68f30f4d Mon Sep 17 00:00:00 2001 From: dswbx Date: Thu, 20 Feb 2025 11:32:24 +0100 Subject: [PATCH] fix media mime guessing when uploading to entity --- app/__test__/core/EventManager.spec.ts | 1 + app/__test__/media/mime-types.spec.ts | 17 +++++++++++++++++ app/src/media/AppMedia.ts | 3 ++- app/src/media/api/MediaController.ts | 13 ++++++------- app/src/media/storage/Storage.ts | 5 ++++- app/src/media/storage/events/index.ts | 16 ++++++++++++++-- app/src/media/storage/mime-types-tiny.ts | 19 +++++++++++++++++++ app/src/media/utils/index.ts | 13 ++++++++++--- app/src/ui/elements/media/Dropzone.tsx | 12 +++++++----- app/vite.dev.ts | 1 + 10 files changed, 81 insertions(+), 19 deletions(-) diff --git a/app/__test__/core/EventManager.spec.ts b/app/__test__/core/EventManager.spec.ts index e332439..5d81e77 100644 --- a/app/__test__/core/EventManager.spec.ts +++ b/app/__test__/core/EventManager.spec.ts @@ -111,6 +111,7 @@ describe("EventManager", async () => { emgr.onEvent(ReturnEvent, async () => "1", "sync"); emgr.onEvent(ReturnEvent, async () => "0", "sync"); + // @todo: fix this // @ts-expect-error must be string emgr.onEvent(ReturnEvent, async () => 0, "sync"); diff --git a/app/__test__/media/mime-types.spec.ts b/app/__test__/media/mime-types.spec.ts index f00016a..55469ff 100644 --- a/app/__test__/media/mime-types.spec.ts +++ b/app/__test__/media/mime-types.spec.ts @@ -34,4 +34,21 @@ describe("media/mime-types", () => { } } }); + + test("isMimeType", () => { + expect(tiny.isMimeType("image/jpeg")).toBe(true); + expect(tiny.isMimeType("image/jpeg", ["image/png"])).toBe(true); + expect(tiny.isMimeType("image/png", ["image/png"])).toBe(false); + expect(tiny.isMimeType("image/png")).toBe(true); + expect(tiny.isMimeType("whatever")).toBe(false); + expect(tiny.isMimeType("text/tab-separated-values")).toBe(true); + }); + + test("extension", () => { + expect(tiny.extension("image/png")).toBe("png"); + expect(tiny.extension("image/jpeg")).toBe("jpeg"); + expect(tiny.extension("application/zip")).toBe("zip"); + expect(tiny.extension("text/tab-separated-values")).toBe("tsv"); + expect(tiny.extension("application/zip")).toBe("zip"); + }); }); diff --git a/app/src/media/AppMedia.ts b/app/src/media/AppMedia.ts index b2040ce..f1736d4 100644 --- a/app/src/media/AppMedia.ts +++ b/app/src/media/AppMedia.ts @@ -124,8 +124,9 @@ export class AppMedia extends Module { const mutator = em.mutator(media); mutator.__unstable_toggleSystemEntityCreation(false); const payload = this.uploadedEventDataToMediaPayload(e.params); - await mutator.insertOne(payload); + const { data } = await mutator.insertOne(payload); mutator.__unstable_toggleSystemEntityCreation(true); + return { data }; }, { mode: "sync", id: "add-data-media" } ); diff --git a/app/src/media/api/MediaController.ts b/app/src/media/api/MediaController.ts index 4194b2b..9758fcf 100644 --- a/app/src/media/api/MediaController.ts +++ b/app/src/media/api/MediaController.ts @@ -72,11 +72,8 @@ export class MediaController extends Controller { // upload file // @todo: add required type for "upload endpoints" - hono.post("/upload/:filename", async (c) => { - const { filename } = c.req.param(); - if (!filename) { - throw new Error("No file name provided"); - } + hono.post("/upload/:filename?", async (c) => { + const reqname = c.req.param("filename"); const body = await getFileFromContext(c); if (!body) { @@ -89,7 +86,9 @@ export class MediaController extends Controller { ); } + const filename = reqname ?? getRandomizedFilename(body as File); const res = await this.getStorage().uploadFile(body, filename); + return c.json(res, HttpStatus.CREATED); }); @@ -191,8 +190,8 @@ export class MediaController extends Controller { ); } - const file_name = getRandomizedFilename(file as File); - const info = await this.getStorage().uploadFile(file, file_name, true); + const filename = getRandomizedFilename(file as File); + const info = await this.getStorage().uploadFile(file, filename, true); const mutator = this.media.em.mutator(media_entity); mutator.__unstable_toggleSystemEntityCreation(false); diff --git a/app/src/media/storage/Storage.ts b/app/src/media/storage/Storage.ts index 24be8f7..6226cf9 100644 --- a/app/src/media/storage/Storage.ts +++ b/app/src/media/storage/Storage.ts @@ -119,7 +119,10 @@ export class Storage implements EmitsEvents { } }; if (!noEmit) { - await this.emgr.emit(new StorageEvents.FileUploadedEvent(eventData)); + const result = await this.emgr.emit(new StorageEvents.FileUploadedEvent(eventData)); + if (result.returned) { + return result.params; + } } return eventData; diff --git a/app/src/media/storage/events/index.ts b/app/src/media/storage/events/index.ts index c285600..0b63cd4 100644 --- a/app/src/media/storage/events/index.ts +++ b/app/src/media/storage/events/index.ts @@ -1,11 +1,23 @@ -import { Event } from "core/events"; +import { Event, InvalidEventReturn } from "core/events"; import type { FileBody, FileUploadPayload } from "../Storage"; export type FileUploadedEventData = FileUploadPayload & { file: FileBody; }; -export class FileUploadedEvent extends Event { +export class FileUploadedEvent extends Event { static override slug = "file-uploaded"; + + override validate(data: object) { + if (typeof data !== "object") { + throw new InvalidEventReturn("object", typeof data); + } + + return this.clone({ + // prepending result, so original is always kept + ...data, + ...this.params + }); + } } export class FileDeletedEvent extends Event<{ name: string }> { diff --git a/app/src/media/storage/mime-types-tiny.ts b/app/src/media/storage/mime-types-tiny.ts index f54b51e..1f90f77 100644 --- a/app/src/media/storage/mime-types-tiny.ts +++ b/app/src/media/storage/mime-types-tiny.ts @@ -77,6 +77,17 @@ export function guess(f: string): string { } export function isMimeType(mime: any, exclude: string[] = []) { + if (exclude.includes(mime)) return false; + + // try quick first + if ( + Object.entries(Q) + .flatMap(([t, e]) => e.map((x) => `${t}/${x}`)) + .includes(mime) + ) { + return true; + } + for (const [k, v] of M.entries()) { if (v === mime && !exclude.includes(k)) { return true; @@ -86,6 +97,14 @@ export function isMimeType(mime: any, exclude: string[] = []) { } export function extension(mime: string) { + for (const [t, e] of Object.entries(Q)) { + for (const _e of e) { + if (mime === `${t}/${_e}`) { + return _e; + } + } + } + for (const [k, v] of M.entries()) { if (v === mime) { return k; diff --git a/app/src/media/utils/index.ts b/app/src/media/utils/index.ts index c042acc..e71e2b7 100644 --- a/app/src/media/utils/index.ts +++ b/app/src/media/utils/index.ts @@ -1,6 +1,7 @@ -import { randomString } from "core/utils"; +import { isFile, randomString } from "core/utils"; +import { extension } from "media/storage/mime-types-tiny"; -export function getExtension(filename: string): string | undefined { +export function getExtensionFromName(filename: string): string | undefined { if (!filename.includes(".")) return; const parts = filename.split("."); @@ -17,6 +18,12 @@ export function getRandomizedFilename(file: File | string, length = 16): string throw new Error("Invalid file name"); } + let ext = getExtensionFromName(filename); + if (isFile(file) && file.type) { + const _ext = extension(file.type); + if (_ext.length > 0) ext = _ext; + } + // @todo: use uuid instead? - return [randomString(length), getExtension(filename)].filter(Boolean).join("."); + return [randomString(length), ext].filter(Boolean).join("."); } diff --git a/app/src/ui/elements/media/Dropzone.tsx b/app/src/ui/elements/media/Dropzone.tsx index 537cfe0..0099160 100644 --- a/app/src/ui/elements/media/Dropzone.tsx +++ b/app/src/ui/elements/media/Dropzone.tsx @@ -279,12 +279,14 @@ export function Dropzone({ const response = JSON.parse(xhr.responseText); console.log("Response:", file, response); - console.log("New state", response.state); - replaceFileState(file.path, { + const newState = { ...response.state, progress: 1, state: "uploaded" - }); + }; + + replaceFileState(file.path, newState); + resolve({ ...file, ...newState }); } catch (e) { setFileState(file.path, "uploaded", 1); console.error("Error parsing response", e); @@ -496,9 +498,9 @@ const Preview: React.FC = ({ file, handleUpload, handleDelete }) = />
-

{file.name}

+

{file.name}

- {file.type} + {file.type} {(file.size / 1024).toFixed(1)} KB
diff --git a/app/vite.dev.ts b/app/vite.dev.ts index cf95359..e63c66d 100644 --- a/app/vite.dev.ts +++ b/app/vite.dev.ts @@ -33,6 +33,7 @@ let routesShown = false; export default { async fetch(request: Request) { if (!app || recreate) { + console.log("[DB]", credentials); app = App.create({ connection: credentials, initialConfig