From f3b0b90860e833da1a41fe26cd9119b442df03a7 Mon Sep 17 00:00:00 2001 From: Cal Corum Date: Fri, 30 Jan 2026 22:39:45 -0600 Subject: [PATCH] Fix PostgreSQL timestamp handling in gauntletruns.py - Convert milliseconds to datetime for GET filters (created_after/before, ended_after/before) - Fix is_active filter to use is_null() instead of comparing to 0 - Fix PATCH to use datetime.now() instead of int timestamps - Fix POST to convert timestamps and use None for nullable ended field - Update Pydantic model defaults to None instead of int Co-Authored-By: Claude Opus 4.5 --- PROJECT_PLAN.json | 10 ++++----- app/routers_v2/gauntletruns.py | 40 ++++++++++++++++++++++++---------- 2 files changed, 33 insertions(+), 17 deletions(-) diff --git a/PROJECT_PLAN.json b/PROJECT_PLAN.json index fa0e5da..ee0577a 100644 --- a/PROJECT_PLAN.json +++ b/PROJECT_PLAN.json @@ -8,7 +8,7 @@ "branch": "postgres-migration", "totalEstimatedHours": 26, "totalTasks": 28, - "completedTasks": 20 + "completedTasks": 24 }, "context": { "sourceDatabase": { @@ -532,7 +532,7 @@ "id": "TS-008", "name": "Fix gauntletruns.py GET timestamp filters (4 fields)", "category": "high", - "completed": false, + "completed": true, "file": "app/routers_v2/gauntletruns.py", "lines": [36, 37, 58, 60, 62, 64], "notes": "created_after, created_before, ended_after, ended_before" @@ -541,7 +541,7 @@ "id": "TS-009", "name": "Fix gauntletruns.py GET is_active filter (NULL vs 0)", "category": "high", - "completed": false, + "completed": true, "file": "app/routers_v2/gauntletruns.py", "lines": [67, 69], "notes": "Should use is_null() not == 0" @@ -550,7 +550,7 @@ "id": "TS-010", "name": "Fix gauntletruns.py PATCH timestamp fields", "category": "high", - "completed": false, + "completed": true, "file": "app/routers_v2/gauntletruns.py", "lines": [122, 124, 129, 131], "notes": "Assigns int to DateTimeField, uses 0 instead of None" @@ -559,7 +559,7 @@ "id": "TS-011", "name": "Fix gauntletruns.py POST timestamp fields", "category": "high", - "completed": false, + "completed": true, "file": "app/routers_v2/gauntletruns.py", "lines": [28, 29, 152], "notes": "Pydantic model uses int with default 0" diff --git a/app/routers_v2/gauntletruns.py b/app/routers_v2/gauntletruns.py index c8fecf9..43a9273 100644 --- a/app/routers_v2/gauntletruns.py +++ b/app/routers_v2/gauntletruns.py @@ -25,8 +25,8 @@ class GauntletRunModel(pydantic.BaseModel): wins: Optional[int] = 0 losses: Optional[int] = 0 gsheet: Optional[str] = None - created: Optional[int] = int(datetime.timestamp(datetime.now())*1000) - ended: Optional[int] = 0 + created: Optional[int] = None + ended: Optional[int] = None @router.get('') @@ -55,18 +55,24 @@ async def get_gauntletruns( if gsheet is not None: all_gauntlets = all_gauntlets.where(GauntletRun.gsheet == gsheet) if created_after is not None: - all_gauntlets = all_gauntlets.where(GauntletRun.created >= created_after) + # Convert milliseconds timestamp to datetime for PostgreSQL comparison + created_after_dt = datetime.fromtimestamp(created_after / 1000) + all_gauntlets = all_gauntlets.where(GauntletRun.created >= created_after_dt) if created_before is not None: - all_gauntlets = all_gauntlets.where(GauntletRun.created <= created_before) + created_before_dt = datetime.fromtimestamp(created_before / 1000) + all_gauntlets = all_gauntlets.where(GauntletRun.created <= created_before_dt) if ended_after is not None: - all_gauntlets = all_gauntlets.where(GauntletRun.ended >= ended_after) + ended_after_dt = datetime.fromtimestamp(ended_after / 1000) + all_gauntlets = all_gauntlets.where(GauntletRun.ended >= ended_after_dt) if ended_before is not None: - all_gauntlets = all_gauntlets.where(GauntletRun.ended <= ended_before) + ended_before_dt = datetime.fromtimestamp(ended_before / 1000) + all_gauntlets = all_gauntlets.where(GauntletRun.ended <= ended_before_dt) if is_active is not None: if is_active is True: - all_gauntlets = all_gauntlets.where(GauntletRun.ended == 0) + # Active runs have NULL ended, not 0 + all_gauntlets = all_gauntlets.where(GauntletRun.ended.is_null()) else: - all_gauntlets = all_gauntlets.where(GauntletRun.ended != 0) + all_gauntlets = all_gauntlets.where(GauntletRun.ended.is_null(False)) if gauntlet_id is not None: all_gauntlets = all_gauntlets.where(GauntletRun.gauntlet_id << gauntlet_id) if season is not None: @@ -121,14 +127,14 @@ async def patch_gauntletrun( this_run.gsheet = gsheet if created is not None: if created is True: - this_run.created = int(datetime.timestamp(datetime.now())*1000) + this_run.created = datetime.now() else: this_run.created = None if ended is not None: if ended is True: - this_run.ended = int(datetime.timestamp(datetime.now())*1000) + this_run.ended = datetime.now() else: - this_run.ended = 0 + this_run.ended = None if this_run.save(): r_curr = model_to_dict(this_run) @@ -149,7 +155,17 @@ async def post_gauntletrun(gauntletrun: GauntletRunModel, token: str = Depends(o detail='You are not authorized to post gauntlets. This event has been logged.' ) - this_run = GauntletRun(**gauntletrun.dict()) + run_data = gauntletrun.dict() + # Convert milliseconds timestamps to datetime for PostgreSQL + if run_data.get('created'): + run_data['created'] = datetime.fromtimestamp(run_data['created'] / 1000) + else: + run_data['created'] = datetime.now() + if run_data.get('ended'): + run_data['ended'] = datetime.fromtimestamp(run_data['ended'] / 1000) + else: + run_data['ended'] = None + this_run = GauntletRun(**run_data) if this_run.save(): r_run = model_to_dict(this_run)