From d2e4d6c588f2632d5d8acef949ed32e74dc6818a Mon Sep 17 00:00:00 2001 From: Bryan Van Deusen Date: Mon, 26 Jan 2026 22:51:03 -0500 Subject: [PATCH] corrected time record type, mismatch caused issues and kept credential verification from occuring --- backend/app/api/credentials.py | 4 ++-- backend/app/api/downloads.py | 4 ++-- backend/app/models/base.py | 8 ++++++-- backend/app/tasks/downloads.py | 17 +++++++++-------- 4 files changed, 19 insertions(+), 14 deletions(-) diff --git a/backend/app/api/credentials.py b/backend/app/api/credentials.py index c7540fe..a88567a 100644 --- a/backend/app/api/credentials.py +++ b/backend/app/api/credentials.py @@ -1,12 +1,12 @@ """Credential management API endpoints.""" -from datetime import datetime from quart import Blueprint, request, jsonify, current_app from sqlalchemy import select from sqlalchemy.ext.asyncio import AsyncSession from app.models.credential import Credential, CredentialType from app.models.setting import Setting, EXTENSION_API_KEY_SETTING, generate_api_key +from app.models.base import utcnow from app.config import get_settings from app.utils.encryption import encrypt_data, decrypt_data @@ -176,7 +176,7 @@ async def verify_credentials(): # TODO: Implement actual verification by making test request # For now, just mark as verified - verified_at = datetime.utcnow() + verified_at = utcnow() credential.last_verified = verified_at await session.commit() diff --git a/backend/app/api/downloads.py b/backend/app/api/downloads.py index 9ab4d7a..0a4e820 100644 --- a/backend/app/api/downloads.py +++ b/backend/app/api/downloads.py @@ -1,11 +1,11 @@ """Download history API endpoints.""" -from datetime import datetime from quart import Blueprint, request, jsonify, current_app from sqlalchemy import select, func, and_ from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy.orm import selectinload +from app.models.base import utcnow from app.models.download import Download, DownloadStatus from app.models.source import Source from app.tasks.downloads import process_download @@ -139,7 +139,7 @@ async def recent_activity(): days = int(request.args.get("days", 7)) from datetime import timedelta - cutoff = datetime.utcnow() - timedelta(days=days) + cutoff = utcnow() - timedelta(days=days) async with current_app.db_engine.connect() as conn: session = AsyncSession(bind=conn) diff --git a/backend/app/models/base.py b/backend/app/models/base.py index 8c454ab..b0c68aa 100644 --- a/backend/app/models/base.py +++ b/backend/app/models/base.py @@ -11,8 +11,12 @@ class Base(DeclarativeBase): def utcnow(): - """Return current UTC time as timezone-aware datetime.""" - return datetime.now(timezone.utc) + """Return current UTC time as naive datetime (no tzinfo). + + Uses naive datetimes because the database columns are + TIMESTAMP WITHOUT TIME ZONE. All datetimes are assumed UTC. + """ + return datetime.now(timezone.utc).replace(tzinfo=None) def format_datetime(dt): diff --git a/backend/app/tasks/downloads.py b/backend/app/tasks/downloads.py index 6133157..6db390c 100644 --- a/backend/app/tasks/downloads.py +++ b/backend/app/tasks/downloads.py @@ -11,6 +11,7 @@ from sqlalchemy.orm import selectinload from app.config import get_settings from app.tasks.celery_app import celery_app +from app.models.base import utcnow from app.models.source import Source from app.models.subscription import Subscription from app.models.download import Download, DownloadStatus, ErrorType as DBErrorType @@ -77,7 +78,7 @@ async def _download_source_async(source_id: int) -> dict: source_id=source.id, url=source.url, status=DownloadStatus.RUNNING, - started_at=datetime.utcnow(), + started_at=utcnow(), ) session.add(download) await session.commit() @@ -123,7 +124,7 @@ async def _download_source_async(source_id: int) -> dict: ) # Update download record - download.completed_at = datetime.utcnow() + download.completed_at = utcnow() download.file_count = dl_result.files_downloaded download.metadata_ = { "stdout": dl_result.stdout[:10000] if dl_result.stdout else None, # Truncate @@ -133,7 +134,7 @@ async def _download_source_async(source_id: int) -> dict: if dl_result.success: download.status = DownloadStatus.COMPLETED - source.last_success = datetime.utcnow() + source.last_success = utcnow() source.error_count = 0 logger.info(f"Download completed for {subscription_name}/{source.platform}: {dl_result.files_downloaded} files") @@ -164,7 +165,7 @@ async def _download_source_async(source_id: int) -> dict: }) # Update source last_check - source.last_check = datetime.utcnow() + source.last_check = utcnow() await session.commit() return { @@ -183,9 +184,9 @@ async def _download_source_async(source_id: int) -> dict: download.status = DownloadStatus.FAILED download.error_type = DBErrorType.UNKNOWN_ERROR download.error_message = str(e) - download.completed_at = datetime.utcnow() + download.completed_at = utcnow() source.error_count = (source.error_count or 0) + 1 - source.last_check = datetime.utcnow() + source.last_check = utcnow() await session.commit() @@ -219,7 +220,7 @@ async def _scheduled_check_async() -> dict: try: async with session_factory() as session: - now = datetime.utcnow() + now = utcnow() # Find sources due for checking: # - enabled = True @@ -286,7 +287,7 @@ async def _cleanup_old_downloads_async() -> dict: try: async with session_factory() as session: - now = datetime.utcnow() + now = utcnow() # Delete completed downloads older than 30 days completed_cutoff = now - timedelta(days=30)