From f6d2bd3b39d7ac48b028485a9e3aed72cc98da2b Mon Sep 17 00:00:00 2001 From: KI-Sparringpartner Date: Mon, 3 Aug 2026 16:27:50 +0200 Subject: [PATCH] =?UTF-8?q?=F0=9F=94=A7=20FIX:=20Python-Typ-Fehler=20behob?= =?UTF-8?q?en=20+=20force-remove=20f=C3=BCr=20Deabonidation?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/routes.py | 59 +++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 45 insertions(+), 14 deletions(-) diff --git a/app/routes.py b/app/routes.py index 4affd89..c981f33 100644 --- a/app/routes.py +++ b/app/routes.py @@ -22,7 +22,7 @@ class UnsubscribeRequest(BaseModel): @router.get("/projects") # Listet abonierte Projekte! async def list_projects(): """Listet lokale (abonierte) Projekte an.""" - from .main import PROJECTS_DIR, ACCESS_TOKEN + from .main import PROJECTS_DIR projects = [] @@ -33,7 +33,7 @@ async def list_projects(): if project_dir.is_dir() and not project_dir.name.startswith('.'): has_git = (project_dir / '.git').exists() - # Prüfe ob dieses Projekt bearbeitet werden darf (chmod-check) + # Pruefe 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({ @@ -41,10 +41,11 @@ async def list_projects(): "path": str(project_dir), "subscribed": True, "has_git": bool(has_git), - "can_write": can_write #✅ NEU: Zeigt ob User Berechtigung hat! + "can_write": can_write }) - projects = all_projects + # Sortiere: Projects mit Changes zuerst (kan signale anzeigen) + projects = sorted(all_projects, key=lambda x: ("." in x["name"]), reverse=True) return {"projects": projects} @@ -52,7 +53,7 @@ async def list_projects(): @router.post("/unsubscribe") # Deabonination Endpoint! async def unsubscribe_repo(req: UnsubscribeRequest): """Loeschen eines abonnierten Projekts.""" - from .main import PROJECTS_DIR + from .main import PROJECTS_DIR, get_token target_dir = PROJECTS_DIR / req.project_name @@ -61,15 +62,44 @@ async def unsubscribe_repo(req: UnsubscribeRequest): try: import shutil - shutil.rmtree(str(target_dir)) - # Erstelle Markierungsdatei um Deabonnierung zu tracken - marker_file = Path(str(PROJECTS_DIR) / f".deleted_{req.project_name}") - marker_file.touch() + # ✅ SCHRITT 1: Alle Berechtigungen normalisieren damit wir es loeschen können! + set_permissions(str(target_dir)) + + # ✅ SCHRITT 2: Mit FORCE-LöSCHUNG entfernen (wegen root-Besitzern) + import stat + + def force_remove_dir(dir_path): + """Erzwingt das Loeschen von Verzeichnissen.""" + for root, dirs, files in os.walk(str(dir_path), topdown=False): + for d in dirs: + dir_full = Path(root) / d + try: + dir_full.chmod(0o755 | stat.S_IWUSR | stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH) + except Exception: + pass + for f in files: + file_full = Path(root) / f + try: + # Schreibrechte wiederherstellen + os.chmod(str(file_full), 0o666) + file_full.unlink() + except Exception: + pass + + force_remove_dir(target_dir) + shutil.rmtree(str(target_dir)) + + return {"success": True, "message": f"{req.project_name} erfolgreich geloescht"} - return {"success": True, "message": f"{target_dir.name} erfolgreich geloescht"} except Exception as e: - return {"success": False, "error": str(e)} + # Fallback mit rm -rf (Unix-Force) + try: + import subprocess + subprocess.run(["rm", "-rf", str(target_dir)], check=True) + return {"success": True, "message": f"{req.project_name} geloescht (force-mode)"} + except Exception as e2: + return {"success": False, "error": str(e)} @router.get("/repos") # Listet Gitea-Repos! @@ -85,7 +115,7 @@ async def list_repos(): headers = {"Authorization": f"token {token}"} async with httpx.AsyncClient() as client: - resp = await client.get(f"{GITEE_URL}/api/v1/user/repos", headers=headers) + resp = await client.get(f"{GITEE_URL.rstrip('/')}/api/v1/user/repos", headers=headers) return resp.json() @@ -106,7 +136,8 @@ async def subscribe_repo(req: SubscribeRequest): if not result["success"]: return {"error": result.get("error"), "success": False} - # ✅ KEINE CHANGES-MARKER NACH DEM KLONEN MEHR! + # ✅ BERECHTIGUNGEN SETZEN NACH DEM KLOONEN! + set_permissions(str(target_dir)) generate_changelog(target_dir) @@ -119,5 +150,5 @@ async def subscribe_repo(req: SubscribeRequest): # Importiere die Funktionen am Ende damit sie verfügbar sind! -from .git_manager import clone_repo +from .git_manager import clone_repo, set_permissions from .changelog_gen import generate_changelog \ No newline at end of file