Nightly: Drag & Drop Upload im Header (Admin) + Server-Upload-Endpoint (/api/upload, MP3/WAV)
This commit is contained in:
parent
c1f4d0f3a0
commit
9e7b572feb
3 changed files with 60 additions and 2 deletions
|
|
@ -2,6 +2,7 @@ import path from 'node:path';
|
||||||
import fs from 'node:fs';
|
import fs from 'node:fs';
|
||||||
import { fileURLToPath } from 'node:url';
|
import { fileURLToPath } from 'node:url';
|
||||||
import express, { Request, Response } from 'express';
|
import express, { Request, Response } from 'express';
|
||||||
|
import multer from 'multer';
|
||||||
import cors from 'cors';
|
import cors from 'cors';
|
||||||
import crypto from 'node:crypto';
|
import crypto from 'node:crypto';
|
||||||
import { Client, GatewayIntentBits, Partials, ChannelType, Events, type Message } from 'discord.js';
|
import { Client, GatewayIntentBits, Partials, ChannelType, Events, type Message } from 'discord.js';
|
||||||
|
|
@ -1028,6 +1029,36 @@ app.post('/api/play-url', async (req: Request, res: Response) => {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// --- Datei-Upload (Admin): MP3/WAV per HTTP hochladen ---
|
||||||
|
const upload = multer({ storage: multer.memoryStorage(), limits: { fileSize: 100 * 1024 * 1024 } });
|
||||||
|
app.post('/api/upload', requireAdmin, upload.single('file'), async (req: Request, res: Response) => {
|
||||||
|
try {
|
||||||
|
const file = (req as any).file as Express.Multer.File | undefined;
|
||||||
|
const folderRaw = String((req.body as any)?.folder ?? '').trim();
|
||||||
|
if (!file) return res.status(400).json({ error: 'file erforderlich' });
|
||||||
|
const orig = file.originalname || 'upload';
|
||||||
|
const lower = orig.toLowerCase();
|
||||||
|
const ext = lower.endsWith('.mp3') ? '.mp3' : lower.endsWith('.wav') ? '.wav' : '';
|
||||||
|
if (!ext) return res.status(400).json({ error: 'Nur MP3 oder WAV erlaubt' });
|
||||||
|
const base = path.parse(orig).name.replace(/[<>:"/\\|?*\x00-\x1f]/g, '_');
|
||||||
|
const safeFolder = folderRaw && !folderRaw.includes('..') ? folderRaw : '';
|
||||||
|
const targetDir = path.join(SOUNDS_DIR, safeFolder);
|
||||||
|
fs.mkdirSync(targetDir, { recursive: true });
|
||||||
|
let targetPath = path.join(targetDir, `${base}${ext}`);
|
||||||
|
let i = 2;
|
||||||
|
while (fs.existsSync(targetPath)) {
|
||||||
|
targetPath = path.join(targetDir, `${base}-${i}${ext}`);
|
||||||
|
i += 1;
|
||||||
|
}
|
||||||
|
fs.writeFileSync(targetPath, file.buffer);
|
||||||
|
const rel = path.relative(SOUNDS_DIR, targetPath).replace(/\\/g, '/');
|
||||||
|
return res.json({ ok: true, relativePath: rel, fileName: path.basename(targetPath) });
|
||||||
|
} catch (e: any) {
|
||||||
|
console.error('upload error:', e);
|
||||||
|
return res.status(500).json({ error: e?.message ?? 'Unbekannter Fehler' });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import React, { useEffect, useMemo, useRef, useState } from 'react';
|
import React, { useEffect, useMemo, useRef, useState } from 'react';
|
||||||
import ReactDOM from 'react-dom';
|
import ReactDOM from 'react-dom';
|
||||||
import { fetchChannels, fetchSounds, playSound, setVolumeLive, getVolume, adminStatus, adminLogin, adminLogout, adminDelete, adminRename, playUrl, fetchCategories, createCategory, assignCategories, clearBadges, updateCategory, deleteCategory, partyStart, partyStop, subscribeEvents } from './api';
|
import { fetchChannels, fetchSounds, playSound, setVolumeLive, getVolume, adminStatus, adminLogin, adminLogout, adminDelete, adminRename, playUrl, fetchCategories, createCategory, assignCategories, clearBadges, updateCategory, deleteCategory, partyStart, partyStop, subscribeEvents, uploadFile } from './api';
|
||||||
import type { VoiceChannelInfo, Sound, Category } from './types';
|
import type { VoiceChannelInfo, Sound, Category } from './types';
|
||||||
import { getCookie, setCookie } from './cookies';
|
import { getCookie, setCookie } from './cookies';
|
||||||
|
|
||||||
|
|
@ -299,7 +299,18 @@ export default function App() {
|
||||||
<div className="broccoli">🥦</div>
|
<div className="broccoli">🥦</div>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
<header className="flex items-center justify-between p-6">
|
<header className="flex items-center justify-between p-6" onDragOver={(e)=>{ e.preventDefault(); }} onDrop={async (e)=>{
|
||||||
|
try{
|
||||||
|
e.preventDefault();
|
||||||
|
if(!isAdmin){ setError('Login required for upload'); return; }
|
||||||
|
const files = Array.from(e.dataTransfer?.files || []).filter(f=>/\.(mp3|wav)$/i.test(f.name));
|
||||||
|
if(files.length===0){ setInfo('Drop MP3/WAV files to upload'); return; }
|
||||||
|
for(const f of files){ await uploadFile(f); }
|
||||||
|
setInfo(`${files.length} file(s) uploaded`);
|
||||||
|
const resp = await fetchSounds(query, activeFolder === '__favs__' ? '__all__' : activeFolder, activeCategoryId || undefined);
|
||||||
|
setSounds(resp.items); setTotal(resp.total); setFolders(resp.folders);
|
||||||
|
}catch(err:any){ setError(err?.message||'Upload failed'); setInfo(null); }
|
||||||
|
}}>
|
||||||
<div className="flex items-center">
|
<div className="flex items-center">
|
||||||
<div>
|
<div>
|
||||||
<h1 className="text-4xl font-bold">
|
<h1 className="text-4xl font-bold">
|
||||||
|
|
|
||||||
|
|
@ -184,6 +184,22 @@ export async function playUrl(url: string, guildId: string, channelId: string, v
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function uploadFile(file: File, folder?: string) {
|
||||||
|
const form = new FormData();
|
||||||
|
form.append('file', file);
|
||||||
|
if (folder) form.append('folder', folder);
|
||||||
|
const res = await fetch(`${API_BASE}/upload`, {
|
||||||
|
method: 'POST',
|
||||||
|
credentials: 'include',
|
||||||
|
body: form
|
||||||
|
});
|
||||||
|
if (!res.ok) {
|
||||||
|
const data = await res.json().catch(()=>({}));
|
||||||
|
throw new Error(data?.error || 'Upload failed');
|
||||||
|
}
|
||||||
|
return res.json();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue