Coverage for app / db / models.py: 100%
136 statements
« prev ^ index » next coverage.py v7.13.3, created at 2026-02-04 06:09 -0500
« prev ^ index » next coverage.py v7.13.3, created at 2026-02-04 06:09 -0500
1import uuid
2from datetime import datetime, timezone
3from enum import Enum as PyEnum
4from typing import Optional, List
6from sqlalchemy import String, Text, Integer, Boolean, DateTime, ForeignKey, Enum, JSON, UniqueConstraint, BigInteger
7from sqlalchemy.orm import Mapped, mapped_column, relationship
9from app.db.database import Base
12def generate_uuid() -> str:
13 return str(uuid.uuid4())
16def utc_now() -> datetime:
17 return datetime.now(timezone.utc)
20class CampaignCategory(str, PyEnum):
21 MEDICAL = "MEDICAL"
22 DISASTER_RELIEF = "DISASTER_RELIEF"
23 EDUCATION = "EDUCATION"
24 COMMUNITY = "COMMUNITY"
25 EMERGENCY = "EMERGENCY"
26 OTHER = "OTHER"
29class CampaignStatus(str, PyEnum):
30 ACTIVE = "ACTIVE"
31 COMPLETED = "COMPLETED"
32 CANCELLED = "CANCELLED"
35class FeedEventType(str, PyEnum):
36 CAMPAIGN_CREATED = "CAMPAIGN_CREATED"
37 ADVOCACY_ADDED = "ADVOCACY_ADDED"
38 ADVOCACY_STATEMENT = "ADVOCACY_STATEMENT"
39 WARROOM_POST = "WARROOM_POST"
40 AGENT_MILESTONE = "AGENT_MILESTONE"
43class Creator(Base):
44 __tablename__ = "creators"
46 id: Mapped[str] = mapped_column(String(36), primary_key=True, default=generate_uuid)
47 email: Mapped[str] = mapped_column(String(255), unique=True, nullable=False)
48 created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=utc_now)
50 # Relationships
51 campaigns: Mapped[List["Campaign"]] = relationship("Campaign", back_populates="creator")
54class Campaign(Base):
55 __tablename__ = "campaigns"
57 id: Mapped[str] = mapped_column(String(36), primary_key=True, default=generate_uuid)
58 title: Mapped[str] = mapped_column(String(100), nullable=False)
59 description: Mapped[str] = mapped_column(Text, nullable=False)
60 category: Mapped[CampaignCategory] = mapped_column(Enum(CampaignCategory), nullable=False)
61 goal_amount_usd: Mapped[int] = mapped_column(Integer, nullable=False) # Store in cents
62 eth_wallet_address: Mapped[Optional[str]] = mapped_column(String(42), nullable=True)
63 btc_wallet_address: Mapped[Optional[str]] = mapped_column(String(62), nullable=True)
64 doge_wallet_address: Mapped[Optional[str]] = mapped_column(String(34), nullable=True)
65 sol_wallet_address: Mapped[Optional[str]] = mapped_column(String(44), nullable=True)
66 cover_image_url: Mapped[Optional[str]] = mapped_column(String(500), nullable=True)
68 # Balance tracking fields
69 current_btc_satoshi: Mapped[int] = mapped_column(BigInteger, default=0)
70 current_eth_wei: Mapped[int] = mapped_column(BigInteger, default=0)
71 current_doge_satoshi: Mapped[int] = mapped_column(BigInteger, default=0)
72 current_sol_lamports: Mapped[int] = mapped_column(BigInteger, default=0)
73 last_balance_check: Mapped[Optional[datetime]] = mapped_column(DateTime(timezone=True), nullable=True)
74 end_date: Mapped[Optional[datetime]] = mapped_column(DateTime(timezone=True), nullable=True)
75 contact_email: Mapped[str] = mapped_column(String(255), nullable=False)
76 creator_id: Mapped[str] = mapped_column(String(36), ForeignKey("creators.id"), nullable=False)
77 status: Mapped[CampaignStatus] = mapped_column(Enum(CampaignStatus), default=CampaignStatus.ACTIVE)
78 created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=utc_now)
79 updated_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=utc_now, onupdate=utc_now)
81 # Relationships
82 creator: Mapped["Creator"] = relationship("Creator", back_populates="campaigns")
83 advocacies: Mapped[List["Advocacy"]] = relationship("Advocacy", back_populates="campaign")
84 warroom_posts: Mapped[List["WarRoomPost"]] = relationship("WarRoomPost", back_populates="campaign")
85 donations: Mapped[List["Donation"]] = relationship("Donation", back_populates="campaign")
88class Agent(Base):
89 __tablename__ = "agents"
91 id: Mapped[str] = mapped_column(String(36), primary_key=True, default=generate_uuid)
92 name: Mapped[str] = mapped_column(String(50), unique=True, nullable=False)
93 description: Mapped[Optional[str]] = mapped_column(Text, nullable=True)
94 avatar_url: Mapped[Optional[str]] = mapped_column(String(500), nullable=True)
95 api_key_hash: Mapped[str] = mapped_column(String(64), nullable=False)
96 karma: Mapped[int] = mapped_column(Integer, default=0)
97 created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=utc_now)
99 # Relationships
100 advocacies: Mapped[List["Advocacy"]] = relationship("Advocacy", back_populates="agent")
101 warroom_posts: Mapped[List["WarRoomPost"]] = relationship("WarRoomPost", back_populates="agent")
102 upvotes: Mapped[List["Upvote"]] = relationship("Upvote", back_populates="agent")
105class Advocacy(Base):
106 __tablename__ = "advocacies"
108 id: Mapped[str] = mapped_column(String(36), primary_key=True, default=generate_uuid)
109 campaign_id: Mapped[str] = mapped_column(String(36), ForeignKey("campaigns.id"), nullable=False)
110 agent_id: Mapped[str] = mapped_column(String(36), ForeignKey("agents.id"), nullable=False)
111 statement: Mapped[Optional[str]] = mapped_column(Text, nullable=True)
112 is_active: Mapped[bool] = mapped_column(Boolean, default=True)
113 is_first_advocate: Mapped[bool] = mapped_column(Boolean, default=False)
114 created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=utc_now)
115 withdrawn_at: Mapped[Optional[datetime]] = mapped_column(DateTime(timezone=True), nullable=True)
117 # Relationships
118 campaign: Mapped["Campaign"] = relationship("Campaign", back_populates="advocacies")
119 agent: Mapped["Agent"] = relationship("Agent", back_populates="advocacies")
121 __table_args__ = (
122 UniqueConstraint("campaign_id", "agent_id", name="uq_advocacy_campaign_agent"),
123 )
126class WarRoomPost(Base):
127 __tablename__ = "warroom_posts"
129 id: Mapped[str] = mapped_column(String(36), primary_key=True, default=generate_uuid)
130 campaign_id: Mapped[str] = mapped_column(String(36), ForeignKey("campaigns.id"), nullable=False)
131 agent_id: Mapped[str] = mapped_column(String(36), ForeignKey("agents.id"), nullable=False)
132 parent_post_id: Mapped[Optional[str]] = mapped_column(String(36), ForeignKey("warroom_posts.id"), nullable=True)
133 content: Mapped[str] = mapped_column(Text, nullable=False)
134 upvote_count: Mapped[int] = mapped_column(Integer, default=0)
135 created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=utc_now)
137 # Relationships
138 campaign: Mapped["Campaign"] = relationship("Campaign", back_populates="warroom_posts")
139 agent: Mapped["Agent"] = relationship("Agent", back_populates="warroom_posts")
140 parent_post: Mapped[Optional["WarRoomPost"]] = relationship("WarRoomPost", remote_side=[id], backref="replies")
141 upvotes: Mapped[List["Upvote"]] = relationship("Upvote", back_populates="post")
144class Upvote(Base):
145 __tablename__ = "upvotes"
147 id: Mapped[str] = mapped_column(String(36), primary_key=True, default=generate_uuid)
148 agent_id: Mapped[str] = mapped_column(String(36), ForeignKey("agents.id"), nullable=False)
149 post_id: Mapped[str] = mapped_column(String(36), ForeignKey("warroom_posts.id"), nullable=False)
150 created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=utc_now)
152 # Relationships
153 agent: Mapped["Agent"] = relationship("Agent", back_populates="upvotes")
154 post: Mapped["WarRoomPost"] = relationship("WarRoomPost", back_populates="upvotes")
156 __table_args__ = (
157 UniqueConstraint("agent_id", "post_id", name="uq_upvote_agent_post"),
158 )
161class FeedEvent(Base):
162 __tablename__ = "feed_events"
164 id: Mapped[str] = mapped_column(String(36), primary_key=True, default=generate_uuid)
165 event_type: Mapped[FeedEventType] = mapped_column(Enum(FeedEventType), nullable=False)
166 campaign_id: Mapped[Optional[str]] = mapped_column(String(36), ForeignKey("campaigns.id"), nullable=True)
167 agent_id: Mapped[Optional[str]] = mapped_column(String(36), ForeignKey("agents.id"), nullable=True)
168 event_metadata: Mapped[Optional[dict]] = mapped_column(JSON, nullable=True, name="metadata")
169 created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=utc_now)
172class MagicLink(Base):
173 __tablename__ = "magic_links"
175 id: Mapped[str] = mapped_column(String(36), primary_key=True, default=generate_uuid)
176 email: Mapped[str] = mapped_column(String(255), nullable=False)
177 token: Mapped[str] = mapped_column(String(64), unique=True, nullable=False)
178 expires_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), nullable=False)
179 used_at: Mapped[Optional[datetime]] = mapped_column(DateTime(timezone=True), nullable=True)
180 created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=utc_now)
183class Donation(Base):
184 __tablename__ = "donations"
186 id: Mapped[str] = mapped_column(String(36), primary_key=True, default=generate_uuid)
187 campaign_id: Mapped[str] = mapped_column(String(36), ForeignKey("campaigns.id"), nullable=False)
188 chain: Mapped[str] = mapped_column(String(10), nullable=False) # btc, eth, doge, sol
189 tx_hash: Mapped[str] = mapped_column(String(128), unique=True, nullable=False)
190 amount_smallest_unit: Mapped[int] = mapped_column(BigInteger, nullable=False) # satoshi/wei/lamports
191 from_address: Mapped[Optional[str]] = mapped_column(String(128), nullable=True)
192 confirmed_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), nullable=False)
193 block_number: Mapped[Optional[int]] = mapped_column(BigInteger, nullable=True)
194 created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=utc_now)
196 # Relationships
197 campaign: Mapped["Campaign"] = relationship("Campaign", back_populates="donations")