import httpx import os from pathlib import Path from fastapi import APIRouter, HTTPException from pydantic import BaseModel router = APIRouter() class SubscribeRequest(BaseModel): """Schema für Subscription Request""" repo_url: str target_name: str = "imported-repo" token_provided: str | None = None class UnsubscribeRequest(BaseModel): """Schema fuer Deabonination""" project_name: str class PushRequest(BaseModel): """Schema für Push mit Changelog-Content""" project_name: str commit_message: str = "AI-bearbeitete Änderungen pushen" changelog_content: str | None = None @router.get("/projects") # Listet abonierte Projekte! async def list_projects(): """Listet lokale (abonierte) Projekte an mit Git-Status.""" from .main import PROJECTS_DIR from .git_manager import get_git_status projects = [] if PROJECTS_DIR.exists() and str(PROJECTS_DIR).strip(): 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() can_write = os.access(str(project_dir), os.W_OK) if project_dir.exists() else False # Git Status checken git_status = {"has_changes": False, "uncommitted_files": []} if has_git: try: git_status = get_git_status(project_dir) except Exception as e: print(f"Git status error: {e}") all_projects.append({ "name": project_dir.name, "path": str(project_dir), "subscribed": True, "has_git": bool(has_git), "can_write": can_write, "has_changes": git_status.get("has_changes", False), "uncommitted_files": git_status.get("uncommitted_files", []) }) # Sortiere: Projects mit Changes zuerst projects = sorted(all_projects, key=lambda x: (not x["has_changes"])) return {"projects": projects} @router.post("/unsubscribe") # 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"} try: # Alles auf Schreibbar setzen fuer Loeschen set_permissions(str(target_dir)) import shutil shutil.rmtree(str(target_dir)) return {"success": True, "message": f"{req.project_name} erfolgreich geloescht"} except Exception as 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! async def list_repos(): """Listet Repositories von Gitea auf.""" from .main import GITEE_URL, ACCESS_TOKEN token = ACCESS_TOKEN or "" if not token: return {"error": "Kein Token vorhanden!", "hint": "Setze ACCESS_TOKEN Umgebungsvariable"} headers = {"Authorization": f"token {token}"} async with httpx.AsyncClient() as client: resp = await client.get(f"{GITEE_URL.rstrip('/')}/api/v1/user/repos", headers=headers) return resp.json() @router.post("/subscribe") # Klont Repo! async def subscribe_repo(req: SubscribeRequest): """Abonniert und klont ein Repository.""" from .main import ACCESS_TOKEN effective_token = req.token_provided or ACCESS_TOKEN or "" if not effective_token: raise HTTPException(status_code=401, detail="Kein TOKEN vorhanden!") target_dir = Path("/projects") / req.target_name result = clone_repo(req.repo_url, target_dir, token=effective_token) if not result["success"]: return {"error": result.get("error"), "success": False} # BERECHTIGUNGEN SETZEN NACH DEM KLOONEN! set_permissions(str(target_dir)) generate_changelog(target_dir) # Initialen Push nach dem ersten Clone (optional) try: push_changes(target_dir, "Initial commit from gitea-sync-ai") except Exception as e: print(f"Initial push skipped: {e}") return { "message": f"✅ {req.target_name} geklont!", "path": str(target_dir), "success": True, "can_write": os.access(str(target_dir), os.W_OK) if target_dir.exists() else False } @router.post("/push") # Push zu Gitea! async def push_to_gitea(req: PushRequest): """Pusht Changes eines Projekts zurueck zu Gitea.""" 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"} # Alles auf Schreibbar setzen fuer Push force_remove_all(str(target_dir)) result = push_changes(target_dir, req.commit_message, req.changelog_content) if result["success"]: return { "message": f"✅ {req.project_name} erfolgreich gepusht!", "path": str(target_dir), "output": result.get("output", ""), "success": True } else: return {"error": result.get("error"), "success": False} @router.post("/push/{project_name}") # Alternative Push-Route! async def push_project(project_name: str): """Pusht Changes eines Projekts zurueck zu Gitea (ohne ChangelogContent).""" from .main import PROJECTS_DIR import stat target_dir = PROJECTS_DIR / project_name if not target_dir.exists(): return {"success": False, "error": f"Projekt '{project_name}' nicht gefunden"} # Alles auf Schreibbar setzen fuer Push force_remove_all(str(target_dir)) result = push_changes(target_dir, "AI-bearbeitete Änderungen") if result["success"]: return { "message": f"✅ {project_name} erfolgreich gepusht!", "path": str(target_dir), "output": result.get("output", ""), "success": True } else: return {"error": result.get("error"), "success": False} # Importiere die Funktionen am Ende damit sie verfügbar sind! from .git_manager import clone_repo, set_permissions, push_changes, force_remove_all, get_git_status from .changelog_gen import generate_changelog