2026-07-01 15:41:27 +02:00
|
|
|
"""SQLAlchemy models for the Mail plugin."""
|
|
|
|
|
|
|
|
|
|
from __future__ import annotations
|
|
|
|
|
|
|
|
|
|
import uuid
|
|
|
|
|
from datetime import datetime
|
|
|
|
|
|
|
|
|
|
from sqlalchemy import (
|
|
|
|
|
Boolean,
|
|
|
|
|
DateTime,
|
|
|
|
|
ForeignKey,
|
|
|
|
|
Index,
|
|
|
|
|
Integer,
|
|
|
|
|
String,
|
|
|
|
|
Text,
|
|
|
|
|
UniqueConstraint,
|
|
|
|
|
)
|
|
|
|
|
from sqlalchemy.dialects.postgresql import UUID as PGUUID
|
|
|
|
|
from sqlalchemy.orm import Mapped, mapped_column
|
|
|
|
|
|
|
|
|
|
from app.core.db import Base, TenantMixin
|
|
|
|
|
|
|
|
|
|
# --- Mail Accounts (F-MAIL-14, F-MAIL-18) ---
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class MailAccount(Base, TenantMixin):
|
|
|
|
|
"""Mail account configuration with encrypted IMAP/SMTP credentials."""
|
|
|
|
|
|
|
|
|
|
__tablename__ = "mail_accounts"
|
|
|
|
|
__table_args__ = (
|
|
|
|
|
Index("ix_mail_accounts_user", "user_id"),
|
|
|
|
|
Index("ix_mail_accounts_tenant", "tenant_id"),
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
id: Mapped[uuid.UUID] = mapped_column(
|
|
|
|
|
PGUUID(as_uuid=True), primary_key=True, default=uuid.uuid4
|
|
|
|
|
)
|
|
|
|
|
user_id: Mapped[uuid.UUID] = mapped_column(PGUUID(as_uuid=True), nullable=False)
|
|
|
|
|
email_address: Mapped[str] = mapped_column(String(255), nullable=False)
|
|
|
|
|
display_name: Mapped[str] = mapped_column(String(255), nullable=False, default="")
|
|
|
|
|
imap_host: Mapped[str] = mapped_column(String(255), nullable=False)
|
|
|
|
|
imap_port: Mapped[int] = mapped_column(Integer, nullable=False, default=993)
|
|
|
|
|
imap_ssl: Mapped[bool] = mapped_column(Boolean, nullable=False, default=True)
|
|
|
|
|
smtp_host: Mapped[str] = mapped_column(String(255), nullable=False)
|
|
|
|
|
smtp_port: Mapped[int] = mapped_column(Integer, nullable=False, default=587)
|
|
|
|
|
smtp_tls: Mapped[bool] = mapped_column(Boolean, nullable=False, default=True)
|
|
|
|
|
username: Mapped[str] = mapped_column(String(255), nullable=False)
|
|
|
|
|
encrypted_password: Mapped[str] = mapped_column(Text, nullable=False)
|
|
|
|
|
is_shared: Mapped[bool] = mapped_column(Boolean, nullable=False, default=False)
|
|
|
|
|
is_active: Mapped[bool] = mapped_column(Boolean, nullable=False, default=True)
|
2026-07-15 19:44:41 +02:00
|
|
|
deleted_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True)
|
2026-07-01 15:41:27 +02:00
|
|
|
|
|
|
|
|
|
|
|
|
|
# --- Mail Folders (F-MAIL-01, F-MAIL-19) ---
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class MailFolder(Base, TenantMixin):
|
|
|
|
|
"""IMAP folder mapped to a mail account."""
|
|
|
|
|
|
|
|
|
|
__tablename__ = "mail_folders"
|
|
|
|
|
__table_args__ = (
|
|
|
|
|
Index("ix_mail_folders_account", "account_id"),
|
|
|
|
|
Index("ix_mail_folders_tenant", "tenant_id"),
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
id: Mapped[uuid.UUID] = mapped_column(
|
|
|
|
|
PGUUID(as_uuid=True), primary_key=True, default=uuid.uuid4
|
|
|
|
|
)
|
|
|
|
|
account_id: Mapped[uuid.UUID] = mapped_column(
|
|
|
|
|
PGUUID(as_uuid=True),
|
|
|
|
|
ForeignKey("mail_accounts.id", ondelete="CASCADE"),
|
|
|
|
|
nullable=False,
|
|
|
|
|
)
|
|
|
|
|
name: Mapped[str] = mapped_column(String(255), nullable=False)
|
|
|
|
|
imap_name: Mapped[str] = mapped_column(String(255), nullable=False)
|
|
|
|
|
parent_id: Mapped[uuid.UUID | None] = mapped_column(
|
|
|
|
|
PGUUID(as_uuid=True),
|
|
|
|
|
ForeignKey("mail_folders.id", ondelete="CASCADE"),
|
|
|
|
|
nullable=True,
|
|
|
|
|
)
|
|
|
|
|
is_standard: Mapped[bool] = mapped_column(Boolean, nullable=False, default=False)
|
|
|
|
|
unread_count: Mapped[int] = mapped_column(Integer, nullable=False, default=0)
|
|
|
|
|
total_count: Mapped[int] = mapped_column(Integer, nullable=False, default=0)
|
2026-07-15 19:44:41 +02:00
|
|
|
deleted_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True)
|
2026-07-01 15:41:27 +02:00
|
|
|
|
|
|
|
|
|
|
|
|
|
# --- Mails (F-MAIL-01, F-MAIL-03, F-MAIL-05) ---
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class Mail(Base, TenantMixin):
|
|
|
|
|
"""Individual email message stored locally after IMAP sync."""
|
|
|
|
|
|
|
|
|
|
__tablename__ = "mails"
|
|
|
|
|
__table_args__ = (
|
|
|
|
|
Index("ix_mails_folder", "folder_id"),
|
|
|
|
|
Index("ix_mails_account", "account_id"),
|
|
|
|
|
Index("ix_mails_tenant", "tenant_id"),
|
|
|
|
|
Index("ix_mails_thread", "thread_id"),
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
id: Mapped[uuid.UUID] = mapped_column(
|
|
|
|
|
PGUUID(as_uuid=True), primary_key=True, default=uuid.uuid4
|
|
|
|
|
)
|
|
|
|
|
account_id: Mapped[uuid.UUID] = mapped_column(
|
|
|
|
|
PGUUID(as_uuid=True),
|
|
|
|
|
ForeignKey("mail_accounts.id", ondelete="CASCADE"),
|
|
|
|
|
nullable=False,
|
|
|
|
|
)
|
|
|
|
|
folder_id: Mapped[uuid.UUID] = mapped_column(
|
|
|
|
|
PGUUID(as_uuid=True),
|
|
|
|
|
ForeignKey("mail_folders.id", ondelete="CASCADE"),
|
|
|
|
|
nullable=False,
|
|
|
|
|
)
|
|
|
|
|
message_id: Mapped[str] = mapped_column(String(512), nullable=False)
|
|
|
|
|
thread_id: Mapped[str] = mapped_column(String(512), nullable=False, default="")
|
|
|
|
|
in_reply_to: Mapped[str | None] = mapped_column(String(512), nullable=True)
|
|
|
|
|
references_header: Mapped[str | None] = mapped_column(Text, nullable=True)
|
|
|
|
|
subject: Mapped[str] = mapped_column(String(512), nullable=False, default="")
|
|
|
|
|
from_address: Mapped[str] = mapped_column(String(255), nullable=False)
|
|
|
|
|
to_addresses: Mapped[str] = mapped_column(Text, nullable=False, default="")
|
|
|
|
|
cc_addresses: Mapped[str] = mapped_column(Text, nullable=False, default="")
|
|
|
|
|
bcc_addresses: Mapped[str] = mapped_column(Text, nullable=False, default="")
|
|
|
|
|
body_text: Mapped[str] = mapped_column(Text, nullable=False, default="")
|
|
|
|
|
body_html: Mapped[str] = mapped_column(Text, nullable=False, default="")
|
|
|
|
|
body_html_sanitized: Mapped[str] = mapped_column(Text, nullable=False, default="")
|
|
|
|
|
is_seen: Mapped[bool] = mapped_column(Boolean, nullable=False, default=False)
|
|
|
|
|
is_flagged: Mapped[bool] = mapped_column(Boolean, nullable=False, default=False)
|
|
|
|
|
is_draft: Mapped[bool] = mapped_column(Boolean, nullable=False, default=False)
|
|
|
|
|
is_answered: Mapped[bool] = mapped_column(Boolean, nullable=False, default=False)
|
|
|
|
|
is_forwarded: Mapped[bool] = mapped_column(Boolean, nullable=False, default=False)
|
|
|
|
|
has_attachments: Mapped[bool] = mapped_column(Boolean, nullable=False, default=False)
|
|
|
|
|
size_bytes: Mapped[int] = mapped_column(Integer, nullable=False, default=0)
|
|
|
|
|
received_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True)
|
|
|
|
|
sent_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True)
|
|
|
|
|
contact_id: Mapped[uuid.UUID | None] = mapped_column(PGUUID(as_uuid=True), nullable=True)
|
|
|
|
|
company_id: Mapped[uuid.UUID | None] = mapped_column(PGUUID(as_uuid=True), nullable=True)
|
2026-07-15 19:44:41 +02:00
|
|
|
deleted_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True)
|
2026-07-01 15:41:27 +02:00
|
|
|
|
|
|
|
|
|
|
|
|
|
# --- Mail Attachments (F-MAIL-04) ---
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class MailAttachment(Base, TenantMixin):
|
|
|
|
|
"""Attachment on a mail, optionally linked to a DMS file."""
|
|
|
|
|
|
|
|
|
|
__tablename__ = "mail_attachments"
|
|
|
|
|
__table_args__ = (
|
|
|
|
|
Index("ix_mail_attachments_mail", "mail_id"),
|
|
|
|
|
Index("ix_mail_attachments_tenant", "tenant_id"),
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
id: Mapped[uuid.UUID] = mapped_column(
|
|
|
|
|
PGUUID(as_uuid=True), primary_key=True, default=uuid.uuid4
|
|
|
|
|
)
|
|
|
|
|
mail_id: Mapped[uuid.UUID] = mapped_column(
|
|
|
|
|
PGUUID(as_uuid=True),
|
|
|
|
|
ForeignKey("mails.id", ondelete="CASCADE"),
|
|
|
|
|
nullable=False,
|
|
|
|
|
)
|
|
|
|
|
filename: Mapped[str] = mapped_column(String(255), nullable=False)
|
|
|
|
|
mime_type: Mapped[str] = mapped_column(
|
|
|
|
|
String(255), nullable=False, default="application/octet-stream"
|
|
|
|
|
)
|
|
|
|
|
size_bytes: Mapped[int] = mapped_column(Integer, nullable=False, default=0)
|
|
|
|
|
storage_path: Mapped[str] = mapped_column(String(1024), nullable=False)
|
|
|
|
|
dms_file_id: Mapped[uuid.UUID | None] = mapped_column(PGUUID(as_uuid=True), nullable=True)
|
|
|
|
|
content_id: Mapped[str | None] = mapped_column(String(255), nullable=True)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# --- Mail Labels (F-MAIL-09) ---
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class MailLabel(Base, TenantMixin):
|
|
|
|
|
"""Custom label/tag for mails (colored)."""
|
|
|
|
|
|
|
|
|
|
__tablename__ = "mail_labels"
|
|
|
|
|
__table_args__ = (Index("ix_mail_labels_tenant", "tenant_id"),)
|
|
|
|
|
|
|
|
|
|
id: Mapped[uuid.UUID] = mapped_column(
|
|
|
|
|
PGUUID(as_uuid=True), primary_key=True, default=uuid.uuid4
|
|
|
|
|
)
|
|
|
|
|
name: Mapped[str] = mapped_column(String(100), nullable=False)
|
|
|
|
|
color: Mapped[str] = mapped_column(String(20), nullable=False, default="#808080")
|
|
|
|
|
user_id: Mapped[uuid.UUID] = mapped_column(PGUUID(as_uuid=True), nullable=False)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class MailLabelAssignment(Base, TenantMixin):
|
|
|
|
|
"""Many-to-many: mail <-> label."""
|
|
|
|
|
|
|
|
|
|
__tablename__ = "mail_label_assignments"
|
|
|
|
|
__table_args__ = (
|
|
|
|
|
UniqueConstraint("mail_id", "label_id", name="uq_mail_label_assignment"),
|
|
|
|
|
Index("ix_mail_label_assign_mail", "mail_id"),
|
|
|
|
|
Index("ix_mail_label_assign_label", "label_id"),
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
id: Mapped[uuid.UUID] = mapped_column(
|
|
|
|
|
PGUUID(as_uuid=True), primary_key=True, default=uuid.uuid4
|
|
|
|
|
)
|
|
|
|
|
mail_id: Mapped[uuid.UUID] = mapped_column(
|
|
|
|
|
PGUUID(as_uuid=True),
|
|
|
|
|
ForeignKey("mails.id", ondelete="CASCADE"),
|
|
|
|
|
nullable=False,
|
|
|
|
|
)
|
|
|
|
|
label_id: Mapped[uuid.UUID] = mapped_column(
|
|
|
|
|
PGUUID(as_uuid=True),
|
|
|
|
|
ForeignKey("mail_labels.id", ondelete="CASCADE"),
|
|
|
|
|
nullable=False,
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# --- Mail Rules (F-MAIL-07) ---
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class MailRule(Base, TenantMixin):
|
|
|
|
|
"""Filter rule: conditions to actions for incoming mails."""
|
|
|
|
|
|
|
|
|
|
__tablename__ = "mail_rules"
|
|
|
|
|
__table_args__ = (
|
|
|
|
|
Index("ix_mail_rules_account", "account_id"),
|
|
|
|
|
Index("ix_mail_rules_tenant", "tenant_id"),
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
id: Mapped[uuid.UUID] = mapped_column(
|
|
|
|
|
PGUUID(as_uuid=True), primary_key=True, default=uuid.uuid4
|
|
|
|
|
)
|
|
|
|
|
account_id: Mapped[uuid.UUID | None] = mapped_column(
|
|
|
|
|
PGUUID(as_uuid=True),
|
|
|
|
|
ForeignKey("mail_accounts.id", ondelete="CASCADE"),
|
|
|
|
|
nullable=True,
|
|
|
|
|
)
|
|
|
|
|
name: Mapped[str] = mapped_column(String(255), nullable=False)
|
|
|
|
|
priority: Mapped[int] = mapped_column(Integer, nullable=False, default=0)
|
|
|
|
|
is_active: Mapped[bool] = mapped_column(Boolean, nullable=False, default=True)
|
|
|
|
|
conditions: Mapped[str] = mapped_column(Text, nullable=False, default="{}")
|
|
|
|
|
actions: Mapped[str] = mapped_column(Text, nullable=False, default="{}")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# --- Mail Templates (F-MAIL-06) ---
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class MailTemplate(Base, TenantMixin):
|
|
|
|
|
"""Email template with placeholder substitution."""
|
|
|
|
|
|
|
|
|
|
__tablename__ = "mail_templates"
|
|
|
|
|
__table_args__ = (Index("ix_mail_templates_tenant", "tenant_id"),)
|
|
|
|
|
|
|
|
|
|
id: Mapped[uuid.UUID] = mapped_column(
|
|
|
|
|
PGUUID(as_uuid=True), primary_key=True, default=uuid.uuid4
|
|
|
|
|
)
|
|
|
|
|
name: Mapped[str] = mapped_column(String(255), nullable=False)
|
|
|
|
|
subject: Mapped[str] = mapped_column(String(512), nullable=False, default="")
|
|
|
|
|
body_html: Mapped[str] = mapped_column(Text, nullable=False, default="")
|
|
|
|
|
user_id: Mapped[uuid.UUID] = mapped_column(PGUUID(as_uuid=True), nullable=False)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# --- Mail Signatures (F-MAIL-13) ---
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class MailSignature(Base, TenantMixin):
|
|
|
|
|
"""Email signature (HTML) per user, optionally per account."""
|
|
|
|
|
|
|
|
|
|
__tablename__ = "mail_signatures"
|
|
|
|
|
__table_args__ = (Index("ix_mail_signatures_tenant", "tenant_id"),)
|
|
|
|
|
|
|
|
|
|
id: Mapped[uuid.UUID] = mapped_column(
|
|
|
|
|
PGUUID(as_uuid=True), primary_key=True, default=uuid.uuid4
|
|
|
|
|
)
|
|
|
|
|
user_id: Mapped[uuid.UUID] = mapped_column(PGUUID(as_uuid=True), nullable=False)
|
|
|
|
|
account_id: Mapped[uuid.UUID | None] = mapped_column(
|
|
|
|
|
PGUUID(as_uuid=True),
|
|
|
|
|
ForeignKey("mail_accounts.id", ondelete="CASCADE"),
|
|
|
|
|
nullable=True,
|
|
|
|
|
)
|
|
|
|
|
name: Mapped[str] = mapped_column(String(255), nullable=False)
|
|
|
|
|
body_html: Mapped[str] = mapped_column(Text, nullable=False, default="")
|
|
|
|
|
is_default: Mapped[bool] = mapped_column(Boolean, nullable=False, default=False)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# --- Vacation Auto-Reply (F-MAIL-08) ---
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class VacationSentLog(Base, TenantMixin):
|
|
|
|
|
"""Dedup log for vacation auto-replies (one per sender per 24 hours)."""
|
|
|
|
|
|
|
|
|
|
__tablename__ = "vacation_sent_log"
|
|
|
|
|
__table_args__ = (
|
|
|
|
|
Index("ix_vacation_sent_log_account", "account_id"),
|
|
|
|
|
Index("ix_vacation_sent_log_tenant", "tenant_id"),
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
id: Mapped[uuid.UUID] = mapped_column(
|
|
|
|
|
PGUUID(as_uuid=True), primary_key=True, default=uuid.uuid4
|
|
|
|
|
)
|
|
|
|
|
account_id: Mapped[uuid.UUID] = mapped_column(
|
|
|
|
|
PGUUID(as_uuid=True),
|
|
|
|
|
ForeignKey("mail_accounts.id", ondelete="CASCADE"),
|
|
|
|
|
nullable=False,
|
|
|
|
|
)
|
|
|
|
|
sender_address: Mapped[str] = mapped_column(String(255), nullable=False)
|
|
|
|
|
sent_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), nullable=False)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# --- Seen-By Tracking (F-MAIL-15) ---
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class MailSeenBy(Base, TenantMixin):
|
|
|
|
|
"""Tracks which users have seen a mail in a shared mailbox."""
|
|
|
|
|
|
|
|
|
|
__tablename__ = "mail_seen_by"
|
|
|
|
|
__table_args__ = (
|
|
|
|
|
UniqueConstraint("mail_id", "user_id", name="uq_mail_seen_by"),
|
|
|
|
|
Index("ix_mail_seen_by_mail", "mail_id"),
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
id: Mapped[uuid.UUID] = mapped_column(
|
|
|
|
|
PGUUID(as_uuid=True), primary_key=True, default=uuid.uuid4
|
|
|
|
|
)
|
|
|
|
|
mail_id: Mapped[uuid.UUID] = mapped_column(
|
|
|
|
|
PGUUID(as_uuid=True),
|
|
|
|
|
ForeignKey("mails.id", ondelete="CASCADE"),
|
|
|
|
|
nullable=False,
|
|
|
|
|
)
|
|
|
|
|
user_id: Mapped[uuid.UUID] = mapped_column(PGUUID(as_uuid=True), nullable=False)
|
|
|
|
|
seen_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), nullable=False)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# --- Delegates (F-MAIL-16) ---
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class MailAccountDelegate(Base, TenantMixin):
|
2026-07-16 00:34:36 +02:00
|
|
|
"""Delegate access to a mail account.
|
|
|
|
|
|
|
|
|
|
access_level values:
|
|
|
|
|
- "read" → Mails lesen, suchen, Threads sehen
|
|
|
|
|
- "write" → read + Flags setzen, verschieben, Entwürfe speichern
|
|
|
|
|
- "delete" → write + Mails löschen
|
|
|
|
|
- "full" → delete + Ordner verwalten + ACLs vergeben
|
|
|
|
|
"""
|
2026-07-01 15:41:27 +02:00
|
|
|
|
|
|
|
|
__tablename__ = "mail_account_delegates"
|
|
|
|
|
__table_args__ = (
|
|
|
|
|
UniqueConstraint("account_id", "delegate_user_id", name="uq_mail_delegate"),
|
|
|
|
|
Index("ix_mail_delegates_account", "account_id"),
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
id: Mapped[uuid.UUID] = mapped_column(
|
|
|
|
|
PGUUID(as_uuid=True), primary_key=True, default=uuid.uuid4
|
|
|
|
|
)
|
|
|
|
|
account_id: Mapped[uuid.UUID] = mapped_column(
|
|
|
|
|
PGUUID(as_uuid=True),
|
|
|
|
|
ForeignKey("mail_accounts.id", ondelete="CASCADE"),
|
|
|
|
|
nullable=False,
|
|
|
|
|
)
|
|
|
|
|
delegate_user_id: Mapped[uuid.UUID] = mapped_column(PGUUID(as_uuid=True), nullable=False)
|
|
|
|
|
access_level: Mapped[str] = mapped_column(String(20), nullable=False, default="read")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# --- Send Permissions (F-MAIL-17) ---
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class MailAccountSendPermission(Base, TenantMixin):
|
|
|
|
|
"""Permission for a user to send as a shared/group mailbox."""
|
|
|
|
|
|
|
|
|
|
__tablename__ = "mail_account_send_permissions"
|
|
|
|
|
__table_args__ = (
|
|
|
|
|
UniqueConstraint("account_id", "user_id", name="uq_mail_send_perm"),
|
|
|
|
|
Index("ix_mail_send_perms_account", "account_id"),
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
id: Mapped[uuid.UUID] = mapped_column(
|
|
|
|
|
PGUUID(as_uuid=True), primary_key=True, default=uuid.uuid4
|
|
|
|
|
)
|
|
|
|
|
account_id: Mapped[uuid.UUID] = mapped_column(
|
|
|
|
|
PGUUID(as_uuid=True),
|
|
|
|
|
ForeignKey("mail_accounts.id", ondelete="CASCADE"),
|
|
|
|
|
nullable=False,
|
|
|
|
|
)
|
|
|
|
|
user_id: Mapped[uuid.UUID] = mapped_column(PGUUID(as_uuid=True), nullable=False)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# --- PGP Keys (F-MAIL-12) ---
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class PgpKey(Base, TenantMixin):
|
|
|
|
|
"""PGP private key for a user (encrypted at rest)."""
|
|
|
|
|
|
|
|
|
|
__tablename__ = "pgp_keys"
|
|
|
|
|
__table_args__ = (Index("ix_pgp_keys_user", "user_id"),)
|
|
|
|
|
|
|
|
|
|
id: Mapped[uuid.UUID] = mapped_column(
|
|
|
|
|
PGUUID(as_uuid=True), primary_key=True, default=uuid.uuid4
|
|
|
|
|
)
|
|
|
|
|
user_id: Mapped[uuid.UUID] = mapped_column(PGUUID(as_uuid=True), nullable=False)
|
|
|
|
|
key_id: Mapped[str] = mapped_column(String(255), nullable=False)
|
|
|
|
|
encrypted_private_key: Mapped[str] = mapped_column(Text, nullable=False)
|
|
|
|
|
public_key_armored: Mapped[str] = mapped_column(Text, nullable=False)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class ContactPgpKey(Base, TenantMixin):
|
|
|
|
|
"""Public PGP key for a contact."""
|
|
|
|
|
|
|
|
|
|
__tablename__ = "contact_pgp_keys"
|
|
|
|
|
__table_args__ = (Index("ix_contact_pgp_keys_contact", "contact_id"),)
|
|
|
|
|
|
|
|
|
|
id: Mapped[uuid.UUID] = mapped_column(
|
|
|
|
|
PGUUID(as_uuid=True), primary_key=True, default=uuid.uuid4
|
|
|
|
|
)
|
|
|
|
|
contact_id: Mapped[uuid.UUID] = mapped_column(PGUUID(as_uuid=True), nullable=False)
|
|
|
|
|
public_key_armored: Mapped[str] = mapped_column(Text, nullable=False)
|
|
|
|
|
key_id: Mapped[str] = mapped_column(String(255), nullable=False, default="")
|