import express from "express"; import multer from "multer"; import axios from "axios"; import sharp from "sharp"; import Link from "../models/Link.js"; import { authMiddleware } from "../middleware/auth.js"; import { parseICO } from "icojs"; const router = express.Router(); // Multer in-memory (niente filesystem) const upload = multer({ storage: multer.memoryStorage() }); router.use(authMiddleware); // Scarica immagine remota come Buffer async function downloadImageAsBuffer(url) { const response = await axios.get(url, { responseType: "arraybuffer", maxRedirects: 5, headers: { "User-Agent": "Mozilla/5.0", "Accept": "image/*" } }); return { buffer: Buffer.from(response.data), mime: response.headers["content-type"] || "" }; } // Converte immagine → WebP 128x128 contain async function processIcon(buffer, mime) { let inputBuffer = buffer; // Se è ICO → converti in PNG if (mime === "image/x-icon" || mime === "image/vnd.microsoft.icon") { const images = await parseICO(buffer); if (!images.length) { throw new Error("ICO non valido"); } // Prendiamo l’immagine più grande dentro l’ICO const best = images.reduce((a, b) => (a.width > b.width ? a : b)); inputBuffer = Buffer.from(best.buffer); } // Ora Sharp può lavorare return await sharp(inputBuffer) .resize(128, 128, { fit: "contain", background: { r: 0, g: 0, b: 0, alpha: 0 } }) .webp({ quality: 90 }) .toBuffer(); } // =============================== // GET LINKS // =============================== router.get("/", async (req, res) => { const links = await Link.find({ owner: req.userId }); res.json(links); }); // =============================== // CREATE LINK // =============================== router.post("/", upload.single("icon"), async (req, res) => { const { url, name, iconURL } = req.body; let originalBuffer = null; // Caso 1: upload file if (req.file) { originalBuffer = req.file.buffer; } // Caso 2: URL remoto else if (iconURL) { originalBuffer = await downloadImageAsBuffer(iconURL); } let processedIcon = null; if (originalBuffer) { processedIcon = await processIcon(originalBuffer.buffer, originalBuffer.mime); } const link = await Link.create({ url, name, owner: req.userId, icon: processedIcon ? { data: processedIcon, mime: "image/webp", size: processedIcon.length } : null }); res.json(link); }); // =============================== // UPDATE LINK // =============================== router.put("/:id", upload.single("icon"), async (req, res) => { const { id } = req.params; const { name, url, iconURL } = req.body; const link = await Link.findOne({ _id: id, owner: req.userId }); if (!link) return res.status(404).json({ error: "Link non trovato" }); let originalBuffer = null; if (req.file) { originalBuffer = req.file.buffer; } else if (iconURL) { originalBuffer = await downloadImageAsBuffer(iconURL); } const update = { name, url }; if (originalBuffer) { const processedIcon = await processIcon(originalBuffer.buffer, originalBuffer.mime); update.icon = { data: processedIcon, mime: "image/webp", size: processedIcon.length }; } const updated = await Link.findOneAndUpdate( { _id: id, owner: req.userId }, update, { new: true } ); res.json(updated); }); // =============================== // DELETE LINK // =============================== router.delete("/:id", async (req, res) => { const link = await Link.findOneAndDelete({ _id: req.params.id, owner: req.userId }); if (!link) return res.status(404).json({ error: "Link non trovato" }); res.json({ success: true }); }); export default router;