-
-
Notifications
You must be signed in to change notification settings - Fork 132
Expand file tree
/
Copy pathschemas.py
More file actions
274 lines (201 loc) · 8.08 KB
/
Copy pathschemas.py
File metadata and controls
274 lines (201 loc) · 8.08 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
"""
Pydantic Models - Request/Response Schemas
------------------------------------------
Data validation and serialization.
"""
from typing import Optional
from pydantic import BaseModel, Field, field_validator
import re
# ----- Filter Model -----
class FiltersModel(BaseModel):
"""Gmail filter options with validation."""
older_than: Optional[str] = Field(
default=None,
description="Filter emails older than (e.g., 7d, 30d, 90d, 180d, 365d)",
)
after_date: Optional[str] = Field(
default=None, description="Filter emails after this date (format: YYYY/MM/DD)"
)
before_date: Optional[str] = Field(
default=None, description="Filter emails before this date (format: YYYY/MM/DD)"
)
larger_than: Optional[str] = Field(
default=None, description="Filter emails larger than (e.g., 1M, 5M, 10M)"
)
category: Optional[str] = Field(default=None, description="Gmail category filter")
sender: Optional[str] = Field(
default=None,
description="Filter emails from specific sender (email address or domain)",
)
label: Optional[str] = Field(default=None, description="Gmail label filter")
mail_scope: Optional[str] = Field(
default="inbox", description="Mail scope: inbox or all"
)
@field_validator("older_than")
@classmethod
def validate_older_than(cls, v) -> Optional[str]:
if v is None or v == "":
return None
if not re.match(r"^\d+d$", v):
raise ValueError('older_than must be in format like "7d", "30d", "365d"')
return v
@field_validator("after_date")
@classmethod
def validate_after_date(cls, v) -> Optional[str]:
if v is None or v == "":
return None
if not re.match(r"^\d{4}/\d{2}/\d{2}$", v):
raise ValueError('after_date must be in format like "2025/01/15"')
return v
@field_validator("before_date")
@classmethod
def validate_before_date(cls, v) -> Optional[str]:
if v is None or v == "":
return None
if not re.match(r"^\d{4}/\d{2}/\d{2}$", v):
raise ValueError('before_date must be in format like "2025/01/15"')
return v
@field_validator("larger_than")
@classmethod
def validate_larger_than(cls, v) -> Optional[str]:
if v is None or v == "":
return None
if not re.match(r"^\d+[KMG]$", v, re.IGNORECASE):
raise ValueError('larger_than must be in format like "1M", "5M", "10M"')
return v
@field_validator("category")
@classmethod
def validate_category(cls, v) -> Optional[str]:
if v is None or v == "":
return None
allowed = ["primary", "social", "promotions", "updates", "forums"]
if v.lower() not in allowed:
raise ValueError(f"category must be one of: {allowed}")
return v.lower()
@field_validator("sender")
@classmethod
def validate_sender(cls, v) -> Optional[str]:
if v is None or v == "":
return None
# Allow email addresses or domain names
sender = v.strip()
if not sender:
return None
# Basic validation: must contain @ or be a domain-like string
if "@" not in sender and "." not in sender:
raise ValueError("sender must be a valid email address or domain")
return sender
@field_validator("mail_scope")
@classmethod
def validate_mail_scope(cls, v) -> str:
if v is None or v == "":
return "inbox"
value = v.strip().lower()
if value not in ("inbox", "all"):
raise ValueError('mail_scope must be either "inbox" or "all"')
return value
# ----- Request Models -----
class ScanRequest(BaseModel):
"""Request to start email scan."""
limit: int = Field(default=500, ge=1, le=5000, description="Max emails to scan")
filters: Optional[FiltersModel] = Field(
default=None, description="Gmail filter options"
)
class MarkReadRequest(BaseModel):
"""Request to mark emails as read."""
count: int = Field(
default=100,
ge=0,
le=100000,
description="Number of emails to mark. Use 0 to mark all.",
)
filters: Optional[FiltersModel] = Field(
default=None, description="Gmail filter options"
)
class DeleteScanRequest(BaseModel):
"""Request to scan senders for deletion."""
limit: int = Field(default=1000, ge=1, le=10000, description="Max emails to scan")
filters: Optional[FiltersModel] = Field(
default=None, description="Gmail filter options"
)
class UnsubscribeRequest(BaseModel):
"""Request to unsubscribe from a sender."""
domain: str = Field(default="", description="Sender domain")
link: str = Field(default="", description="Unsubscribe link URL")
class DeleteEmailsRequest(BaseModel):
"""Request to delete emails from a sender."""
sender: str = Field(default="", description="Sender email address")
mail_scope: str = Field(default="inbox", description="Mail scope: inbox or all")
@field_validator("mail_scope")
@classmethod
def validate_mail_scope(cls, v) -> str:
if v is None or v == "":
return "inbox"
value = v.strip().lower()
if value not in ("inbox", "all"):
raise ValueError('mail_scope must be either "inbox" or "all"')
return value
class DeleteBulkRequest(BaseModel):
"""Request to delete emails from multiple senders."""
senders: list[str] = Field(default=[], description="List of sender addresses")
mail_scope: str = Field(default="inbox", description="Mail scope: inbox or all")
@field_validator("mail_scope")
@classmethod
def validate_mail_scope(cls, v) -> str:
if v is None or v == "":
return "inbox"
value = v.strip().lower()
if value not in ("inbox", "all"):
raise ValueError('mail_scope must be either "inbox" or "all"')
return value
class DownloadEmailsRequest(BaseModel):
"""Request to download emails from selected senders."""
senders: list[str] = Field(default=[], description="List of sender addresses")
class CreateLabelRequest(BaseModel):
"""Request to create a new Gmail label."""
name: str = Field(..., min_length=1, max_length=100, description="Label name")
class ApplyLabelRequest(BaseModel):
"""Request to apply a label to emails from selected senders."""
label_id: str = Field(..., description="Gmail label ID to apply")
senders: list[str] = Field(default=[], description="List of sender addresses")
class RemoveLabelRequest(BaseModel):
"""Request to remove a label from selected senders."""
label_id: str = Field(..., description="Gmail label ID to remove")
senders: list[str] = Field(default=[], description="List of sender addresses")
class ArchiveRequest(BaseModel):
"""Request to archive emails from selected senders."""
senders: list[str] = Field(default=[], description="List of sender addresses")
class MarkImportantRequest(BaseModel):
"""Request to mark/unmark emails as important."""
senders: list[str] = Field(default=[], description="List of sender addresses")
important: bool = Field(
default=True, description="True to mark important, False to unmark"
)
# ----- Response Models -----
class StatusResponse(BaseModel):
"""Generic status response."""
status: str
class AuthStatusResponse(BaseModel):
"""Authentication status response."""
email: Optional[str] = None
logged_in: bool = False
class ScanStatusResponse(BaseModel):
"""Scan progress status response."""
progress: int = 0
message: str = "Ready"
done: bool = False
error: Optional[str] = None
class UnreadCountResponse(BaseModel):
"""Unread email count response."""
count: int = 0
error: Optional[str] = None
class UnsubscribeResponse(BaseModel):
"""Unsubscribe action response."""
success: bool
message: str
domain: Optional[str] = None
class DeleteResponse(BaseModel):
"""Delete action response."""
success: bool
deleted: int = 0
message: Optional[str] = None