Fix frontend URLs for production deployment and resolve database issues
- Update API base URL from localhost to https://api.hive.home.deepblack.cloud - Update WebSocket URL to https://hive.home.deepblack.cloud for proper TLS routing - Remove metadata field from Project model to fix SQLAlchemy conflict - Remove index from JSON expertise column in AgentRole to fix PostgreSQL indexing - Update push script to use local registry instead of Docker Hub - Add Gitea repository support and monitoring endpoints 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
294
backend/app/api/repository.py
Normal file
294
backend/app/api/repository.py
Normal file
@@ -0,0 +1,294 @@
|
||||
"""
|
||||
Repository management API endpoints
|
||||
"""
|
||||
|
||||
from fastapi import APIRouter, Depends, HTTPException, BackgroundTasks
|
||||
from sqlalchemy.orm import Session
|
||||
from typing import List, Dict, Any, Optional
|
||||
from datetime import datetime
|
||||
|
||||
from ..core.database import get_db
|
||||
from ..models.project import Project
|
||||
from ..services.repository_service import repository_service
|
||||
from ..auth.auth import get_current_user
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
@router.get("/repositories", response_model=List[Dict[str, Any]])
|
||||
async def list_repositories(
|
||||
db: Session = Depends(get_db),
|
||||
current_user: dict = Depends(get_current_user)
|
||||
):
|
||||
"""List all repositories with bzzz integration enabled"""
|
||||
try:
|
||||
projects = db.query(Project).filter(
|
||||
Project.bzzz_enabled == True
|
||||
).all()
|
||||
|
||||
repositories = []
|
||||
for project in projects:
|
||||
repo_data = {
|
||||
"id": project.id,
|
||||
"name": project.name,
|
||||
"description": project.description,
|
||||
"provider": project.provider or "github",
|
||||
"provider_base_url": project.provider_base_url,
|
||||
"owner": project.git_owner,
|
||||
"repository": project.git_repository,
|
||||
"branch": project.git_branch,
|
||||
"status": project.status,
|
||||
"bzzz_enabled": project.bzzz_enabled,
|
||||
"ready_to_claim": project.ready_to_claim,
|
||||
"auto_assignment": getattr(project, "auto_assignment", True),
|
||||
"created_at": project.created_at.isoformat() if project.created_at else None
|
||||
}
|
||||
repositories.append(repo_data)
|
||||
|
||||
return repositories
|
||||
|
||||
except Exception as e:
|
||||
raise HTTPException(status_code=500, detail=f"Failed to list repositories: {str(e)}")
|
||||
|
||||
@router.post("/repositories/sync")
|
||||
async def sync_repositories(
|
||||
background_tasks: BackgroundTasks,
|
||||
repository_ids: Optional[List[int]] = None,
|
||||
db: Session = Depends(get_db),
|
||||
current_user: dict = Depends(get_current_user)
|
||||
):
|
||||
"""Sync tasks from repositories"""
|
||||
try:
|
||||
if repository_ids:
|
||||
# Sync specific repositories
|
||||
projects = db.query(Project).filter(
|
||||
Project.id.in_(repository_ids),
|
||||
Project.bzzz_enabled == True
|
||||
).all()
|
||||
|
||||
if not projects:
|
||||
raise HTTPException(status_code=404, detail="No matching repositories found")
|
||||
|
||||
results = {"synced_projects": 0, "new_tasks": 0, "assigned_tasks": 0, "errors": []}
|
||||
|
||||
for project in projects:
|
||||
try:
|
||||
sync_result = await repository_service.sync_project_tasks(db, project)
|
||||
results["synced_projects"] += 1
|
||||
results["new_tasks"] += sync_result.get("new_tasks", 0)
|
||||
results["assigned_tasks"] += sync_result.get("assigned_tasks", 0)
|
||||
except Exception as e:
|
||||
results["errors"].append(f"Project {project.name}: {str(e)}")
|
||||
|
||||
return results
|
||||
else:
|
||||
# Sync all repositories in background
|
||||
background_tasks.add_task(repository_service.sync_all_repositories, db)
|
||||
return {"message": "Repository sync started in background", "status": "initiated"}
|
||||
|
||||
except HTTPException:
|
||||
raise
|
||||
except Exception as e:
|
||||
raise HTTPException(status_code=500, detail=f"Failed to sync repositories: {str(e)}")
|
||||
|
||||
@router.get("/repositories/{repository_id}/stats")
|
||||
async def get_repository_stats(
|
||||
repository_id: int,
|
||||
db: Session = Depends(get_db),
|
||||
current_user: dict = Depends(get_current_user)
|
||||
):
|
||||
"""Get task statistics for a specific repository"""
|
||||
try:
|
||||
stats = await repository_service.get_project_task_stats(db, repository_id)
|
||||
return stats
|
||||
|
||||
except Exception as e:
|
||||
raise HTTPException(status_code=500, detail=f"Failed to get repository stats: {str(e)}")
|
||||
|
||||
@router.post("/repositories/{repository_id}/sync")
|
||||
async def sync_repository(
|
||||
repository_id: int,
|
||||
db: Session = Depends(get_db),
|
||||
current_user: dict = Depends(get_current_user)
|
||||
):
|
||||
"""Sync tasks from a specific repository"""
|
||||
try:
|
||||
project = db.query(Project).filter(
|
||||
Project.id == repository_id,
|
||||
Project.bzzz_enabled == True
|
||||
).first()
|
||||
|
||||
if not project:
|
||||
raise HTTPException(status_code=404, detail="Repository not found or bzzz integration not enabled")
|
||||
|
||||
result = await repository_service.sync_project_tasks(db, project)
|
||||
return result
|
||||
|
||||
except HTTPException:
|
||||
raise
|
||||
except Exception as e:
|
||||
raise HTTPException(status_code=500, detail=f"Failed to sync repository: {str(e)}")
|
||||
|
||||
@router.put("/repositories/{repository_id}/config")
|
||||
async def update_repository_config(
|
||||
repository_id: int,
|
||||
config_data: Dict[str, Any],
|
||||
db: Session = Depends(get_db),
|
||||
current_user: dict = Depends(get_current_user)
|
||||
):
|
||||
"""Update repository configuration"""
|
||||
try:
|
||||
project = db.query(Project).filter(Project.id == repository_id).first()
|
||||
|
||||
if not project:
|
||||
raise HTTPException(status_code=404, detail="Repository not found")
|
||||
|
||||
# Update allowed configuration fields
|
||||
if "auto_assignment" in config_data:
|
||||
setattr(project, "auto_assignment", config_data["auto_assignment"])
|
||||
|
||||
if "bzzz_enabled" in config_data:
|
||||
project.bzzz_enabled = config_data["bzzz_enabled"]
|
||||
|
||||
if "ready_to_claim" in config_data:
|
||||
project.ready_to_claim = config_data["ready_to_claim"]
|
||||
|
||||
if "status" in config_data and config_data["status"] in ["active", "inactive", "archived"]:
|
||||
project.status = config_data["status"]
|
||||
|
||||
db.commit()
|
||||
|
||||
return {"message": "Repository configuration updated", "repository_id": repository_id}
|
||||
|
||||
except HTTPException:
|
||||
raise
|
||||
except Exception as e:
|
||||
db.rollback()
|
||||
raise HTTPException(status_code=500, detail=f"Failed to update repository config: {str(e)}")
|
||||
|
||||
@router.get("/repositories/{repository_id}/tasks")
|
||||
async def get_repository_tasks(
|
||||
repository_id: int,
|
||||
limit: int = 50,
|
||||
db: Session = Depends(get_db),
|
||||
current_user: dict = Depends(get_current_user)
|
||||
):
|
||||
"""Get available tasks from a repository"""
|
||||
try:
|
||||
project = db.query(Project).filter(
|
||||
Project.id == repository_id,
|
||||
Project.bzzz_enabled == True
|
||||
).first()
|
||||
|
||||
if not project:
|
||||
raise HTTPException(status_code=404, detail="Repository not found or bzzz integration not enabled")
|
||||
|
||||
# Get repository client and fetch tasks
|
||||
repo_client = await repository_service._get_repository_client(project)
|
||||
if not repo_client:
|
||||
raise HTTPException(status_code=500, detail="Failed to create repository client")
|
||||
|
||||
tasks = await repo_client.list_available_tasks()
|
||||
|
||||
# Limit results
|
||||
if len(tasks) > limit:
|
||||
tasks = tasks[:limit]
|
||||
|
||||
return {
|
||||
"repository_id": repository_id,
|
||||
"repository_name": project.name,
|
||||
"provider": project.provider or "github",
|
||||
"tasks": tasks,
|
||||
"total_tasks": len(tasks)
|
||||
}
|
||||
|
||||
except HTTPException:
|
||||
raise
|
||||
except Exception as e:
|
||||
raise HTTPException(status_code=500, detail=f"Failed to get repository tasks: {str(e)}")
|
||||
|
||||
@router.post("/repositories/discover")
|
||||
async def discover_repositories(
|
||||
provider: str = "gitea",
|
||||
base_url: str = "http://192.168.1.113:3000",
|
||||
db: Session = Depends(get_db),
|
||||
current_user: dict = Depends(get_current_user)
|
||||
):
|
||||
"""Discover repositories from a provider (placeholder for future implementation)"""
|
||||
try:
|
||||
# This would implement repository discovery functionality
|
||||
# For now, return the manually configured repositories
|
||||
|
||||
existing_repos = db.query(Project).filter(
|
||||
Project.provider == provider,
|
||||
Project.provider_base_url == base_url
|
||||
).all()
|
||||
|
||||
discovered = []
|
||||
for repo in existing_repos:
|
||||
discovered.append({
|
||||
"name": repo.name,
|
||||
"owner": repo.git_owner,
|
||||
"repository": repo.git_repository,
|
||||
"description": repo.description,
|
||||
"already_configured": True
|
||||
})
|
||||
|
||||
return {
|
||||
"provider": provider,
|
||||
"base_url": base_url,
|
||||
"discovered_repositories": discovered
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
raise HTTPException(status_code=500, detail=f"Failed to discover repositories: {str(e)}")
|
||||
|
||||
@router.post("/webhook/{repository_id}")
|
||||
async def handle_repository_webhook(
|
||||
repository_id: int,
|
||||
payload: Dict[str, Any],
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
"""Handle webhook events from repositories"""
|
||||
try:
|
||||
project = db.query(Project).filter(Project.id == repository_id).first()
|
||||
|
||||
if not project:
|
||||
raise HTTPException(status_code=404, detail="Repository not found")
|
||||
|
||||
# Log the webhook event (would be stored in webhook_events table)
|
||||
event_type = payload.get("action", "unknown")
|
||||
|
||||
# For now, just trigger a sync if it's an issue event
|
||||
if "issue" in payload and event_type in ["opened", "labeled", "unlabeled"]:
|
||||
# Check if it's a bzzz-task
|
||||
issue = payload.get("issue", {})
|
||||
labels = [label["name"] for label in issue.get("labels", [])]
|
||||
|
||||
if "bzzz-task" in labels:
|
||||
# Trigger task sync for this project
|
||||
await repository_service.sync_project_tasks(db, project)
|
||||
|
||||
return {
|
||||
"message": "Webhook processed, task sync triggered",
|
||||
"event_type": event_type,
|
||||
"issue_number": issue.get("number")
|
||||
}
|
||||
|
||||
return {"message": "Webhook received", "event_type": event_type}
|
||||
|
||||
except HTTPException:
|
||||
raise
|
||||
except Exception as e:
|
||||
raise HTTPException(status_code=500, detail=f"Failed to process webhook: {str(e)}")
|
||||
|
||||
@router.delete("/repositories/cache")
|
||||
async def clear_task_cache(
|
||||
current_user: dict = Depends(get_current_user)
|
||||
):
|
||||
"""Clear the task cache"""
|
||||
try:
|
||||
await repository_service.cleanup_old_cache(max_age_hours=0) # Clear all
|
||||
return {"message": "Task cache cleared"}
|
||||
|
||||
except Exception as e:
|
||||
raise HTTPException(status_code=500, detail=f"Failed to clear cache: {str(e)}")
|
||||
Reference in New Issue
Block a user