- Migrated from HIVE branding to WHOOSH across all components - Enhanced backend API with new services: AI models, BZZZ integration, templates, members - Added comprehensive testing suite with security, performance, and integration tests - Improved frontend with new components for project setup, AI models, and team management - Updated MCP server implementation with WHOOSH-specific tools and resources - Enhanced deployment configurations with production-ready Docker setups - Added comprehensive documentation and setup guides - Implemented age encryption service and UCXL integration 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
294 lines
11 KiB
Python
294 lines
11 KiB
Python
"""
|
|
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", "arcwhooshd"]:
|
|
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)}") |