Files
microdao-daarion/services/dao-service/models.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

225 lines
6.5 KiB
Python

"""
DAO Service Models
Phase 8: DAO Dashboard (Governance + Treasury + Voting)
"""
from pydantic import BaseModel, Field
from typing import Optional, List
from datetime import datetime
from decimal import Decimal
# ============================================================================
# DAO Models
# ============================================================================
class DaoBase(BaseModel):
slug: str = Field(..., min_length=3, max_length=50)
name: str = Field(..., min_length=1, max_length=100)
description: Optional[str] = None
class DaoCreate(DaoBase):
microdao_id: str
governance_model: Optional[str] = "simple"
voting_period_seconds: Optional[int] = 604800 # 7 days
quorum_percent: Optional[int] = 20
class DaoUpdate(BaseModel):
name: Optional[str] = Field(None, min_length=1, max_length=100)
description: Optional[str] = None
governance_model: Optional[str] = None
voting_period_seconds: Optional[int] = None
quorum_percent: Optional[int] = None
is_active: Optional[bool] = None
class DaoRead(DaoBase):
id: str
microdao_id: str
owner_user_id: str
governance_model: str
voting_period_seconds: int
quorum_percent: int
is_active: bool
created_at: datetime
updated_at: datetime
# ============================================================================
# DAO Member Models
# ============================================================================
class DaoMember(BaseModel):
id: str
dao_id: str
user_id: str
role: str # 'owner' | 'admin' | 'member' | 'guest'
joined_at: datetime
class MemberAdd(BaseModel):
user_id: str
role: str = "member"
# ============================================================================
# Treasury Models
# ============================================================================
class DaoTreasuryItem(BaseModel):
token_symbol: str
contract_address: Optional[str] = None
balance: Decimal
class TreasuryUpdate(BaseModel):
token_symbol: str
delta: Decimal # positive or negative
class TreasurySet(BaseModel):
token_symbol: str
balance: Decimal
# ============================================================================
# Proposal Models
# ============================================================================
class ProposalBase(BaseModel):
slug: str = Field(..., min_length=3, max_length=50)
title: str = Field(..., min_length=1, max_length=200)
description: Optional[str] = None
class ProposalCreate(ProposalBase):
start_at: Optional[datetime] = None
end_at: Optional[datetime] = None
governance_model_override: Optional[str] = None
quorum_percent_override: Optional[int] = None
class ProposalUpdate(BaseModel):
title: Optional[str] = Field(None, min_length=1, max_length=200)
description: Optional[str] = None
start_at: Optional[datetime] = None
end_at: Optional[datetime] = None
class ProposalRead(ProposalBase):
id: str
dao_id: str
created_by_user_id: str
created_at: datetime
start_at: Optional[datetime]
end_at: Optional[datetime]
status: str # 'draft' | 'active' | 'passed' | 'rejected' | 'executed'
governance_model_override: Optional[str]
quorum_percent_override: Optional[int]
class ProposalWithVotes(ProposalRead):
"""Proposal with voting statistics"""
votes_yes: int = 0
votes_no: int = 0
votes_abstain: int = 0
total_weight_yes: Decimal = Decimal('0')
total_weight_no: Decimal = Decimal('0')
total_weight_abstain: Decimal = Decimal('0')
quorum_reached: bool = False
is_passed: bool = False
# ============================================================================
# Vote Models
# ============================================================================
class VoteCreate(BaseModel):
vote_value: str = Field(..., pattern="^(yes|no|abstain)$")
class VoteRead(BaseModel):
id: str
proposal_id: str
voter_user_id: str
vote_value: str # 'yes' | 'no' | 'abstain'
weight: Decimal
raw_power: Optional[Decimal]
created_at: datetime
# ============================================================================
# Role Models
# ============================================================================
class DaoRole(BaseModel):
id: str
dao_id: str
code: str
name: str
description: Optional[str]
created_at: datetime
class RoleCreate(BaseModel):
code: str
name: str
description: Optional[str] = None
class RoleAssignment(BaseModel):
id: str
dao_id: str
user_id: str
role_code: str
assigned_at: datetime
class RoleAssignmentCreate(BaseModel):
user_id: str
role_code: str
# ============================================================================
# Audit Log Models
# ============================================================================
class AuditLogEntry(BaseModel):
id: str
dao_id: str
actor_user_id: Optional[str]
event_type: str
event_payload: Optional[dict]
created_at: datetime
# ============================================================================
# Overview Models (Aggregated)
# ============================================================================
class DaoOverview(BaseModel):
dao: DaoRead
members_count: int = 0
active_proposals_count: int = 0
total_proposals_count: int = 0
treasury_items: List[DaoTreasuryItem] = []
class DaoStats(BaseModel):
"""Statistics for DAO"""
members_count: int = 0
proposals_count: int = 0
active_proposals_count: int = 0
total_votes_cast: int = 0
treasury_value_usd: Optional[Decimal] = None
# ============================================================================
# Governance Result Models
# ============================================================================
class ProposalResult(BaseModel):
"""Final result of a proposal"""
proposal_id: str
status: str
votes_yes: int
votes_no: int
votes_abstain: int
total_weight_yes: Decimal
total_weight_no: Decimal
total_weight_abstain: Decimal
total_eligible_voters: int
quorum_required: Decimal
quorum_reached: bool
is_passed: bool
winning_option: Optional[str] # 'yes' | 'no' | None
# ============================================================================
# WebSocket Event Models
# ============================================================================
class DaoEvent(BaseModel):
"""Real-time DAO event for WebSocket"""
event_type: str
dao_id: str
payload: dict
timestamp: datetime