From 708bb9eec7ea4360b26709fb2a3f82dd139ed600 Mon Sep 17 00:00:00 2001 From: Fringg Date: Mon, 23 Feb 2026 21:26:16 +0300 Subject: [PATCH] fix: migrate all remaining naive timestamp columns to timestamptz Old universal_migration.py created some tables (including email_templates) with `timestamp` (naive) columns and had a catch-all that converted all naive columns to `timestamptz` on each startup. After switching to Alembic, that catch-all stopped running. Users whose email_templates table was created by universal_migration.py before the catch-all ran still have naive `timestamp` columns. The code uses `datetime.now(UTC)` (timezone-aware), causing asyncpg to raise: "can't subtract offset-naive and offset-aware datetimes" Migration 0007 finds and converts ALL remaining naive timestamp columns in public schema to timestamptz, assuming UTC for existing data. Fixes: email template save returning 503 with DataError --- .../versions/0007_fix_naive_timestamps.py | 67 +++++++++++++++++++ 1 file changed, 67 insertions(+) create mode 100644 migrations/alembic/versions/0007_fix_naive_timestamps.py diff --git a/migrations/alembic/versions/0007_fix_naive_timestamps.py b/migrations/alembic/versions/0007_fix_naive_timestamps.py new file mode 100644 index 00000000..da399184 --- /dev/null +++ b/migrations/alembic/versions/0007_fix_naive_timestamps.py @@ -0,0 +1,67 @@ +"""fix all remaining naive timestamp columns to timestamptz + +Revision ID: 0007 +Revises: 0006 +Create Date: 2026-02-23 + +The old universal_migration.py created some tables with `timestamp` (naive) +columns and had a catch-all migration that converted ALL naive timestamp +columns to `timestamptz` on every startup. When universal_migration.py was +replaced with Alembic, that catch-all migration stopped running. + +Databases where `email_templates` (and potentially other tables) were created +by universal_migration.py before the catch-all ran still have naive columns. +The code uses `datetime.now(UTC)` (timezone-aware), causing asyncpg to raise: + "can't subtract offset-naive and offset-aware datetimes" + +This migration finds and converts ALL remaining naive timestamp columns +in public schema to timestamptz, assuming UTC for existing data. +""" + +from typing import Sequence, Union + +import sqlalchemy as sa +from alembic import op + +# revision identifiers, used by Alembic. +revision: str = '0007' +down_revision: Union[str, None] = '0006' +branch_labels: Union[str, Sequence[str], None] = None +depends_on: Union[str, Sequence[str], None] = None + + +def upgrade() -> None: + conn = op.get_bind() + + # Find all naive timestamp columns in public schema + result = conn.execute( + sa.text(""" + SELECT table_name, column_name + FROM information_schema.columns + WHERE table_schema = 'public' + AND data_type = 'timestamp without time zone' + ORDER BY table_name, column_name + """) + ) + columns = result.fetchall() + + if not columns: + return + + # Set timezone context for the conversion + conn.execute(sa.text("SET LOCAL timezone = 'UTC'")) + + for table_name, column_name in columns: + op.execute( + sa.text( + f'ALTER TABLE "{table_name}" ' + f'ALTER COLUMN "{column_name}" TYPE TIMESTAMPTZ ' + f"USING \"{column_name}\" AT TIME ZONE 'UTC'" + ) + ) + + +def downgrade() -> None: + # No-op: converting back to naive timestamps would lose timezone info + # and re-introduce the original bug. + pass