Files
microdao-daarion/services/dao-service/repository_votes.py
Apple 3de3c8cb36 feat: Add presence heartbeat for Matrix online status
- matrix-gateway: POST /internal/matrix/presence/online endpoint
- usePresenceHeartbeat hook with activity tracking
- Auto away after 5 min inactivity
- Offline on page close/visibility change
- Integrated in MatrixChatRoom component
2025-11-27 00:19:40 -08:00

147 lines
4.7 KiB
Python

"""
Votes Repository — Database operations for votes
Phase 8: DAO Dashboard
"""
import uuid
from typing import List, Optional
from decimal import Decimal
import asyncpg
from models import VoteCreate, VoteRead
class VoteRepository:
def __init__(self, db_pool: asyncpg.Pool):
self.db = db_pool
async def create_or_update_vote(
self,
proposal_id: str,
voter_user_id: str,
vote_value: str,
weight: Decimal,
raw_power: Optional[Decimal] = None
) -> VoteRead:
"""Create or update vote (user can change their vote)"""
vote_id = uuid.uuid4()
row = await self.db.fetchrow(
"""
INSERT INTO dao_votes (id, proposal_id, voter_user_id, vote_value, weight, raw_power)
VALUES ($1, $2, $3, $4, $5, $6)
ON CONFLICT (proposal_id, voter_user_id) DO UPDATE
SET vote_value = EXCLUDED.vote_value,
weight = EXCLUDED.weight,
raw_power = EXCLUDED.raw_power,
created_at = NOW()
RETURNING id, proposal_id, voter_user_id, vote_value, weight, raw_power, created_at
""",
vote_id,
uuid.UUID(proposal_id),
uuid.UUID(voter_user_id),
vote_value,
weight,
raw_power
)
return self._row_to_vote(row)
async def get_vote(
self,
proposal_id: str,
voter_user_id: str
) -> Optional[VoteRead]:
"""Get user's vote on a proposal"""
row = await self.db.fetchrow(
"""
SELECT id, proposal_id, voter_user_id, vote_value, weight, raw_power, created_at
FROM dao_votes
WHERE proposal_id = $1 AND voter_user_id = $2
""",
uuid.UUID(proposal_id),
uuid.UUID(voter_user_id)
)
if not row:
return None
return self._row_to_vote(row)
async def list_votes_for_proposal(
self,
proposal_id: str
) -> List[VoteRead]:
"""List all votes for a proposal"""
rows = await self.db.fetch(
"""
SELECT id, proposal_id, voter_user_id, vote_value, weight, raw_power, created_at
FROM dao_votes
WHERE proposal_id = $1
ORDER BY created_at DESC
""",
uuid.UUID(proposal_id)
)
return [self._row_to_vote(row) for row in rows]
async def count_votes_by_value(
self,
proposal_id: str
) -> dict:
"""Count votes by value (yes/no/abstain)"""
row = await self.db.fetchrow(
"""
SELECT
COUNT(CASE WHEN vote_value = 'yes' THEN 1 END) as yes_count,
COUNT(CASE WHEN vote_value = 'no' THEN 1 END) as no_count,
COUNT(CASE WHEN vote_value = 'abstain' THEN 1 END) as abstain_count,
COUNT(*) as total_count
FROM dao_votes
WHERE proposal_id = $1
""",
uuid.UUID(proposal_id)
)
return {
'yes': row['yes_count'] or 0,
'no': row['no_count'] or 0,
'abstain': row['abstain_count'] or 0,
'total': row['total_count'] or 0
}
async def get_vote_weights_sum(
self,
proposal_id: str
) -> dict:
"""Get sum of weights by vote value"""
row = await self.db.fetchrow(
"""
SELECT
COALESCE(SUM(CASE WHEN vote_value = 'yes' THEN weight ELSE 0 END), 0) as yes_weight,
COALESCE(SUM(CASE WHEN vote_value = 'no' THEN weight ELSE 0 END), 0) as no_weight,
COALESCE(SUM(CASE WHEN vote_value = 'abstain' THEN weight ELSE 0 END), 0) as abstain_weight,
COALESCE(SUM(weight), 0) as total_weight
FROM dao_votes
WHERE proposal_id = $1
""",
uuid.UUID(proposal_id)
)
return {
'yes': Decimal(str(row['yes_weight'] or 0)),
'no': Decimal(str(row['no_weight'] or 0)),
'abstain': Decimal(str(row['abstain_weight'] or 0)),
'total': Decimal(str(row['total_weight'] or 0))
}
def _row_to_vote(self, row: asyncpg.Record) -> VoteRead:
"""Convert database row to VoteRead"""
return VoteRead(
id=str(row['id']),
proposal_id=str(row['proposal_id']),
voter_user_id=str(row['voter_user_id']),
vote_value=row['vote_value'],
weight=row['weight'],
raw_power=row['raw_power'],
created_at=row['created_at']
)