diff --git a/apps/governance-service/scripts/setup-database.sql b/apps/governance-service/scripts/setup-database.sql new file mode 100644 index 00000000..337b4b28 --- /dev/null +++ b/apps/governance-service/scripts/setup-database.sql @@ -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 diff --git a/apps/governance-service/src/governance_service/main.py b/apps/governance-service/src/governance_service/main.py index 49dae60d..d9dc72f2 100644 --- a/apps/governance-service/src/governance_service/main.py +++ b/apps/governance-service/src/governance_service/main.py @@ -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) diff --git a/apps/governance-service/src/governance_service/services/__init__.py b/apps/governance-service/src/governance_service/services/__init__.py new file mode 100644 index 00000000..56d4c980 --- /dev/null +++ b/apps/governance-service/src/governance_service/services/__init__.py @@ -0,0 +1,7 @@ +""" +Governance Service services +""" + +from .governance_service import GovernanceService + +__all__ = ["GovernanceService"] diff --git a/apps/governance-service/src/governance_service/services/governance_service.py b/apps/governance-service/src/governance_service/services/governance_service.py new file mode 100644 index 00000000..56e21c93 --- /dev/null +++ b/apps/governance-service/src/governance_service/services/governance_service.py @@ -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, + } diff --git a/apps/governance-service/src/governance_service/storage.py b/apps/governance-service/src/governance_service/storage.py new file mode 100644 index 00000000..264a03ed --- /dev/null +++ b/apps/governance-service/src/governance_service/storage.py @@ -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