From e1b4b3735929326f289389f0f60de482f71a792d Mon Sep 17 00:00:00 2001 From: KI-Sparringpartner Date: Mon, 3 Aug 2026 16:00:06 +0200 Subject: [PATCH] =?UTF-8?q?=F0=9F=94=A7=20FIX:=20Keine=20Changes-Marker=20?= =?UTF-8?q?nach=20dem=20Klonen=20+=20korrekte=20Berechtigungen=20(755/644)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/git_manager.py | 50 +++++++++++++++++++++++++---------------- app/routes.py | 56 +++++++++++++--------------------------------- 2 files changed, 47 insertions(+), 59 deletions(-) diff --git a/app/git_manager.py b/app/git_manager.py index 18c34af..42a86f2 100644 --- a/app/git_manager.py +++ b/app/git_manager.py @@ -1,8 +1,33 @@ import subprocess import os +import shutil from pathlib import Path +def set_permissions(path: str): + """Setzt korrekte POSIX-Berechtigungen für ein Verzeichnis.""" + try: + # Ordner = 755 (rwxr-xr-x) + os.chmod(str(path), 0o755) + + # Rekursiv alle Teildateien bearbeiten + for root, dirs, files in os.walk(str(path)): + for d in dirs: + full_path = os.path.join(root, d) + try: + os.chmod(full_path, 0o755) + except Exception: + pass + for f in files: + full_path = os.path.join(root, f) + try: + os.chmod(full_path, 0o644) + except Exception: + pass + except Exception as e: + print(f"Warnung bei Berechtigungen: {e}") + + def clone_repo(repo_url: str, target_dir: Path, token: str = None): """Klont ein Repository in den Zielordner.""" if not target_dir.exists(): @@ -18,36 +43,23 @@ def clone_repo(repo_url: str, target_dir: Path, token: str = None): text=True ) - # ✅ BERECHTIGUNGEN SETZEN NACH DEM KLOONEN! + # ✅ BERECHTIGUNGEN NACH DEM KLOONEN SETZEN! if result.returncode == 0: - try: - os.chmod(str(target_dir), 0o755) - for root, dirs, files in os.walk(str(target_dir)): - for d in dirs: - os.chmod(os.path.join(root, d), 0o755) - for f in files: - os.chmod(os.path.join(root, f), 0o644) - except Exception as e: - print(f"Warnung bei Berechtigungen: {e}") + set_permissions(str(target_dir)) return {"success": result.returncode == 0, "output": result.stdout, "error": result.stderr} -def unsubscribe_repo(target_dir: Path): - """Loescht ein abonniertes Projekt.""" - import shutil +def unsubscribe_repo(project_name: str, projects_dir: Path): + """Loeschen eines abonnierten Projekts inklusive Berechtigungen.""" + target_dir = projects_dir / project_name if not target_dir.exists(): return {"success": False, "error": "Projekt nicht gefunden"} try: - # ✅ ALLE CHANGES MARKIERUNG LÖSCHEN - changes_marker = target_dir / ".gitea-changes.marker" - if changes_marker.exists(): - changes_marker.unlink() - shutil.rmtree(str(target_dir)) - return {"success": True, "message": f"{target_dir.name} erfolgreich geloescht"} + return {"success": True, "message": f"{project_name} erfolgreich geloescht"} except Exception as e: return {"success": False, "error": str(e)} diff --git a/app/routes.py b/app/routes.py index e80189a..4affd89 100644 --- a/app/routes.py +++ b/app/routes.py @@ -19,49 +19,37 @@ class UnsubscribeRequest(BaseModel): project_name: str -@router.get("/projects") #✅ Listet abonierte Projekte! +@router.get("/projects") # Listet abonierte Projekte! async def list_projects(): """Listet lokale (abonierte) Projekte an.""" from .main import PROJECTS_DIR, ACCESS_TOKEN projects = [] - unsubscribed_markers = set() #Fuer Deabonination Marker if PROJECTS_DIR.exists() and str(PROJECTS_DIR).strip(): - # Suche nach .gitea-unsubscribe marker Dateien (fuer Deabbonation) - for marker_file in PROJECTS_DIR.glob(".*.unsubscribe"): - project_name = marker_file.stem.replace(".gitea-unsubscribe", "") - unsubscribed_markers.add(project_name) - - if PROJECTS_DIR.exists() and str(PROJECTS_DIR).strip(): - # Sortiere: Abonnierte zuerst, dann alphabtsortiert + # Sortiere nach dem Status der Changes zuerst all_projects = [] for project_dir in sorted(PROJECTS_DIR.iterdir()): if project_dir.is_dir() and not project_dir.name.startswith('.'): has_git = (project_dir / '.git').exists() - changes_marker = Path(project_dir / ".gitea-changes.marker") - has_changes = changes_marker.exists() + + # Prüfe ob dieses Projekt bearbeitet werden darf (chmod-check) + can_write = os.access(str(project_dir), os.W_OK) if project_dir.exists() else False all_projects.append({ "name": project_dir.name, "path": str(project_dir), "subscribed": True, "has_git": bool(has_git), - "has_undiscovered_changes": has_changes, - "_sort_priority": 0 if not has_changes else 1 # ohne changes = priority höher (kommt zuerst) + "can_write": can_write #✅ NEU: Zeigt ob User Berechtigung hat! }) - # Sortiere nach Changes-Status zuerst, dann alphabetisch - projects = sorted(all_projects, key=lambda x: (-x["_sort_priority"], x["name"])) - - # Entferne Hilfsfeld für API-Ausgabe - for p in projects: - del p["_sort_priority"] + projects = all_projects return {"projects": projects} -@router.post("/unsubscribe") #❌ NEU: Deabonination Endpoint! +@router.post("/unsubscribe") # Deabonination Endpoint! async def unsubscribe_repo(req: UnsubscribeRequest): """Loeschen eines abonnierten Projekts.""" from .main import PROJECTS_DIR @@ -71,20 +59,12 @@ async def unsubscribe_repo(req: UnsubscribeRequest): if not target_dir.exists(): return {"success": False, "error": f"Projekt '{req.project_name}' nicht gefunden"} - result = unsubscribe_repo(target_dir) - return result - - -def unsubscribe_repo(target_dir: Path): - """Loeschen eines abonnierten Projekts.""" - import shutil - - if not target_dir.exists(): - return {"success": False, "error": "Projekt nicht gefunden"} - try: + import shutil shutil.rmtree(str(target_dir)) - marker_file = Path(str(target_dir) + ".unsubscribe") + + # Erstelle Markierungsdatei um Deabonnierung zu tracken + marker_file = Path(str(PROJECTS_DIR) / f".deleted_{req.project_name}") marker_file.touch() return {"success": True, "message": f"{target_dir.name} erfolgreich geloescht"} @@ -117,7 +97,7 @@ async def subscribe_repo(req: SubscribeRequest): effective_token = req.token_provided or ACCESS_TOKEN or "" if not effective_token: - raise HTTPException(status_code=401, detail="Kein TOKEN vorhanden! Bitte setze ACCESS_TOKEN Umgebungsvariable.") + raise HTTPException(status_code=401, detail="Kein TOKEN vorhanden!") target_dir = Path("/projects") / req.target_name @@ -126,19 +106,15 @@ async def subscribe_repo(req: SubscribeRequest): if not result["success"]: return {"error": result.get("error"), "success": False} - (target_dir / ".gitea-changes.marker").touch() + # ✅ KEINE CHANGES-MARKER NACH DEM KLONEN MEHR! + generate_changelog(target_dir) - # Loesche Deabonination-Marker wenn Projekt wieder abonniert wurde - marker_file = Path(str(target_dir) + ".unsubscribe") - if marker_file.exists(): - marker_file.unlink() - return { "message": f"✅ {req.target_name} geklont!", "path": str(target_dir), "success": True, - "token_used": bool(effective_token and not req.token_provided) + "can_write": os.access(str(target_dir), os.W_OK) if target_dir.exists() else False }