Add ability to upload and apply custom logos to social media posts

Implement functionality for uploading, applying, and deleting custom logos as overlays on images, along with backend processing and UI updates.

Replit-Commit-Author: Agent
Replit-Commit-Session-Id: ae4037a0-2a6f-4530-9bac-79b543286bda
Replit-Commit-Checkpoint-Type: intermediate_checkpoint
Replit-Commit-Event-Id: 91999614-5187-4902-b7a1-bf9e372de9c8
Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/397bca8c-984f-43ff-841a-10897aeb8140/ae4037a0-2a6f-4530-9bac-79b543286bda/JgqpaNl
This commit is contained in:
michaelschal committed 2025-11-18 08:47:42 +00:00
1 parent 6ee2b61dd9
commit 225e4f2292
6 files changed
+498 -257

No files matched your search

+159 -1
View File
@@ -521,7 +521,7 @@ export async function registerRoutes(app: Express): Promise<Server> {
// 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 { imageUrl, ribbon, priceBadge, logo } = req.body;
const user = req.user as User;
const userId = user.id;
@@ -533,6 +533,7 @@ export async function registerRoutes(app: Express): Promise<Server> {
console.log("📥 Image URL:", imageUrl);
console.log("🎀 Ribbon:", ribbon);
console.log("💰 Price Badge:", priceBadge);
console.log("🏢 Logo:", logo);
// Download image from URL
const imageResponse = await fetch(imageUrl);
@@ -657,6 +658,86 @@ export async function registerRoutes(app: Express): Promise<Server> {
});
}
// Add logo overlay
if (logo && logo.enabled) {
console.log("🏢 Adding logo overlay...");
// Get Cloudinary config to get logo public ID
const cloudinaryConfig = await storage.getAnyCloudinaryConfig();
if (cloudinaryConfig && cloudinaryConfig.logoPublicId) {
try {
// Build logo URL from Cloudinary
const logoUrl = `https://res.cloudinary.com/${cloudinaryConfig.cloudName}/image/upload/${cloudinaryConfig.logoPublicId}`;
console.log("📥 Downloading logo from:", logoUrl);
// Download logo
const logoResponse = await fetch(logoUrl);
const logoBuffer = Buffer.from(await logoResponse.arrayBuffer());
// Determine logo size based on selection
const logoSizes = {
small: Math.round(width * 0.15), // 15% of image width
medium: Math.round(width * 0.25), // 25% of image width
large: Math.round(width * 0.35) // 35% of image width
};
const logoWidth = logoSizes[logo.size as keyof typeof logoSizes] || logoSizes.medium;
// Resize logo preserving aspect ratio and apply opacity
const resizedLogo = await sharp(logoBuffer)
.resize({ width: logoWidth, fit: 'contain' })
.ensureAlpha()
.toBuffer();
// Get resized logo dimensions
const logoMetadata = await sharp(resizedLogo).metadata();
const logoHeight = logoMetadata.height || logoWidth;
// Calculate position with padding
const padding = 20;
let logoX = padding;
let logoY = padding;
if (logo.position === 'north_east') {
logoX = width - logoWidth - padding;
} else if (logo.position === 'south_west') {
logoY = height - logoHeight - padding;
} else if (logo.position === 'south_east') {
logoX = width - logoWidth - padding;
logoY = height - logoHeight - padding;
} else if (logo.position === 'center') {
logoX = Math.round((width - logoWidth) / 2);
logoY = Math.round((height - logoHeight) / 2);
}
// Apply opacity by manipulating alpha channel
let logoWithOpacity = resizedLogo;
if (logo.opacity < 100) {
// Create a semi-transparent version of the logo
const opacityFactor = logo.opacity / 100;
logoWithOpacity = await sharp(resizedLogo)
.ensureAlpha()
.linear(opacityFactor, 0)
.toBuffer();
}
// Add logo overlay
overlays.push({
input: logoWithOpacity,
top: logoY,
left: logoX,
blend: 'over'
});
console.log(`✅ Logo added at position ${logo.position} with ${logo.opacity}% opacity`);
} catch (logoError) {
console.error("⚠️ Failed to apply logo:", logoError);
// Continue without logo if it fails
}
} else {
console.log("⚠️ No logo configured in settings");
}
}
// Apply all overlays
if (overlays.length > 0) {
image = image.composite(overlays);
@@ -1295,6 +1376,83 @@ export async function registerRoutes(app: Express): Promise<Server> {
}
});
// Upload company logo
app.post("/api/cloudinary/logo", requireAdmin, upload.single("logo"), async (req, res) => {
try {
if (!req.file) {
return res.status(400).json({ error: "No logo file uploaded" });
}
const user = req.user as User;
const userId = user.id;
// 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 first."
});
}
// Delete old logo if exists
if (cloudinaryConfig.logoPublicId) {
try {
await cloudinaryService.deleteLogo(cloudinaryConfig.logoPublicId);
} catch (error) {
console.warn("Failed to delete old logo:", error);
// Continue anyway - not critical
}
}
// Upload new logo to Cloudinary
const uploadResult = await cloudinaryService.uploadLogo(
req.file.buffer,
req.file.originalname
);
// Update config with new logo public ID
const updatedConfig = await storage.updateCloudinaryConfig(userId, {
logoPublicId: uploadResult.publicId,
});
res.json({
logoPublicId: uploadResult.publicId,
logoUrl: uploadResult.url,
});
} catch (error) {
console.error("Error uploading logo:", error);
const errorMessage = error instanceof Error ? error.message : "Failed to upload logo";
res.status(500).json({ error: errorMessage });
}
});
// Delete company logo
app.delete("/api/cloudinary/logo", requireAdmin, async (req, res) => {
try {
const user = req.user as User;
const userId = user.id;
const cloudinaryConfig = await storage.getCloudinaryConfig(userId);
if (!cloudinaryConfig || !cloudinaryConfig.logoPublicId) {
return res.status(404).json({ error: "No logo found" });
}
// Delete from Cloudinary
await cloudinaryService.deleteLogo(cloudinaryConfig.logoPublicId);
// Remove from config
await storage.updateCloudinaryConfig(userId, {
logoPublicId: null,
});
res.json({ success: true });
} catch (error) {
console.error("Error deleting logo:", error);
const errorMessage = error instanceof Error ? error.message : "Failed to delete logo";
res.status(500).json({ error: errorMessage });
}
});
// OpenRouter models list
app.get("/api/openrouter/models", requireAuth, async (req, res) => {
try {
+55
View File
@@ -132,6 +132,61 @@ class CloudinaryService {
return uploadResult.secure_url;
}
async uploadLogo(buffer: Buffer, fileName: string): Promise<{ publicId: string; url: string }> {
const config = await storage.getAnyCloudinaryConfig();
if (!config) {
throw new Error('Cloudinary configuration not found. Please ask an administrator to configure Cloudinary in Settings first.');
}
cloudinary.config({
cloud_name: config.cloudName,
api_key: config.apiKey,
api_secret: config.apiSecret,
secure: true,
});
const uploadResult = await new Promise<any>((resolve, reject) => {
const uploadStream = cloudinary.uploader.upload_stream(
{
folder: 'social-flow/logos',
public_id: `logo-${Date.now()}-${fileName.replace(/\.[^/.]+$/, '')}`,
resource_type: 'image',
},
(error, result) => {
if (error) reject(error);
else resolve(result);
}
);
uploadStream.end(buffer);
});
return {
publicId: uploadResult.public_id,
url: uploadResult.secure_url,
};
}
async deleteLogo(publicId: string): Promise<void> {
const config = await storage.getAnyCloudinaryConfig();
if (!config) {
throw new Error('Cloudinary configuration not found. Please ask an administrator to configure Cloudinary in Settings first.');
}
cloudinary.config({
cloud_name: config.cloudName,
api_key: config.apiKey,
api_secret: config.apiSecret,
secure: true,
});
await cloudinary.uploader.destroy(publicId, {
resource_type: 'image',
});
}
}
export const cloudinaryService = new CloudinaryService();