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:
19
apps/governance-service/scripts/setup-database.sql
Normal file
19
apps/governance-service/scripts/setup-database.sql
Normal 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
|
||||
@@ -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)
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
"""
|
||||
Governance Service services
|
||||
"""
|
||||
|
||||
from .governance_service import GovernanceService
|
||||
|
||||
__all__ = ["GovernanceService"]
|
||||
@@ -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,
|
||||
}
|
||||
42
apps/governance-service/src/governance_service/storage.py
Normal file
42
apps/governance-service/src/governance_service/storage.py
Normal 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
|
||||
Reference in New Issue
Block a user