feat: District Portals API (DB-based, no hardcodes)

Backend:
- GET /api/v1/districts - list all districts from DB
- GET /api/v1/districts/{slug} - district detail with lead agent, core team, rooms, nodes

repo_city methods:
- get_districts() - SELECT FROM microdaos WHERE dao_type='district'
- get_district_by_slug()
- get_district_lead_agent() - with fallback to orchestrator
- get_district_core_team()
- get_district_agents()
- get_district_rooms() - by slug prefix
- get_district_nodes()
- get_district_stats()

Task doc: TASK_PHASE_DISTRICT_PORTALS_v1.md
This commit is contained in:
Apple
2025-11-30 11:37:56 -08:00
parent 066aae724a
commit 0fd05f678a
3 changed files with 775 additions and 0 deletions

View File

@@ -1123,6 +1123,142 @@ async def get_city_room_by_slug(slug: str):
raise HTTPException(status_code=500, detail="Failed to get city room")
# =============================================================================
# Districts API (DB-based, no hardcodes)
# =============================================================================
@api_router.get("/districts")
async def get_districts():
"""
Отримати список всіх District-ів.
District = microdao з dao_type = 'district'
"""
try:
districts = await repo_city.get_districts()
result = []
for d in districts:
# Get lead agent for each district
lead_agent = await repo_city.get_district_lead_agent(d["id"])
# Get rooms count
rooms = await repo_city.get_district_rooms(d["slug"])
result.append({
"id": d["id"],
"slug": d["slug"],
"name": d["name"],
"description": d.get("description"),
"dao_type": d["dao_type"],
"lead_agent": {
"id": lead_agent["id"],
"name": lead_agent["name"],
"avatar_url": lead_agent.get("avatar_url")
} if lead_agent else None,
"rooms_count": len(rooms),
"rooms": [{"id": r["id"], "slug": r["slug"], "name": r["name"]} for r in rooms[:3]]
})
return result
except Exception as e:
logger.error(f"Failed to get districts: {e}")
raise HTTPException(status_code=500, detail="Failed to get districts")
@api_router.get("/districts/{slug}")
async def get_district_detail(slug: str):
"""
Отримати деталі District-а за slug.
"""
try:
district = await repo_city.get_district_by_slug(slug)
if not district:
raise HTTPException(status_code=404, detail=f"District not found: {slug}")
# Get lead agent
lead_agent = await repo_city.get_district_lead_agent(district["id"])
# Get core team
core_team = await repo_city.get_district_core_team(district["id"])
# Get all agents
agents = await repo_city.get_district_agents(district["id"])
# Get rooms
rooms = await repo_city.get_district_rooms(district["slug"])
# Get nodes
nodes = await repo_city.get_district_nodes(district["id"])
# Get stats
stats = await repo_city.get_district_stats(district["id"], district["slug"])
return {
"district": {
"id": district["id"],
"slug": district["slug"],
"name": district["name"],
"description": district.get("description"),
"dao_type": district["dao_type"]
},
"lead_agent": {
"id": lead_agent["id"],
"name": lead_agent["name"],
"kind": lead_agent.get("kind"),
"status": lead_agent.get("status"),
"avatar_url": lead_agent.get("avatar_url"),
"gov_level": lead_agent.get("gov_level")
} if lead_agent else None,
"core_team": [
{
"id": a["id"],
"name": a["name"],
"kind": a.get("kind"),
"status": a.get("status"),
"avatar_url": a.get("avatar_url"),
"role": a.get("membership_role")
} for a in core_team
],
"agents": [
{
"id": a["id"],
"name": a["name"],
"kind": a.get("kind"),
"status": a.get("status"),
"avatar_url": a.get("avatar_url"),
"role": a.get("membership_role"),
"is_core": a.get("is_core", False)
} for a in agents
],
"rooms": [
{
"id": r["id"],
"slug": r["slug"],
"name": r["name"],
"description": r.get("description"),
"matrix_room_id": r.get("matrix_room_id"),
"room_role": r.get("room_role"),
"is_public": r.get("is_public", True)
} for r in rooms
],
"nodes": [
{
"id": n["id"],
"name": n["name"],
"kind": n.get("kind"),
"status": n.get("status"),
"location": n.get("location")
} for n in nodes
],
"stats": stats
}
except HTTPException:
raise
except Exception as e:
logger.error(f"Failed to get district {slug}: {e}")
raise HTTPException(status_code=500, detail="Failed to get district")
@router.get("/rooms/{room_id}", response_model=CityRoomDetail)
async def get_city_room(room_id: str):
"""