Initial commit: IMAP Mail Filter Service

This commit is contained in:
Stefan Hacker
2026-03-19 13:02:44 +01:00
parent 44fb27801d
commit 61c4384111
34 changed files with 2345 additions and 0 deletions
View File
+103
View File
@@ -0,0 +1,103 @@
import enum
from datetime import datetime
from sqlalchemy import Boolean, DateTime, Enum, ForeignKey, Integer, String, func
from sqlalchemy.orm import Mapped, mapped_column, relationship
from app.database import Base
class ConditionField(str, enum.Enum):
FROM = "from"
TO = "to"
SUBJECT = "subject"
BODY = "body"
HAS_ATTACHMENT = "has_attachment"
class MatchType(str, enum.Enum):
CONTAINS = "contains"
REGEX = "regex"
EXACT = "exact"
class ActionType(str, enum.Enum):
MOVE = "move"
FORWARD = "forward"
DELETE = "delete"
MARK_READ = "mark_read"
class Account(Base):
__tablename__ = "accounts"
id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
name: Mapped[str] = mapped_column(String(100))
imap_host: Mapped[str] = mapped_column(String(255))
imap_port: Mapped[int] = mapped_column(Integer, default=993)
use_ssl: Mapped[bool] = mapped_column(Boolean, default=True)
username: Mapped[str] = mapped_column(String(255))
password: Mapped[str] = mapped_column(String(255))
smtp_host: Mapped[str | None] = mapped_column(String(255), nullable=True)
smtp_port: Mapped[int | None] = mapped_column(Integer, nullable=True)
smtp_username: Mapped[str | None] = mapped_column(String(255), nullable=True)
smtp_password: Mapped[str | None] = mapped_column(String(255), nullable=True)
poll_interval_seconds: Mapped[int] = mapped_column(Integer, default=120)
enabled: Mapped[bool] = mapped_column(Boolean, default=True)
last_poll_at: Mapped[datetime | None] = mapped_column(DateTime, nullable=True)
created_at: Mapped[datetime] = mapped_column(DateTime, server_default=func.now())
updated_at: Mapped[datetime] = mapped_column(
DateTime, server_default=func.now(), onupdate=func.now()
)
filter_rules: Mapped[list["FilterRule"]] = relationship(
back_populates="account", cascade="all, delete-orphan"
)
class FilterRule(Base):
__tablename__ = "filter_rules"
id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
account_id: Mapped[int] = mapped_column(ForeignKey("accounts.id", ondelete="CASCADE"))
name: Mapped[str] = mapped_column(String(200))
priority: Mapped[int] = mapped_column(Integer, default=100)
enabled: Mapped[bool] = mapped_column(Boolean, default=True)
stop_processing: Mapped[bool] = mapped_column(Boolean, default=False)
source_folder: Mapped[str] = mapped_column(String(255), default="INBOX")
created_at: Mapped[datetime] = mapped_column(DateTime, server_default=func.now())
updated_at: Mapped[datetime] = mapped_column(
DateTime, server_default=func.now(), onupdate=func.now()
)
account: Mapped["Account"] = relationship(back_populates="filter_rules")
conditions: Mapped[list["FilterCondition"]] = relationship(
back_populates="rule", cascade="all, delete-orphan"
)
actions: Mapped[list["FilterAction"]] = relationship(
back_populates="rule", cascade="all, delete-orphan"
)
class FilterCondition(Base):
__tablename__ = "filter_conditions"
id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
rule_id: Mapped[int] = mapped_column(ForeignKey("filter_rules.id", ondelete="CASCADE"))
field: Mapped[ConditionField] = mapped_column(Enum(ConditionField))
match_type: Mapped[MatchType] = mapped_column(Enum(MatchType))
value: Mapped[str] = mapped_column(String(500))
negate: Mapped[bool] = mapped_column(Boolean, default=False)
rule: Mapped["FilterRule"] = relationship(back_populates="conditions")
class FilterAction(Base):
__tablename__ = "filter_actions"
id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
rule_id: Mapped[int] = mapped_column(ForeignKey("filter_rules.id", ondelete="CASCADE"))
action_type: Mapped[ActionType] = mapped_column(Enum(ActionType))
parameter: Mapped[str | None] = mapped_column(String(500), nullable=True)
rule: Mapped["FilterRule"] = relationship(back_populates="actions")