feat: replace Cloudinary with MinIO for all media storage

- Create server/services/minio.ts: S3-compatible service (same interface as
  cloudinaryService) using @aws-sdk/client-s3 and MINIO_ENDPOINT env var
- Replace all cloudinaryService imports with minioService across routes.ts,
  routes/reels.ts, routes/remotion.ts, services/media-purge.ts
- Replace Cloudinary logo URL pattern (res.cloudinary.com/...) with
  buildMinioUrl() helper throughout all server routes
- GET /api/cloudinary/config now returns a logoUrl field (full MinIO URL)
  so clients never need to build the URL themselves
- Update settings pages (desktop + mobile): labels changed to MinIO
  (Bucket Name, Access Key, Secret Key)
- Update image-editor.tsx: remove Cloudinary URL transformation, use logoUrl
- Update media-utils.ts: simplify getVideoThumbnailUrl (no URL transforms)
- Add MINIO_ENDPOINT and MINIO_PUBLIC_URL to docker-compose.yml + .env.example
- DB table reuse: cloudinary_config.cloud_name = bucket, api_key = access key,
  api_secret = secret key — no migration needed

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
MichaelandClaude Sonnet 4.6 committed 2026-03-24 21:05:37 +01:00
1 parent b0e11cb8ab
commit e39fa1026d
13 files changed
+1832 -118

No files matched your search

+5 -40
View File
@@ -112,45 +112,10 @@ export default function ImageEditor() {
});
// Generate Cloudinary transformation URL
// Set preview URL from selected media
useEffect(() => {
if (!selectedMedia) {
setPreviewUrl("");
return;
}
// Start with base image URL
let url = selectedMedia.originalUrl;
// Extract Cloudinary parts
const cloudinaryPattern = /https:\/\/res\.cloudinary\.com\/([^\/]+)\/image\/upload\//;
const match = url.match(cloudinaryPattern);
if (!match) {
setPreviewUrl(url);
return;
}
const [fullMatch, cloudName] = match;
const imagePath = url.replace(fullMatch, '');
// Build transformations array
const transformations: string[] = [];
// Base resize
transformations.push('w_800,h_800,c_fit');
// Note: Ribbon and price badge overlays are rendered as CSS elements in preview
// They are NOT added to Cloudinary transformation URL
// This allows for better visual control and positioning
// Logo overlay will be added by backend when saving
// Preview just shows the base image with CSS overlays
// Construct final URL
const transformedUrl = `https://res.cloudinary.com/${cloudName}/image/upload/${transformations.join('/')}/${imagePath}`;
setPreviewUrl(transformedUrl);
if (!selectedMedia) { setPreviewUrl(""); return; }
setPreviewUrl(selectedMedia.originalUrl);
}, [selectedMedia, ribbon, priceBadge, logo]);
return (
@@ -620,9 +585,9 @@ export default function ImageEditor() {
)}
{/* Logo Overlay (CSS) */}
{logo.enabled && cloudinaryConfig && (cloudinaryConfig as any).logoPublicId && (
{logo.enabled && cloudinaryConfig && (cloudinaryConfig as any).logoUrl && (
<img
src={`https://res.cloudinary.com/${(cloudinaryConfig as any).cloudName}/image/upload/${(cloudinaryConfig as any).logoPublicId}`}
src={(cloudinaryConfig as any).logoUrl}
alt="Logo"
className={`absolute ${
logo.position === "north_east" ? "top-4 right-4" :