/** * TASKMATE - Git Route * ===================== * API-Endpoints für Git-Operationen */ const express = require('express'); const router = express.Router(); const path = require('path'); const fs = require('fs'); const os = require('os'); const { getDb } = require('../database'); const logger = require('../utils/logger'); const gitService = require('../services/gitService'); const giteaService = require('../services/giteaService'); const multer = require('multer'); // Fester Pfad für Server-Modus (TaskMate Source-Code) const SERVER_SOURCE_PATH = '/app/taskmate-source'; // Temporäres Verzeichnis für Browser-Uploads const TEMP_UPLOAD_DIR = path.join(os.tmpdir(), 'taskmate-git-uploads'); // Multer-Konfiguration für Git-Uploads (beliebige Dateitypen) const gitUploadStorage = multer.diskStorage({ destination: (req, file, cb) => { // Erstelle eindeutiges temporäres Verzeichnis pro Upload-Session const sessionId = req.body.sessionId || Date.now().toString(); const sessionDir = path.join(TEMP_UPLOAD_DIR, sessionId); // Relativen Pfad aus dem Dateinamen extrahieren (wird vom Frontend gesendet) const relativePath = file.originalname; const fileDir = path.join(sessionDir, path.dirname(relativePath)); fs.mkdirSync(fileDir, { recursive: true }); cb(null, fileDir); }, filename: (req, file, cb) => { // Nur den Dateinamen ohne Pfad cb(null, path.basename(file.originalname)); } }); const gitUpload = multer({ storage: gitUploadStorage, limits: { fileSize: 50 * 1024 * 1024, // 50MB pro Datei files: 500 // Maximal 500 Dateien } }); /** * Hilfsfunktion: Anwendung für Projekt abrufen */ function getApplicationForProject(projectId) { const db = getDb(); return db.prepare('SELECT * FROM applications WHERE project_id = ?').get(projectId); } /** * POST /api/git/clone * Repository klonen */ router.post('/clone', async (req, res) => { try { const { projectId, repoUrl, localPath, branch } = req.body; if (!localPath) { return res.status(400).json({ error: 'localPath ist erforderlich' }); } if (!repoUrl) { return res.status(400).json({ error: 'repoUrl ist erforderlich' }); } // Clone ausführen const result = await gitService.cloneRepository(repoUrl, localPath, { branch }); if (result.success && projectId) { // Anwendung aktualisieren const db = getDb(); db.prepare('UPDATE applications SET last_sync = CURRENT_TIMESTAMP WHERE project_id = ?').run(projectId); } res.json(result); } catch (error) { logger.error('Fehler beim Klonen:', error); res.status(500).json({ error: 'Serverfehler', details: error.message }); } }); /** * GET /api/git/status/:projectId * Git-Status für ein Projekt abrufen */ router.get('/status/:projectId', (req, res) => { try { const { projectId } = req.params; const application = getApplicationForProject(projectId); if (!application) { return res.status(404).json({ error: 'Keine Anwendung für dieses Projekt konfiguriert' }); } const result = gitService.getStatus(application.local_path); res.json(result); } catch (error) { logger.error('Fehler beim Abrufen des Status:', error); res.status(500).json({ error: 'Serverfehler' }); } }); /** * POST /api/git/pull/:projectId * Pull für ein Projekt ausführen */ router.post('/pull/:projectId', (req, res) => { try { const { projectId } = req.params; const { branch } = req.body; const application = getApplicationForProject(projectId); if (!application) { return res.status(404).json({ error: 'Keine Anwendung für dieses Projekt konfiguriert' }); } // Fetch zuerst gitService.fetchRemote(application.local_path); // Dann Pull const result = gitService.pullChanges(application.local_path, { branch }); if (result.success) { // Sync-Zeitpunkt aktualisieren const db = getDb(); db.prepare('UPDATE applications SET last_sync = CURRENT_TIMESTAMP WHERE project_id = ?').run(projectId); } res.json(result); } catch (error) { logger.error('Fehler beim Pull:', error); res.status(500).json({ error: 'Serverfehler' }); } }); /** * POST /api/git/push/:projectId * Push für ein Projekt ausführen */ router.post('/push/:projectId', (req, res) => { try { const { projectId } = req.params; const { branch } = req.body; const application = getApplicationForProject(projectId); if (!application) { return res.status(404).json({ error: 'Keine Anwendung für dieses Projekt konfiguriert' }); } // Prüfe ob Remote existiert if (!gitService.hasRemote(application.local_path)) { return res.json({ success: false, error: 'Kein Remote konfiguriert. Bitte Repository zuerst vorbereiten.' }); } // Versuche normalen Push, falls das fehlschlägt wegen fehlendem Upstream, push mit -u let result = gitService.pushChanges(application.local_path, { branch }); // Falls Push wegen fehlendem Upstream fehlschlägt, versuche mit -u if (!result.success && result.error && result.error.includes('no upstream')) { const currentBranch = branch || 'main'; result = gitService.pushWithUpstream(application.local_path, currentBranch); } if (result.success) { // Sync-Zeitpunkt aktualisieren const db = getDb(); db.prepare('UPDATE applications SET last_sync = CURRENT_TIMESTAMP WHERE project_id = ?').run(projectId); } res.json(result); } catch (error) { logger.error('Fehler beim Push:', error); res.status(500).json({ error: 'Serverfehler' }); } }); /** * POST /api/git/commit/:projectId * Commit für ein Projekt erstellen */ router.post('/commit/:projectId', (req, res) => { try { const { projectId } = req.params; const { message, stageAll } = req.body; const application = getApplicationForProject(projectId); if (!application) { return res.status(404).json({ error: 'Keine Anwendung für dieses Projekt konfiguriert' }); } if (!message) { return res.status(400).json({ error: 'Commit-Nachricht ist erforderlich' }); } // Optional: Alle Änderungen stagen if (stageAll !== false) { const stageResult = gitService.stageAll(application.local_path); if (!stageResult.success) { return res.json(stageResult); } } // Autor aus eingeloggtem Benutzer const author = req.user ? { name: req.user.display_name || req.user.username, email: req.user.email || `${req.user.username.toLowerCase()}@taskmate.local` } : null; // Commit erstellen mit Autor const result = gitService.commit(application.local_path, message, author); res.json(result); } catch (error) { logger.error('Fehler beim Commit:', error); res.status(500).json({ error: 'Serverfehler' }); } }); /** * GET /api/git/commits/:projectId * Commit-Historie für ein Projekt abrufen */ router.get('/commits/:projectId', (req, res) => { try { const { projectId } = req.params; const limit = parseInt(req.query.limit) || 20; const application = getApplicationForProject(projectId); if (!application) { return res.status(404).json({ error: 'Keine Anwendung für dieses Projekt konfiguriert' }); } const result = gitService.getCommitHistory(application.local_path, limit); res.json(result); } catch (error) { logger.error('Fehler beim Abrufen der Commits:', error); res.status(500).json({ error: 'Serverfehler' }); } }); /** * GET /api/git/branches/:projectId * Branches für ein Projekt abrufen */ router.get('/branches/:projectId', (req, res) => { try { const { projectId } = req.params; const application = getApplicationForProject(projectId); if (!application) { return res.status(404).json({ error: 'Keine Anwendung für dieses Projekt konfiguriert' }); } const result = gitService.getBranches(application.local_path); res.json(result); } catch (error) { logger.error('Fehler beim Abrufen der Branches:', error); res.status(500).json({ error: 'Serverfehler' }); } }); /** * POST /api/git/checkout/:projectId * Branch wechseln */ router.post('/checkout/:projectId', (req, res) => { try { const { projectId } = req.params; const { branch } = req.body; const application = getApplicationForProject(projectId); if (!application) { return res.status(404).json({ error: 'Keine Anwendung für dieses Projekt konfiguriert' }); } if (!branch) { return res.status(400).json({ error: 'Branch ist erforderlich' }); } const result = gitService.checkoutBranch(application.local_path, branch); res.json(result); } catch (error) { logger.error('Fehler beim Branch-Wechsel:', error); res.status(500).json({ error: 'Serverfehler' }); } }); /** * POST /api/git/fetch/:projectId * Fetch von Remote ausführen */ router.post('/fetch/:projectId', (req, res) => { try { const { projectId } = req.params; const application = getApplicationForProject(projectId); if (!application) { return res.status(404).json({ error: 'Keine Anwendung für dieses Projekt konfiguriert' }); } const result = gitService.fetchRemote(application.local_path); res.json(result); } catch (error) { logger.error('Fehler beim Fetch:', error); res.status(500).json({ error: 'Serverfehler' }); } }); /** * POST /api/git/stage/:projectId * Alle Änderungen stagen */ router.post('/stage/:projectId', (req, res) => { try { const { projectId } = req.params; const application = getApplicationForProject(projectId); if (!application) { return res.status(404).json({ error: 'Keine Anwendung für dieses Projekt konfiguriert' }); } const result = gitService.stageAll(application.local_path); res.json(result); } catch (error) { logger.error('Fehler beim Stagen:', error); res.status(500).json({ error: 'Serverfehler' }); } }); /** * GET /api/git/remote/:projectId * Remote-URL abrufen */ router.get('/remote/:projectId', (req, res) => { try { const { projectId } = req.params; const application = getApplicationForProject(projectId); if (!application) { return res.status(404).json({ error: 'Keine Anwendung für dieses Projekt konfiguriert' }); } const result = gitService.getRemoteUrl(application.local_path); res.json(result); } catch (error) { logger.error('Fehler beim Abrufen der Remote-URL:', error); res.status(500).json({ error: 'Serverfehler' }); } }); /** * POST /api/git/validate-path * Pfad validieren */ router.post('/validate-path', (req, res) => { try { const { path } = req.body; if (!path) { return res.status(400).json({ error: 'Pfad ist erforderlich' }); } const isAccessible = gitService.isPathAccessible(path); const isRepo = isAccessible ? gitService.isGitRepository(path) : false; const hasRemote = isRepo ? gitService.hasRemote(path) : false; res.json({ valid: isAccessible, isRepository: isRepo, hasRemote: hasRemote, containerPath: gitService.windowsToContainerPath(path) }); } catch (error) { logger.error('Fehler bei der Pfad-Validierung:', error); res.status(500).json({ error: 'Serverfehler' }); } }); /** * POST /api/git/prepare/:projectId * Repository für Gitea vorbereiten (init, remote hinzufügen) */ router.post('/prepare/:projectId', (req, res) => { try { const { projectId } = req.params; const { repoUrl, branch } = req.body; const application = getApplicationForProject(projectId); if (!application) { return res.status(404).json({ error: 'Keine Anwendung für dieses Projekt konfiguriert' }); } if (!repoUrl) { return res.status(400).json({ error: 'repoUrl ist erforderlich' }); } const result = gitService.prepareForGitea(application.local_path, repoUrl, { branch }); if (result.success) { logger.info(`Repository vorbereitet für Projekt ${projectId}: ${repoUrl}`); } res.json(result); } catch (error) { logger.error('Fehler beim Vorbereiten des Repositories:', error); res.status(500).json({ error: 'Serverfehler' }); } }); /** * POST /api/git/set-remote/:projectId * Remote für ein Projekt setzen/aktualisieren */ router.post('/set-remote/:projectId', (req, res) => { try { const { projectId } = req.params; const { repoUrl } = req.body; const application = getApplicationForProject(projectId); if (!application) { return res.status(404).json({ error: 'Keine Anwendung für dieses Projekt konfiguriert' }); } if (!repoUrl) { return res.status(400).json({ error: 'repoUrl ist erforderlich' }); } // Prüfe ob Git-Repo existiert if (!gitService.isGitRepository(application.local_path)) { // Initialisiere Repository const initResult = gitService.initRepository(application.local_path); if (!initResult.success) { return res.json(initResult); } } // Remote setzen const result = gitService.setRemote(application.local_path, repoUrl); res.json(result); } catch (error) { logger.error('Fehler beim Setzen des Remotes:', error); res.status(500).json({ error: 'Serverfehler' }); } }); /** * POST /api/git/init-push/:projectId * Initialen Push mit Upstream-Tracking * Body: { targetBranch?: string, force?: boolean } * - targetBranch: Optional - Ziel-Branch auf Remote (z.B. "main" auch wenn lokal "master") * - force: Optional - Force-Push um Remote zu überschreiben */ router.post('/init-push/:projectId', async (req, res) => { try { const { projectId } = req.params; const { targetBranch, force } = req.body; const application = getApplicationForProject(projectId); if (!application) { return res.status(404).json({ error: 'Keine Anwendung für dieses Projekt konfiguriert' }); } // targetBranch kann null sein - dann wird der lokale Branch-Name verwendet // force: boolean - überschreibt Remote-Branch bei Konflikten const result = gitService.pushWithUpstream(application.local_path, targetBranch || null, 'origin', force === true); if (result.success) { // Sync-Zeitpunkt aktualisieren const db = getDb(); db.prepare('UPDATE applications SET last_sync = CURRENT_TIMESTAMP WHERE project_id = ?').run(projectId); // Default-Branch in Gitea aktualisieren wenn der gepushte Branch abweicht if (application.gitea_repo_url && result.branch) { try { // Owner/Repo aus URL extrahieren (z.B. https://gitea.../AegisSight/TaskMate.git) const urlMatch = application.gitea_repo_url.match(/\/([^\/]+)\/([^\/]+?)(?:\.git)?$/); if (urlMatch) { const owner = urlMatch[1]; const repoName = urlMatch[2]; const actualBranch = result.branch; // Default-Branch in Gitea setzen const updateResult = await giteaService.updateRepository(owner, repoName, { defaultBranch: actualBranch }); if (updateResult.success) { logger.info(`Default-Branch in Gitea auf '${actualBranch}' gesetzt für ${owner}/${repoName}`); result.giteaUpdated = true; result.defaultBranch = actualBranch; } else { logger.warn(`Konnte Default-Branch in Gitea nicht aktualisieren: ${updateResult.error}`); result.giteaUpdated = false; } } } catch (giteaError) { logger.warn('Fehler beim Aktualisieren des Default-Branch in Gitea:', giteaError); result.giteaUpdated = false; } } } res.json(result); } catch (error) { logger.error('Fehler beim initialen Push:', error); res.status(500).json({ error: 'Serverfehler' }); } }); /** * POST /api/git/rename-branch/:projectId * Branch umbenennen * Body: { oldName: string, newName: string } */ router.post('/rename-branch/:projectId', (req, res) => { try { const { projectId } = req.params; const { oldName, newName } = req.body; const application = getApplicationForProject(projectId); if (!application) { return res.status(404).json({ error: 'Keine Anwendung für dieses Projekt konfiguriert' }); } if (!oldName || !newName) { return res.status(400).json({ error: 'oldName und newName sind erforderlich' }); } const result = gitService.renameBranch(application.local_path, oldName, newName); res.json(result); } catch (error) { logger.error('Fehler beim Umbenennen des Branches:', error); res.status(500).json({ error: 'Serverfehler' }); } }); // ============================================ // SERVER-MODUS ENDPOINTS // Für direkte Git-Operationen auf dem TaskMate Source-Code // ============================================ /** * GET /api/git/server/status * Git-Status für Server-Dateien abrufen */ router.get('/server/status', (req, res) => { try { // Prüfe ob der Pfad existiert und ein Git-Repo ist if (!gitService.isPathAccessible(SERVER_SOURCE_PATH)) { return res.status(500).json({ success: false, error: 'Server-Verzeichnis nicht erreichbar' }); } if (!gitService.isGitRepository(SERVER_SOURCE_PATH)) { return res.status(500).json({ success: false, error: 'Server-Verzeichnis ist kein Git-Repository' }); } const result = gitService.getStatus(SERVER_SOURCE_PATH); res.json(result); } catch (error) { logger.error('[Server-Git] Fehler beim Status:', error); res.status(500).json({ error: 'Serverfehler', details: error.message }); } }); /** * GET /api/git/server/branches * Branches für Server-Dateien abrufen */ router.get('/server/branches', (req, res) => { try { const result = gitService.getBranches(SERVER_SOURCE_PATH); res.json(result); } catch (error) { logger.error('[Server-Git] Fehler beim Abrufen der Branches:', error); res.status(500).json({ error: 'Serverfehler' }); } }); /** * GET /api/git/server/commits * Commit-Historie für Server-Dateien abrufen */ router.get('/server/commits', (req, res) => { try { const limit = parseInt(req.query.limit) || 20; const result = gitService.getCommitHistory(SERVER_SOURCE_PATH, limit); res.json(result); } catch (error) { logger.error('[Server-Git] Fehler beim Abrufen der Commits:', error); res.status(500).json({ error: 'Serverfehler' }); } }); /** * GET /api/git/server/remote * Remote-URL für Server-Dateien abrufen */ router.get('/server/remote', (req, res) => { try { const result = gitService.getRemoteUrl(SERVER_SOURCE_PATH); res.json(result); } catch (error) { logger.error('[Server-Git] Fehler beim Abrufen der Remote-URL:', error); res.status(500).json({ error: 'Serverfehler' }); } }); /** * POST /api/git/server/stage * Alle Änderungen für Server-Dateien stagen */ router.post('/server/stage', (req, res) => { try { const result = gitService.stageAll(SERVER_SOURCE_PATH); res.json(result); } catch (error) { logger.error('[Server-Git] Fehler beim Stagen:', error); res.status(500).json({ error: 'Serverfehler' }); } }); /** * POST /api/git/server/commit * Commit für Server-Dateien erstellen */ router.post('/server/commit', (req, res) => { try { const { message, stageAll } = req.body; if (!message) { return res.status(400).json({ error: 'Commit-Nachricht ist erforderlich' }); } // Optional: Alle Änderungen stagen if (stageAll !== false) { const stageResult = gitService.stageAll(SERVER_SOURCE_PATH); if (!stageResult.success) { return res.json(stageResult); } } // Autor aus eingeloggtem Benutzer const author = req.user ? { name: req.user.display_name || req.user.username, email: req.user.email || `${req.user.username.toLowerCase()}@taskmate.local` } : null; const result = gitService.commit(SERVER_SOURCE_PATH, message, author); res.json(result); } catch (error) { logger.error('[Server-Git] Fehler beim Commit:', error); res.status(500).json({ error: 'Serverfehler' }); } }); /** * POST /api/git/server/push * Push für Server-Dateien ausführen */ router.post('/server/push', (req, res) => { try { const { branch, force } = req.body; // Prüfe ob Remote existiert if (!gitService.hasRemote(SERVER_SOURCE_PATH)) { return res.json({ success: false, error: 'Kein Remote konfiguriert' }); } let result; if (force) { // Force Push result = gitService.pushWithUpstream(SERVER_SOURCE_PATH, branch || null, 'origin', true); } else { // Normaler Push result = gitService.pushChanges(SERVER_SOURCE_PATH, { branch }); // Falls Push wegen fehlendem Upstream fehlschlägt, versuche mit -u if (!result.success && result.error && result.error.includes('no upstream')) { result = gitService.pushWithUpstream(SERVER_SOURCE_PATH, branch || null); } } res.json(result); } catch (error) { logger.error('[Server-Git] Fehler beim Push:', error); res.status(500).json({ error: 'Serverfehler' }); } }); /** * POST /api/git/server/pull * Pull für Server-Dateien ausführen */ router.post('/server/pull', (req, res) => { try { const { branch } = req.body; // Fetch zuerst gitService.fetchRemote(SERVER_SOURCE_PATH); // Dann Pull const result = gitService.pullChanges(SERVER_SOURCE_PATH, { branch }); res.json(result); } catch (error) { logger.error('[Server-Git] Fehler beim Pull:', error); res.status(500).json({ error: 'Serverfehler' }); } }); /** * POST /api/git/server/fetch * Fetch für Server-Dateien ausführen */ router.post('/server/fetch', (req, res) => { try { const result = gitService.fetchRemote(SERVER_SOURCE_PATH); res.json(result); } catch (error) { logger.error('[Server-Git] Fehler beim Fetch:', error); res.status(500).json({ error: 'Serverfehler' }); } }); /** * POST /api/git/server/checkout * Branch für Server-Dateien wechseln */ router.post('/server/checkout', (req, res) => { try { const { branch } = req.body; if (!branch) { return res.status(400).json({ error: 'Branch ist erforderlich' }); } const result = gitService.checkoutBranch(SERVER_SOURCE_PATH, branch); res.json(result); } catch (error) { logger.error('[Server-Git] Fehler beim Branch-Wechsel:', error); res.status(500).json({ error: 'Serverfehler' }); } }); /** * GET /api/git/server/info * Grundlegende Infos über Server-Repository */ router.get('/server/info', (req, res) => { try { const isAccessible = gitService.isPathAccessible(SERVER_SOURCE_PATH); const isRepo = isAccessible ? gitService.isGitRepository(SERVER_SOURCE_PATH) : false; const hasRemote = isRepo ? gitService.hasRemote(SERVER_SOURCE_PATH) : false; let remoteUrl = null; if (hasRemote) { const remoteResult = gitService.getRemoteUrl(SERVER_SOURCE_PATH); remoteUrl = remoteResult.url || null; } res.json({ path: SERVER_SOURCE_PATH, hostPath: '/home/claude-dev/TaskMate', accessible: isAccessible, isRepository: isRepo, hasRemote: hasRemote, remoteUrl: remoteUrl }); } catch (error) { logger.error('[Server-Git] Fehler beim Info-Abruf:', error); res.status(500).json({ error: 'Serverfehler' }); } }); // ============================================ // BROWSER-UPLOAD ENDPOINTS // Für lokale Verzeichnis-Uploads vom Browser // ============================================ /** * Hilfsfunktion: Verzeichnis rekursiv löschen */ function deleteFolderRecursive(dirPath) { if (fs.existsSync(dirPath)) { fs.readdirSync(dirPath).forEach((file) => { const curPath = path.join(dirPath, file); if (fs.lstatSync(curPath).isDirectory()) { deleteFolderRecursive(curPath); } else { fs.unlinkSync(curPath); } }); fs.rmdirSync(dirPath); } } /** * POST /api/git/browser-upload * Empfängt Dateien vom Browser und pusht sie ins Gitea * * Body (multipart/form-data): * - files: Die hochgeladenen Dateien (originalname enthält relativen Pfad) * - repoUrl: Die Gitea-Repository-URL * - branch: Der Ziel-Branch (default: main) * - commitMessage: Die Commit-Nachricht * - sessionId: Eindeutige Session-ID für den Upload */ router.post('/browser-upload', gitUpload.array('files', 500), async (req, res) => { const sessionId = req.body.sessionId || Date.now().toString(); const sessionDir = path.join(TEMP_UPLOAD_DIR, sessionId); try { const { repoUrl, branch = 'main', commitMessage } = req.body; const files = req.files; // Validierung if (!repoUrl) { return res.status(400).json({ error: 'Repository-URL ist erforderlich' }); } if (!commitMessage) { return res.status(400).json({ error: 'Commit-Nachricht ist erforderlich' }); } if (!files || files.length === 0) { return res.status(400).json({ error: 'Keine Dateien hochgeladen' }); } logger.info(`[Browser-Upload] ${files.length} Dateien empfangen für ${repoUrl}`); // Git-Repository initialisieren const initResult = gitService.initRepository(sessionDir); if (!initResult.success) { throw new Error('Git-Initialisierung fehlgeschlagen: ' + initResult.error); } // Remote hinzufügen const remoteResult = gitService.addRemote(sessionDir, repoUrl, 'origin'); if (!remoteResult.success) { throw new Error('Remote hinzufügen fehlgeschlagen: ' + remoteResult.error); } // Autor aus eingeloggtem Benutzer const author = req.user ? { name: req.user.display_name || req.user.username, email: req.user.email || `${req.user.username.toLowerCase()}@taskmate.local` } : null; // Alle Dateien stagen const stageResult = gitService.stageAll(sessionDir); if (!stageResult.success) { throw new Error('Staging fehlgeschlagen: ' + stageResult.error); } // Commit erstellen const commitResult = gitService.commit(sessionDir, commitMessage, author); if (!commitResult.success) { throw new Error('Commit fehlgeschlagen: ' + commitResult.error); } // Push mit Upstream const pushResult = gitService.pushWithUpstream(sessionDir, branch, 'origin', false); if (!pushResult.success) { // Bei Fehler: Versuche Force-Push falls Branch existiert if (pushResult.error && pushResult.error.includes('rejected')) { logger.warn('[Browser-Upload] Normaler Push abgelehnt, versuche mit Force...'); const forcePushResult = gitService.pushWithUpstream(sessionDir, branch, 'origin', true); if (!forcePushResult.success) { throw new Error('Push fehlgeschlagen: ' + forcePushResult.error); } } else { throw new Error('Push fehlgeschlagen: ' + pushResult.error); } } logger.info(`[Browser-Upload] Erfolgreich nach ${repoUrl}/${branch} gepusht`); // Erfolgreich - Aufräumen deleteFolderRecursive(sessionDir); res.json({ success: true, message: `${files.length} Dateien erfolgreich nach ${branch} gepusht`, filesCount: files.length, branch: branch, commit: commitResult.hash || null }); } catch (error) { logger.error('[Browser-Upload] Fehler:', error); // Bei Fehler aufräumen try { deleteFolderRecursive(sessionDir); } catch (cleanupError) { logger.warn('[Browser-Upload] Aufräumen fehlgeschlagen:', cleanupError); } res.status(500).json({ success: false, error: error.message || 'Upload fehlgeschlagen' }); } }); /** * POST /api/git/browser-upload-prepare * Bereitet einen Upload vor und gibt eine Session-ID zurück */ router.post('/browser-upload-prepare', (req, res) => { const sessionId = `upload_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`; const sessionDir = path.join(TEMP_UPLOAD_DIR, sessionId); try { fs.mkdirSync(sessionDir, { recursive: true }); res.json({ success: true, sessionId: sessionId, maxFileSize: 50 * 1024 * 1024, // 50MB maxFiles: 500 }); } catch (error) { logger.error('[Browser-Upload] Prepare fehlgeschlagen:', error); res.status(500).json({ error: 'Vorbereitung fehlgeschlagen' }); } }); /** * DELETE /api/git/browser-upload/:sessionId * Löscht eine Upload-Session (für Abbruch) */ router.delete('/browser-upload/:sessionId', (req, res) => { const { sessionId } = req.params; const sessionDir = path.join(TEMP_UPLOAD_DIR, sessionId); try { if (fs.existsSync(sessionDir)) { deleteFolderRecursive(sessionDir); } res.json({ success: true }); } catch (error) { logger.error('[Browser-Upload] Löschen fehlgeschlagen:', error); res.status(500).json({ error: 'Löschen fehlgeschlagen' }); } }); module.exports = router;