326 lines
7.8 KiB
Python
326 lines
7.8 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 SendCampaignNowRequest(BaseModel):
|
|
model_config = ConfigDict(extra="forbid")
|
|
|
|
version_id: str | None = None
|
|
include_warnings: bool = True
|
|
check_files: bool = False
|
|
validate_before_send: bool = True
|
|
build_before_send: bool = True
|
|
dry_run: bool = False
|
|
use_rate_limit: bool = True
|
|
enqueue_imap_task: bool = False
|
|
|
|
|
|
class SendCampaignNowResponse(BaseModel):
|
|
result: dict[str, Any]
|
|
|
|
|
|
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)
|