Files
TaskMate/backend/routes/git.js
2025-12-30 19:17:07 +00:00

970 Zeilen
28 KiB
JavaScript

/**
* 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;