feat(api): add file upload api, get a upload url, then upload file, max 50mb for now

This commit is contained in:
BennyKok
2024-01-16 00:49:50 +08:00
parent 478859d9b5
commit 1ced8095dc
6 changed files with 358 additions and 216 deletions
+114
View File
@@ -0,0 +1,114 @@
import type { App } from "@/routes/app";
import { authError } from "@/routes/authError";
import { getFileDownloadUrl } from "@/server/getFileDownloadUrl";
import { handleResourceUpload } from "@/server/resource";
import { z, createRoute } from "@hono/zod-openapi";
import { customAlphabet } from "nanoid";
export const nanoid = customAlphabet(
"123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"
);
const prefixes = {
img: "img",
vid: "vid",
} as const;
export function newId(prefix: keyof typeof prefixes): string {
return [prefixes[prefix], nanoid(16)].join("_");
}
const uploadUrlRoute = createRoute({
method: "get",
path: "/upload-url",
tags: ["files"],
summary: "Upload any files to the storage",
description:
"Usually when you run something, you want to upload a file, image etc.",
request: {
query: z.object({
type: z.enum(["image/png", "image/jpg", "image/jpeg"]),
file_size: z
.string()
.refine((value) => !isNaN(Number(value)), {
message: "file_size must be a number",
path: ["file_size"],
})
.refine((value) => Number(value) > 0, {
message: "file_size cannot be less than or equal to 0",
path: ["file_size"],
})
.transform((v) => parseFloat(v)),
}),
},
responses: {
200: {
content: {
"application/json": {
schema: z.object({
upload_url: z.string(),
file_id: z.string(),
download_url: z.string(),
}),
},
},
description: "Retrieve the output",
},
500: {
content: {
"application/json": {
schema: z.object({
error: z.string(),
}),
},
},
description: "Error when generating upload url",
},
...authError,
},
});
export const registerUploadRoute = (app: App) => {
app.openapi(uploadUrlRoute, async (c) => {
const data = c.req.valid("query");
const sizeLimit = 50;
try {
if (data.file_size > sizeLimit * 1024 * 1024) {
// Check if the file size is greater than 50MB
throw new Error(`File size exceeds ${sizeLimit}MB limit`);
}
const id = newId("img");
const filePath = `inputs/${id}.${data.type.split("/")[1]}`;
const uploadUrl = await handleResourceUpload({
resourceBucket: process.env.SPACES_BUCKET,
resourceId: filePath,
resourceType: data.type,
isPublic: true,
});
return c.json(
{
upload_url: uploadUrl,
file_id: id,
download_url: await getFileDownloadUrl(filePath),
},
200
);
} catch (error: unknown) {
const errorMessage =
error instanceof Error ? error.message : "Unknown error";
return c.json(
{
error: errorMessage,
},
{
status: 500,
}
);
}
});
};