diff --git a/backend/src/tools/kalkulator3d/routes.js b/backend/src/tools/kalkulator3d/routes.js index 298f50c..773f13e 100644 --- a/backend/src/tools/kalkulator3d/routes.js +++ b/backend/src/tools/kalkulator3d/routes.js @@ -31,12 +31,81 @@ router.get('/', authenticate, (req, res) => { ORDER BY c.created_at DESC `).all(uid); + // Geteilt von mir + const sharedByMe = db.prepare(` + SELECT c.*, u.username as shared_with_name, cs.created_at as shared_at + FROM calculations c + JOIN calculation_shares cs ON cs.calc_id = c.id + JOIN users u ON u.id = cs.shared_with + WHERE c.user_id = ? + ORDER BY c.name ASC + `).all(uid); + + // has_files auch für shared items prüfen (anhand owner's Ordner) + const sharedWithFiles = shared.map(i => { + const ownerBase = db.prepare( + "SELECT id FROM folders WHERE user_id=? AND name='3D-Modelle' AND parent_id IS NULL" + ).get(i.user_id); + let has_files = false; + if (ownerBase) { + const sub = db.prepare( + 'SELECT id FROM folders WHERE user_id=? AND name=? AND parent_id=?' + ).get(i.user_id, i.name, ownerBase.id); + if (sub) { + const cnt = db.prepare('SELECT COUNT(*) AS c FROM files WHERE folder_id=?').get(sub.id); + has_files = cnt.c > 0; + } + } + return { ...i, has_files }; + }); + res.json({ - own: items.map(i => ({ ...i, has_files: !!filesMap[i.name] })), - shared: shared, + own: items.map(i => ({ ...i, has_files: !!filesMap[i.name] })), + shared: sharedWithFiles, + sharedByMe: sharedByMe.map(i => ({ ...i, has_files: !!filesMap[i.name] })), }); }); +// Dateien eines Modells abrufen (für Owner + shared users) +router.get('/:id/files', authenticate, (req, res) => { + const uid = req.user.id; + const calc = db.prepare('SELECT * FROM calculations WHERE id=?').get(req.params.id); + if (!calc) return res.status(404).json({ error: 'Nicht gefunden' }); + const isOwner = calc.user_id === uid; + const isShared = db.prepare('SELECT id FROM calculation_shares WHERE calc_id=? AND shared_with=?').get(calc.id, uid); + if (!isOwner && !isShared) return res.status(403).json({ error: 'Kein Zugriff' }); + + const baseFolder = db.prepare( + "SELECT id FROM folders WHERE user_id=? AND name='3D-Modelle' AND parent_id IS NULL" + ).get(calc.user_id); + if (!baseFolder) return res.json([]); + const sub = db.prepare('SELECT id FROM folders WHERE user_id=? AND name=? AND parent_id=?') + .get(calc.user_id, calc.name, baseFolder.id); + if (!sub) return res.json([]); + res.json(db.prepare('SELECT * FROM files WHERE folder_id=? ORDER BY created_at ASC').all(sub.id)); +}); + +// Einzelne Datei herunterladen (Owner + shared users) +router.get('/:id/files/:fileId/download', authenticate, (req, res) => { + const uid = req.user.id; + const calc = db.prepare('SELECT * FROM calculations WHERE id=?').get(req.params.id); + if (!calc) return res.status(404).json({ error: 'Nicht gefunden' }); + const isOwner = calc.user_id === uid; + const isShared = db.prepare('SELECT id FROM calculation_shares WHERE calc_id=? AND shared_with=?').get(calc.id, uid); + if (!isOwner && !isShared) return res.status(403).json({ error: 'Kein Zugriff' }); + + const file = db.prepare('SELECT * FROM files WHERE id=?').get(req.params.fileId); + if (!file) return res.status(404).json({ error: 'Datei nicht gefunden' }); + + const path = require('path'); + const fs = require('fs'); + const UPLOAD_DIR = process.env.UPLOAD_DIR || '/data/uploads'; + const fp = path.join(UPLOAD_DIR, file.filename); + if (!fs.existsSync(fp)) return res.status(404).json({ error: 'Datei fehlt' }); + res.download(fp, file.originalname); +}); + + // Share-Management router.get('/:id/shares', authenticate, (req, res) => { const calc = db.prepare('SELECT * FROM calculations WHERE id=? AND user_id=?').get(req.params.id, req.user.id); diff --git a/frontend/src/tools/kalkulator3d.jsx b/frontend/src/tools/kalkulator3d.jsx index 775b6b4..0269eb8 100644 --- a/frontend/src/tools/kalkulator3d.jsx +++ b/frontend/src/tools/kalkulator3d.jsx @@ -541,6 +541,68 @@ export default function Kalkulator3D({ toast, editData, onEditDone, mobile, setA } // ── Archiv ────────────────────────────────────────────────────────────── +// ── Datei-Downloads für ein Archiv-Modell ──────────────────────────────────── +function ModelFiles({ calcId, toast }) { + const [files, setFiles] = useState(null); // null = noch nicht geladen + const [open, setOpen] = useState(false); + + const load = async () => { + if (files !== null) return; + try { setFiles(await api(`/tools/kalkulator3d/${calcId}/files`)); } + catch { setFiles([]); } + }; + + const download = async f => { + try { + const res = await fetch(`/api/tools/kalkulator3d/${calcId}/files/${f.id}/download`, { + headers:{ Authorization:`Bearer ${localStorage.getItem('sk_token')}` } + }); + if (!res.ok) { toast('Download fehlgeschlagen','error'); return; } + const blob = await res.blob(); + const a = Object.assign(document.createElement('a'),{ href:URL.createObjectURL(blob), download:f.originalname }); + document.body.appendChild(a); a.click(); document.body.removeChild(a); + setTimeout(()=>URL.revokeObjectURL(a.href),1000); + } catch(e){ toast(e.message,'error'); } + }; + + const toggle = () => { if (!open) load(); setOpen(v=>!v); }; + + return ( +