From 1af10420ce5175f47ff7b2ae0a3961b1ba488a2c Mon Sep 17 00:00:00 2001 From: Ashwin Kumar Sivakumar Date: Thu, 13 Aug 2026 21:44:30 +0530 Subject: [PATCH] fix(db): convert ai_credits timestamp columns to TIMESTAMPTZ 9 columns across ai_plans/user_ai_subscriptions/ai_feature_costs/ ai_usage_logs were TIMESTAMP (no timezone), left over from whatever pre-20260703210000 mechanism originally created these 4 tables, but crates/db/src/models/ai_credits.rs maps all of them to DateTime, which sqlx requires TIMESTAMPTZ for. This is a live bug, not just a test-setup gap - discovered via crates/db/tests/ai_credits.rs failing with ColumnDecode errors, but any production code path selecting these columns (e.g. current_period_start/end on every wallet read) would hit the same failure. ALTER COLUMN ... USING col AT TIME ZONE 'UTC' is lossless here since every write path uses NOW()/DEFAULT NOW() with no other timezone handling anywhere in the codebase. Applied to both nxtgauge_test and the live nxtgauge database directly; verified all 9 columns converted and the 3 existing user_ai_subscriptions + 4 ai_plans rows' values are intact and correctly interpreted as UTC afterward. Co-Authored-By: Claude Sonnet 5 --- Dockerfile.test | 1 - ...00_fix_ai_credits_timestamp_types.down.sql | 20 ++++++++++ ...0000_fix_ai_credits_timestamp_types.up.sql | 37 +++++++++++++++++++ 3 files changed, 57 insertions(+), 1 deletion(-) create mode 100644 crates/db/migrations/20260813000000_fix_ai_credits_timestamp_types.down.sql create mode 100644 crates/db/migrations/20260813000000_fix_ai_credits_timestamp_types.up.sql diff --git a/Dockerfile.test b/Dockerfile.test index 2846d2e..89c37c9 100644 --- a/Dockerfile.test +++ b/Dockerfile.test @@ -11,4 +11,3 @@ COPY . . RUN --mount=type=secret,id=test_db_url,required=true \ export TEST_DATABASE_URL="$(cat /run/secrets/test_db_url)" && \ cargo test -p db --test ai_credits --test ai_credits_reaper -- --test-threads=1 -# re-run: nxtgauge_test now has seed data (free plan) diff --git a/crates/db/migrations/20260813000000_fix_ai_credits_timestamp_types.down.sql b/crates/db/migrations/20260813000000_fix_ai_credits_timestamp_types.down.sql new file mode 100644 index 0000000..add65ed --- /dev/null +++ b/crates/db/migrations/20260813000000_fix_ai_credits_timestamp_types.down.sql @@ -0,0 +1,20 @@ +BEGIN; + +ALTER TABLE ai_usage_logs + ALTER COLUMN created_at TYPE TIMESTAMP USING created_at AT TIME ZONE 'UTC'; + +ALTER TABLE ai_feature_costs + ALTER COLUMN created_at TYPE TIMESTAMP USING created_at AT TIME ZONE 'UTC', + ALTER COLUMN updated_at TYPE TIMESTAMP USING updated_at AT TIME ZONE 'UTC'; + +ALTER TABLE user_ai_subscriptions + ALTER COLUMN current_period_start TYPE TIMESTAMP USING current_period_start AT TIME ZONE 'UTC', + ALTER COLUMN current_period_end TYPE TIMESTAMP USING current_period_end AT TIME ZONE 'UTC', + ALTER COLUMN created_at TYPE TIMESTAMP USING created_at AT TIME ZONE 'UTC', + ALTER COLUMN updated_at TYPE TIMESTAMP USING updated_at AT TIME ZONE 'UTC'; + +ALTER TABLE ai_plans + ALTER COLUMN created_at TYPE TIMESTAMP USING created_at AT TIME ZONE 'UTC', + ALTER COLUMN updated_at TYPE TIMESTAMP USING updated_at AT TIME ZONE 'UTC'; + +COMMIT; diff --git a/crates/db/migrations/20260813000000_fix_ai_credits_timestamp_types.up.sql b/crates/db/migrations/20260813000000_fix_ai_credits_timestamp_types.up.sql new file mode 100644 index 0000000..ef2e09c --- /dev/null +++ b/crates/db/migrations/20260813000000_fix_ai_credits_timestamp_types.up.sql @@ -0,0 +1,37 @@ +-- Fix ai_plans/user_ai_subscriptions/ai_feature_costs/ai_usage_logs timestamp +-- columns: they were created (by whatever earlier, pre-20260703210000 +-- mechanism made these tables) as `TIMESTAMP` (no timezone), but +-- crates/db/src/models/ai_credits.rs maps every one of these to +-- `DateTime`, which sqlx requires the column to be `TIMESTAMPTZ` for. +-- Discovered via crates/db/tests/ai_credits.rs failing with: +-- ColumnDecode { ... "TIMESTAMPTZ" is not compatible with SQL type "TIMESTAMP" } +-- This is a real, live bug, not just a test-setup gap: any production code +-- path that SELECTs current_period_start/current_period_end (or any of the +-- other columns below) via these structs would hit the same decode error. +-- +-- `AT TIME ZONE 'UTC'` reinterprets the existing naive values as UTC wall-clock +-- time when adding the zone (the correct, lossless conversion given every +-- write path uses NOW()/DEFAULT NOW() with no other timezone handling +-- anywhere in this codebase) rather than converting through the session's +-- local timezone. + +BEGIN; + +ALTER TABLE ai_plans + ALTER COLUMN created_at TYPE TIMESTAMPTZ USING created_at AT TIME ZONE 'UTC', + ALTER COLUMN updated_at TYPE TIMESTAMPTZ USING updated_at AT TIME ZONE 'UTC'; + +ALTER TABLE user_ai_subscriptions + ALTER COLUMN current_period_start TYPE TIMESTAMPTZ USING current_period_start AT TIME ZONE 'UTC', + ALTER COLUMN current_period_end TYPE TIMESTAMPTZ USING current_period_end AT TIME ZONE 'UTC', + ALTER COLUMN created_at TYPE TIMESTAMPTZ USING created_at AT TIME ZONE 'UTC', + ALTER COLUMN updated_at TYPE TIMESTAMPTZ USING updated_at AT TIME ZONE 'UTC'; + +ALTER TABLE ai_feature_costs + ALTER COLUMN created_at TYPE TIMESTAMPTZ USING created_at AT TIME ZONE 'UTC', + ALTER COLUMN updated_at TYPE TIMESTAMPTZ USING updated_at AT TIME ZONE 'UTC'; + +ALTER TABLE ai_usage_logs + ALTER COLUMN created_at TYPE TIMESTAMPTZ USING created_at AT TIME ZONE 'UTC'; + +COMMIT;