mirror of
https://github.com/R0m1k3/Socialflow.git
synced 2026-10-23 06:58:12 +02:00
feat: Add MinIO endpoint/public URL configuration via settings UI
Store endpointUrl and publicUrl in cloudinary_config DB table so MinIO connection can be fully configured from the app settings without needing environment variables. - DB migration: adds endpoint_url and public_url columns - Schema: adds endpointUrl/publicUrl to cloudinaryConfig table - MinIO service: reads endpoint from DB (falls back to MINIO_ENDPOINT env) - buildMinioUrl: accepts optional publicUrl override from DB config - All routes updated to pass config.publicUrl to buildMinioUrl - Settings UI (desktop + mobile): adds Endpoint URL and Public URL fields Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
1 parent
841ffb306c
commit
ba94e7cc7d
8 files changed
+105
-21
No files matched your search
@@ -20,6 +20,8 @@ export default function SettingsMobile() {
|
|||||||
const [cloudName, setCloudName] = useState("");
|
const [cloudName, setCloudName] = useState("");
|
||||||
const [apiKey, setApiKey] = useState("");
|
const [apiKey, setApiKey] = useState("");
|
||||||
const [apiSecret, setApiSecret] = useState("");
|
const [apiSecret, setApiSecret] = useState("");
|
||||||
|
const [endpointUrl, setEndpointUrl] = useState("");
|
||||||
|
const [publicUrl, setPublicUrl] = useState("");
|
||||||
const [openrouterApiKey, setOpenrouterApiKey] = useState("");
|
const [openrouterApiKey, setOpenrouterApiKey] = useState("");
|
||||||
const [openrouterModel, setOpenrouterModel] = useState("anthropic/claude-3.5-sonnet");
|
const [openrouterModel, setOpenrouterModel] = useState("anthropic/claude-3.5-sonnet");
|
||||||
const [openrouterSystemPrompt, setOpenrouterSystemPrompt] = useState("Tu es un expert en marketing des réseaux sociaux. Génère 3 variations de textes engageants pour des publications Facebook et Instagram à partir des informations produit fournies. Chaque variation doit être unique, captivante et optimisée pour l'engagement.");
|
const [openrouterSystemPrompt, setOpenrouterSystemPrompt] = useState("Tu es un expert en marketing des réseaux sociaux. Génère 3 variations de textes engageants pour des publications Facebook et Instagram à partir des informations produit fournies. Chaque variation doit être unique, captivante et optimisée pour l'engagement.");
|
||||||
@@ -50,6 +52,8 @@ export default function SettingsMobile() {
|
|||||||
if (cloudinaryConfig) {
|
if (cloudinaryConfig) {
|
||||||
setCloudName((cloudinaryConfig as any).cloudName || "");
|
setCloudName((cloudinaryConfig as any).cloudName || "");
|
||||||
setApiKey((cloudinaryConfig as any).apiKey || "");
|
setApiKey((cloudinaryConfig as any).apiKey || "");
|
||||||
|
setEndpointUrl((cloudinaryConfig as any).endpointUrl || "");
|
||||||
|
setPublicUrl((cloudinaryConfig as any).publicUrl || "");
|
||||||
|
|
||||||
if ((cloudinaryConfig as any).logoUrl) {
|
if ((cloudinaryConfig as any).logoUrl) {
|
||||||
setLogoPreview((cloudinaryConfig as any).logoUrl);
|
setLogoPreview((cloudinaryConfig as any).logoUrl);
|
||||||
@@ -70,6 +74,8 @@ export default function SettingsMobile() {
|
|||||||
mutationFn: () => {
|
mutationFn: () => {
|
||||||
const payload: any = {
|
const payload: any = {
|
||||||
cloudName,
|
cloudName,
|
||||||
|
endpointUrl: endpointUrl || undefined,
|
||||||
|
publicUrl: publicUrl || undefined,
|
||||||
};
|
};
|
||||||
|
|
||||||
if (apiKey && apiKey.trim() !== "") {
|
if (apiKey && apiKey.trim() !== "") {
|
||||||
@@ -368,7 +374,7 @@ export default function SettingsMobile() {
|
|||||||
Configuration Stockage MinIO
|
Configuration Stockage MinIO
|
||||||
</CardTitle>
|
</CardTitle>
|
||||||
<CardDescription className="text-xs">
|
<CardDescription className="text-xs">
|
||||||
Configurez MinIO pour le stockage des médias (endpoint via variable MINIO_ENDPOINT)
|
Configurez MinIO pour le stockage des médias
|
||||||
</CardDescription>
|
</CardDescription>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent className="space-y-4 p-5 pt-0">
|
<CardContent className="space-y-4 p-5 pt-0">
|
||||||
@@ -414,6 +420,36 @@ export default function SettingsMobile() {
|
|||||||
Votre Secret Key restera sécurisé et ne sera jamais exposé
|
Votre Secret Key restera sécurisé et ne sera jamais exposé
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label htmlFor="endpointUrl" className="text-sm">URL Endpoint (interne)</Label>
|
||||||
|
<Input
|
||||||
|
id="endpointUrl"
|
||||||
|
type="url"
|
||||||
|
value={endpointUrl}
|
||||||
|
onChange={(e) => setEndpointUrl(e.target.value)}
|
||||||
|
placeholder="http://minio:9000"
|
||||||
|
className="min-h-[48px]"
|
||||||
|
data-testid="input-endpoint-url"
|
||||||
|
/>
|
||||||
|
<p className="text-xs text-muted-foreground">
|
||||||
|
URL interne de votre serveur MinIO (ex: http://192.168.1.10:9000)
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label htmlFor="publicUrl" className="text-sm">URL Publique</Label>
|
||||||
|
<Input
|
||||||
|
id="publicUrl"
|
||||||
|
type="url"
|
||||||
|
value={publicUrl}
|
||||||
|
onChange={(e) => setPublicUrl(e.target.value)}
|
||||||
|
placeholder="https://media.votre-domaine.com"
|
||||||
|
className="min-h-[48px]"
|
||||||
|
data-testid="input-public-url"
|
||||||
|
/>
|
||||||
|
<p className="text-xs text-muted-foreground">
|
||||||
|
URL publique pour accéder aux fichiers depuis internet
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
<Button
|
<Button
|
||||||
onClick={() => saveCloudinaryMutation.mutate()}
|
onClick={() => saveCloudinaryMutation.mutate()}
|
||||||
disabled={saveCloudinaryMutation.isPending || !cloudName || !apiKey || !apiSecret}
|
disabled={saveCloudinaryMutation.isPending || !cloudName || !apiKey || !apiSecret}
|
||||||
|
|||||||
@@ -20,6 +20,8 @@ export default function Settings() {
|
|||||||
const [cloudName, setCloudName] = useState("");
|
const [cloudName, setCloudName] = useState("");
|
||||||
const [apiKey, setApiKey] = useState("");
|
const [apiKey, setApiKey] = useState("");
|
||||||
const [apiSecret, setApiSecret] = useState("");
|
const [apiSecret, setApiSecret] = useState("");
|
||||||
|
const [endpointUrl, setEndpointUrl] = useState("");
|
||||||
|
const [publicUrl, setPublicUrl] = useState("");
|
||||||
const [openrouterApiKey, setOpenrouterApiKey] = useState("");
|
const [openrouterApiKey, setOpenrouterApiKey] = useState("");
|
||||||
const [openrouterModel, setOpenrouterModel] = useState("anthropic/claude-3.5-sonnet");
|
const [openrouterModel, setOpenrouterModel] = useState("anthropic/claude-3.5-sonnet");
|
||||||
const [openrouterSystemPrompt, setOpenrouterSystemPrompt] = useState("Tu es un expert en marketing des réseaux sociaux. Génère 3 variations de textes engageants pour des publications Facebook et Instagram à partir des informations produit fournies. Chaque variation doit être unique, captivante et optimisée pour l'engagement.");
|
const [openrouterSystemPrompt, setOpenrouterSystemPrompt] = useState("Tu es un expert en marketing des réseaux sociaux. Génère 3 variations de textes engageants pour des publications Facebook et Instagram à partir des informations produit fournies. Chaque variation doit être unique, captivante et optimisée pour l'engagement.");
|
||||||
@@ -56,6 +58,8 @@ export default function Settings() {
|
|||||||
if (cloudinaryConfig) {
|
if (cloudinaryConfig) {
|
||||||
setCloudName((cloudinaryConfig as any).cloudName || "");
|
setCloudName((cloudinaryConfig as any).cloudName || "");
|
||||||
setApiKey((cloudinaryConfig as any).apiKey || "");
|
setApiKey((cloudinaryConfig as any).apiKey || "");
|
||||||
|
setEndpointUrl((cloudinaryConfig as any).endpointUrl || "");
|
||||||
|
setPublicUrl((cloudinaryConfig as any).publicUrl || "");
|
||||||
|
|
||||||
if ((cloudinaryConfig as any).logoUrl) {
|
if ((cloudinaryConfig as any).logoUrl) {
|
||||||
setLogoPreview((cloudinaryConfig as any).logoUrl);
|
setLogoPreview((cloudinaryConfig as any).logoUrl);
|
||||||
@@ -129,6 +133,8 @@ export default function Settings() {
|
|||||||
mutationFn: () => {
|
mutationFn: () => {
|
||||||
const payload: any = {
|
const payload: any = {
|
||||||
cloudName,
|
cloudName,
|
||||||
|
endpointUrl: endpointUrl || undefined,
|
||||||
|
publicUrl: publicUrl || undefined,
|
||||||
};
|
};
|
||||||
|
|
||||||
// N'inclure apiKey et apiSecret que s'ils ne sont pas vides
|
// N'inclure apiKey et apiSecret que s'ils ne sont pas vides
|
||||||
@@ -455,7 +461,7 @@ export default function Settings() {
|
|||||||
Configuration Stockage MinIO
|
Configuration Stockage MinIO
|
||||||
</CardTitle>
|
</CardTitle>
|
||||||
<CardDescription>
|
<CardDescription>
|
||||||
Configurez MinIO pour le stockage des médias (endpoint configuré via variable d'environnement MINIO_ENDPOINT)
|
Configurez MinIO pour le stockage des médias
|
||||||
</CardDescription>
|
</CardDescription>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent className="space-y-4">
|
<CardContent className="space-y-4">
|
||||||
@@ -498,6 +504,34 @@ export default function Settings() {
|
|||||||
Votre Secret Key restera sécurisé et ne sera jamais exposé
|
Votre Secret Key restera sécurisé et ne sera jamais exposé
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label htmlFor="endpointUrl">URL Endpoint (interne)</Label>
|
||||||
|
<Input
|
||||||
|
id="endpointUrl"
|
||||||
|
type="url"
|
||||||
|
value={endpointUrl}
|
||||||
|
onChange={(e) => setEndpointUrl(e.target.value)}
|
||||||
|
placeholder="http://minio:9000"
|
||||||
|
data-testid="input-endpoint-url"
|
||||||
|
/>
|
||||||
|
<p className="text-xs text-muted-foreground">
|
||||||
|
URL interne de votre serveur MinIO (ex: http://minio:9000 ou http://192.168.1.10:9000)
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label htmlFor="publicUrl">URL Publique</Label>
|
||||||
|
<Input
|
||||||
|
id="publicUrl"
|
||||||
|
type="url"
|
||||||
|
value={publicUrl}
|
||||||
|
onChange={(e) => setPublicUrl(e.target.value)}
|
||||||
|
placeholder="https://media.votre-domaine.com"
|
||||||
|
data-testid="input-public-url"
|
||||||
|
/>
|
||||||
|
<p className="text-xs text-muted-foreground">
|
||||||
|
URL publique pour accéder aux fichiers depuis internet (ex: https://media.example.com)
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
<Button
|
<Button
|
||||||
onClick={() => saveCloudinaryMutation.mutate()}
|
onClick={() => saveCloudinaryMutation.mutate()}
|
||||||
disabled={saveCloudinaryMutation.isPending || !cloudName || !apiKey || !apiSecret}
|
disabled={saveCloudinaryMutation.isPending || !cloudName || !apiKey || !apiSecret}
|
||||||
|
|||||||
@@ -111,6 +111,18 @@ export async function migrate() {
|
|||||||
ALTER TABLE "posts"
|
ALTER TABLE "posts"
|
||||||
ADD COLUMN IF NOT EXISTS "generation_error" text;
|
ADD COLUMN IF NOT EXISTS "generation_error" text;
|
||||||
`);
|
`);
|
||||||
|
|
||||||
|
// cloudinary_config.endpoint_url (MinIO internal endpoint URL)
|
||||||
|
await client.query(`
|
||||||
|
ALTER TABLE "cloudinary_config"
|
||||||
|
ADD COLUMN IF NOT EXISTS "endpoint_url" text;
|
||||||
|
`);
|
||||||
|
|
||||||
|
// cloudinary_config.public_url (MinIO public-facing base URL)
|
||||||
|
await client.query(`
|
||||||
|
ALTER TABLE "cloudinary_config"
|
||||||
|
ADD COLUMN IF NOT EXISTS "public_url" text;
|
||||||
|
`);
|
||||||
// page_analytics_history.page_engagement
|
// page_analytics_history.page_engagement
|
||||||
await client.query(`
|
await client.query(`
|
||||||
ALTER TABLE "page_analytics_history"
|
ALTER TABLE "page_analytics_history"
|
||||||
|
|||||||
+2
-2
@@ -822,7 +822,7 @@ export async function registerRoutes(app: Express): Promise<Server> {
|
|||||||
if (cloudinaryConfig && cloudinaryConfig.logoPublicId) {
|
if (cloudinaryConfig && cloudinaryConfig.logoPublicId) {
|
||||||
try {
|
try {
|
||||||
// Build logo URL from Cloudinary
|
// Build logo URL from Cloudinary
|
||||||
const logoUrl = buildMinioUrl(cloudinaryConfig.cloudName, cloudinaryConfig.logoPublicId);
|
const logoUrl = buildMinioUrl(cloudinaryConfig.cloudName, cloudinaryConfig.logoPublicId, cloudinaryConfig.publicUrl);
|
||||||
console.log("📥 Downloading logo from:", logoUrl);
|
console.log("📥 Downloading logo from:", logoUrl);
|
||||||
|
|
||||||
// Download logo
|
// Download logo
|
||||||
@@ -1631,7 +1631,7 @@ export async function registerRoutes(app: Express): Promise<Server> {
|
|||||||
|
|
||||||
// Don't send the API secret to the client
|
// Don't send the API secret to the client
|
||||||
const { apiSecret, ...safeConfig } = config;
|
const { apiSecret, ...safeConfig } = config;
|
||||||
const logoUrl = config.logoPublicId ? buildMinioUrl(config.cloudName, config.logoPublicId) : null;
|
const logoUrl = config.logoPublicId ? buildMinioUrl(config.cloudName, config.logoPublicId, config.publicUrl) : null;
|
||||||
res.json({ ...safeConfig, logoUrl });
|
res.json({ ...safeConfig, logoUrl });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error fetching Cloudinary config:", error);
|
console.error("Error fetching Cloudinary config:", error);
|
||||||
|
|||||||
@@ -343,7 +343,7 @@ reelsRouter.post('/reels/preview', async (req: Request, res: Response) => {
|
|||||||
try {
|
try {
|
||||||
const config = await storage.getCloudinaryConfig();
|
const config = await storage.getCloudinaryConfig();
|
||||||
if (config && config.logoPublicId) {
|
if (config && config.logoPublicId) {
|
||||||
watermarkUrl = buildMinioUrl(config.cloudName, config.logoPublicId);
|
watermarkUrl = buildMinioUrl(config.cloudName, config.logoPublicId, config.publicUrl);
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error('Error fetching watermark configuration', e);
|
console.error('Error fetching watermark configuration', e);
|
||||||
@@ -556,7 +556,7 @@ async function processReelBackground(
|
|||||||
try {
|
try {
|
||||||
const config = await storage.getCloudinaryConfig();
|
const config = await storage.getCloudinaryConfig();
|
||||||
if (config && config.logoPublicId) {
|
if (config && config.logoPublicId) {
|
||||||
watermarkUrl = buildMinioUrl(config.cloudName, config.logoPublicId);
|
watermarkUrl = buildMinioUrl(config.cloudName, config.logoPublicId, config.publicUrl);
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error('Error fetching watermark configuration', e);
|
console.error('Error fetching watermark configuration', e);
|
||||||
|
|||||||
@@ -128,7 +128,7 @@ remotionRouter.post("/render", upload.fields([{ name: "images", maxCount: 4 }, {
|
|||||||
try {
|
try {
|
||||||
const cloudinaryConfig = await dbStorage.getCloudinaryConfig();
|
const cloudinaryConfig = await dbStorage.getCloudinaryConfig();
|
||||||
if (cloudinaryConfig?.cloudName && cloudinaryConfig?.logoPublicId) {
|
if (cloudinaryConfig?.cloudName && cloudinaryConfig?.logoPublicId) {
|
||||||
logoUrl = buildMinioUrl(cloudinaryConfig.cloudName, cloudinaryConfig.logoPublicId);
|
logoUrl = buildMinioUrl(cloudinaryConfig.cloudName, cloudinaryConfig.logoPublicId, cloudinaryConfig.publicUrl);
|
||||||
console.log("🏢 Logo URL:", logoUrl);
|
console.log("🏢 Logo URL:", logoUrl);
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
|||||||
+14
-14
@@ -3,9 +3,10 @@ import { storage } from '../storage';
|
|||||||
import fs from 'fs';
|
import fs from 'fs';
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
|
|
||||||
function getS3Client(accessKey: string, secretKey: string): S3Client {
|
function getS3Client(accessKey: string, secretKey: string, endpointUrl?: string | null): S3Client {
|
||||||
|
const endpoint = endpointUrl || process.env.MINIO_ENDPOINT || 'http://localhost:9000';
|
||||||
return new S3Client({
|
return new S3Client({
|
||||||
endpoint: process.env.MINIO_ENDPOINT || 'http://localhost:9000',
|
endpoint,
|
||||||
region: 'us-east-1',
|
region: 'us-east-1',
|
||||||
credentials: { accessKeyId: accessKey, secretAccessKey: secretKey },
|
credentials: { accessKeyId: accessKey, secretAccessKey: secretKey },
|
||||||
forcePathStyle: true,
|
forcePathStyle: true,
|
||||||
@@ -14,11 +15,10 @@ function getS3Client(accessKey: string, secretKey: string): S3Client {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Builds the public URL for a stored object.
|
* Builds the public URL for a stored object.
|
||||||
* MINIO_PUBLIC_URL = public-facing base (e.g. https://media.example.com)
|
* Uses DB publicUrl > MINIO_PUBLIC_URL env > MINIO_ENDPOINT env > localhost fallback.
|
||||||
* Object URL = ${MINIO_PUBLIC_URL}/${bucketName}/${objectKey}
|
|
||||||
*/
|
*/
|
||||||
export function buildMinioUrl(bucketName: string, objectKey: string): string {
|
export function buildMinioUrl(bucketName: string, objectKey: string, publicUrlOverride?: string | null): string {
|
||||||
const base = (process.env.MINIO_PUBLIC_URL || process.env.MINIO_ENDPOINT || 'http://localhost:9000').replace(/\/$/, '');
|
const base = (publicUrlOverride || process.env.MINIO_PUBLIC_URL || process.env.MINIO_ENDPOINT || 'http://localhost:9000').replace(/\/$/, '');
|
||||||
return `${base}/${bucketName}/${objectKey}`;
|
return `${base}/${bucketName}/${objectKey}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -38,7 +38,7 @@ class MinioService {
|
|||||||
const config = await storage.getCloudinaryConfig();
|
const config = await storage.getCloudinaryConfig();
|
||||||
if (!config) throw new Error('MinIO non configuré. Veuillez configurer le stockage dans les Paramètres.');
|
if (!config) throw new Error('MinIO non configuré. Veuillez configurer le stockage dans les Paramètres.');
|
||||||
|
|
||||||
const client = getS3Client(config.apiKey, config.apiSecret);
|
const client = getS3Client(config.apiKey, config.apiSecret, config.endpointUrl);
|
||||||
const bucket = config.cloudName;
|
const bucket = config.cloudName;
|
||||||
const safeName = path.basename(fileName).replace(/\s+/g, '-');
|
const safeName = path.basename(fileName).replace(/\s+/g, '-');
|
||||||
const objectKey = `social-flow/${Date.now()}-${safeName}`;
|
const objectKey = `social-flow/${Date.now()}-${safeName}`;
|
||||||
@@ -52,7 +52,7 @@ class MinioService {
|
|||||||
ContentType: mimeType,
|
ContentType: mimeType,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const url = buildMinioUrl(bucket, objectKey);
|
const url = buildMinioUrl(bucket, objectKey, config.publicUrl);
|
||||||
return { publicId: objectKey, originalUrl: url, facebookFeedUrl: url, instagramFeedUrl: url, instagramStoryUrl: url };
|
return { publicId: objectKey, originalUrl: url, facebookFeedUrl: url, instagramFeedUrl: url, instagramStoryUrl: url };
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -60,7 +60,7 @@ class MinioService {
|
|||||||
const config = await storage.getCloudinaryConfig();
|
const config = await storage.getCloudinaryConfig();
|
||||||
if (!config) throw new Error('MinIO non configuré.');
|
if (!config) throw new Error('MinIO non configuré.');
|
||||||
|
|
||||||
const client = getS3Client(config.apiKey, config.apiSecret);
|
const client = getS3Client(config.apiKey, config.apiSecret, config.endpointUrl);
|
||||||
await client.send(new DeleteObjectCommand({ Bucket: config.cloudName, Key: publicId }));
|
await client.send(new DeleteObjectCommand({ Bucket: config.cloudName, Key: publicId }));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -68,7 +68,7 @@ class MinioService {
|
|||||||
const config = await storage.getCloudinaryConfig();
|
const config = await storage.getCloudinaryConfig();
|
||||||
if (!config) throw new Error('MinIO non configuré.');
|
if (!config) throw new Error('MinIO non configuré.');
|
||||||
|
|
||||||
const client = getS3Client(config.apiKey, config.apiSecret);
|
const client = getS3Client(config.apiKey, config.apiSecret, config.endpointUrl);
|
||||||
const safeName = path.basename(originalFileName).replace(/\s+/g, '-');
|
const safeName = path.basename(originalFileName).replace(/\s+/g, '-');
|
||||||
const objectKey = `social-flow/stories/story-${Date.now()}-${safeName}`;
|
const objectKey = `social-flow/stories/story-${Date.now()}-${safeName}`;
|
||||||
|
|
||||||
@@ -79,14 +79,14 @@ class MinioService {
|
|||||||
ContentType: 'image/jpeg',
|
ContentType: 'image/jpeg',
|
||||||
}));
|
}));
|
||||||
|
|
||||||
return buildMinioUrl(config.cloudName, objectKey);
|
return buildMinioUrl(config.cloudName, objectKey, config.publicUrl);
|
||||||
}
|
}
|
||||||
|
|
||||||
async uploadLogo(buffer: Buffer, fileName: string): Promise<{ publicId: string; url: string }> {
|
async uploadLogo(buffer: Buffer, fileName: string): Promise<{ publicId: string; url: string }> {
|
||||||
const config = await storage.getCloudinaryConfig();
|
const config = await storage.getCloudinaryConfig();
|
||||||
if (!config) throw new Error('MinIO non configuré.');
|
if (!config) throw new Error('MinIO non configuré.');
|
||||||
|
|
||||||
const client = getS3Client(config.apiKey, config.apiSecret);
|
const client = getS3Client(config.apiKey, config.apiSecret, config.endpointUrl);
|
||||||
const ext = path.extname(fileName) || '.png';
|
const ext = path.extname(fileName) || '.png';
|
||||||
const objectKey = `social-flow/logos/logo-${Date.now()}${ext}`;
|
const objectKey = `social-flow/logos/logo-${Date.now()}${ext}`;
|
||||||
|
|
||||||
@@ -97,14 +97,14 @@ class MinioService {
|
|||||||
ContentType: fileName.match(/\.png$/i) ? 'image/png' : 'image/jpeg',
|
ContentType: fileName.match(/\.png$/i) ? 'image/png' : 'image/jpeg',
|
||||||
}));
|
}));
|
||||||
|
|
||||||
return { publicId: objectKey, url: buildMinioUrl(config.cloudName, objectKey) };
|
return { publicId: objectKey, url: buildMinioUrl(config.cloudName, objectKey, config.publicUrl) };
|
||||||
}
|
}
|
||||||
|
|
||||||
async deleteLogo(publicId: string): Promise<void> {
|
async deleteLogo(publicId: string): Promise<void> {
|
||||||
const config = await storage.getCloudinaryConfig();
|
const config = await storage.getCloudinaryConfig();
|
||||||
if (!config) throw new Error('MinIO non configuré.');
|
if (!config) throw new Error('MinIO non configuré.');
|
||||||
|
|
||||||
const client = getS3Client(config.apiKey, config.apiSecret);
|
const client = getS3Client(config.apiKey, config.apiSecret, config.endpointUrl);
|
||||||
await client.send(new DeleteObjectCommand({ Bucket: config.cloudName, Key: publicId }));
|
await client.send(new DeleteObjectCommand({ Bucket: config.cloudName, Key: publicId }));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -117,6 +117,8 @@ export const cloudinaryConfig = pgTable("cloudinary_config", {
|
|||||||
apiKey: text("api_key").notNull(),
|
apiKey: text("api_key").notNull(),
|
||||||
apiSecret: text("api_secret").notNull(),
|
apiSecret: text("api_secret").notNull(),
|
||||||
logoPublicId: text("logo_public_id"),
|
logoPublicId: text("logo_public_id"),
|
||||||
|
endpointUrl: text("endpoint_url"),
|
||||||
|
publicUrl: text("public_url"),
|
||||||
createdAt: timestamp("created_at").defaultNow(),
|
createdAt: timestamp("created_at").defaultNow(),
|
||||||
updatedAt: timestamp("updated_at").defaultNow(),
|
updatedAt: timestamp("updated_at").defaultNow(),
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in new issue
Block a user