feat(api): add file upload api, get a upload url, then upload file, max 50mb for now
This commit is contained in:
@@ -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,
|
||||
}
|
||||
);
|
||||
}
|
||||
});
|
||||
};
|
||||
Reference in New Issue
Block a user