From 34963103d342d05cf4c1870a0564ae7eb9f343e2 Mon Sep 17 00:00:00 2001 From: Todd Hahn Date: Wed, 9 Sep 2026 19:52:01 -0400 Subject: [PATCH 1/4] Updated application versioning to 1.9.2 --- backend/constants.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/backend/constants.py b/backend/constants.py index 98910af..76d1400 100644 --- a/backend/constants.py +++ b/backend/constants.py @@ -8,8 +8,8 @@ VERSIONS = { - "API_VERSION": "1.9.1", - "APPLICATION_VERSION": "1.9.1", + "API_VERSION": "1.9.2", + "APPLICATION_VERSION": "1.9.2", } API_RATES = { From 0f8640b88c5a02309907ba315fce3bbbb703d606 Mon Sep 17 00:00:00 2001 From: Todd Hahn Date: Wed, 9 Sep 2026 19:53:15 -0400 Subject: [PATCH 2/4] Corrected PostgreSQL-only `now()` server default to dialect-aware equivalent --- .../migrations/versions/a1b2c3d4e5f6_add_price_history.py | 2 +- .../versions/e5f6a7b8c9d0_add_converted_currencies.py | 2 +- .../migrations/versions/ede0c3064869_initial_schema.py | 8 ++++---- .../versions/f4639b2e6053_add_wishlist_entries.py | 2 +- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/backend/migrations/versions/a1b2c3d4e5f6_add_price_history.py b/backend/migrations/versions/a1b2c3d4e5f6_add_price_history.py index 7dad6a3..27ec7cb 100644 --- a/backend/migrations/versions/a1b2c3d4e5f6_add_price_history.py +++ b/backend/migrations/versions/a1b2c3d4e5f6_add_price_history.py @@ -22,7 +22,7 @@ def upgrade() -> None: 'price_history', sa.Column('id', sa.Integer(), nullable=False), sa.Column('card_id', sa.Integer(), nullable=False), - sa.Column('recorded_at', sa.DateTime(timezone=True), server_default=sa.text('now()'), nullable=True), + sa.Column('recorded_at', sa.DateTime(timezone=True), server_default=sa.func.now(), nullable=True), sa.Column('price_usd', sa.Float(), nullable=True), sa.Column('price_usd_foil', sa.Float(), nullable=True), sa.Column('price_eur', sa.Float(), nullable=True), diff --git a/backend/migrations/versions/e5f6a7b8c9d0_add_converted_currencies.py b/backend/migrations/versions/e5f6a7b8c9d0_add_converted_currencies.py index b03f6d3..32961fd 100644 --- a/backend/migrations/versions/e5f6a7b8c9d0_add_converted_currencies.py +++ b/backend/migrations/versions/e5f6a7b8c9d0_add_converted_currencies.py @@ -24,7 +24,7 @@ def upgrade() -> None: sa.Column('code', sa.String(10), nullable=False), sa.Column('symbol', sa.String(10), nullable=False), sa.Column('rate', sa.Float(), nullable=True), - sa.Column('added_at', sa.DateTime(timezone=True), server_default=sa.text('now()'), nullable=True), + sa.Column('added_at', sa.DateTime(timezone=True), server_default=sa.func.now(), nullable=True), sa.Column('rate_updated_at', sa.DateTime(timezone=True), nullable=True), sa.PrimaryKeyConstraint('id'), sa.UniqueConstraint('code', name='uq_converted_currency_code'), diff --git a/backend/migrations/versions/ede0c3064869_initial_schema.py b/backend/migrations/versions/ede0c3064869_initial_schema.py index d91ce8d..2c5c392 100644 --- a/backend/migrations/versions/ede0c3064869_initial_schema.py +++ b/backend/migrations/versions/ede0c3064869_initial_schema.py @@ -39,7 +39,7 @@ def upgrade() -> None: sa.Column('price_usd', sa.Float(), nullable=True), sa.Column('price_usd_foil', sa.Float(), nullable=True), sa.Column('price_eur', sa.Float(), nullable=True), - sa.Column('last_fetched', sa.DateTime(timezone=True), server_default=sa.text('now()'), nullable=True), + sa.Column('last_fetched', sa.DateTime(timezone=True), server_default=sa.func.now(), nullable=True), sa.PrimaryKeyConstraint('id') ) op.create_index(op.f('ix_cards_id'), 'cards', ['id'], unique=False) @@ -52,7 +52,7 @@ def upgrade() -> None: sa.Column('hashed_password', sa.String(length=255), nullable=False), sa.Column('is_active', sa.Boolean(), nullable=True), sa.Column('is_admin', sa.Boolean(), nullable=True), - sa.Column('created_at', sa.DateTime(timezone=True), server_default=sa.text('now()'), nullable=True), + sa.Column('created_at', sa.DateTime(timezone=True), server_default=sa.func.now(), nullable=True), sa.PrimaryKeyConstraint('id'), sa.UniqueConstraint('email') ) @@ -67,7 +67,7 @@ def upgrade() -> None: sa.Column('condition', sa.String(length=10), nullable=True), sa.Column('language', sa.String(length=10), nullable=True), sa.Column('notes', sa.Text(), nullable=True), - sa.Column('added_at', sa.DateTime(timezone=True), server_default=sa.text('now()'), nullable=True), + sa.Column('added_at', sa.DateTime(timezone=True), server_default=sa.func.now(), nullable=True), sa.ForeignKeyConstraint(['card_id'], ['cards.id'], ), sa.ForeignKeyConstraint(['user_id'], ['users.id'], ), sa.PrimaryKeyConstraint('id'), @@ -81,7 +81,7 @@ def upgrade() -> None: sa.Column('format', sa.String(length=50), nullable=True), sa.Column('description', sa.Text(), nullable=True), sa.Column('is_public', sa.Boolean(), nullable=True), - sa.Column('created_at', sa.DateTime(timezone=True), server_default=sa.text('now()'), nullable=True), + sa.Column('created_at', sa.DateTime(timezone=True), server_default=sa.func.now(), nullable=True), sa.Column('updated_at', sa.DateTime(timezone=True), nullable=True), sa.ForeignKeyConstraint(['user_id'], ['users.id'], ), sa.PrimaryKeyConstraint('id') diff --git a/backend/migrations/versions/f4639b2e6053_add_wishlist_entries.py b/backend/migrations/versions/f4639b2e6053_add_wishlist_entries.py index c80efd4..eebdd5c 100644 --- a/backend/migrations/versions/f4639b2e6053_add_wishlist_entries.py +++ b/backend/migrations/versions/f4639b2e6053_add_wishlist_entries.py @@ -28,7 +28,7 @@ def upgrade() -> None: sa.Column('target_price', sa.Float(), nullable=True), sa.Column('foil', sa.Boolean(), nullable=True), sa.Column('notes', sa.Text(), nullable=True), - sa.Column('added_at', sa.DateTime(timezone=True), server_default=sa.text('now()'), nullable=True), + sa.Column('added_at', sa.DateTime(timezone=True), server_default=sa.func.now(), nullable=True), sa.ForeignKeyConstraint(['card_id'], ['cards.id'], ), sa.ForeignKeyConstraint(['user_id'], ['users.id'], ), sa.PrimaryKeyConstraint('id'), From 3ca3017c181d7fd4a72010d20395d6abae75fd9e Mon Sep 17 00:00:00 2001 From: Todd Hahn Date: Wed, 9 Sep 2026 19:53:43 -0400 Subject: [PATCH 3/4] Added migration to repair `now()` server defaults on existing SQLite databases --- ...a7b8_fix_now_server_defaults_for_sqlite.py | 50 +++++++++++++++++++ 1 file changed, 50 insertions(+) create mode 100644 backend/migrations/versions/c3d4e5f6a7b8_fix_now_server_defaults_for_sqlite.py diff --git a/backend/migrations/versions/c3d4e5f6a7b8_fix_now_server_defaults_for_sqlite.py b/backend/migrations/versions/c3d4e5f6a7b8_fix_now_server_defaults_for_sqlite.py new file mode 100644 index 0000000..00f8394 --- /dev/null +++ b/backend/migrations/versions/c3d4e5f6a7b8_fix_now_server_defaults_for_sqlite.py @@ -0,0 +1,50 @@ +"""fix now() server defaults for sqlite compatibility + +Revision ID: c3d4e5f6a7b8 +Revises: f1a2b3c4d5e6 +Create Date: 2026-09-09 + +""" +from typing import Sequence, Union + +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision: str = 'c3d4e5f6a7b8' +down_revision: Union[str, Sequence[str], None] = 'f1a2b3c4d5e6' +branch_labels: Union[str, Sequence[str], None] = None +depends_on: Union[str, Sequence[str], None] = None + +_COLUMNS = [ + ('cards', 'last_fetched'), + ('users', 'created_at'), + ('collection_entries', 'added_at'), + ('decks', 'created_at'), + ('price_history', 'recorded_at'), + ('wishlist_entries', 'added_at'), + ('converted_currencies', 'added_at'), +] + + +def upgrade() -> None: + for table, column in _COLUMNS: + with op.batch_alter_table(table) as batch_op: + batch_op.alter_column( + column, + existing_type=sa.DateTime(timezone=True), + existing_nullable=True, + server_default=sa.func.now(), + ) + + +def downgrade() -> None: + for table, column in _COLUMNS: + with op.batch_alter_table(table) as batch_op: + batch_op.alter_column( + column, + existing_type=sa.DateTime(timezone=True), + existing_nullable=True, + server_default=sa.text('now()'), + ) From ec3e201ccb40ed0715f92ef0c2e47278e5c3d9c4 Mon Sep 17 00:00:00 2001 From: Todd Hahn Date: Wed, 9 Sep 2026 19:53:57 -0400 Subject: [PATCH 4/4] Updated CHANGELOG for v1.9.2 --- CHANGELOG.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 98aaa0f..430c2f9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,16 @@ # Changelog +## v1.9.2 + +### Fixed + +- `added_at`, `created_at`, `recorded_at`, and `last_fetched` columns (`cards`, `users`, `collection_entries`, `decks`, `price_history`, `wishlist_entries`, `converted_currencies`) used `server_default=sa.text('now()')`, valid Postgres SQL but not a SQLite function, causing `sqlite3.OperationalError: unknown function: now()` on any insert that didn't explicitly set the timestamp (e.g. importing a card) when running on SQLite +- Migrations now use `sa.func.now()`, which SQLAlchemy compiles per dialect (`CURRENT_TIMESTAMP` on SQLite, `now()` on Postgres); new migration `c3d4e5f6a7b8` repairs existing SQLite databases in place via `batch_alter_table`, applied automatically on next `alembic upgrade head` + +### Changed + +- Version bumped from 1.9.1 to 1.9.2 within `constants.py`. + ## v1.9.1 ### Added