diff --git a/backend/src/db.js b/backend/src/db.js index 8255f0c..221723e 100644 --- a/backend/src/db.js +++ b/backend/src/db.js @@ -521,13 +521,11 @@ if (!db.prepare("SELECT name FROM sqlite_master WHERE type='table' AND name='cal `); } -// upload_shares Fehlversuche-Tracking +// short_url auf upload_shares { - const usCols = db.pragma('table_info(upload_shares)').map(c => c.name); - if (!usCols.includes('failed_attempts')) - db.exec('ALTER TABLE upload_shares ADD COLUMN failed_attempts INTEGER DEFAULT 0'); - if (!usCols.includes('deactivated_reason')) - db.exec("ALTER TABLE upload_shares ADD COLUMN deactivated_reason TEXT DEFAULT NULL"); + const cols = db.pragma('table_info(upload_shares)').map(c => c.name); + if (!cols.includes('short_url')) + db.exec('ALTER TABLE upload_shares ADD COLUMN short_url TEXT DEFAULT NULL'); } module.exports = db; diff --git a/backend/src/tools/dateien/upload-share.js b/backend/src/tools/dateien/upload-share.js index a145fd8..14fdbe7 100644 --- a/backend/src/tools/dateien/upload-share.js +++ b/backend/src/tools/dateien/upload-share.js @@ -1,4 +1,5 @@ const express = require('express'); +const https = require('https'); const bcrypt = require('bcryptjs'); const crypto = require('crypto'); const multer = require('multer'); @@ -8,6 +9,20 @@ const db = require('../../db'); const { authenticate, requireAdmin } = require('../../middleware/auth'); const router = express.Router(); +// Node-seitige URL-Kürzung (kein CORS-Problem) +function shortenUrl(longUrl) { + return new Promise(resolve => { + const url = `https://is.gd/create.php?format=simple&url=${encodeURIComponent(longUrl)}`; + const req = https.get(url, { headers:{'User-Agent':'DickenDock/1.0'} }, res => { + let data = ''; + res.on('data', d => data += d); + res.on('end', () => resolve(data.trim().startsWith('http') ? data.trim() : null)); + }); + req.on('error', () => resolve(null)); + req.setTimeout(5000, () => { req.destroy(); resolve(null); }); + }); +} + const UPLOAD_DIR = process.env.UPLOAD_DIR || '/data/uploads'; function generateToken() { return crypto.randomBytes(20).toString('base64url'); } @@ -40,7 +55,7 @@ router.get('/', authenticate, (req, res) => { res.json({ shares, folder }); }); -router.post('/', authenticate, (req, res) => { +router.post('/', authenticate, async (req, res) => { if (!canUseUploadShare(req.user.id)) return res.status(403).json({ error:'Keine Berechtigung' }); const { password, expires_hours=24, max_size_mb=10 } = req.body; if (!password?.trim()) return res.status(400).json({ error:'Passwort erforderlich' }); @@ -53,7 +68,13 @@ router.post('/', authenticate, (req, res) => { VALUES (?,?,?,?,?,?,datetime('now','localtime')) `).run(req.user.id, token, hash, expires, Number(max_size_mb), folder.id); const share = db.prepare('SELECT * FROM upload_shares WHERE id=?').get(r.lastInsertRowid); - res.json({ ...share, url:`/u/${token}` }); + // URL direkt beim Erstellen kürzen (server-seitig, kein CORS) + const baseUrl = `${req.protocol}://${req.get('host')}`; + const fullUrl = `${baseUrl}/u/${token}`; + let shortUrl = null; + try { shortUrl = await shortenUrl(fullUrl); } catch {} + if (shortUrl) db.prepare('UPDATE upload_shares SET short_url=? WHERE id=?').run(shortUrl, share.id); + res.json({ ...share, short_url: shortUrl, url: fullUrl }); }); router.delete('/:id', authenticate, (req, res) => { diff --git a/frontend/src/tools/dateien.jsx b/frontend/src/tools/dateien.jsx index 1bf1167..b3d97b8 100644 --- a/frontend/src/tools/dateien.jsx +++ b/frontend/src/tools/dateien.jsx @@ -170,8 +170,6 @@ function UploadShareManager({ toast }) { const [busy, setBusy] = useState(false); const [copied, setCopied] = useState(null); const [sharePws, setSharePws] = useState({}); // {shareId: password} für Passwort-Kopieren - const [shortUrls, setShortUrls] = useState({}); // {shareId: shortUrl} - const [shortening, setShortening] = useState(null); // shareId being shortened const load = () => api('/upload-shares').then(d=>{ setShares(d.shares||[]); setFolder(d.folder); }).catch(()=>{}); useEffect(()=>{ load(); },[]); @@ -220,41 +218,22 @@ function UploadShareManager({ toast }) { setShortening(null); }; - const doCopy = (text) => { - // Fallback-Kopieren ohne navigator.clipboard (funktioniert auch in PWA/mobile) + const copyLink = (shareId, token, shortUrl) => { + // Kurz-URL vom Server nehmen (bereits beim Erstellen generiert), sonst Original + const url = shortUrl || `${window.location.origin}/u/${token}`; + // Synchron kopieren – kein await nötig if (navigator.clipboard) { - navigator.clipboard.writeText(text).catch(() => { - const ta = document.createElement('textarea'); - ta.value = text; ta.style.cssText='position:fixed;opacity:0'; - document.body.appendChild(ta); ta.focus(); ta.select(); - document.execCommand('copy'); document.body.removeChild(ta); - }); - } else { - const ta = document.createElement('textarea'); - ta.value = text; ta.style.cssText='position:fixed;opacity:0'; - document.body.appendChild(ta); ta.focus(); ta.select(); - document.execCommand('copy'); document.body.removeChild(ta); - } + navigator.clipboard.writeText(url).catch(() => fallbackCopy(url)); + } else { fallbackCopy(url); } + setCopied(token); setTimeout(()=>setCopied(null), 2500); }; - const copyLink = (shareId, token) => { - // SOFORT kopieren (synchron, innerhalb User-Gesture-Kontext) - const url = shortUrls[shareId] || `${window.location.origin}/u/${token}`; - doCopy(url); - setCopied(token); setTimeout(()=>setCopied(null), 2500); - - // Falls noch keine Kurz-URL: im Hintergrund generieren (für nächsten Klick) - if (!shortUrls[shareId] && shortening !== shareId) { - setShortening(shareId); - const fullUrl = `${window.location.origin}/u/${token}`; - fetch(`https://is.gd/create.php?format=simple&url=${encodeURIComponent(fullUrl)}`) - .then(r => r.text()) - .then(short => { - if (short.startsWith('http')) setShortUrls(p=>({...p,[shareId]:short.trim()})); - }) - .catch(()=>{}) - .finally(()=>setShortening(null)); - } + const fallbackCopy = (text) => { + const ta = document.createElement('textarea'); + ta.value = text; ta.style.cssText = 'position:fixed;top:0;left:0;opacity:0.01'; + document.body.appendChild(ta); ta.focus(); ta.select(); + try { document.execCommand('copy'); } catch {} + document.body.removeChild(ta); }; const loadLogs = () => { @@ -366,9 +345,9 @@ function UploadShareManager({ toast }) {