VYBECODEDDDDDDDDD
This commit is contained in:
+127
@@ -0,0 +1,127 @@
|
||||
import type { Server } from "bun";
|
||||
import { loadConfig } from "./config";
|
||||
import { json, jsonError, notFound, securityHeaders } from "./http";
|
||||
import { log } from "./log";
|
||||
import { RateLimiter } from "./ratelimit";
|
||||
import { handleDelete } from "./routes/delete";
|
||||
import { handleDownload } from "./routes/download";
|
||||
import { handleUpload } from "./routes/upload";
|
||||
import { Storage } from "./storage";
|
||||
|
||||
const cfg = loadConfig();
|
||||
|
||||
const storage = new Storage(cfg.storageDir);
|
||||
await storage.init();
|
||||
const sweep = await storage.sweep();
|
||||
if (sweep.removedFiles > 0 || sweep.removedMetadata > 0) {
|
||||
log(
|
||||
"info",
|
||||
`startup sweep removed ${sweep.removedFiles} orphaned file(s), ${sweep.removedMetadata} orphaned metadata record(s)`,
|
||||
);
|
||||
}
|
||||
|
||||
const limiter = new RateLimiter(cfg.rateLimitMax, cfg.rateLimitWindowMs);
|
||||
const startedAt = Date.now();
|
||||
|
||||
const STATIC_ROUTES: Record<string, { file: string; type: string }> = {
|
||||
"/": { file: "./public/index.html", type: "text/html; charset=utf-8" },
|
||||
"/index.html": { file: "./public/index.html", type: "text/html; charset=utf-8" },
|
||||
"/style.css": { file: "./public/style.css", type: "text/css; charset=utf-8" },
|
||||
"/app.js": { file: "./public/app.js", type: "text/javascript; charset=utf-8" },
|
||||
};
|
||||
|
||||
const staticContent = new Map<string, { body: string; type: string }>();
|
||||
for (const [route, info] of Object.entries(STATIC_ROUTES)) {
|
||||
staticContent.set(route, {
|
||||
body: await Bun.file(new URL(info.file, import.meta.url)).text(),
|
||||
type: info.type,
|
||||
});
|
||||
}
|
||||
|
||||
const server = Bun.serve({
|
||||
hostname: cfg.host,
|
||||
port: cfg.port,
|
||||
maxRequestBodySize: Infinity,
|
||||
fetch(req, srv) {
|
||||
return route(req, srv).catch((err) => {
|
||||
log("error", `unhandled error: ${err instanceof Error ? (err.stack ?? err.message) : String(err)}`);
|
||||
return jsonError(500, "internal error");
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
function clientIp(req: Request, srv: Server<undefined>): string {
|
||||
if (cfg.trustProxy) {
|
||||
const xff = req.headers.get("x-forwarded-for");
|
||||
if (xff) {
|
||||
const first = xff.split(",")[0]?.trim();
|
||||
if (first) return first;
|
||||
}
|
||||
const cf = req.headers.get("cf-connecting-ip");
|
||||
if (cf) return cf;
|
||||
}
|
||||
return srv.requestIP(req)?.address ?? "unknown";
|
||||
}
|
||||
|
||||
function serveStatic(path: string): Response {
|
||||
const entry = staticContent.get(path);
|
||||
if (!entry) return notFound();
|
||||
return new Response(entry.body, {
|
||||
headers: {
|
||||
"content-type": entry.type,
|
||||
"cache-control": "no-cache",
|
||||
"content-security-policy":
|
||||
"default-src 'self'; base-uri 'none'; form-action 'self'; frame-ancestors 'none'",
|
||||
...securityHeaders(),
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
async function route(req: Request, srv: Server<undefined>): Promise<Response> {
|
||||
const url = new URL(req.url);
|
||||
const path = url.pathname;
|
||||
const method = req.method;
|
||||
|
||||
if (method === "GET" && staticContent.has(path)) return serveStatic(path);
|
||||
|
||||
if (path === "/api/health" && method === "GET") {
|
||||
return json({ status: "ok", uptimeSeconds: Math.round((Date.now() - startedAt) / 1000) });
|
||||
}
|
||||
|
||||
if (path === "/api/upload" && method === "POST") {
|
||||
return handleUpload(req, storage, cfg.maxFileSize);
|
||||
}
|
||||
|
||||
if (path.startsWith("/d/") && (method === "GET" || method === "HEAD")) {
|
||||
if (cfg.rateLimitEnabled && !limiter.check(clientIp(req, srv))) {
|
||||
return jsonError(429, "too many requests");
|
||||
}
|
||||
return handleDownload(req, storage, path.slice(3));
|
||||
}
|
||||
|
||||
if (path.startsWith("/api/files/") && method === "DELETE") {
|
||||
if (cfg.rateLimitEnabled && !limiter.check(clientIp(req, srv))) {
|
||||
return jsonError(429, "too many requests");
|
||||
}
|
||||
return handleDelete(req, storage, path.slice("/api/files/".length));
|
||||
}
|
||||
|
||||
return notFound();
|
||||
}
|
||||
|
||||
log(
|
||||
"info",
|
||||
`listening on http://${cfg.host}:${cfg.port} storage=${cfg.storageDir} maxUpload=${cfg.maxFileSize}`,
|
||||
);
|
||||
|
||||
for (const signal of ["SIGINT", "SIGTERM"] as const) {
|
||||
process.once(signal, () => {
|
||||
log("info", `received ${signal}, shutting down`);
|
||||
server.stop();
|
||||
process.exit(0);
|
||||
});
|
||||
}
|
||||
|
||||
process.on("unhandledRejection", (reason) => {
|
||||
log("error", `unhandled rejection: ${reason instanceof Error ? reason.message : String(reason)}`);
|
||||
});
|
||||
Reference in New Issue
Block a user