Extract governance services to governance-service

- Created GovernanceService with basic CRUD operations
- Created storage.py for database session management
- Updated main.py to include database initialization and governance endpoints:
  - GET /v1/governance/profiles
  - GET /v1/governance/profiles/{profile_id}
  - POST /v1/governance/profiles
  - GET /v1/governance/proposals
  - GET /v1/governance/proposals/{proposal_id}
  - POST /v1/governance/proposals
  - GET /v1/governance/votes
  - POST /v1/governance/votes
  - GET /v1/governance/treasury
  - GET /v1/governance/analytics
- Created database setup script for aitbc_governance database

This completes Phase 4.6c: Extract governance services and Phase 4.6d: Setup separate database for governance service
This commit is contained in:
aitbc
2026-04-30 11:46:06 +02:00
parent 9bc26ad0c4
commit f500987b8a
5 changed files with 281 additions and 1 deletions

View File

@@ -0,0 +1,19 @@
-- Setup database for Governance service
-- Create database
CREATE DATABASE aitbc_governance;
-- Create user
CREATE USER aitbc_governance WITH PASSWORD 'password';
-- Grant privileges
GRANT ALL PRIVILEGES ON DATABASE aitbc_governance TO aitbc_governance;
-- Connect to the database
\c aitbc_governance
-- Grant schema privileges
GRANT ALL ON SCHEMA public TO aitbc_governance;
-- Exit
\q

View File

@@ -6,8 +6,9 @@ Manages governance operations
from contextlib import asynccontextmanager
from typing import AsyncIterator
from fastapi import FastAPI
from fastapi import FastAPI, Depends
from pydantic import BaseModel
from sqlalchemy.ext.asyncio import AsyncSession
from aitbc import (
configure_logging,
@@ -18,6 +19,9 @@ from aitbc import (
ErrorHandlerMiddleware,
)
from .storage import init_db, get_session
from .services.governance_service import GovernanceService
# Configure structured logging
configure_logging(level="INFO")
logger = get_logger(__name__)
@@ -27,6 +31,7 @@ logger = get_logger(__name__)
async def lifespan(app: FastAPI) -> AsyncIterator[None]:
"""Lifecycle events for the Governance Service."""
logger.info("Starting Governance Service")
await init_db()
yield
logger.info("Shutting down Governance Service")
@@ -67,6 +72,104 @@ async def governance_status() -> dict[str, str]:
}
async def get_governance_service(session: AsyncSession = Depends(get_session)) -> GovernanceService:
"""Get governance service instance"""
return GovernanceService(session)
@app.get("/v1/governance/profiles")
async def get_profiles(
role: str | None = None,
user_id: str | None = None,
svc: GovernanceService = Depends(get_governance_service),
):
"""Get governance profiles"""
return svc.list_profiles(role=role, user_id=user_id)
@app.get("/v1/governance/profiles/{profile_id}")
async def get_profile(
profile_id: str,
svc: GovernanceService = Depends(get_governance_service),
):
"""Get a specific governance profile"""
return svc.get_profile(profile_id)
@app.post("/v1/governance/profiles")
async def create_profile(
profile_data: dict,
svc: GovernanceService = Depends(get_governance_service),
):
"""Create a new governance profile"""
return svc.create_profile(profile_data)
@app.get("/v1/governance/proposals")
async def get_proposals(
status: str | None = None,
category: str | None = None,
proposer_id: str | None = None,
svc: GovernanceService = Depends(get_governance_service),
):
"""Get governance proposals"""
return svc.list_proposals(status=status, category=category, proposer_id=proposer_id)
@app.get("/v1/governance/proposals/{proposal_id}")
async def get_proposal(
proposal_id: str,
svc: GovernanceService = Depends(get_governance_service),
):
"""Get a specific proposal"""
return svc.get_proposal(proposal_id)
@app.post("/v1/governance/proposals")
async def create_proposal(
proposal_data: dict,
svc: GovernanceService = Depends(get_governance_service),
):
"""Create a new proposal"""
return svc.create_proposal(proposal_data)
@app.get("/v1/governance/votes")
async def get_votes(
proposal_id: str | None = None,
voter_id: str | None = None,
svc: GovernanceService = Depends(get_governance_service),
):
"""Get votes"""
return svc.list_votes(proposal_id=proposal_id, voter_id=voter_id)
@app.post("/v1/governance/votes")
async def create_vote(
vote_data: dict,
svc: GovernanceService = Depends(get_governance_service),
):
"""Create a new vote"""
return svc.create_vote(vote_data)
@app.get("/v1/governance/treasury")
async def get_treasury(
svc: GovernanceService = Depends(get_governance_service),
):
"""Get DAO treasury"""
return svc.get_treasury()
@app.get("/v1/governance/analytics")
async def get_analytics(
period: str = "monthly",
svc: GovernanceService = Depends(get_governance_service),
):
"""Get governance analytics"""
return await svc.get_analytics(period=period)
if __name__ == "__main__":
import uvicorn
uvicorn.run(app, host="0.0.0.0", port=8105)

