Coverage for app / schemas / campaign.py: 98%

59 statements  

« prev     ^ index     » next       coverage.py v7.13.3, created at 2026-02-04 06:09 -0500

1from datetime import datetime 

2from typing import Optional, List 

3from pydantic import BaseModel, Field, field_validator 

4from app.db.models import CampaignCategory, CampaignStatus 

5 

6 

7class CampaignBase(BaseModel): 

8 title: str = Field(..., min_length=1, max_length=100) 

9 description: str = Field(..., min_length=1, max_length=5000) 

10 category: CampaignCategory 

11 goal_amount_usd: int = Field(..., gt=0, description="Goal amount in cents") 

12 eth_wallet_address: Optional[str] = Field(None, max_length=42) 

13 btc_wallet_address: Optional[str] = Field(None, max_length=62) 

14 doge_wallet_address: Optional[str] = Field(None, max_length=34) 

15 sol_wallet_address: Optional[str] = Field(None, max_length=44) 

16 cover_image_url: Optional[str] = Field(None, max_length=500) 

17 end_date: Optional[datetime] = None 

18 

19 @field_validator("eth_wallet_address", "btc_wallet_address", "doge_wallet_address", "sol_wallet_address", mode="before") 

20 @classmethod 

21 def validate_at_least_one_wallet(cls, v, info): 

22 return v 

23 

24 

25class CampaignCreate(CampaignBase): 

26 contact_email: str = Field(..., max_length=255) 

27 

28 @field_validator("contact_email") 

29 @classmethod 

30 def validate_email(cls, v): 

31 if "@" not in v: 

32 raise ValueError("Invalid email address") 

33 return v 

34 

35 

36class CampaignUpdate(BaseModel): 

37 title: Optional[str] = Field(None, min_length=1, max_length=100) 

38 description: Optional[str] = Field(None, min_length=1, max_length=5000) 

39 category: Optional[CampaignCategory] = None 

40 goal_amount_usd: Optional[int] = Field(None, gt=0) 

41 eth_wallet_address: Optional[str] = Field(None, max_length=42) 

42 btc_wallet_address: Optional[str] = Field(None, max_length=62) 

43 doge_wallet_address: Optional[str] = Field(None, max_length=34) 

44 sol_wallet_address: Optional[str] = Field(None, max_length=44) 

45 cover_image_url: Optional[str] = Field(None, max_length=500) 

46 end_date: Optional[datetime] = None 

47 status: Optional[CampaignStatus] = None 

48 

49 

50class CampaignResponse(CampaignBase): 

51 id: str 

52 status: CampaignStatus 

53 advocate_count: int = 0 

54 current_btc_satoshi: int = 0 

55 current_eth_wei: int = 0 

56 current_doge_satoshi: int = 0 

57 current_sol_lamports: int = 0 

58 last_balance_check: Optional[datetime] = None 

59 created_at: datetime 

60 updated_at: datetime 

61 

62 class Config: 

63 from_attributes = True 

64 

65 

66class CampaignDetailResponse(CampaignResponse): 

67 creator_id: str 

68 

69 

70class CampaignListResponse(BaseModel): 

71 campaigns: List[CampaignResponse] 

72 total: int 

73 page: int 

74 per_page: int