mirror of
https://github.com/shishantbiswas/bknd.git
synced 2026-03-16 04:27:21 +00:00
updated media api and added tests, fixed body limit
This commit is contained in:
@@ -14,8 +14,28 @@ export class MediaApi extends ModuleApi<MediaApiOptions> {
|
||||
return this.get(["files"]);
|
||||
}
|
||||
|
||||
getFile(filename: string) {
|
||||
return this.get(["file", filename]);
|
||||
getFileResponse(filename: string) {
|
||||
return this.get(["file", filename], undefined, {
|
||||
headers: {
|
||||
Accept: "*/*"
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async getFile(filename: string): Promise<Blob> {
|
||||
const { res } = await this.getFileResponse(filename);
|
||||
if (!res.ok || !res.body) {
|
||||
throw new Error("Failed to fetch file");
|
||||
}
|
||||
return await res.blob();
|
||||
}
|
||||
|
||||
async getFileStream(filename: string): Promise<ReadableStream<Uint8Array>> {
|
||||
const { res } = await this.getFileResponse(filename);
|
||||
if (!res.ok || !res.body) {
|
||||
throw new Error("Failed to fetch file");
|
||||
}
|
||||
return res.body;
|
||||
}
|
||||
|
||||
getFileUploadUrl(file: FileWithPath): string {
|
||||
@@ -32,10 +52,46 @@ export class MediaApi extends ModuleApi<MediaApiOptions> {
|
||||
});
|
||||
}
|
||||
|
||||
uploadFile(file: File) {
|
||||
const formData = new FormData();
|
||||
formData.append("file", file);
|
||||
return this.post(["upload"], formData);
|
||||
uploadFile(body: File | ReadableStream, filename?: string) {
|
||||
let type: string = "application/octet-stream";
|
||||
let name: string = filename || "";
|
||||
try {
|
||||
type = (body as File).type;
|
||||
if (!filename) {
|
||||
name = (body as File).name;
|
||||
}
|
||||
} catch (e) {}
|
||||
|
||||
if (name && name.length > 0 && name.includes("/")) {
|
||||
name = name.split("/").pop() || "";
|
||||
}
|
||||
|
||||
if (!name || name.length === 0) {
|
||||
throw new Error("Invalid filename");
|
||||
}
|
||||
|
||||
return this.post(["upload", name], body, {
|
||||
headers: {
|
||||
"Content-Type": type
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async upload(item: Request | Response | string | File | ReadableStream, filename?: string) {
|
||||
if (item instanceof Request || typeof item === "string") {
|
||||
const res = await this.fetcher(item);
|
||||
if (!res.ok || !res.body) {
|
||||
throw new Error("Failed to fetch file");
|
||||
}
|
||||
return this.uploadFile(res.body, filename);
|
||||
} else if (item instanceof Response) {
|
||||
if (!item.body) {
|
||||
throw new Error("Invalid response");
|
||||
}
|
||||
return this.uploadFile(item.body, filename);
|
||||
}
|
||||
|
||||
return this.uploadFile(item, filename);
|
||||
}
|
||||
|
||||
deleteFile(filename: string) {
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { tbValidator as tb } from "core";
|
||||
import { Type } from "core/utils";
|
||||
import { bodyLimit } from "hono/body-limit";
|
||||
import { isDebug, tbValidator as tb } from "core";
|
||||
import { HttpStatus, Type, getFileFromContext } from "core/utils";
|
||||
import type { StorageAdapter } from "media";
|
||||
import { StorageEvents, getRandomizedFilename } from "media";
|
||||
import { Controller } from "modules/Controller";
|
||||
@@ -42,7 +41,6 @@ export class MediaController extends Controller {
|
||||
if (!filename) {
|
||||
throw new Error("No file name provided");
|
||||
}
|
||||
//console.log("getting file", filename, headersToObject(c.req.raw.headers));
|
||||
|
||||
await this.getStorage().emgr.emit(new StorageEvents.FileAccessEvent({ name: filename }));
|
||||
return await this.getStorageAdapter().getObject(filename, c.req.raw.headers);
|
||||
@@ -59,24 +57,39 @@ export class MediaController extends Controller {
|
||||
return c.json({ message: "File deleted" });
|
||||
});
|
||||
|
||||
const uploadSizeMiddleware = bodyLimit({
|
||||
maxSize: this.getStorage().getConfig().body_max_size,
|
||||
onError: (c: any) => {
|
||||
return c.text(`Payload exceeds ${this.getStorage().getConfig().body_max_size}`, 413);
|
||||
}
|
||||
});
|
||||
const maxSize = this.getStorage().getConfig().body_max_size ?? Number.POSITIVE_INFINITY;
|
||||
|
||||
if (isDebug()) {
|
||||
hono.post("/inspect", async (c) => {
|
||||
const file = await getFileFromContext(c);
|
||||
return c.json({
|
||||
type: file?.type,
|
||||
name: file?.name,
|
||||
size: file?.size
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// upload file
|
||||
// @todo: add required type for "upload endpoints"
|
||||
hono.post("/upload/:filename", uploadSizeMiddleware, async (c) => {
|
||||
hono.post("/upload/:filename", async (c) => {
|
||||
const { filename } = c.req.param();
|
||||
if (!filename) {
|
||||
throw new Error("No file name provided");
|
||||
}
|
||||
|
||||
const file = await this.getStorage().getFileFromRequest(c);
|
||||
console.log("----file", file);
|
||||
return c.json(await this.getStorage().uploadFile(file, filename));
|
||||
const body = await getFileFromContext(c);
|
||||
if (!body) {
|
||||
return c.json({ error: "No file provided" }, HttpStatus.BAD_REQUEST);
|
||||
}
|
||||
if (body.size > maxSize) {
|
||||
return c.json(
|
||||
{ error: `Max size (${maxSize} bytes) exceeded` },
|
||||
HttpStatus.PAYLOAD_TOO_LARGE
|
||||
);
|
||||
}
|
||||
|
||||
return c.json(await this.getStorage().uploadFile(body, filename), HttpStatus.CREATED);
|
||||
});
|
||||
|
||||
// add upload file to entity
|
||||
@@ -89,23 +102,21 @@ export class MediaController extends Controller {
|
||||
overwrite: Type.Optional(booleanLike)
|
||||
})
|
||||
),
|
||||
uploadSizeMiddleware,
|
||||
async (c) => {
|
||||
const entity_name = c.req.param("entity");
|
||||
const field_name = c.req.param("field");
|
||||
const entity_id = Number.parseInt(c.req.param("id"));
|
||||
console.log("params", { entity_name, field_name, entity_id });
|
||||
|
||||
// check if entity exists
|
||||
const entity = this.media.em.entity(entity_name);
|
||||
if (!entity) {
|
||||
return c.json({ error: `Entity "${entity_name}" not found` }, 404);
|
||||
return c.json({ error: `Entity "${entity_name}" not found` }, HttpStatus.NOT_FOUND);
|
||||
}
|
||||
|
||||
// check if field exists and is of type MediaField
|
||||
const field = entity.field(field_name);
|
||||
if (!field || !(field instanceof MediaField)) {
|
||||
return c.json({ error: `Invalid field "${field_name}"` }, 400);
|
||||
return c.json({ error: `Invalid field "${field_name}"` }, HttpStatus.BAD_REQUEST);
|
||||
}
|
||||
|
||||
const media_entity = this.media.getMediaEntity().name as "media";
|
||||
@@ -127,7 +138,10 @@ export class MediaController extends Controller {
|
||||
if (count >= max_items) {
|
||||
// if overwrite not set, abort early
|
||||
if (!overwrite) {
|
||||
return c.json({ error: `Max items (${max_items}) reached` }, 400);
|
||||
return c.json(
|
||||
{ error: `Max items (${max_items}) reached` },
|
||||
HttpStatus.BAD_REQUEST
|
||||
);
|
||||
}
|
||||
|
||||
// if already more in database than allowed, abort early
|
||||
@@ -135,7 +149,7 @@ export class MediaController extends Controller {
|
||||
if (count > max_items) {
|
||||
return c.json(
|
||||
{ error: `Max items (${max_items}) exceeded already with ${count} items.` },
|
||||
400
|
||||
HttpStatus.UNPROCESSABLE_ENTITY
|
||||
);
|
||||
}
|
||||
|
||||
@@ -161,11 +175,21 @@ export class MediaController extends Controller {
|
||||
if (!exists) {
|
||||
return c.json(
|
||||
{ error: `Entity "${entity_name}" with ID "${entity_id}" doesn't exist found` },
|
||||
404
|
||||
HttpStatus.NOT_FOUND
|
||||
);
|
||||
}
|
||||
|
||||
const file = await getFileFromContext(c);
|
||||
if (!file) {
|
||||
return c.json({ error: "No file provided" }, HttpStatus.BAD_REQUEST);
|
||||
}
|
||||
if (file.size > maxSize) {
|
||||
return c.json(
|
||||
{ error: `Max size (${maxSize} bytes) exceeded` },
|
||||
HttpStatus.PAYLOAD_TOO_LARGE
|
||||
);
|
||||
}
|
||||
|
||||
const file = await this.getStorage().getFileFromRequest(c);
|
||||
const file_name = getRandomizedFilename(file as File);
|
||||
const info = await this.getStorage().uploadFile(file, file_name, true);
|
||||
|
||||
@@ -185,7 +209,7 @@ export class MediaController extends Controller {
|
||||
}
|
||||
}
|
||||
|
||||
return c.json({ ok: true, result: result.data, ...info });
|
||||
return c.json({ ok: true, result: result.data, ...info }, HttpStatus.CREATED);
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user