205 lines
6.6 KiB
Python
205 lines
6.6 KiB
Python
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 |