Commit 9403955f authored by Administrator's avatar Administrator

Update backend/routes/chat_routes.py via Son of Anton

parent 356bf7b4
"""
Chat CRUD + message streaming — v4.2.0 with permission enforcement.
Chat CRUD + message streaming — v4.3.0 with GitLab for users, branch auto-create, commit fixes.
"""
import json
......@@ -55,8 +55,46 @@ class CommitFromChatBody(BaseModel):
branch: str
commit_message: str
files: list[dict]
create_branch_if_missing: bool = True
# ═══════════════════════════════════════════════════
# User-facing: Available Repos & Branches
# ═══════════════════════════════════════════════════
@router.get("/available-repos")
def list_available_repos(user: User = Depends(get_current_user), db: Session = Depends(get_db)):
"""List linked repos that any user with gitlab permission can select for chats."""
perms = get_user_permissions(user.id, db)
if not perms.get("can_use_gitlab"):
return []
repos = db.query(LinkedRepo).order_by(LinkedRepo.created_at.desc()).all()
return [_repo_brief(r) for r in repos]
@router.get("/repos/{repo_id}/branches")
async def get_repo_branches(repo_id: str, user: User = Depends(get_current_user), db: Session = Depends(get_db)):
"""Get branches for a linked repo — available to users with gitlab permission."""
check_feature(user.id, "use_gitlab", db)
repo = db.query(LinkedRepo).filter(LinkedRepo.id == repo_id).first()
if not repo:
raise HTTPException(404, "Repository not found")
settings = db.query(GitLabSettings).first()
if not settings or not settings.is_active:
raise HTTPException(400, "GitLab not configured")
try:
branches = await gitlab_service.list_branches(
settings.gitlab_url, settings.gitlab_token, repo.gitlab_project_id
)
return branches
except gitlab_service.GitLabError as e:
raise HTTPException(e.status_code, e.detail)
# ═══════════════════════════════════════════════════
# Chat CRUD
# ═══════════════════════════════════════════════════
@router.get("")
def list_chats(user: User = Depends(get_current_user), db: Session = Depends(get_db)):
chats = db.query(Chat).filter(Chat.user_id == user.id).order_by(Chat.updated_at.desc()).all()
......@@ -142,6 +180,10 @@ def get_messages(chat_id: str, user: User = Depends(get_current_user), db: Sessi
return msgs
# ═══════════════════════════════════════════════════
# Streaming & Generation
# ═══════════════════════════════════════════════════
@router.get("/{chat_id}/generating")
def check_generating(chat_id: str, user: User = Depends(get_current_user)):
return {"active": gen_manager.is_active(chat_id)}
......@@ -198,6 +240,10 @@ async def send_message(chat_id: str, body: SendMessageBody, user: User = Depends
return StreamingResponse(generate(), media_type="text/event-stream")
# ═══════════════════════════════════════════════════
# GitLab Commit from Chat (FIXED)
# ═══════════════════════════════════════════════════
@router.post("/{chat_id}/commit")
async def commit_from_chat(
chat_id: str,
......@@ -207,8 +253,9 @@ async def commit_from_chat(
):
"""
Commit files from chat to linked GitLab repo.
Auto-detects whether each file should be 'create' or 'update'
by checking the repo tree, so it never fails on wrong action type.
- Auto-detects create vs update per file.
- Auto-creates branch if it doesn't exist and create_branch_if_missing=True.
- Returns clear success/failure with details.
"""
check_feature(user.id, "use_gitlab", db)
......@@ -216,7 +263,7 @@ async def commit_from_chat(
if not chat:
raise HTTPException(404, "Chat not found")
if not chat.linked_repo_id:
raise HTTPException(400, "No repository linked")
raise HTTPException(400, "No repository linked to this chat")
repo = db.query(LinkedRepo).filter(LinkedRepo.id == chat.linked_repo_id).first()
if not repo:
......@@ -224,87 +271,124 @@ async def commit_from_chat(
settings = db.query(GitLabSettings).first()
if not settings or not settings.is_active:
raise HTTPException(400, "GitLab not configured")
raise HTTPException(400, "GitLab not configured. Ask your admin to set it up.")
branch = body.branch.strip()
if not branch:
branch = repo.default_branch or "main"
# ── Step 1: Check if branch exists, create if needed ──
branch_exists = True
try:
branches = await gitlab_service.list_branches(
settings.gitlab_url, settings.gitlab_token, repo.gitlab_project_id
)
branch_names = {b["name"] for b in branches}
if branch not in branch_names:
branch_exists = False
except Exception:
# If we can't list branches, we'll try anyway
pass
# ── Fetch repo tree to know which files already exist ──
if not branch_exists and body.create_branch_if_missing:
try:
await gitlab_service.create_branch(
settings.gitlab_url, settings.gitlab_token,
repo.gitlab_project_id, branch, repo.default_branch or "main",
)
except gitlab_service.GitLabError as e:
if "already exists" not in str(e.detail).lower():
raise HTTPException(e.status_code, f"Could not create branch '{branch}': {e.detail}")
# ── Step 2: Fetch repo tree to detect create vs update ──
existing_paths = set()
try:
tree = await gitlab_service.get_tree(
settings.gitlab_url,
settings.gitlab_token,
repo.gitlab_project_id,
ref=body.branch,
recursive=True,
settings.gitlab_url, settings.gitlab_token,
repo.gitlab_project_id, ref=branch, recursive=True,
)
existing_paths = {
item["path"] for item in tree if item["type"] == "blob"
}
existing_paths = {item["path"] for item in tree if item["type"] == "blob"}
except Exception:
# If tree fetch fails (empty repo, network issue, etc.),
# we'll try all as "create" since we can't know what exists
# Empty repo or branch just created — all files are "create"
pass
# ── Build actions with auto-detected create/update ──
# ── Step 3: Build commit actions ──
actions = []
committed_files = []
for f in body.files:
file_path = f.get("file_path", "")
file_path = f.get("file_path", "").strip()
content = f.get("content", "")
requested_action = f.get("action", "auto")
if not file_path or not content:
continue
file_exists = file_path in existing_paths
# Smart action resolution
if requested_action in ("auto", "upsert"):
# Auto-detect: update if exists, create if not
actual_action = "update" if file_exists else "create"
elif requested_action == "update" and not file_exists:
# User said update but file doesn't exist → create instead
actual_action = "create"
elif requested_action == "create" and file_exists:
# User said create but file already exists → update instead
actual_action = "update"
else:
actual_action = requested_action
actual_action = "update" if file_exists else "create"
actions.append({
"action": actual_action,
"file_path": file_path,
"content": content,
})
committed_files.append({"file_path": file_path, "action": actual_action})
if not actions:
raise HTTPException(400, "No valid files to commit")
# ── Step 4: Execute commit ──
try:
result = await gitlab_service.commit_files(
settings.gitlab_url,
settings.gitlab_token,
repo.gitlab_project_id,
body.branch,
body.commit_message,
actions,
settings.gitlab_url, settings.gitlab_token,
repo.gitlab_project_id, branch, body.commit_message, actions,
)
gen_manager.invalidate_repo_cache(repo.id)
return {
"ok": True,
"commit": result,
"files_committed": len(actions),
"files": committed_files,
"branch": branch,
"branch_created": not branch_exists and body.create_branch_if_missing,
}
except gitlab_service.GitLabError as e:
# Check if it's a "already exists" type error (file was committed despite error)
error_detail = str(e.detail).lower()
if "a]ready exists" in error_detail or "already been taken" in error_detail:
# Files might have been committed — retry with update action
for a in actions:
if a["action"] == "create":
a["action"] = "update"
try:
result = await gitlab_service.commit_files(
settings.gitlab_url, settings.gitlab_token,
repo.gitlab_project_id, branch, body.commit_message, actions,
)
gen_manager.invalidate_repo_cache(repo.id)
return {
"ok": True,
"commit": result,
"files_committed": len(actions),
"files": committed_files,
"branch": branch,
"retried": True,
}
except gitlab_service.GitLabError as e2:
raise HTTPException(e2.status_code, f"Commit failed on retry: {e2.detail}")
raise HTTPException(e.status_code, f"Commit failed: {e.detail}")
@router.post("/{chat_id}/refresh-repo")
async def refresh_repo_context(chat_id: str, user: User = Depends(get_current_user), db: Session = Depends(get_db)):
chat = db.query(Chat).filter(Chat.id == chat_id, Chat.user_id == user.id).first()
if not chat or not chat.linked_repo_id: raise HTTPException(400, "No repo linked")
if not chat or not chat.linked_repo_id:
raise HTTPException(400, "No repo linked")
gen_manager.invalidate_repo_cache(chat.linked_repo_id)
return {"ok": True}
# ═══════════════════════════════════════════════════
# Helpers
# ═══════════════════════════════════════════════════
def _sse(data):
return f"data: {json.dumps(data)}\n\n"
......@@ -347,4 +431,17 @@ def _att_brief(a):
"id": a.id, "original_filename": a.original_filename,
"mime_type": a.mime_type, "file_type": a.file_type,
"file_size": a.file_size,
}
def _repo_brief(r):
return {
"id": r.id,
"gitlab_project_id": r.gitlab_project_id,
"name": r.name,
"path_with_namespace": r.path_with_namespace,
"default_branch": r.default_branch,
"web_url": r.web_url,
"description": r.description or "",
"map_status": r.map_status or "none",
}
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment