Coverage for modules\email\schemas.py: 100%

135 statements  

« prev     ^ index     » next       coverage.py v7.16.0, created at 2026-09-05 09:25 +0800

1from pydantic import BaseModel, EmailStr, Field 

2from typing import Optional, List 

3from uuid import UUID 

4from datetime import datetime 

5 

6 

7class EmailAccountOut(BaseModel): 

8 id: UUID 

9 provider: str 

10 connection_type: str 

11 email_address: str 

12 display_name: str 

13 is_primary: bool 

14 sync_enabled: bool 

15 last_sync_at: Optional[datetime] = None 

16 is_verified: bool 

17 unread_inbox_count: int = 0 

18 

19 class Config: 

20 from_attributes = True 

21 

22 

23class EmailAccountCreateIn(BaseModel): 

24 """Connect email via IMAP/SMTP (Outlook/Thunderbird style).""" 

25 provider: str = Field(default='other') 

26 email_address: EmailStr 

27 display_name: str = "" 

28 imap_host: str = Field(default="", description="IMAP server address") 

29 imap_port: int = Field(default=993) 

30 smtp_host: str = Field(default="", description="SMTP server address") 

31 smtp_port: int = Field(default=587) 

32 password: str = Field(description="Email password or app password") 

33 use_ssl: bool = True 

34 

35 

36class EmailAccountUpdateIn(BaseModel): 

37 display_name: Optional[str] = None 

38 is_primary: Optional[bool] = None 

39 sync_enabled: Optional[bool] = None 

40 sync_interval: Optional[int] = None 

41 

42 

43class EmailFolderOut(BaseModel): 

44 id: str # Can be UUID string or integer as string 

45 name: str 

46 folder_type: str 

47 unread_count: int 

48 

49 class Config: 

50 from_attributes = True 

51 

52 

53class EmailFolderCreateIn(BaseModel): 

54 """Create a custom email folder.""" 

55 name: str = Field(max_length=128) 

56 folder_type: str = Field(default='custom') # custom is the only user-creatable type 

57 

58 

59class EmailFilterCondition(BaseModel): 

60 """A single condition for email filtering.""" 

61 field: str = Field(description="Field to match: from, to, subject, body, cc") 

62 operator: str = Field(description="Operator: contains, equals, starts_with, ends_with, regex") 

63 value: str = Field(description="Value to match against") 

64 

65 

66class EmailFilterCreateIn(BaseModel): 

67 """Create an email filter/rule.""" 

68 name: str = Field(max_length=128) 

69 conditions: List[EmailFilterCondition] = Field(description="List of conditions (all must match)") 

70 action_type: str = Field(description="Action: move_to_folder, mark_read, star, delete") 

71 action_folder_id: Optional[UUID] = None 

72 is_active: bool = True 

73 priority: int = 0 

74 

75 

76class EmailFilterOut(BaseModel): 

77 id: UUID 

78 name: str 

79 conditions: dict 

80 action_type: str 

81 action_folder_id: Optional[UUID] = None 

82 is_active: bool 

83 priority: int 

84 

85 class Config: 

86 from_attributes = True 

87 

88 

89class EmailAddress(BaseModel): 

90 email: str 

91 name: Optional[str] = None 

92 

93 

94class EmailMessageOut(BaseModel): 

95 id: UUID 

96 message_id: str 

97 subject: str 

98 snippet: str 

99 from_address: str 

100 from_name: Optional[str] = None 

101 to_addresses: List[EmailAddress] 

102 cc_addresses: Optional[List[EmailAddress]] = None 

103 is_read: bool 

104 is_starred: bool 

105 is_draft: bool 

106 is_sent: bool 

107 sent_at: Optional[datetime] = None 

108 received_at: Optional[datetime] = None 

109 folder_id: Optional[UUID] = None 

110 account_id: Optional[UUID] = None 

111 

112 class Config: 

113 from_attributes = True 

114 

115 

116class EmailMessageDetailOut(EmailMessageOut): 

117 body_text: Optional[str] = None 

118 body_html: Optional[str] = None 

119 attachments: List['EmailAttachmentOut'] = [] 

120 

121 class Config: 

122 from_attributes = True 

123 

124 

125class EmailAttachmentOut(BaseModel): 

126 id: UUID 

127 file_name: str 

128 content_type: str 

129 size_bytes: int 

130 file_path: Optional[str] = None 

131 

132 class Config: 

133 from_attributes = True 

134 

135 

136class EmailSendIn(BaseModel): 

137 """Send an email.""" 

138 to: List[EmailStr] 

139 cc: Optional[List[EmailStr]] = None 

140 bcc: Optional[List[EmailStr]] = None 

141 subject: str 

142 body: str 

143 is_html: bool = False 

144 

145 

146class EmailDraftIn(BaseModel): 

147 """Save a draft.""" 

148 to: Optional[List[EmailStr]] = None 

149 cc: Optional[List[EmailStr]] = None 

150 bcc: Optional[List[EmailStr]] = None 

151 subject: str = "" 

152 body: str = "" 

153 

154 

155class EmailMessageUpdateIn(BaseModel): 

156 is_read: Optional[bool] = None 

157 is_starred: Optional[bool] = None 

158 is_flagged: Optional[bool] = None 

159 

160 

161class InternalEmailOut(BaseModel): 

162 id: UUID 

163 subject: str 

164 body: str 

165 sender_name: str 

166 sender_email: str 

167 recipients: List[EmailAddress] 

168 sent_at: datetime 

169 is_read: bool 

170 

171 class Config: 

172 from_attributes = True 

173 

174 

175class InternalEmailSendIn(BaseModel): 

176 """Send internal ERP email.""" 

177 to: List[UUID] # User IDs 

178 subject: str = Field(..., max_length=512) 

179 body: str 

180 

181 

182class InternalEmailCountOut(BaseModel): 

183 unread_count: int 

184 total_count: int 

185 

186 

187# Update forward references 

188EmailMessageDetailOut.model_rebuild()