diff --git a/app/git_manager.py b/app/git_manager.py index 0c03267..18c34af 100644 --- a/app/git_manager.py +++ b/app/git_manager.py @@ -1,4 +1,5 @@ import subprocess +import os from pathlib import Path @@ -17,9 +18,40 @@ def clone_repo(repo_url: str, target_dir: Path, token: str = None): text=True ) + # ✅ BERECHTIGUNGEN SETZEN NACH DEM KLOONEN! + 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}") + return {"success": result.returncode == 0, "output": result.stdout, "error": result.stderr} +def unsubscribe_repo(target_dir: Path): + """Loescht ein abonniertes Projekt.""" + import shutil + + 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"} + except Exception as e: + return {"success": False, "error": str(e)} + + def push_changes(repo_path: Path, message: str = "Update 🧠"): """Pusht lokale Änderungen ins Repository.""" result = subprocess.run( diff --git a/app/main.py b/app/main.py index c59455d..00bdead 100644 --- a/app/main.py +++ b/app/main.py @@ -5,7 +5,7 @@ from fastapi.responses import HTMLResponse # Umgebungsvariablen mit Fallback GITEE_URL = os.getenv("GITEE_URL", "https://git.carabella.ch/") -ACCESS_TOKEN = os.getenv("ACCESS_TOKEN", "") # Leerlassen, falls nicht gesetzt +ACCESS_TOKEN = os.getenv("ACCESS_TOKEN", "") # Leerlassen falls nicht gesetzt COMMIT_NAME = os.getenv("COMMIT_NAME", "KI-Sparringpartner") # Projekt-Ordner (gemountet im Docker) diff --git a/app/routes.py b/app/routes.py index 8334bbd..e80189a 100644 --- a/app/routes.py +++ b/app/routes.py @@ -1,4 +1,5 @@ import httpx +import os from pathlib import Path from fastapi import APIRouter, HTTPException from pydantic import BaseModel @@ -13,39 +14,90 @@ class SubscribeRequest(BaseModel): token_provided: str | None = None -@router.get("/projects") #✅ NEU: Listet abonierte Projekte! +class UnsubscribeRequest(BaseModel): + """Schema fuer Deabonination""" + project_name: str + + +@router.get("/projects") #✅ Listet abonierte Projekte! async def list_projects(): """Listet lokale (abonierte) Projekte an.""" - from .main import PROJECTS_DIR, get_token, ACCESS_TOKEN + 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 + all_projects = [] for project_dir in sorted(PROJECTS_DIR.iterdir()): if project_dir.is_dir() and not project_dir.name.startswith('.'): - # Prüfe ob es ein Git-Repo ist has_git = (project_dir / '.git').exists() - - # Prüfe auf unentdeckte Changes - changes_marker = project_dir /'.gitea-changes.marker' + changes_marker = Path(project_dir / ".gitea-changes.marker") has_changes = changes_marker.exists() - projects.append({ + all_projects.append({ "name": project_dir.name, "path": str(project_dir), "subscribed": True, "has_git": bool(has_git), - "has_undiscovered_changes": has_changes + "has_undiscovered_changes": has_changes, + "_sort_priority": 0 if not has_changes else 1 # ohne changes = priority höher (kommt zuerst) }) + # 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"] + return {"projects": projects} -@router.get("/repos") #✅ VORHANDEN: Listet Gitea-Repos! +@router.post("/unsubscribe") #❌ NEU: Deabonination Endpoint! +async def unsubscribe_repo(req: UnsubscribeRequest): + """Loeschen eines abonnierten Projekts.""" + from .main import PROJECTS_DIR + + target_dir = PROJECTS_DIR / req.project_name + + 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: + shutil.rmtree(str(target_dir)) + marker_file = Path(str(target_dir) + ".unsubscribe") + marker_file.touch() + + return {"success": True, "message": f"{target_dir.name} erfolgreich geloescht"} + except Exception as e: + return {"success": False, "error": str(e)} + + +@router.get("/repos") # Listet Gitea-Repos! async def list_repos(): """Listet Repositories von Gitea auf.""" from .main import GITEE_URL, ACCESS_TOKEN - token = ACCESS_TOKEN or "" #✅ Direkter Zugriff! + token = ACCESS_TOKEN or "" if not token: return {"error": "Kein Token vorhanden!", "hint": "Setze ACCESS_TOKEN Umgebungsvariable"} @@ -57,7 +109,7 @@ async def list_repos(): return resp.json() -@router.post("/subscribe") #✅ VORHANDEN: Clont Repo! +@router.post("/subscribe") # Klont Repo! async def subscribe_repo(req: SubscribeRequest): """Abonniert und klont ein Repository.""" from .main import ACCESS_TOKEN @@ -74,11 +126,14 @@ async def subscribe_repo(req: SubscribeRequest): if not result["success"]: return {"error": result.get("error"), "success": False} - # Marker für Changes erstellen (target_dir / ".gitea-changes.marker").touch() - 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), @@ -87,6 +142,6 @@ async def subscribe_repo(req: SubscribeRequest): } -# Importiere hier damit die Funktionen verfügbar sind! +# Importiere die Funktionen am Ende damit sie verfügbar sind! from .git_manager import clone_repo from .changelog_gen import generate_changelog \ No newline at end of file