Files
multi-seal-mail/server/app/api/v1/schemas.py

309 lines
7.3 KiB
Python

from __future__ import annotations
from datetime import datetime
from typing import Any, Literal
from pydantic import BaseModel, ConfigDict, Field
from app.mailer.campaign.models import ImapConfig, SmtpConfig
class CampaignCreateRequest(BaseModel):
model_config = ConfigDict(extra="forbid")
config: dict[str, Any]
source_filename: str | None = None
source_base_path: str | None = None
class CampaignCreateMinimalRequest(BaseModel):
model_config = ConfigDict(extra="forbid")
external_id: str
name: str
description: str | None = None
current_flow: str = "create"
current_step: str = "basics"
class CampaignVersionUpdateRequest(BaseModel):
model_config = ConfigDict(extra="forbid")
campaign_json: dict[str, Any] | None = None
current_flow: str | None = None
current_step: str | None = None
workflow_state: str | None = None
is_complete: bool | None = None
editor_state: dict[str, Any] | None = None
source_filename: str | None = None
source_base_path: str | None = None
class CampaignVersionSetStepRequest(BaseModel):
model_config = ConfigDict(extra="forbid")
current_flow: str | None = None
current_step: str
class CampaignPartialValidationRequest(BaseModel):
model_config = ConfigDict(extra="forbid")
campaign_json: dict[str, Any] | None = None
section: str | None = None
class CampaignVersionResponse(BaseModel):
model_config = ConfigDict(from_attributes=True)
id: str
campaign_id: str
version_number: int
schema_version: str
source_filename: str | None = None
source_base_path: str | None = None
workflow_state: str = "editing"
current_flow: str = "manual"
current_step: str | None = None
is_complete: bool = False
editor_state: dict[str, Any] = Field(default_factory=dict)
autosaved_at: datetime | None = None
published_at: datetime | None = None
locked_at: datetime | None = None
locked_by_user_id: str | None = None
created_at: datetime
updated_at: datetime
validation_summary: dict[str, Any] | None = None
build_summary: dict[str, Any] | None = None
class CampaignVersionDetailResponse(CampaignVersionResponse):
raw_json: dict[str, Any]
class CampaignPartialValidationResponse(BaseModel):
ok: bool
section: str | None = None
error_count: int
warning_count: int
info_count: int
issues: list[dict[str, Any]]
class CampaignResponse(BaseModel):
model_config = ConfigDict(from_attributes=True)
id: str
external_id: str
name: str
description: str | None = None
status: str
current_version_id: str | None = None
created_at: datetime
updated_at: datetime
class CampaignCreateResponse(BaseModel):
campaign: CampaignResponse
version: CampaignVersionResponse
class CampaignListResponse(BaseModel):
campaigns: list[CampaignResponse]
class CampaignJobsResponse(BaseModel):
jobs: list[dict[str, Any]]
class ValidateCampaignRequest(BaseModel):
model_config = ConfigDict(extra="forbid")
check_files: bool = False
class BuildCampaignRequest(BaseModel):
model_config = ConfigDict(extra="forbid")
write_eml: bool = True
class MailSmtpTestRequest(SmtpConfig):
"""SMTP settings supplied directly from the WebUI mail settings form."""
class MailImapTestRequest(ImapConfig):
"""IMAP settings supplied directly from the WebUI mail settings form."""
enabled: bool = True
class MailConnectionTestResponse(BaseModel):
ok: bool
protocol: Literal["smtp", "imap"]
host: str | None = None
port: int | None = None
security: str | None = None
message: str
details: dict[str, Any] = Field(default_factory=dict)
class MailImapFolderResponse(BaseModel):
name: str
flags: list[str] = Field(default_factory=list)
class MailImapFolderListResponse(BaseModel):
ok: bool
protocol: Literal["imap"] = "imap"
host: str | None = None
port: int | None = None
security: str | None = None
message: str
folders: list[MailImapFolderResponse] = Field(default_factory=list)
detected_sent_folder: str | None = None
details: dict[str, Any] = Field(default_factory=dict)
class ApiKeyCreateRequest(BaseModel):
model_config = ConfigDict(extra="forbid")
name: str
scopes: list[str] = Field(default_factory=list)
class ApiKeyCreateResponse(BaseModel):
id: str
name: str
prefix: str
scopes: list[str]
secret: str
class QueueCampaignRequest(BaseModel):
model_config = ConfigDict(extra="forbid")
version_id: str | None = None
include_warnings: bool = True
enqueue_celery: bool = True
dry_run: bool = False
class QueueCampaignResponse(BaseModel):
campaign_id: str
version_id: str
queued_count: int
skipped_count: int
blocked_count: int
enqueued_count: int
dry_run: bool = False
class AppendSentRequest(BaseModel):
model_config = ConfigDict(extra="forbid")
enqueue_celery: bool = True
dry_run: bool = False
class CampaignActionResponse(BaseModel):
result: dict[str, Any]
class ReportEmailRequest(BaseModel):
model_config = ConfigDict(extra="forbid")
to: list[str]
include_jobs: bool = False
attach_jobs_csv: bool = True
attach_report_json: bool = False
dry_run: bool = False
class ReportEmailResponse(BaseModel):
result: dict[str, Any]
class AuditLogItemResponse(BaseModel):
model_config = ConfigDict(from_attributes=True)
id: str
tenant_id: str | None = None
user_id: str | None = None
api_key_id: str | None = None
action: str
object_type: str | None = None
object_id: str | None = None
details: dict[str, Any] | None = None
created_at: datetime
class AuditLogListResponse(BaseModel):
items: list[AuditLogItemResponse]
class LoginRequest(BaseModel):
model_config = ConfigDict(extra="forbid")
email: str
password: str
# Kept optional for backwards compatibility and future tenant-switch login flows.
# The WebUI no longer sends it. If omitted, the backend resolves the user by email.
tenant_slug: str | None = None
class TenantInfo(BaseModel):
id: str
slug: str
name: str
class TenantMembershipInfo(TenantInfo):
roles: list[str] = Field(default_factory=list)
is_active: bool = True
class UserInfo(BaseModel):
id: str
email: str
display_name: str | None = None
is_tenant_admin: bool = False
class RoleInfo(BaseModel):
id: str
slug: str
name: str
permissions: list[str] = Field(default_factory=list)
class GroupInfo(BaseModel):
id: str
slug: str
name: str
class LoginResponse(BaseModel):
access_token: str
token_type: str = "bearer"
expires_at: datetime
user: UserInfo
# Backwards-compatible alias for the active tenant.
tenant: TenantInfo
active_tenant: TenantInfo
tenants: list[TenantMembershipInfo] = Field(default_factory=list)
scopes: list[str]
roles: list[RoleInfo] = Field(default_factory=list)
groups: list[GroupInfo] = Field(default_factory=list)
class MeResponse(BaseModel):
user: UserInfo
# Backwards-compatible alias for the active tenant.
tenant: TenantInfo
active_tenant: TenantInfo
tenants: list[TenantMembershipInfo] = Field(default_factory=list)
scopes: list[str]
roles: list[RoleInfo] = Field(default_factory=list)
groups: list[GroupInfo] = Field(default_factory=list)