Feat: Drag & Drop MP3/WAV Upload mit Progress-Tracking
Backend: - multer reaktiviert (war auskommentiert) mit diskStorage + Collision-Handling - /api/upload (POST, admin-protected): bis zu 20 Dateien gleichzeitig - MP3/WAV-Filter (50MB Limit), sofortige Hintergrund-Normalisierung nach Upload Frontend: - Globale window dragenter/dragleave/drop Listener mit Counter gegen false-positives - Drag-Overlay: Vollbild-Blur + animierter Drop-Zone (pulsierender Accent-Border, bouncing Icon) - Upload-Queue: floating Card bottom-right mit Per-Datei Progressbar + Status-Icons (sync-Animation beim Hochladen, check_circle grün, error rot) - Auto-Refresh der Soundliste + Analytics nach Upload - Auto-Dismiss der Queue nach 3.5s Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
a61663166f
commit
52c86240af
4 changed files with 426 additions and 3 deletions
|
|
@ -2,7 +2,7 @@ import path from 'node:path';
|
|||
import fs from 'node:fs';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import express, { Request, Response } from 'express';
|
||||
// import multer from 'multer';
|
||||
import multer from 'multer';
|
||||
import cors from 'cors';
|
||||
import crypto from 'node:crypto';
|
||||
import { Client, GatewayIntentBits, Partials, ChannelType, Events, type Message, VoiceState } from 'discord.js';
|
||||
|
|
@ -1029,6 +1029,47 @@ app.post('/api/admin/sounds/rename', requireAdmin, (req: Request, res: Response)
|
|||
}
|
||||
});
|
||||
|
||||
// --- Datei-Upload (Drag & Drop) ---
|
||||
const uploadStorage = multer.diskStorage({
|
||||
destination: (_req, _file, cb) => cb(null, SOUNDS_DIR),
|
||||
filename: (_req, file, cb) => {
|
||||
const safe = file.originalname.replace(/[<>:"/\\|?*\x00-\x1f]/g, '_');
|
||||
const { name, ext } = path.parse(safe);
|
||||
let finalName = safe;
|
||||
let i = 2;
|
||||
while (fs.existsSync(path.join(SOUNDS_DIR, finalName))) {
|
||||
finalName = `${name}-${i}${ext}`;
|
||||
i++;
|
||||
}
|
||||
cb(null, finalName);
|
||||
},
|
||||
});
|
||||
const uploadMulter = multer({
|
||||
storage: uploadStorage,
|
||||
fileFilter: (_req, file, cb) => {
|
||||
const ext = path.extname(file.originalname).toLowerCase();
|
||||
cb(null, ext === '.mp3' || ext === '.wav');
|
||||
},
|
||||
limits: { fileSize: 50 * 1024 * 1024, files: 20 },
|
||||
});
|
||||
|
||||
app.post('/api/upload', requireAdmin, (req: Request, res: Response) => {
|
||||
uploadMulter.array('files', 20)(req, res, async (err) => {
|
||||
if (err) return res.status(400).json({ error: err.message ?? 'Upload fehlgeschlagen' });
|
||||
const files = (req as any).files as Express.Multer.File[] | undefined;
|
||||
if (!files?.length) return res.status(400).json({ error: 'Keine gültigen Dateien (nur MP3/WAV)' });
|
||||
const saved = files.map(f => ({ name: f.filename, size: f.size }));
|
||||
// Normalisierung im Hintergrund starten
|
||||
if (NORMALIZE_ENABLE) {
|
||||
for (const f of files) {
|
||||
normalizeToCache(f.path).catch(e => console.warn(`Norm after upload failed (${f.filename}):`, e));
|
||||
}
|
||||
}
|
||||
console.log(`${new Date().toISOString()} | Upload: ${files.map(f => f.filename).join(', ')}`);
|
||||
res.json({ ok: true, files: saved });
|
||||
});
|
||||
});
|
||||
|
||||
// --- Kategorien API ---
|
||||
app.get('/api/categories', (_req: Request, res: Response) => {
|
||||
res.json({ categories: persistedState.categories ?? [] });
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue