mirror of
https://github.com/R0m1k3/Socialflow.git
synced 2026-10-11 17:26:45 +02:00
feat: Replace external storage with local file storage + auto cleanup
Media is now stored directly on the server disk (no external service). Files are served via Express static middleware at /uploads/*. Cleanup rules: - Videos deleted immediately after successful publish to Facebook/Instagram - Videos older than 7 days purged daily by cron job - Images older than 30 days purged daily by cron job Changes: - server/services/minio.ts: replaced S3 client with local fs read/write - server/index.ts: added static serving for /uploads/media, /logos, /stories - server/services/media-purge.ts: new rules (images 30d, videos 7d) - server/services/scheduler.ts: delete local videos after successful publish - docker-compose.yml: add media_uploads, logos_uploads, stories_uploads volumes - settings UI: removed credentials card, shows local storage info - package.json: removed googleapis (unneeded) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
1 parent
ba94e7cc7d
commit
7733acbc22
9 files changed
+145
-378
No files matched your search
@@ -365,99 +365,25 @@ export default function SettingsMobile() {
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{/* MinIO */}
|
||||
{/* Stockage local */}
|
||||
{isAdmin && (
|
||||
<Card className="rounded-2xl border-border/50 shadow-lg">
|
||||
<CardHeader className="p-5">
|
||||
<CardTitle className="flex items-center gap-2 text-base">
|
||||
<Cloud className="w-5 h-5" />
|
||||
Configuration Stockage MinIO
|
||||
Stockage des médias
|
||||
</CardTitle>
|
||||
<CardDescription className="text-xs">
|
||||
Configurez MinIO pour le stockage des médias
|
||||
Les médias sont stockés localement sur le serveur
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-4 p-5 pt-0">
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="cloudName" className="text-sm">Nom du bucket</Label>
|
||||
<Input
|
||||
id="cloudName"
|
||||
type="text"
|
||||
value={cloudName}
|
||||
onChange={(e) => setCloudName(e.target.value)}
|
||||
placeholder="socialflow"
|
||||
className="min-h-[48px]"
|
||||
data-testid="input-cloud-name"
|
||||
/>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
Nom du bucket MinIO dans lequel stocker les fichiers
|
||||
</p>
|
||||
<CardContent className="p-5 pt-0">
|
||||
<div className="rounded-lg bg-muted p-4 space-y-1 text-sm text-muted-foreground">
|
||||
<p>✓ Images et vidéos stockées sur le disque du serveur</p>
|
||||
<p>✓ Vidéos supprimées automatiquement après publication</p>
|
||||
<p>✓ Images supprimées automatiquement après 30 jours</p>
|
||||
<p>✓ Vidéos non publiées supprimées après 7 jours</p>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="apiKey" className="text-sm">Access Key</Label>
|
||||
<Input
|
||||
id="apiKey"
|
||||
type="text"
|
||||
value={apiKey}
|
||||
onChange={(e) => setApiKey(e.target.value)}
|
||||
placeholder="minioadmin"
|
||||
className="min-h-[48px]"
|
||||
data-testid="input-api-key"
|
||||
/>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="apiSecret" className="text-sm">Secret Key</Label>
|
||||
<Input
|
||||
id="apiSecret"
|
||||
type="password"
|
||||
value={apiSecret}
|
||||
onChange={(e) => setApiSecret(e.target.value)}
|
||||
placeholder="••••••••••••••••"
|
||||
className="min-h-[48px]"
|
||||
data-testid="input-api-secret"
|
||||
/>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
Votre Secret Key restera sécurisé et ne sera jamais exposé
|
||||
</p>
|
||||
</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
|
||||
onClick={() => saveCloudinaryMutation.mutate()}
|
||||
disabled={saveCloudinaryMutation.isPending || !cloudName || !apiKey || !apiSecret}
|
||||
data-testid="button-save-cloudinary"
|
||||
className="w-full min-h-[48px]"
|
||||
>
|
||||
{saveCloudinaryMutation.isPending ? "Enregistrement..." : "Enregistrer MinIO"}
|
||||
</Button>
|
||||
</CardContent>
|
||||
</Card>
|
||||
)}
|
||||
|
||||
@@ -458,88 +458,19 @@ export default function Settings() {
|
||||
<CardHeader className="p-6">
|
||||
<CardTitle className="flex items-center gap-2">
|
||||
<Cloud className="w-5 h-5" />
|
||||
Configuration Stockage MinIO
|
||||
Stockage des médias
|
||||
</CardTitle>
|
||||
<CardDescription>
|
||||
Configurez MinIO pour le stockage des médias
|
||||
Les médias sont stockés localement sur le serveur
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-4">
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="cloudName">Nom du bucket</Label>
|
||||
<Input
|
||||
id="cloudName"
|
||||
type="text"
|
||||
value={cloudName}
|
||||
onChange={(e) => setCloudName(e.target.value)}
|
||||
placeholder="socialflow"
|
||||
data-testid="input-cloud-name"
|
||||
/>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
Nom du bucket MinIO dans lequel stocker les fichiers
|
||||
</p>
|
||||
<CardContent>
|
||||
<div className="rounded-lg bg-muted p-4 space-y-1 text-sm text-muted-foreground">
|
||||
<p>✓ Images et vidéos stockées sur le disque du serveur</p>
|
||||
<p>✓ Vidéos supprimées automatiquement après publication</p>
|
||||
<p>✓ Images supprimées automatiquement après 30 jours</p>
|
||||
<p>✓ Vidéos non publiées supprimées après 7 jours</p>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="apiKey">Access Key</Label>
|
||||
<Input
|
||||
id="apiKey"
|
||||
type="text"
|
||||
value={apiKey}
|
||||
onChange={(e) => setApiKey(e.target.value)}
|
||||
placeholder="minioadmin"
|
||||
data-testid="input-api-key"
|
||||
/>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="apiSecret">Secret Key</Label>
|
||||
<Input
|
||||
id="apiSecret"
|
||||
type="password"
|
||||
value={apiSecret}
|
||||
onChange={(e) => setApiSecret(e.target.value)}
|
||||
placeholder="••••••••••••••••"
|
||||
data-testid="input-api-secret"
|
||||
/>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
Votre Secret Key restera sécurisé et ne sera jamais exposé
|
||||
</p>
|
||||
</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
|
||||
onClick={() => saveCloudinaryMutation.mutate()}
|
||||
disabled={saveCloudinaryMutation.isPending || !cloudName || !apiKey || !apiSecret}
|
||||
data-testid="button-save-cloudinary"
|
||||
className="w-full"
|
||||
>
|
||||
{saveCloudinaryMutation.isPending ? "Enregistrement..." : "Enregistrer MinIO"}
|
||||
</Button>
|
||||
</CardContent>
|
||||
</Card>
|
||||
)}
|
||||
|
||||
Reference in new issue
Block a user