Major Features: ✅ JWT Bearer Token authentication system with secure token management ✅ API key generation and management with scoped permissions ✅ Complete user management (registration, login, logout, password change) ✅ Frontend authentication components and context integration Backend Architecture Improvements: ✅ CORS configuration via environment variables (CORS_ORIGINS) ✅ Dependency injection pattern for unified coordinator ✅ Database schema fixes with UUID support and SQLAlchemy compliance ✅ Task persistence replaced in-memory storage with database-backed system ✅ Service separation following Single Responsibility Principle ✅ Fixed SQLAlchemy metadata column naming conflicts Infrastructure & Testing: ✅ Comprehensive Jest unit testing and Playwright e2e testing infrastructure ✅ GitHub Actions CI/CD pipeline integration ✅ Enhanced API clients matching PROJECT_PLAN.md specifications ✅ Docker Swarm deployment with proper networking and service connectivity Database & Security: ✅ UUID-based user models with proper validation ✅ Unified database schema with authentication tables ✅ Token blacklisting and refresh token management ✅ Secure password hashing with bcrypt ✅ API key scoping and permissions system API Enhancements: ✅ Authentication endpoints (/api/auth/*) ✅ Task management with database persistence ✅ Enhanced monitoring and health check endpoints ✅ Comprehensive error handling and validation Deployment: ✅ Successfully deployed to Docker Swarm at https://hive.home.deepblack.cloud ✅ All services operational with proper networking ✅ Environment-based configuration support 🛠️ Technical Debt Resolved: - Fixed global coordinator instances with proper dependency injection - Replaced hardcoded CORS origins with environment variables - Unified User model schema conflicts across authentication system - Implemented database persistence for critical task storage - Created comprehensive testing infrastructure This release transforms Hive from a development prototype into a production-ready distributed AI orchestration platform with enterprise-grade authentication, proper architectural patterns, and robust deployment infrastructure. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
205 lines
7.5 KiB
Python
205 lines
7.5 KiB
Python
from fastapi import APIRouter, Depends, HTTPException, Query
|
|
from typing import List, Dict, Any, Optional
|
|
from ..core.auth_deps import get_current_user_context
|
|
from ..core.unified_coordinator_refactored import UnifiedCoordinatorRefactored as UnifiedCoordinator
|
|
|
|
router = APIRouter()
|
|
|
|
# Dependency function for coordinator injection (will be overridden by main.py)
|
|
def get_coordinator() -> UnifiedCoordinator:
|
|
"""This will be overridden by main.py dependency injection"""
|
|
pass
|
|
|
|
@router.post("/tasks")
|
|
async def create_task(
|
|
task_data: Dict[str, Any],
|
|
coordinator: UnifiedCoordinator = Depends(get_coordinator),
|
|
current_user: Dict[str, Any] = Depends(get_current_user_context)
|
|
):
|
|
"""Create a new development task"""
|
|
try:
|
|
# Extract task details
|
|
task_type_str = task_data.get("type", "python")
|
|
priority = task_data.get("priority", 5)
|
|
context = task_data.get("context", {})
|
|
|
|
# Create task using coordinator
|
|
task_id = await coordinator.submit_task(task_data)
|
|
|
|
return {
|
|
"id": task_id,
|
|
"type": task_type_str,
|
|
"priority": priority,
|
|
"status": "pending",
|
|
"context": context,
|
|
}
|
|
except Exception as e:
|
|
raise HTTPException(status_code=500, detail=str(e))
|
|
|
|
@router.get("/tasks/{task_id}")
|
|
async def get_task(
|
|
task_id: str,
|
|
coordinator: UnifiedCoordinator = Depends(get_coordinator),
|
|
current_user: Dict[str, Any] = Depends(get_current_user_context)
|
|
):
|
|
"""Get details of a specific task"""
|
|
task = await coordinator.get_task_status(task_id)
|
|
if not task:
|
|
raise HTTPException(status_code=404, detail="Task not found")
|
|
|
|
return task
|
|
|
|
@router.get("/tasks")
|
|
async def get_tasks(
|
|
status: Optional[str] = Query(None, description="Filter by task status"),
|
|
agent: Optional[str] = Query(None, description="Filter by assigned agent"),
|
|
workflow_id: Optional[str] = Query(None, description="Filter by workflow ID"),
|
|
limit: int = Query(50, description="Maximum number of tasks to return"),
|
|
coordinator: UnifiedCoordinator = Depends(get_coordinator),
|
|
current_user: Dict[str, Any] = Depends(get_current_user_context)
|
|
):
|
|
"""Get list of tasks with optional filtering (includes database tasks)"""
|
|
|
|
try:
|
|
# Get tasks from database (more comprehensive than in-memory only)
|
|
db_tasks = coordinator.task_service.get_tasks(
|
|
status=status,
|
|
agent_id=agent,
|
|
workflow_id=workflow_id,
|
|
limit=limit
|
|
)
|
|
|
|
# Convert ORM tasks to coordinator tasks for consistent response format
|
|
tasks = []
|
|
for orm_task in db_tasks:
|
|
coordinator_task = coordinator.task_service.coordinator_task_from_orm(orm_task)
|
|
tasks.append({
|
|
"id": coordinator_task.id,
|
|
"type": coordinator_task.type.value,
|
|
"priority": coordinator_task.priority,
|
|
"status": coordinator_task.status.value,
|
|
"context": coordinator_task.context,
|
|
"assigned_agent": coordinator_task.assigned_agent,
|
|
"result": coordinator_task.result,
|
|
"created_at": coordinator_task.created_at,
|
|
"completed_at": coordinator_task.completed_at,
|
|
"workflow_id": coordinator_task.workflow_id,
|
|
})
|
|
|
|
# Get total count for the response
|
|
total_count = len(db_tasks)
|
|
|
|
return {
|
|
"tasks": tasks,
|
|
"total": total_count,
|
|
"source": "database",
|
|
"filters_applied": {
|
|
"status": status,
|
|
"agent": agent,
|
|
"workflow_id": workflow_id
|
|
}
|
|
}
|
|
|
|
except Exception as e:
|
|
# Fallback to in-memory tasks if database fails
|
|
all_tasks = list(coordinator.tasks.values())
|
|
|
|
# Apply filters
|
|
filtered_tasks = all_tasks
|
|
|
|
if status:
|
|
try:
|
|
status_enum = TaskStatus(status)
|
|
filtered_tasks = [t for t in filtered_tasks if t.status == status_enum]
|
|
except ValueError:
|
|
raise HTTPException(status_code=400, detail=f"Invalid status: {status}")
|
|
|
|
if agent:
|
|
filtered_tasks = [t for t in filtered_tasks if t.assigned_agent == agent]
|
|
|
|
if workflow_id:
|
|
filtered_tasks = [t for t in filtered_tasks if t.workflow_id == workflow_id]
|
|
|
|
# Sort by creation time (newest first) and limit
|
|
filtered_tasks.sort(key=lambda t: t.created_at or 0, reverse=True)
|
|
filtered_tasks = filtered_tasks[:limit]
|
|
|
|
# Format response
|
|
tasks = []
|
|
for task in filtered_tasks:
|
|
tasks.append({
|
|
"id": task.id,
|
|
"type": task.type.value,
|
|
"priority": task.priority,
|
|
"status": task.status.value,
|
|
"context": task.context,
|
|
"assigned_agent": task.assigned_agent,
|
|
"result": task.result,
|
|
"created_at": task.created_at,
|
|
"completed_at": task.completed_at,
|
|
"workflow_id": task.workflow_id,
|
|
})
|
|
|
|
return {
|
|
"tasks": tasks,
|
|
"total": len(tasks),
|
|
"source": "memory_fallback",
|
|
"database_error": str(e),
|
|
"filtered": len(all_tasks) != len(tasks),
|
|
}
|
|
|
|
@router.get("/tasks/statistics")
|
|
async def get_task_statistics(
|
|
coordinator: UnifiedCoordinator = Depends(get_coordinator),
|
|
current_user: Dict[str, Any] = Depends(get_current_user_context)
|
|
):
|
|
"""Get comprehensive task statistics"""
|
|
try:
|
|
db_stats = coordinator.task_service.get_task_statistics()
|
|
|
|
# Get in-memory statistics
|
|
memory_stats = {
|
|
"in_memory_active": len([t for t in coordinator.tasks.values() if t.status == TaskStatus.IN_PROGRESS]),
|
|
"in_memory_pending": len(coordinator.task_queue),
|
|
"in_memory_total": len(coordinator.tasks)
|
|
}
|
|
|
|
return {
|
|
"database_statistics": db_stats,
|
|
"memory_statistics": memory_stats,
|
|
"coordinator_status": "operational" if coordinator.is_initialized else "initializing"
|
|
}
|
|
|
|
except Exception as e:
|
|
raise HTTPException(status_code=500, detail=f"Failed to get task statistics: {str(e)}")
|
|
|
|
@router.delete("/tasks/{task_id}")
|
|
async def delete_task(
|
|
task_id: str,
|
|
coordinator: UnifiedCoordinator = Depends(get_coordinator),
|
|
current_user: Dict[str, Any] = Depends(get_current_user_context)
|
|
):
|
|
"""Delete a specific task"""
|
|
try:
|
|
# Remove from database
|
|
success = coordinator.task_service.delete_task(task_id)
|
|
if not success:
|
|
raise HTTPException(status_code=404, detail="Task not found")
|
|
|
|
# Remove from in-memory cache if present
|
|
if hasattr(coordinator, 'tasks') and task_id in coordinator.tasks:
|
|
del coordinator.tasks[task_id]
|
|
|
|
# Remove from task queue if present
|
|
coordinator.task_queue = [t for t in coordinator.task_queue if t.id != task_id]
|
|
|
|
# Delete from database
|
|
success = coordinator.task_service.delete_task(task_id)
|
|
|
|
if success:
|
|
return {"message": f"Task {task_id} deleted successfully"}
|
|
else:
|
|
raise HTTPException(status_code=404, detail="Task not found")
|
|
|
|
except Exception as e:
|
|
raise HTTPException(status_code=500, detail=f"Failed to delete task: {str(e)}") |