View File

@@ -0,0 +1,7 @@
"""
Governance Service services
"""
from .governance_service import GovernanceService
__all__ = ["GovernanceService"]

View File

@@ -0,0 +1,109 @@
"""
Governance service for managing governance operations
"""
from typing import Any
from sqlmodel import Session, select
from ..domain.governance import GovernanceProfile, Proposal, Vote, DaoTreasury
class GovernanceService:
def __init__(self, session: Session):
self.session = session
def list_profiles(
self,
role: str | None = None,
user_id: str | None = None,
) -> list[GovernanceProfile]:
"""List governance profiles"""
stmt = select(GovernanceProfile)
if role:
stmt = stmt.where(GovernanceProfile.role == role)
if user_id:
stmt = stmt.where(GovernanceProfile.user_id == user_id)
return list(self.session.execute(stmt).all())
def get_profile(self, profile_id: str) -> GovernanceProfile | None:
"""Get a specific governance profile"""
stmt = select(GovernanceProfile).where(GovernanceProfile.profile_id == profile_id)
result = self.session.execute(stmt).first()
return result[0] if result else None
def create_profile(self, profile_data: dict) -> GovernanceProfile:
"""Create a new governance profile"""
profile = GovernanceProfile(**profile_data)
self.session.add(profile)
self.session.commit()
self.session.refresh(profile)
return profile
def list_proposals(
self,
status: str | None = None,
category: str | None = None,
proposer_id: str | None = None,
) -> list[Proposal]:
"""List governance proposals"""
stmt = select(Proposal)
if status:
stmt = stmt.where(Proposal.status == status)
if category:
stmt = stmt.where(Proposal.category == category)
if proposer_id:
stmt = stmt.where(Proposal.proposer_id == proposer_id)
return list(self.session.execute(stmt).all())
def get_proposal(self, proposal_id: str) -> Proposal | None:
"""Get a specific proposal"""
stmt = select(Proposal).where(Proposal.proposal_id == proposal_id)
result = self.session.execute(stmt).first()
return result[0] if result else None
def create_proposal(self, proposal_data: dict) -> Proposal:
"""Create a new proposal"""
proposal = Proposal(**proposal_data)
self.session.add(proposal)
self.session.commit()
self.session.refresh(proposal)
return proposal
def list_votes(
self,
proposal_id: str | None = None,
voter_id: str | None = None,
) -> list[Vote]:
"""List votes"""
stmt = select(Vote)
if proposal_id:
stmt = stmt.where(Vote.proposal_id == proposal_id)
if voter_id:
stmt = stmt.where(Vote.voter_id == voter_id)
return list(self.session.execute(stmt).all())
def create_vote(self, vote_data: dict) -> Vote:
"""Create a new vote"""
vote = Vote(**vote_data)
self.session.add(vote)
self.session.commit()
self.session.refresh(vote)
return vote
def get_treasury(self) -> DaoTreasury | None:
"""Get DAO treasury"""
stmt = select(DaoTreasury).where(DaoTreasury.treasury_id == "main_treasury")
result = self.session.execute(stmt).first()
return result[0] if result else None
async def get_analytics(self, period: str = "monthly") -> dict[str, Any]:
"""Get governance analytics"""
# Placeholder for analytics logic
return {
"period": period,
"total_proposals": 0,
"active_proposals": 0,
"passed_proposals": 0,
"total_votes": 0,
}

View File

@@ -0,0 +1,42 @@
"""
Database session management for Governance service
"""
from contextlib import asynccontextmanager
from typing import AsyncIterator
from sqlalchemy.ext.asyncio import AsyncSession, create_async_engine
from sqlmodel import SQLModel
from aitbc import get_logger
logger = get_logger(__name__)
# Database URL from environment variable or default
DATABASE_URL = "postgresql+asyncpg://aitbc_governance:password@localhost:5432/aitbc_governance"
# Create async engine
engine = create_async_engine(DATABASE_URL, echo=False)
async def init_db() -> None:
"""Initialize database tables"""
from .domain.governance import (
GovernanceProfile,
Proposal,
Vote,
DaoTreasury,
TransparencyReport,
)
async with engine.begin() as conn:
await conn.run_sync(SQLModel.metadata.create_all)
logger.info("Governance service database initialized")
@asynccontextmanager
async def get_session() -> AsyncIterator[AsyncSession]:
"""Get database session"""
async with AsyncSession(engine) as session:
yield session