feat(city-service): add Governance, Audit, Incidents API endpoints
- Added repo_governance.py with database operations - Added routes_governance.py (/api/v1/governance/*) - Added routes_audit.py (/api/v1/audit/*) - Added routes_incidents.py (/api/v1/incidents/*) - Updated main.py to include new routers
This commit is contained in:
277
services/city-service/routes_governance.py
Normal file
277
services/city-service/routes_governance.py
Normal file
@@ -0,0 +1,277 @@
|
||||
"""
|
||||
Governance API Routes для DAARION City Service
|
||||
/api/v1/governance/*
|
||||
"""
|
||||
|
||||
from fastapi import APIRouter, HTTPException, Query
|
||||
from pydantic import BaseModel, Field
|
||||
from typing import Optional, List
|
||||
import logging
|
||||
|
||||
import repo_governance as repo
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
router = APIRouter(prefix="/api/v1/governance", tags=["governance"])
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# Pydantic Models
|
||||
# =============================================================================
|
||||
|
||||
class AgentSummary(BaseModel):
|
||||
id: str
|
||||
display_name: str
|
||||
kind: Optional[str] = None
|
||||
avatar_url: Optional[str] = None
|
||||
status: Optional[str] = None
|
||||
gov_level: Optional[str] = None
|
||||
node_id: Optional[str] = None
|
||||
role: Optional[str] = None
|
||||
is_core: Optional[bool] = None
|
||||
|
||||
|
||||
class AgentRolesResponse(BaseModel):
|
||||
agent: dict
|
||||
assignments: List[dict]
|
||||
permissions: List[dict]
|
||||
|
||||
|
||||
class PromoteRequest(BaseModel):
|
||||
agent_id: str
|
||||
new_level: str
|
||||
actor_id: str = "dais-demo-user"
|
||||
|
||||
|
||||
class RevokeRequest(BaseModel):
|
||||
agent_id: str
|
||||
reason: str
|
||||
revocation_type: str = "soft" # soft or hard
|
||||
actor_id: str = "dais-demo-user"
|
||||
|
||||
|
||||
class SuspendRequest(BaseModel):
|
||||
agent_id: str
|
||||
reason: str
|
||||
actor_id: str = "dais-demo-user"
|
||||
|
||||
|
||||
class ReinstateRequest(BaseModel):
|
||||
agent_id: str
|
||||
actor_id: str = "dais-demo-user"
|
||||
|
||||
|
||||
class CheckPermissionRequest(BaseModel):
|
||||
agent_id: str
|
||||
action: str
|
||||
target: str
|
||||
scope_type: Optional[str] = None
|
||||
scope_id: Optional[str] = None
|
||||
|
||||
|
||||
class CheckPermissionResponse(BaseModel):
|
||||
allowed: bool
|
||||
reason: str
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# Routes
|
||||
# =============================================================================
|
||||
|
||||
@router.get("/agents/city", response_model=List[AgentSummary])
|
||||
async def get_city_governance_agents():
|
||||
"""
|
||||
Отримати City Governance агентів (DAARWIZZ, DARIO, DARIA)
|
||||
"""
|
||||
try:
|
||||
agents = await repo.get_city_governance_agents()
|
||||
return agents
|
||||
except Exception as e:
|
||||
logger.error(f"Error fetching city governance agents: {e}")
|
||||
raise HTTPException(status_code=500, detail=str(e))
|
||||
|
||||
|
||||
@router.get("/agents/district/{district_id}", response_model=List[AgentSummary])
|
||||
async def get_district_agents(district_id: str):
|
||||
"""
|
||||
Отримати агентів дистрикту (District Lead + core-team)
|
||||
"""
|
||||
try:
|
||||
agents = await repo.get_district_agents(district_id)
|
||||
return agents
|
||||
except Exception as e:
|
||||
logger.error(f"Error fetching district agents: {e}")
|
||||
raise HTTPException(status_code=500, detail=str(e))
|
||||
|
||||
|
||||
@router.get("/agents/microdao/{microdao_id}", response_model=List[AgentSummary])
|
||||
async def get_microdao_agents(microdao_id: str):
|
||||
"""
|
||||
Отримати агентів MicroDAO (Orchestrator + workers)
|
||||
"""
|
||||
try:
|
||||
agents = await repo.get_microdao_agents(microdao_id)
|
||||
return agents
|
||||
except Exception as e:
|
||||
logger.error(f"Error fetching microdao agents: {e}")
|
||||
raise HTTPException(status_code=500, detail=str(e))
|
||||
|
||||
|
||||
@router.get("/agents/by-level/{level}", response_model=List[AgentSummary])
|
||||
async def get_agents_by_level(level: str):
|
||||
"""
|
||||
Отримати агентів за рівнем gov_level
|
||||
|
||||
Рівні: guest, personal, member, worker, core_team, orchestrator, district_lead, city_governance
|
||||
"""
|
||||
try:
|
||||
agents = await repo.get_agents_by_level(level)
|
||||
return agents
|
||||
except Exception as e:
|
||||
logger.error(f"Error fetching agents by level: {e}")
|
||||
raise HTTPException(status_code=500, detail=str(e))
|
||||
|
||||
|
||||
@router.get("/agent/{agent_id}/roles", response_model=AgentRolesResponse)
|
||||
async def get_agent_roles(agent_id: str):
|
||||
"""
|
||||
Отримати ролі та повноваження агента
|
||||
"""
|
||||
try:
|
||||
result = await repo.get_agent_roles(agent_id)
|
||||
if "error" in result:
|
||||
raise HTTPException(status_code=404, detail=result["error"])
|
||||
return result
|
||||
except HTTPException:
|
||||
raise
|
||||
except Exception as e:
|
||||
logger.error(f"Error fetching agent roles: {e}")
|
||||
raise HTTPException(status_code=500, detail=str(e))
|
||||
|
||||
|
||||
@router.post("/agent/promote")
|
||||
async def promote_agent(request: PromoteRequest):
|
||||
"""
|
||||
Підвищити агента до нового рівня
|
||||
"""
|
||||
try:
|
||||
result = await repo.promote_agent(
|
||||
agent_id=request.agent_id,
|
||||
new_level=request.new_level,
|
||||
actor_id=request.actor_id
|
||||
)
|
||||
if not result:
|
||||
raise HTTPException(status_code=404, detail="Agent not found")
|
||||
return {"success": True, "agent": result}
|
||||
except HTTPException:
|
||||
raise
|
||||
except Exception as e:
|
||||
logger.error(f"Error promoting agent: {e}")
|
||||
raise HTTPException(status_code=500, detail=str(e))
|
||||
|
||||
|
||||
@router.post("/agent/demote")
|
||||
async def demote_agent(request: PromoteRequest):
|
||||
"""
|
||||
Понизити рівень агента
|
||||
"""
|
||||
try:
|
||||
result = await repo.demote_agent(
|
||||
agent_id=request.agent_id,
|
||||
new_level=request.new_level,
|
||||
actor_id=request.actor_id
|
||||
)
|
||||
if not result:
|
||||
raise HTTPException(status_code=404, detail="Agent not found")
|
||||
return {"success": True, "agent": result}
|
||||
except HTTPException:
|
||||
raise
|
||||
except Exception as e:
|
||||
logger.error(f"Error demoting agent: {e}")
|
||||
raise HTTPException(status_code=500, detail=str(e))
|
||||
|
||||
|
||||
@router.post("/agent/revoke")
|
||||
async def revoke_agent(request: RevokeRequest):
|
||||
"""
|
||||
Відкликати агента (soft/hard)
|
||||
|
||||
- soft: тимчасове призупинення
|
||||
- hard: повне відкликання з блокуванням DAIS ключів
|
||||
"""
|
||||
try:
|
||||
result = await repo.revoke_agent(
|
||||
agent_id=request.agent_id,
|
||||
actor_id=request.actor_id,
|
||||
reason=request.reason,
|
||||
revocation_type=request.revocation_type
|
||||
)
|
||||
if not result:
|
||||
raise HTTPException(status_code=404, detail="Agent not found")
|
||||
return {"success": True, "agent": result}
|
||||
except HTTPException:
|
||||
raise
|
||||
except Exception as e:
|
||||
logger.error(f"Error revoking agent: {e}")
|
||||
raise HTTPException(status_code=500, detail=str(e))
|
||||
|
||||
|
||||
@router.post("/agent/suspend")
|
||||
async def suspend_agent(request: SuspendRequest):
|
||||
"""
|
||||
Тимчасово призупинити агента
|
||||
"""
|
||||
try:
|
||||
result = await repo.suspend_agent(
|
||||
agent_id=request.agent_id,
|
||||
actor_id=request.actor_id,
|
||||
reason=request.reason
|
||||
)
|
||||
if not result:
|
||||
raise HTTPException(status_code=404, detail="Agent not found")
|
||||
return {"success": True, "agent": result}
|
||||
except HTTPException:
|
||||
raise
|
||||
except Exception as e:
|
||||
logger.error(f"Error suspending agent: {e}")
|
||||
raise HTTPException(status_code=500, detail=str(e))
|
||||
|
||||
|
||||
@router.post("/agent/reinstate")
|
||||
async def reinstate_agent(request: ReinstateRequest):
|
||||
"""
|
||||
Відновити призупиненого агента
|
||||
"""
|
||||
try:
|
||||
result = await repo.reinstate_agent(
|
||||
agent_id=request.agent_id,
|
||||
actor_id=request.actor_id
|
||||
)
|
||||
if not result:
|
||||
raise HTTPException(status_code=404, detail="Agent not found")
|
||||
return {"success": True, "agent": result}
|
||||
except HTTPException:
|
||||
raise
|
||||
except Exception as e:
|
||||
logger.error(f"Error reinstating agent: {e}")
|
||||
raise HTTPException(status_code=500, detail=str(e))
|
||||
|
||||
|
||||
@router.post("/check", response_model=CheckPermissionResponse)
|
||||
async def check_permission(request: CheckPermissionRequest):
|
||||
"""
|
||||
Перевірити чи має агент право на дію
|
||||
"""
|
||||
try:
|
||||
result = await repo.check_permission(
|
||||
agent_id=request.agent_id,
|
||||
action=request.action,
|
||||
target=request.target,
|
||||
scope_type=request.scope_type,
|
||||
scope_id=request.scope_id
|
||||
)
|
||||
return result
|
||||
except Exception as e:
|
||||
logger.error(f"Error checking permission: {e}")
|
||||
raise HTTPException(status_code=500, detail=str(e))
|
||||
|
||||
Reference in New Issue
Block a user