diff --git a/attached_assets/image_1760175257880.png b/attached_assets/image_1760175257880.png new file mode 100644 index 0000000..7a215f0 Binary files /dev/null and b/attached_assets/image_1760175257880.png differ diff --git a/server/routes.ts b/server/routes.ts index 8d7f5b7..a5a52a6 100644 --- a/server/routes.ts +++ b/server/routes.ts @@ -517,6 +517,164 @@ export async function registerRoutes(app: Express): Promise { } }); + // 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;