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

1import uuid 

2from datetime import datetime, timezone 

3from enum import Enum as PyEnum 

4from typing import Optional, List 

5 

6from sqlalchemy import String, Text, Integer, Boolean, DateTime, ForeignKey, Enum, JSON, UniqueConstraint, BigInteger 

7from sqlalchemy.orm import Mapped, mapped_column, relationship 

8 

9from app.db.database import Base 

10 

11 

12def generate_uuid() -> str: 

13 return str(uuid.uuid4()) 

14 

15 

16def utc_now() -> datetime: 

17 return datetime.now(timezone.utc) 

18 

19 

20class CampaignCategory(str, PyEnum): 

21 MEDICAL = "MEDICAL" 

22 DISASTER_RELIEF = "DISASTER_RELIEF" 

23 EDUCATION = "EDUCATION" 

24 COMMUNITY = "COMMUNITY" 

25 EMERGENCY = "EMERGENCY" 

26 OTHER = "OTHER" 

27 

28 

29class CampaignStatus(str, PyEnum): 

30 ACTIVE = "ACTIVE" 

31 COMPLETED = "COMPLETED" 

32 CANCELLED = "CANCELLED" 

33 

34 

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" 

41 

42 

43class Creator(Base): 

44 __tablename__ = "creators" 

45 

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) 

49 

50 # Relationships 

51 campaigns: Mapped[List["Campaign"]] = relationship("Campaign", back_populates="creator") 

52 

53 

54class Campaign(Base): 

55 __tablename__ = "campaigns" 

56 

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) 

67 

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) 

80 

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") 

86 

87 

88class Agent(Base): 

89 __tablename__ = "agents" 

90 

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) 

98 

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") 

103 

104 

105class Advocacy(Base): 

106 __tablename__ = "advocacies" 

107 

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) 

116 

117 # Relationships 

118 campaign: Mapped["Campaign"] = relationship("Campaign", back_populates="advocacies") 

119 agent: Mapped["Agent"] = relationship("Agent", back_populates="advocacies") 

120 

121 __table_args__ = ( 

122 UniqueConstraint("campaign_id", "agent_id", name="uq_advocacy_campaign_agent"), 

123 ) 

124 

125 

126class WarRoomPost(Base): 

127 __tablename__ = "warroom_posts" 

128 

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) 

136 

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") 

142 

143 

144class Upvote(Base): 

145 __tablename__ = "upvotes" 

146 

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) 

151 

152 # Relationships 

153 agent: Mapped["Agent"] = relationship("Agent", back_populates="upvotes") 

154 post: Mapped["WarRoomPost"] = relationship("WarRoomPost", back_populates="upvotes") 

155 

156 __table_args__ = ( 

157 UniqueConstraint("agent_id", "post_id", name="uq_upvote_agent_post"), 

158 ) 

159 

160 

161class FeedEvent(Base): 

162 __tablename__ = "feed_events" 

163 

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) 

170 

171 

172class MagicLink(Base): 

173 __tablename__ = "magic_links" 

174 

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) 

181 

182 

183class Donation(Base): 

184 __tablename__ = "donations" 

185 

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) 

195 

196 # Relationships 

197 campaign: Mapped["Campaign"] = relationship("Campaign", back_populates="donations")