Files
gh-christianlouis-inboxconv…/backend/app/models/schemas.py
T

575 lines
15 KiB
Python

"""
Pydantic schemas for API request/response validation.
"""
from datetime import datetime
from typing import Optional, Dict, Any, List
from pydantic import BaseModel, ConfigDict, EmailStr, Field
from enum import Enum
# Enums matching database models
class SubscriptionTier(str, Enum):
FREE = "free"
BASIC = "basic"
PRO = "pro"
ENTERPRISE = "enterprise"
class MailProtocol(str, Enum):
POP3 = "pop3"
POP3_SSL = "pop3_ssl"
IMAP = "imap"
IMAP_SSL = "imap_ssl"
class AccountStatus(str, Enum):
ACTIVE = "active"
INACTIVE = "inactive"
ERROR = "error"
TESTING = "testing"
class DeliveryMethod(str, Enum):
SMTP = "smtp"
GMAIL_API = "gmail_api"
class NotificationChannel(str, Enum):
EMAIL = "email"
TELEGRAM = "telegram"
WEBHOOK = "webhook"
SLACK = "slack"
DISCORD = "discord"
# User Schemas
class UserBase(BaseModel):
email: EmailStr
full_name: Optional[str] = None
class UserCreate(UserBase):
password: Optional[str] = None
class UserUpdate(BaseModel):
full_name: Optional[str] = None
email: Optional[EmailStr] = None
class UserResponse(UserBase):
id: int
is_active: bool
subscription_tier: SubscriptionTier
subscription_status: str
created_at: datetime
model_config = ConfigDict(from_attributes=True)
class UserDetailResponse(UserResponse):
google_id: Optional[str] = None
oauth_provider: Optional[str] = None
stripe_customer_id: Optional[str] = None
subscription_expires_at: Optional[datetime] = None
last_login_at: Optional[datetime] = None
is_superuser: bool = False
model_config = ConfigDict(from_attributes=True)
# Authentication Schemas
class Token(BaseModel):
access_token: str
refresh_token: str
token_type: str = "bearer"
class TokenPayload(BaseModel):
sub: Optional[str] = None
exp: Optional[int] = None
type: Optional[str] = None
class GoogleAuthRequest(BaseModel):
code: str
redirect_uri: str
# Mail Account Schemas
class MailAccountBase(BaseModel):
name: str = Field(..., max_length=255)
email_address: EmailStr
protocol: MailProtocol = MailProtocol.POP3_SSL
host: str = Field(..., max_length=255)
port: int = Field(..., gt=0, lt=65536)
use_ssl: bool = True
use_tls: bool = False
username: str = Field(..., max_length=255)
forward_to: EmailStr
delivery_method: DeliveryMethod = DeliveryMethod.GMAIL_API
is_enabled: bool = True
check_interval_minutes: int = Field(default=5, gt=0, le=1440)
max_emails_per_check: int = Field(default=50, gt=0, le=1000)
delete_after_forward: bool = True
provider_name: Optional[str] = Field(None, max_length=100)
debug_logging: bool = False
class MailAccountCreate(MailAccountBase):
password: str # Will be encrypted before storage
class MailAccountUpdate(BaseModel):
name: Optional[str] = Field(None, max_length=255)
email_address: Optional[EmailStr] = None
protocol: Optional[MailProtocol] = None
host: Optional[str] = Field(None, max_length=255)
port: Optional[int] = Field(None, gt=0, lt=65536)
use_ssl: Optional[bool] = None
use_tls: Optional[bool] = None
username: Optional[str] = Field(None, max_length=255)
password: Optional[str] = None
forward_to: Optional[EmailStr] = None
delivery_method: Optional[DeliveryMethod] = None
is_enabled: Optional[bool] = None
check_interval_minutes: Optional[int] = Field(None, gt=0, le=1440)
max_emails_per_check: Optional[int] = Field(None, gt=0, le=1000)
delete_after_forward: Optional[bool] = None
provider_name: Optional[str] = Field(None, max_length=100)
debug_logging: Optional[bool] = None
class MailAccountResponse(MailAccountBase):
id: int
user_id: int
status: AccountStatus
delivery_method: DeliveryMethod
provider_name: Optional[str] = None
auto_detected: bool
total_emails_processed: int
total_emails_failed: int
last_check_at: Optional[datetime] = None
last_successful_check_at: Optional[datetime] = None
last_error_at: Optional[datetime] = None
last_error_message: Optional[str] = None
created_at: datetime
updated_at: datetime
# Don't expose password in responses; username is safe to return
password: str = Field(exclude=True, default="")
model_config = ConfigDict(from_attributes=True)
class MailAccountTestRequest(BaseModel):
"""Test connection to mail server"""
host: str
port: int
protocol: MailProtocol
username: str
password: str
use_ssl: bool = True
use_tls: bool = False
class MailAccountTestResponse(BaseModel):
success: bool
message: str
details: Optional[Dict[str, Any]] = None
class MailAccountAutoDetectRequest(BaseModel):
"""Auto-detect mail server settings"""
email_address: EmailStr
class MailAccountAutoDetectResponse(BaseModel):
success: bool
suggestions: List[Dict[str, Any]]
# Processing Run Schemas
class ProcessingRunResponse(BaseModel):
id: int
mail_account_id: int
started_at: datetime
completed_at: Optional[datetime] = None
duration_seconds: Optional[float] = None
emails_fetched: int
emails_forwarded: int
emails_failed: int
status: str
error_message: Optional[str] = None
model_config = ConfigDict(from_attributes=True)
class ProcessingRunDetailResponse(ProcessingRunResponse):
"""ProcessingRunResponse with optional account metadata."""
account_name: Optional[str] = None
account_email: Optional[str] = None
model_config = ConfigDict(from_attributes=True)
# Processing Log Schemas
class ProcessingLogResponse(BaseModel):
id: int
timestamp: datetime
level: str
message: str
email_subject: Optional[str] = None
email_from: Optional[str] = None
success: bool
model_config = ConfigDict(from_attributes=True)
class ProcessingLogDetailResponse(ProcessingLogResponse):
"""ProcessingLogResponse with additional fields."""
mail_account_id: int
processing_run_id: Optional[int] = None
email_size_bytes: Optional[int] = None
error_details: Optional[Dict[str, Any]] = None
model_config = ConfigDict(from_attributes=True)
class PaginatedProcessingRunsResponse(BaseModel):
items: List[ProcessingRunDetailResponse]
total: int
page: int
page_size: int
pages: int
class PaginatedProcessingLogsResponse(BaseModel):
items: List[ProcessingLogDetailResponse]
total: int
page: int
page_size: int
pages: int
class AdminProcessingRunResponse(ProcessingRunDetailResponse):
"""ProcessingRunDetailResponse with user info for admin views."""
user_id: Optional[int] = None
user_email: Optional[str] = None
model_config = ConfigDict(from_attributes=True)
class PaginatedAdminRunsResponse(BaseModel):
items: List[AdminProcessingRunResponse]
total: int
page: int
page_size: int
pages: int
class AdminProcessingLogResponse(ProcessingLogDetailResponse):
"""ProcessingLogDetailResponse with user info for admin views."""
user_id: int
user_email: Optional[str] = None
model_config = ConfigDict(from_attributes=True)
class PaginatedAdminLogsResponse(BaseModel):
items: List[AdminProcessingLogResponse]
total: int
page: int
page_size: int
pages: int
# Notification Config Schemas
class NotificationConfigBase(BaseModel):
name: str = Field(
default="My Notification",
max_length=255,
description="Friendly name for this notification channel",
)
channel: NotificationChannel
apprise_url: Optional[str] = Field(None, description="Apprise notification URL")
is_enabled: bool = True
config: Dict[str, Any] = Field(
default_factory=dict,
description="Legacy channel-specific configuration (deprecated in favour of apprise_url)",
)
notify_on_errors: bool = True
notify_on_success: bool = False
notify_threshold: int = Field(default=3, gt=0, le=100)
class NotificationConfigCreate(NotificationConfigBase):
pass
class NotificationConfigUpdate(BaseModel):
name: Optional[str] = Field(None, max_length=255)
channel: Optional[NotificationChannel] = None
apprise_url: Optional[str] = None
is_enabled: Optional[bool] = None
config: Optional[Dict[str, Any]] = None
notify_on_errors: Optional[bool] = None
notify_on_success: Optional[bool] = None
notify_threshold: Optional[int] = Field(None, gt=0, le=100)
class NotificationConfigResponse(NotificationConfigBase):
id: int
user_id: int
created_at: datetime
updated_at: datetime
model_config = ConfigDict(from_attributes=True)
class NotificationTestRequest(BaseModel):
apprise_url: str = Field(..., description="Apprise URL to test")
class NotificationTestResponse(BaseModel):
success: bool
message: str
# Admin Notification Config Schemas
class AdminNotificationConfigBase(BaseModel):
name: str = Field(..., max_length=255)
apprise_url: str = Field(..., description="Apprise notification URL")
is_enabled: bool = True
notify_on_errors: bool = True
notify_on_system_events: bool = True
description: Optional[str] = None
class AdminNotificationConfigCreate(AdminNotificationConfigBase):
pass
class AdminNotificationConfigUpdate(BaseModel):
name: Optional[str] = Field(None, max_length=255)
apprise_url: Optional[str] = None
is_enabled: Optional[bool] = None
notify_on_errors: Optional[bool] = None
notify_on_system_events: Optional[bool] = None
description: Optional[str] = None
class AdminNotificationConfigResponse(AdminNotificationConfigBase):
id: int
created_at: datetime
updated_at: datetime
model_config = ConfigDict(from_attributes=True)
# Subscription Schemas
class SubscriptionPlanResponse(BaseModel):
id: int
tier: SubscriptionTier
name: str
description: Optional[str] = None
price_monthly: float
price_yearly: Optional[float] = None
max_mail_accounts: int
max_emails_per_day: int
check_interval_minutes: int
support_level: str
features: Optional[Dict[str, Any]] = None
is_active: bool
model_config = ConfigDict(from_attributes=True)
class SubscriptionCheckoutRequest(BaseModel):
tier: SubscriptionTier
billing_period: str = Field(..., pattern="^(monthly|yearly)$")
success_url: str
cancel_url: str
class SubscriptionCheckoutResponse(BaseModel):
checkout_url: str
session_id: str
# Statistics Schemas
class AccountStatistics(BaseModel):
total_accounts: int
active_accounts: int
inactive_accounts: int
error_accounts: int
total_emails_processed: int
total_emails_failed: int
class ProcessingStatistics(BaseModel):
last_24h_processed: int
last_24h_failed: int
last_7d_processed: int
last_7d_failed: int
success_rate: float
class DashboardStatistics(BaseModel):
account_stats: AccountStatistics
processing_stats: ProcessingStatistics
recent_runs: List[ProcessingRunResponse]
recent_errors: List[ProcessingLogResponse]
# Mail Server Preset Schemas
class MailServerPresetResponse(BaseModel):
id: int
provider_name: str
provider_domain: str
configs: Dict[str, Any]
is_verified: bool
model_config = ConfigDict(from_attributes=True)
# Gmail Credential Schemas
class GmailCredentialCreate(BaseModel):
access_token: str
refresh_token: Optional[str] = None
gmail_email: EmailStr
class GmailCredentialResponse(BaseModel):
id: int
user_id: int
gmail_email: str
is_valid: bool
import_label_templates: List[str] = Field(default_factory=list)
default_import_label_templates: List[str] = Field(default_factory=list)
last_verified_at: Optional[datetime] = None
created_at: datetime
updated_at: datetime
model_config = ConfigDict(from_attributes=True)
# Provider Wizard Schemas
class ProviderPreset(BaseModel):
id: str
name: str
icon: Optional[str] = None
domains: List[str]
imap_ssl: Optional[Dict[str, Any]] = None
pop3_ssl: Optional[Dict[str, Any]] = None
notes: Optional[str] = None
class ProviderListResponse(BaseModel):
providers: List[ProviderPreset]
# User SMTP Config Schemas
class UserSmtpConfigBase(BaseModel):
host: str = "smtp.gmail.com"
port: int = Field(587, gt=0, lt=65536)
username: str = ""
sender_email: str = ""
use_tls: bool = True
class UserSmtpConfigUpdate(UserSmtpConfigBase):
password: Optional[str] = None # Only provided when changing the password
class UserSmtpConfigResponse(UserSmtpConfigBase):
id: int
user_id: int
has_password: bool # True if a password is stored (value is never returned)
created_at: datetime
updated_at: datetime
model_config = ConfigDict(from_attributes=True)
class SmtpTestResponse(BaseModel):
success: bool
message: str
# Gmail OAuth Schemas
class GmailAuthorizeResponse(BaseModel):
authorization_url: str
class GmailCallbackRequest(BaseModel):
code: str
redirect_uri: str
class GmailImportLabelsUpdate(BaseModel):
import_label_templates: List[str] = Field(default_factory=list)
# Admin Schemas
class AdminUserListResponse(BaseModel):
id: int
email: str
full_name: Optional[str] = None
is_active: bool
is_superuser: bool
subscription_tier: SubscriptionTier
subscription_status: str
google_id: Optional[str] = None
oauth_provider: Optional[str] = None
last_login_at: Optional[datetime] = None
created_at: datetime
mail_account_count: int = 0
model_config = ConfigDict(from_attributes=True)
class AdminUserUpdate(BaseModel):
full_name: Optional[str] = None
email: Optional[EmailStr] = None
is_active: Optional[bool] = None
is_superuser: Optional[bool] = None
subscription_tier: Optional[SubscriptionTier] = None
subscription_status: Optional[str] = None
class SubscriptionPlanCreate(BaseModel):
tier: SubscriptionTier
name: str
description: Optional[str] = None
price_monthly: float = 0.0
price_yearly: Optional[float] = None
max_mail_accounts: int = 1
max_emails_per_day: int = 1000
check_interval_minutes: int = 5
support_level: str = "community"
features: Optional[Dict[str, Any]] = None
is_active: bool = True
class SubscriptionPlanUpdate(BaseModel):
name: Optional[str] = None
description: Optional[str] = None
price_monthly: Optional[float] = None
price_yearly: Optional[float] = None
max_mail_accounts: Optional[int] = None
max_emails_per_day: Optional[int] = None
check_interval_minutes: Optional[int] = None
support_level: Optional[str] = None
features: Optional[Dict[str, Any]] = None
is_active: Optional[bool] = None