Files
picpeak/backend/src/services/watermarkService.js
T
paul 4e2075c638
Mirror to GitHub / mirror (push) Successful in 25s
Test and Lint / backend-test (push) Successful in 1m7s
continuous-integration/drone/push Build is passing
Test and Lint / frontend-test (push) Successful in 2m12s
Version and Release / version-bump (push) Successful in 33s
Version and Release / trigger-drone (push) Successful in 3s
fix: prevent double date formatting in email templates
- Remove pre-formatting of dates before passing to email processor
- Let email processor handle all date formatting based on recipient language
- Fix Invalid Date errors by passing raw date values instead of formatted strings
- Remove unused formatDate imports and variables

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-07-16 23:14:32 +02:00

226 lines
6.6 KiB
JavaScript

const sharp = require('sharp');
const path = require('path');
const fs = require('fs').promises;
const { db } = require('../database/db');
class WatermarkService {
constructor() {
this.cache = new Map();
this.cacheMaxAge = 3600000; // 1 hour in milliseconds
}
/**
* Get watermark settings from database
*/
async getWatermarkSettings() {
try {
const settings = await db('app_settings')
.whereIn('setting_key', [
'branding_watermark_enabled',
'branding_watermark_logo_path',
'branding_watermark_position',
'branding_watermark_opacity',
'branding_watermark_size',
'branding_company_name'
])
.select('setting_key', 'setting_value');
const settingsObj = {};
settings.forEach(setting => {
try {
settingsObj[setting.setting_key] = JSON.parse(setting.setting_value);
} catch (e) {
settingsObj[setting.setting_key] = setting.setting_value;
}
});
return {
enabled: settingsObj.branding_watermark_enabled || false,
logoPath: settingsObj.branding_watermark_logo_path || null,
position: settingsObj.branding_watermark_position || 'bottom-right',
opacity: parseInt(settingsObj.branding_watermark_opacity || 50),
size: parseInt(settingsObj.branding_watermark_size || 15),
companyName: settingsObj.branding_company_name || 'Photo Gallery'
};
} catch (error) {
console.error('Error fetching watermark settings:', error);
return null;
}
}
/**
* Calculate position coordinates based on position string
*/
getPositionCoordinates(imageWidth, imageHeight, watermarkWidth, watermarkHeight, position) {
const padding = 20;
let left, top;
switch (position) {
case 'top-left':
left = padding;
top = padding;
break;
case 'top-right':
left = imageWidth - watermarkWidth - padding;
top = padding;
break;
case 'bottom-left':
left = padding;
top = imageHeight - watermarkHeight - padding;
break;
case 'bottom-right':
left = imageWidth - watermarkWidth - padding;
top = imageHeight - watermarkHeight - padding;
break;
case 'center':
left = Math.floor((imageWidth - watermarkWidth) / 2);
top = Math.floor((imageHeight - watermarkHeight) / 2);
break;
default:
// Default to bottom-right
left = imageWidth - watermarkWidth - padding;
top = imageHeight - watermarkHeight - padding;
}
return { left: Math.max(0, left), top: Math.max(0, top) };
}
/**
* Apply watermark to an image
*/
async applyWatermark(imagePath, settings) {
try {
if (!settings || !settings.enabled) {
// Return original image if watermarking is disabled
return await fs.readFile(imagePath);
}
// Check cache first
const cacheKey = `${imagePath}_${JSON.stringify(settings)}`;
const cached = this.cache.get(cacheKey);
if (cached && Date.now() - cached.timestamp < this.cacheMaxAge) {
return cached.buffer;
}
// Load the main image
const image = sharp(imagePath);
const metadata = await image.metadata();
let watermarkBuffer;
let watermarkMetadata;
// Try to use logo watermark first
if (settings.logoPath) {
try {
const watermarkImage = sharp(settings.logoPath);
watermarkMetadata = await watermarkImage.metadata();
// Calculate watermark size based on percentage of main image
const scaleFactor = settings.size / 100;
const targetWidth = Math.floor(metadata.width * scaleFactor);
const targetHeight = Math.floor(watermarkMetadata.height * (targetWidth / watermarkMetadata.width));
// Resize watermark and apply opacity
watermarkBuffer = await watermarkImage
.resize(targetWidth, targetHeight, { fit: 'inside' })
.composite([{
input: Buffer.from([255, 255, 255, Math.floor(255 * (settings.opacity / 100))]),
raw: {
width: 1,
height: 1,
channels: 4
},
tile: true,
blend: 'dest-in'
}])
.toBuffer();
watermarkMetadata = { width: targetWidth, height: targetHeight };
} catch (error) {
console.error('Error processing watermark logo:', error);
watermarkBuffer = null;
}
}
// If no logo or logo failed, create text watermark
if (!watermarkBuffer) {
const fontSize = Math.max(16, Math.floor(metadata.width * 0.03));
const padding = 10;
// Create SVG text watermark
const svg = `
<svg width="${settings.companyName.length * fontSize * 0.6 + padding * 2}" height="${fontSize + padding * 2}">
<rect x="0" y="0" width="100%" height="100%" fill="black" opacity="0.5" rx="5"/>
<text x="${padding}" y="${fontSize + padding/2}"
font-family="Arial, sans-serif"
font-size="${fontSize}"
fill="white"
opacity="${settings.opacity / 100}">
${settings.companyName}
</text>
</svg>
`;
watermarkBuffer = Buffer.from(svg);
watermarkMetadata = {
width: settings.companyName.length * fontSize * 0.6 + padding * 2,
height: fontSize + padding * 2
};
}
// Calculate position
const position = this.getPositionCoordinates(
metadata.width,
metadata.height,
watermarkMetadata.width,
watermarkMetadata.height,
settings.position
);
// Apply watermark
const watermarkedBuffer = await image
.composite([{
input: watermarkBuffer,
top: position.top,
left: position.left
}])
.toBuffer();
// Cache the result
this.cache.set(cacheKey, {
buffer: watermarkedBuffer,
timestamp: Date.now()
});
// Clean old cache entries
this.cleanCache();
return watermarkedBuffer;
} catch (error) {
console.error('Error applying watermark:', error);
// Return original image on error
return await fs.readFile(imagePath);
}
}
/**
* Clean old cache entries
*/
cleanCache() {
const now = Date.now();
for (const [key, value] of this.cache.entries()) {
if (now - value.timestamp > this.cacheMaxAge) {
this.cache.delete(key);
}
}
}
/**
* Clear entire cache
*/
clearCache() {
this.cache.clear();
}
}
module.exports = new WatermarkService();