2023-11-29 01:34:09 +01:00
|
|
|
import { errorResponse } from "@response";
|
|
|
|
|
import { applyConfig } from "@api";
|
|
|
|
|
import type { MatchedRoute } from "bun";
|
|
|
|
|
|
|
|
|
|
export const meta = applyConfig({
|
|
|
|
|
allowedMethods: ["GET"],
|
|
|
|
|
route: "/media/:id",
|
|
|
|
|
ratelimits: {
|
|
|
|
|
max: 100,
|
|
|
|
|
duration: 60,
|
|
|
|
|
},
|
|
|
|
|
auth: {
|
|
|
|
|
required: false,
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
export default async (
|
|
|
|
|
req: Request,
|
|
|
|
|
matchedRoute: MatchedRoute
|
|
|
|
|
): Promise<Response> => {
|
|
|
|
|
// TODO: Add checks for disabled or not email verified accounts
|
|
|
|
|
|
|
|
|
|
const id = matchedRoute.params.id;
|
|
|
|
|
|
2023-11-29 21:29:29 +01:00
|
|
|
// parse `Range` header
|
|
|
|
|
const [start = 0, end = Infinity] = (
|
|
|
|
|
(req.headers.get("Range") || "")
|
|
|
|
|
.split("=") // ["Range: bytes", "0-100"]
|
|
|
|
|
.at(-1) || ""
|
|
|
|
|
) // "0-100"
|
|
|
|
|
.split("-") // ["0", "100"]
|
|
|
|
|
.map(Number); // [0, 100]
|
|
|
|
|
|
2023-11-29 01:34:09 +01:00
|
|
|
// Serve file from filesystem
|
|
|
|
|
const file = Bun.file(`./uploads/${id}`);
|
|
|
|
|
|
2023-11-29 21:29:29 +01:00
|
|
|
const buffer = await file.arrayBuffer();
|
|
|
|
|
|
2023-11-29 01:34:09 +01:00
|
|
|
if (!(await file.exists())) return errorResponse("File not found", 404);
|
|
|
|
|
|
2023-11-29 21:29:29 +01:00
|
|
|
// Can't directly copy file into Response because this crashes Bun for now
|
|
|
|
|
return new Response(buffer, {
|
|
|
|
|
headers: {
|
|
|
|
|
"Content-Type": file.type || "application/octet-stream",
|
|
|
|
|
"Content-Length": `${file.size - start}`,
|
|
|
|
|
"Content-Range": `bytes ${start}-${end}/${file.size}`,
|
|
|
|
|
},
|
|
|
|
|
});
|
2023-11-29 01:34:09 +01:00
|
|
|
};
|