| import { Router, type Request, type Response, type NextFunction } from "express"; |
| import { writeFileSync, mkdirSync } from "fs"; |
| import { join } from "path"; |
| import { randomUUID } from "crypto"; |
| import multer from "multer"; |
| import { extractToken } from "../auth.js"; |
| import { isHfStorageEnabled, setUserToken, uploadImageToHf } from "../hf-storage.js"; |
| import { getDataDir } from "../utils.js"; |
|
|
| |
| |
| |
| const UPLOAD_WINDOW_MS = 60_000; |
| const UPLOAD_MAX_PER_WINDOW = 30; |
| const uploadBuckets = new Map<string, number[]>(); |
|
|
| function rateLimitUpload(req: Request, res: Response, next: NextFunction): void { |
| const key = |
| (req.headers["x-forwarded-for"] as string | undefined)?.split(",")[0]?.trim() || |
| req.ip || |
| req.socket.remoteAddress || |
| "unknown"; |
| const now = Date.now(); |
| const cutoff = now - UPLOAD_WINDOW_MS; |
| const bucket = (uploadBuckets.get(key) || []).filter((ts) => ts > cutoff); |
| if (bucket.length >= UPLOAD_MAX_PER_WINDOW) { |
| const retryAfter = Math.ceil((bucket[0] + UPLOAD_WINDOW_MS - now) / 1000); |
| res.setHeader("Retry-After", String(Math.max(1, retryAfter))); |
| res.status(429).json({ error: "Too many uploads, please slow down." }); |
| return; |
| } |
| bucket.push(now); |
| uploadBuckets.set(key, bucket); |
| next(); |
| } |
|
|
| export function createUploadRouter(): Router { |
| const router = Router(); |
| const uploadMiddleware = multer({ storage: multer.memoryStorage(), limits: { fileSize: 10 * 1024 * 1024 } }); |
|
|
| router.post("/api/upload", rateLimitUpload, uploadMiddleware.single("file"), async (req, res) => { |
| try { |
| const file = (req as any).file as Express.Multer.File | undefined; |
| if (!file) { |
| res.status(400).json({ error: "No file provided" }); |
| return; |
| } |
|
|
| const ext = file.originalname.split(".").pop() || "png"; |
| const filename = `${randomUUID()}.${ext}`; |
|
|
| const userToken = extractToken(req.headers.cookie) ?? undefined; |
| if (userToken) setUserToken(userToken); |
|
|
| if (isHfStorageEnabled()) { |
| const url = await uploadImageToHf(file.buffer, filename, userToken); |
| res.json({ url }); |
| return; |
| } |
|
|
| const uploadsDir = join(getDataDir(), "uploads"); |
| mkdirSync(uploadsDir, { recursive: true }); |
| writeFileSync(join(uploadsDir, filename), file.buffer); |
| res.json({ url: `/uploads/${filename}` }); |
| } catch (err: any) { |
| console.error("[upload] error:", err); |
| res.status(500).json({ error: err.message || "Upload failed" }); |
| } |
| }); |
|
|
| return router; |
| } |
|
|