from __future__ import annotations from datetime import date, datetime, timezone from sqlalchemy import Boolean, Date, DateTime, Float, ForeignKey, Integer, String, Text, UniqueConstraint from sqlalchemy.orm import Mapped, mapped_column, relationship from app.db import Base def utcnow() -> datetime: return datetime.now(timezone.utc) class InboxStatus(Base): __tablename__ = "inbox_statuses" id: Mapped[int] = mapped_column(primary_key=True) inbox_id: Mapped[str] = mapped_column(String(120), unique=True, index=True) label: Mapped[str] = mapped_column(String(200)) domain: Mapped[str] = mapped_column(String(255), index=True) folder: Mapped[str] = mapped_column(String(255)) recipient: Mapped[str] = mapped_column(String(320)) enabled: Mapped[bool] = mapped_column(Boolean, default=True) last_check_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True)) last_success_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True)) last_error_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True)) last_error: Mapped[str | None] = mapped_column(Text) last_new_messages: Mapped[int] = mapped_column(Integer, default=0) last_reports_imported: Mapped[int] = mapped_column(Integer, default=0) created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=utcnow) updated_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=utcnow, onupdate=utcnow) class MailMessage(Base): __tablename__ = "mail_messages" __table_args__ = (UniqueConstraint("inbox_id", "folder", "imap_uid", name="uq_message_uid"),) id: Mapped[int] = mapped_column(primary_key=True) inbox_id: Mapped[str] = mapped_column(String(120), index=True) imap_uid: Mapped[str] = mapped_column(String(120)) message_id: Mapped[str | None] = mapped_column(String(500)) folder: Mapped[str] = mapped_column(String(255)) subject: Mapped[str | None] = mapped_column(Text) sender: Mapped[str | None] = mapped_column(Text) recipient: Mapped[str | None] = mapped_column(Text) message_date: Mapped[datetime | None] = mapped_column(DateTime(timezone=True)) seen: Mapped[bool] = mapped_column(Boolean, default=False) status: Mapped[str] = mapped_column(String(40), default="skipped") error: Mapped[str | None] = mapped_column(Text) processed_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True)) created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=utcnow) reports: Mapped[list["Report"]] = relationship(back_populates="mail_message") class Report(Base): __tablename__ = "reports" id: Mapped[int] = mapped_column(primary_key=True) inbox_id: Mapped[str] = mapped_column(String(120), index=True) mail_message_id: Mapped[int | None] = mapped_column(ForeignKey("mail_messages.id")) raw_xml_sha256: Mapped[str] = mapped_column(String(64), unique=True, index=True) report_id: Mapped[str | None] = mapped_column(String(500), index=True) org_name: Mapped[str | None] = mapped_column(String(255), index=True) org_email: Mapped[str | None] = mapped_column(String(320)) extra_contact_info: Mapped[str | None] = mapped_column(Text) domain: Mapped[str] = mapped_column(String(255), index=True) date_begin: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), index=True) date_end: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), index=True) policy_p: Mapped[str | None] = mapped_column(String(40)) policy_sp: Mapped[str | None] = mapped_column(String(40)) policy_pct: Mapped[int | None] = mapped_column(Integer) adkim: Mapped[str | None] = mapped_column(String(20)) aspf: Mapped[str | None] = mapped_column(String(20)) fo: Mapped[str | None] = mapped_column(String(80)) created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=utcnow) mail_message: Mapped[MailMessage | None] = relationship(back_populates="reports") records: Mapped[list["Record"]] = relationship(back_populates="report", cascade="all, delete-orphan") class Record(Base): __tablename__ = "records" id: Mapped[int] = mapped_column(primary_key=True) report_id: Mapped[int] = mapped_column(ForeignKey("reports.id"), index=True) source_ip: Mapped[str] = mapped_column(String(80), index=True) source_reverse_dns: Mapped[str | None] = mapped_column(String(255)) source_asn: Mapped[str | None] = mapped_column(String(80)) source_country: Mapped[str | None] = mapped_column(String(80)) count: Mapped[int] = mapped_column(Integer, default=0) disposition: Mapped[str | None] = mapped_column(String(40), index=True) policy_dkim: Mapped[str | None] = mapped_column(String(40)) policy_spf: Mapped[str | None] = mapped_column(String(40)) dkim_aligned: Mapped[bool] = mapped_column(Boolean, default=False) spf_aligned: Mapped[bool] = mapped_column(Boolean, default=False) dmarc_pass: Mapped[bool] = mapped_column(Boolean, default=False) header_from: Mapped[str | None] = mapped_column(String(255), index=True) reason_type: Mapped[str | None] = mapped_column(String(120)) reason_comment: Mapped[str | None] = mapped_column(Text) known_sender_id: Mapped[str | None] = mapped_column(String(120), index=True) known_sender_name: Mapped[str | None] = mapped_column(String(255)) is_known_sender: Mapped[bool] = mapped_column(Boolean, default=False, index=True) created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=utcnow) report: Mapped[Report] = relationship(back_populates="records") auth_results: Mapped[list["AuthResult"]] = relationship(back_populates="record", cascade="all, delete-orphan") class AuthResult(Base): __tablename__ = "auth_results" id: Mapped[int] = mapped_column(primary_key=True) record_id: Mapped[int] = mapped_column(ForeignKey("records.id"), index=True) auth_type: Mapped[str] = mapped_column(String(20), index=True) domain: Mapped[str | None] = mapped_column(String(255), index=True) selector: Mapped[str | None] = mapped_column(String(120)) scope: Mapped[str | None] = mapped_column(String(120)) result: Mapped[str | None] = mapped_column(String(120)) human_result: Mapped[str | None] = mapped_column(Text) created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=utcnow) record: Mapped[Record] = relationship(back_populates="auth_results") class SkippedReportPayload(Base): __tablename__ = "skipped_report_payloads" __table_args__ = ( UniqueConstraint("inbox_id", "folder", "imap_uid", "raw_xml_sha256", "reason", name="uq_skipped_report_payload"), ) id: Mapped[int] = mapped_column(primary_key=True) inbox_id: Mapped[str] = mapped_column(String(120), index=True) folder: Mapped[str] = mapped_column(String(255)) imap_uid: Mapped[str] = mapped_column(String(120)) message_id: Mapped[str | None] = mapped_column(String(500)) mail_message_id: Mapped[int | None] = mapped_column(ForeignKey("mail_messages.id")) reason: Mapped[str] = mapped_column(String(80), index=True) raw_xml_sha256: Mapped[str | None] = mapped_column(String(64), index=True) existing_report_id: Mapped[int | None] = mapped_column(ForeignKey("reports.id")) report_identifier: Mapped[str | None] = mapped_column(String(500), index=True) reporting_org: Mapped[str | None] = mapped_column(String(255), index=True) report_date: Mapped[date | None] = mapped_column(Date, index=True) created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=utcnow) class Alert(Base): __tablename__ = "alerts" id: Mapped[int] = mapped_column(primary_key=True) fingerprint: Mapped[str] = mapped_column(String(500), unique=True, index=True) inbox_id: Mapped[str] = mapped_column(String(120), index=True) domain: Mapped[str] = mapped_column(String(255), index=True) severity: Mapped[str] = mapped_column(String(40), index=True) type: Mapped[str] = mapped_column(String(120), index=True) title: Mapped[str] = mapped_column(String(500)) summary: Mapped[str] = mapped_column(Text) details_json: Mapped[str] = mapped_column(Text, default="{}") llm_summary: Mapped[str | None] = mapped_column(Text) llm_risk: Mapped[str | None] = mapped_column(Text) llm_recommended_action: Mapped[str | None] = mapped_column(Text) status: Mapped[str] = mapped_column(String(40), default="open", index=True) first_seen_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=utcnow) last_seen_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=utcnow) created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=utcnow) updated_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=utcnow, onupdate=utcnow) class DailyStat(Base): __tablename__ = "daily_stats" __table_args__ = (UniqueConstraint("domain", "date", name="uq_daily_stat_domain_date"),) id: Mapped[int] = mapped_column(primary_key=True) domain: Mapped[str] = mapped_column(String(255), index=True) date: Mapped[date] = mapped_column(Date, index=True) total_messages: Mapped[int] = mapped_column(Integer, default=0) dmarc_pass_count: Mapped[int] = mapped_column(Integer, default=0) dmarc_fail_count: Mapped[int] = mapped_column(Integer, default=0) spf_aligned_count: Mapped[int] = mapped_column(Integer, default=0) spf_failed_count: Mapped[int] = mapped_column(Integer, default=0) dkim_aligned_count: Mapped[int] = mapped_column(Integer, default=0) dkim_failed_count: Mapped[int] = mapped_column(Integer, default=0) unknown_source_count: Mapped[int] = mapped_column(Integer, default=0) known_source_count: Mapped[int] = mapped_column(Integer, default=0) quarantine_count: Mapped[int] = mapped_column(Integer, default=0) reject_count: Mapped[int] = mapped_column(Integer, default=0) top_reporters_json: Mapped[str] = mapped_column(Text, default="[]") top_sources_json: Mapped[str] = mapped_column(Text, default="[]") created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=utcnow) updated_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=utcnow, onupdate=utcnow) class LLMReport(Base): __tablename__ = "llm_reports" id: Mapped[int] = mapped_column(primary_key=True) domain: Mapped[str] = mapped_column(String(255), index=True) period_start: Mapped[datetime] = mapped_column(DateTime(timezone=True), index=True) period_end: Mapped[datetime] = mapped_column(DateTime(timezone=True), index=True) report_type: Mapped[str] = mapped_column(String(40), index=True) input_json: Mapped[str] = mapped_column(Text) output_json: Mapped[str] = mapped_column(Text) plain_text: Mapped[str] = mapped_column(Text) created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=utcnow)