From 773ca663f9b5ddfb69cd889f06008e1ec8ff87e0 Mon Sep 17 00:00:00 2001 From: michaelschal <35957947-michaelschal@users.noreply.replit.com> Date: Sat, 11 Oct 2025 09:41:32 +0000 Subject: [PATCH] Add ability to apply image overlays on the server Introduces a new POST endpoint `/api/media/apply-overlays` that uses Sharp to apply customizable ribbon and price badge overlays to images server-side. Replit-Commit-Author: Agent Replit-Commit-Session-Id: ae4037a0-2a6f-4530-9bac-79b543286bda Replit-Commit-Checkpoint-Type: full_checkpoint Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/397bca8c-984f-43ff-841a-10897aeb8140/ae4037a0-2a6f-4530-9bac-79b543286bda/Hpj2VzR --- attached_assets/image_1760175257880.png | Bin 0 -> 15021 bytes server/routes.ts | 315 ++++++++++++------------ 2 files changed, 158 insertions(+), 157 deletions(-) create mode 100644 attached_assets/image_1760175257880.png diff --git a/attached_assets/image_1760175257880.png b/attached_assets/image_1760175257880.png new file mode 100644 index 0000000000000000000000000000000000000000..7a215f02ae6bc2337fd6460b5862000b05f0a041 GIT binary patch literal 15021 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 GL;fGrqzA { } }); + // Apply overlays to image using Sharp (server-side processing) + // IMPORTANT: This must be BEFORE /api/media/:id to avoid being intercepted by :id parameter + app.post("/api/media/apply-overlays", requireAuth, async (req, res) => { + try { + const { imageUrl, ribbon, priceBadge } = req.body; + const user = req.user as User; + const userId = user.id; + + if (!imageUrl) { + return res.status(400).json({ error: "Image URL required" }); + } + + console.log("🎨 Applying overlays server-side with Sharp..."); + console.log("📥 Image URL:", imageUrl); + console.log("🎀 Ribbon:", ribbon); + console.log("💰 Price Badge:", priceBadge); + + // Download image from URL + const imageResponse = await fetch(imageUrl); + const imageBuffer = Buffer.from(await imageResponse.arrayBuffer()); + console.log("✅ Image downloaded:", imageBuffer.length, "bytes"); + + // Load with Sharp to process + const sharp = (await import("sharp")).default; + let image = sharp(imageBuffer); + + // Get image metadata for dimensions + const metadata = await image.metadata(); + const width = metadata.width!; + const height = metadata.height!; + + console.log(`📐 Image dimensions: ${width}x${height}`); + + // Create SVG overlays + const overlays: any[] = []; + + // Add ribbon overlay + if (ribbon) { + const ribbonSize = 150; + const positions: Record = { + top_left: { x: 0, y: 0, rotation: -45 }, + top_right: { x: width - ribbonSize, y: 0, rotation: 45 }, + bottom_left: { x: 0, y: height - ribbonSize, rotation: -135 }, + bottom_right: { x: width - ribbonSize, y: height - ribbonSize, rotation: 135 } + }; + + const pos = positions[ribbon.position] || positions.top_left; + const color = ribbon.color === 'red' ? '#dc2626' : '#eab308'; + const fontSize = ribbon.text.length <= 5 ? 22 : ribbon.text.length <= 8 ? 18 : ribbon.text.length <= 11 ? 15 : 12; + + const ribbonSvg = ` + + + + ${ribbon.text} + + + `; + + overlays.push({ + input: Buffer.from(ribbonSvg), + top: pos.y, + left: pos.x + }); + } + + // Add price badge overlay + if (priceBadge) { + const padding = 16; + const badgeText = `€${priceBadge.price}`; + const fontSize = priceBadge.size; + const textWidth = badgeText.length * fontSize * 0.6; // Approximate + const badgeWidth = textWidth + padding * 2; + const badgeHeight = fontSize + padding; + const color = priceBadge.color === 'red' ? '#dc2626' : '#eab308'; + + let x = padding; + let y = padding; + + if (priceBadge.position === 'north_east') { + x = width - badgeWidth - padding; + } else if (priceBadge.position === 'south_west') { + y = height - badgeHeight - padding; + } else if (priceBadge.position === 'south_east') { + x = width - badgeWidth - padding; + y = height - badgeHeight - padding; + } + + const badgeSvg = ` + + + + ${badgeText} + + + `; + + overlays.push({ + input: Buffer.from(badgeSvg), + top: y, + left: x + }); + } + + // Apply all overlays + if (overlays.length > 0) { + image = image.composite(overlays); + } + + // Convert to buffer + const outputBuffer = await image.jpeg({ quality: 95 }).toBuffer(); + + console.log("✅ Overlays applied, uploading to Cloudinary..."); + + // Check if Cloudinary is configured + const cloudinaryConfig = await storage.getCloudinaryConfig(userId); + if (!cloudinaryConfig) { + return res.status(400).json({ + error: "Cloudinary not configured. Please configure Cloudinary in Settings first." + }); + } + + // Upload to Cloudinary + const uploadResult = await cloudinaryService.uploadMedia( + outputBuffer, + `edited_${Date.now()}.jpg`, + userId, + 'image/jpeg' + ); + + // Save to database + const mediaItem = await storage.createMedia({ + userId, + type: "image", + cloudinaryPublicId: uploadResult.publicId, + originalUrl: uploadResult.originalUrl, + facebookFeedUrl: uploadResult.facebookFeedUrl, + instagramFeedUrl: uploadResult.instagramFeedUrl, + instagramStoryUrl: uploadResult.instagramStoryUrl, + fileName: `edited_${Date.now()}.jpg`, + fileSize: outputBuffer.length, + }); + + console.log("✅ Image saved with ID:", mediaItem.id); + res.json(mediaItem); + } catch (error) { + console.error("💥 Error applying overlays:", error); + const errorMessage = error instanceof Error ? error.message : "Failed to apply overlays"; + res.status(500).json({ error: errorMessage }); + } + }); + // Delete media app.delete("/api/media/:id", requireAuth, async (req, res) => { try { @@ -1171,163 +1329,6 @@ export async function registerRoutes(app: Express): Promise { } }); - // Apply overlays to image using Sharp (server-side processing) - app.post("/api/media/apply-overlays", requireAuth, async (req, res) => { - try { - const { imageUrl, ribbon, priceBadge } = req.body; - const user = req.user as User; - const userId = user.id; - - if (!imageUrl) { - return res.status(400).json({ error: "Image URL required" }); - } - - console.log("🎨 Applying overlays server-side with Sharp..."); - console.log("📥 Image URL:", imageUrl); - console.log("🎀 Ribbon:", ribbon); - console.log("💰 Price Badge:", priceBadge); - - // Download image from URL - const imageResponse = await fetch(imageUrl); - const imageBuffer = Buffer.from(await imageResponse.arrayBuffer()); - console.log("✅ Image downloaded:", imageBuffer.length, "bytes"); - - // Load with Sharp to process - const sharp = (await import("sharp")).default; - let image = sharp(imageBuffer); - - // Get image metadata for dimensions - const metadata = await image.metadata(); - const width = metadata.width!; - const height = metadata.height!; - - console.log(`📐 Image dimensions: ${width}x${height}`); - - // Create SVG overlays - const overlays: any[] = []; - - // Add ribbon overlay - if (ribbon) { - const ribbonSize = 150; - const positions: Record = { - top_left: { x: 0, y: 0, rotation: -45 }, - top_right: { x: width - ribbonSize, y: 0, rotation: 45 }, - bottom_left: { x: 0, y: height - ribbonSize, rotation: -135 }, - bottom_right: { x: width - ribbonSize, y: height - ribbonSize, rotation: 135 } - }; - - const pos = positions[ribbon.position] || positions.top_left; - const color = ribbon.color === 'red' ? '#dc2626' : '#eab308'; - const fontSize = ribbon.text.length <= 5 ? 22 : ribbon.text.length <= 8 ? 18 : ribbon.text.length <= 11 ? 15 : 12; - - const ribbonSvg = ` - - - - ${ribbon.text} - - - `; - - overlays.push({ - input: Buffer.from(ribbonSvg), - top: pos.y, - left: pos.x - }); - } - - // Add price badge overlay - if (priceBadge) { - const padding = 16; - const badgeText = `€${priceBadge.price}`; - const fontSize = priceBadge.size; - const textWidth = badgeText.length * fontSize * 0.6; // Approximate - const badgeWidth = textWidth + padding * 2; - const badgeHeight = fontSize + padding; - const color = priceBadge.color === 'red' ? '#dc2626' : '#eab308'; - - let x = padding; - let y = padding; - - if (priceBadge.position === 'north_east') { - x = width - badgeWidth - padding; - } else if (priceBadge.position === 'south_west') { - y = height - badgeHeight - padding; - } else if (priceBadge.position === 'south_east') { - x = width - badgeWidth - padding; - y = height - badgeHeight - padding; - } - - const badgeSvg = ` - - - - ${badgeText} - - - `; - - overlays.push({ - input: Buffer.from(badgeSvg), - top: y, - left: x - }); - } - - // Apply all overlays - if (overlays.length > 0) { - image = image.composite(overlays); - } - - // Convert to buffer - const outputBuffer = await image.jpeg({ quality: 95 }).toBuffer(); - - console.log("✅ Overlays applied, uploading to Cloudinary..."); - - // Check if Cloudinary is configured - const cloudinaryConfig = await storage.getCloudinaryConfig(userId); - if (!cloudinaryConfig) { - return res.status(400).json({ - error: "Cloudinary not configured. Please configure Cloudinary in Settings first." - }); - } - - // Upload to Cloudinary - const uploadResult = await cloudinaryService.uploadMedia( - outputBuffer, - `edited_${Date.now()}.jpg`, - userId, - 'image/jpeg' - ); - - // Save to database - const mediaItem = await storage.createMedia({ - userId, - type: "image", - cloudinaryPublicId: uploadResult.publicId, - originalUrl: uploadResult.originalUrl, - facebookFeedUrl: uploadResult.facebookFeedUrl, - instagramFeedUrl: uploadResult.instagramFeedUrl, - instagramStoryUrl: uploadResult.instagramStoryUrl, - fileName: `edited_${Date.now()}.jpg`, - fileSize: outputBuffer.length, - }); - - console.log("✅ Image saved with ID:", mediaItem.id); - res.json(mediaItem); - } catch (error) { - console.error("💥 Error applying overlays:", error); - const errorMessage = error instanceof Error ? error.message : "Failed to apply overlays"; - res.status(500).json({ error: errorMessage }); - } - }); - const httpServer = createServer(app); return httpServer;