"""snooze, templates, subscriptions, tracker counts - Snooze: a thread can leave the Inbox until a set time (``snoozed_until``); ``snoozed_at`` lets a reply that arrives meanwhile wake it early, and ``unsnoozed_at`` brings a woken thread to the top of the Inbox. - Templates: saved replies, per user, inserted from the compose window. - Subscriptions: the mailing-list headers of each message (``List-Id``, ``List-Unsubscribe``, one-click support), read during sync. NULL ``list_unsubscribe`` means "not read yet" (filled in by a backfill job), '' means the message has none. ``subscription_states`` records what the user did about a sender (unsubscribed when). - Tracker blocking: how many tracking pixels were removed from a message's HTML. Revision ID: 0028 Revises: 0027 Create Date: 2026-10-18 """ from __future__ import annotations from collections.abc import Sequence from alembic import op revision: str = "0028" down_revision: str | None = "0027" branch_labels: str | Sequence[str] | None = None depends_on: str | Sequence[str] | None = None def upgrade() -> None: op.execute( """ ALTER TABLE threads ADD COLUMN snoozed_until timestamptz, ADD COLUMN snoozed_at timestamptz, ADD COLUMN unsnoozed_at timestamptz """ ) op.execute( "CREATE INDEX ix_threads_snoozed ON threads (snoozed_until) WHERE snoozed_until IS NOT NULL" ) op.execute( """ ALTER TABLE messages ADD COLUMN list_id text, ADD COLUMN list_unsubscribe text, ADD COLUMN list_unsubscribe_post boolean NOT NULL DEFAULT false """ ) op.execute( "CREATE INDEX ix_messages_user_list ON messages (user_id) " "WHERE list_unsubscribe IS NOT NULL AND list_unsubscribe <> ''" ) op.execute( "CREATE INDEX ix_messages_list_unread ON messages (user_id) WHERE list_unsubscribe IS NULL" ) op.execute( """ CREATE TABLE subscription_states ( user_id uuid NOT NULL REFERENCES users(id) ON DELETE CASCADE, key text NOT NULL, unsubscribed_at timestamptz NOT NULL DEFAULT now(), method text NOT NULL, PRIMARY KEY (user_id, key) ) """ ) op.execute( """ CREATE TABLE mail_templates ( id uuid PRIMARY KEY DEFAULT gen_random_uuid(), user_id uuid NOT NULL REFERENCES users(id) ON DELETE CASCADE, name text NOT NULL, subject text NOT NULL DEFAULT '', body_html text NOT NULL DEFAULT '', created_at timestamptz NOT NULL DEFAULT now(), updated_at timestamptz NOT NULL DEFAULT now() ) """ ) op.execute("CREATE INDEX ix_mail_templates_user ON mail_templates (user_id)") op.execute("ALTER TABLE body_parts ADD COLUMN trackers_blocked integer NOT NULL DEFAULT 0") def downgrade() -> None: op.execute("ALTER TABLE body_parts DROP COLUMN IF EXISTS trackers_blocked") op.execute("DROP TABLE IF EXISTS mail_templates") op.execute("DROP TABLE IF EXISTS subscription_states") op.execute("DROP INDEX IF EXISTS ix_messages_list_unread") op.execute("DROP INDEX IF EXISTS ix_messages_user_list") op.execute( """ ALTER TABLE messages DROP COLUMN IF EXISTS list_id, DROP COLUMN IF EXISTS list_unsubscribe, DROP COLUMN IF EXISTS list_unsubscribe_post """ ) op.execute("DROP INDEX IF EXISTS ix_threads_snoozed") op.execute( """ ALTER TABLE threads DROP COLUMN IF EXISTS snoozed_until, DROP COLUMN IF EXISTS snoozed_at, DROP COLUMN IF EXISTS unsnoozed_at """ )