mirror of
https://github.com/R0m1k3/Socialflow.git
synced 2026-10-14 18:58:30 +02:00
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:
1 parent
6ee2b61dd9
commit
225e4f2292
6 files changed
+498
-257
No files matched your search
+159
-1
@@ -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 {
|
||||
|
||||
@@ -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();
|
||||
Reference in new issue
Block a user