fix media mime guessing when uploading to entity

This commit is contained in:
dswbx
2025-02-20 11:32:24 +01:00
parent eaa7276173
commit 94c40de011
10 changed files with 81 additions and 19 deletions

View File

@@ -111,6 +111,7 @@ describe("EventManager", async () => {
emgr.onEvent(ReturnEvent, async () => "1", "sync"); emgr.onEvent(ReturnEvent, async () => "1", "sync");
emgr.onEvent(ReturnEvent, async () => "0", "sync"); emgr.onEvent(ReturnEvent, async () => "0", "sync");
// @todo: fix this
// @ts-expect-error must be string // @ts-expect-error must be string
emgr.onEvent(ReturnEvent, async () => 0, "sync"); emgr.onEvent(ReturnEvent, async () => 0, "sync");

View File

@@ -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");
});
}); });

View File

@@ -124,8 +124,9 @@ export class AppMedia extends Module<typeof mediaConfigSchema> {
const mutator = em.mutator(media); const mutator = em.mutator(media);
mutator.__unstable_toggleSystemEntityCreation(false); mutator.__unstable_toggleSystemEntityCreation(false);
const payload = this.uploadedEventDataToMediaPayload(e.params); const payload = this.uploadedEventDataToMediaPayload(e.params);
await mutator.insertOne(payload); const { data } = await mutator.insertOne(payload);
mutator.__unstable_toggleSystemEntityCreation(true); mutator.__unstable_toggleSystemEntityCreation(true);
return { data };
}, },
{ mode: "sync", id: "add-data-media" } { mode: "sync", id: "add-data-media" }
); );

View File

@@ -72,11 +72,8 @@ export class MediaController extends Controller {
// upload file // upload file
// @todo: add required type for "upload endpoints" // @todo: add required type for "upload endpoints"
hono.post("/upload/:filename", async (c) => { hono.post("/upload/:filename?", async (c) => {
const { filename } = c.req.param(); const reqname = c.req.param("filename");
if (!filename) {
throw new Error("No file name provided");
}
const body = await getFileFromContext(c); const body = await getFileFromContext(c);
if (!body) { 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); const res = await this.getStorage().uploadFile(body, filename);
return c.json(res, HttpStatus.CREATED); return c.json(res, HttpStatus.CREATED);
}); });
@@ -191,8 +190,8 @@ export class MediaController extends Controller {
); );
} }
const file_name = getRandomizedFilename(file as File); const filename = getRandomizedFilename(file as File);
const info = await this.getStorage().uploadFile(file, file_name, true); const info = await this.getStorage().uploadFile(file, filename, true);
const mutator = this.media.em.mutator(media_entity); const mutator = this.media.em.mutator(media_entity);
mutator.__unstable_toggleSystemEntityCreation(false); mutator.__unstable_toggleSystemEntityCreation(false);

View File

@@ -119,7 +119,10 @@ export class Storage implements EmitsEvents {
} }
}; };
if (!noEmit) { 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; return eventData;

View File

@@ -1,11 +1,23 @@
import { Event } from "core/events"; import { Event, InvalidEventReturn } from "core/events";
import type { FileBody, FileUploadPayload } from "../Storage"; import type { FileBody, FileUploadPayload } from "../Storage";
export type FileUploadedEventData = FileUploadPayload & { export type FileUploadedEventData = FileUploadPayload & {
file: FileBody; file: FileBody;
}; };
export class FileUploadedEvent extends Event<FileUploadedEventData> { export class FileUploadedEvent extends Event<FileUploadedEventData, object> {
static override slug = "file-uploaded"; 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 }> { export class FileDeletedEvent extends Event<{ name: string }> {

View File

@@ -77,6 +77,17 @@ export function guess(f: string): string {
} }
export function isMimeType(mime: any, exclude: 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()) { for (const [k, v] of M.entries()) {
if (v === mime && !exclude.includes(k)) { if (v === mime && !exclude.includes(k)) {
return true; return true;
@@ -86,6 +97,14 @@ export function isMimeType(mime: any, exclude: string[] = []) {
} }
export function extension(mime: 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()) { for (const [k, v] of M.entries()) {
if (v === mime) { if (v === mime) {
return k; return k;

View File

@@ -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; if (!filename.includes(".")) return;
const parts = filename.split("."); const parts = filename.split(".");
@@ -17,6 +18,12 @@ export function getRandomizedFilename(file: File | string, length = 16): string
throw new Error("Invalid file name"); 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? // @todo: use uuid instead?
return [randomString(length), getExtension(filename)].filter(Boolean).join("."); return [randomString(length), ext].filter(Boolean).join(".");
} }

View File

@@ -279,12 +279,14 @@ export function Dropzone({
const response = JSON.parse(xhr.responseText); const response = JSON.parse(xhr.responseText);
console.log("Response:", file, response); console.log("Response:", file, response);
console.log("New state", response.state); const newState = {
replaceFileState(file.path, {
...response.state, ...response.state,
progress: 1, progress: 1,
state: "uploaded" state: "uploaded"
}); };
replaceFileState(file.path, newState);
resolve({ ...file, ...newState });
} catch (e) { } catch (e) {
setFileState(file.path, "uploaded", 1); setFileState(file.path, "uploaded", 1);
console.error("Error parsing response", e); console.error("Error parsing response", e);
@@ -496,9 +498,9 @@ const Preview: React.FC<PreviewProps> = ({ file, handleUpload, handleDelete }) =
/> />
</div> </div>
<div className="flex flex-col px-1.5 py-1"> <div className="flex flex-col px-1.5 py-1">
<p className="truncate">{file.name}</p> <p className="truncate select-text">{file.name}</p>
<div className="flex flex-row justify-between text-sm font-mono opacity-50 text-nowrap gap-2"> <div className="flex flex-row justify-between text-sm font-mono opacity-50 text-nowrap gap-2">
<span className="truncate">{file.type}</span> <span className="truncate select-text">{file.type}</span>
<span>{(file.size / 1024).toFixed(1)} KB</span> <span>{(file.size / 1024).toFixed(1)} KB</span>
</div> </div>
</div> </div>

View File

@@ -33,6 +33,7 @@ let routesShown = false;
export default { export default {
async fetch(request: Request) { async fetch(request: Request) {
if (!app || recreate) { if (!app || recreate) {
console.log("[DB]", credentials);
app = App.create({ app = App.create({
connection: credentials, connection: credentials,
initialConfig initialConfig