Fix frontend URLs for production deployment and resolve database issues
Some checks failed
Frontend Tests / unit-tests (push) Has been cancelled
Frontend Tests / e2e-tests (push) Has been cancelled

- 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:
anthonyrawlins
2025-07-28 09:16:22 +10:00
parent 9262e63374
commit 1e81daaf18
8 changed files with 989 additions and 17 deletions

View 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)}")