fix: apply Phase 1-2 correctness and constant extraction from PR #273

- Replace datetime.utcnow() with datetime.now(timezone.utc) in 4 files
- Extract duplicate literals to constants in 5 files
  - models.py: "files.id" → _FILES_ID_FK
  - upload_to_email.py: "logo.png" → _LOGO_FILENAME
  - general.py: "%B %d, %Y" → _DATE_DISPLAY_FORMAT
  - files.py: "File not found" → _FILE_NOT_FOUND
  - upload_to_google_drive.py: Google token URL → _GOOGLE_TOKEN_URL

Co-authored-by: christianlouis <361235+christianlouis@users.noreply.github.com>
This commit is contained in:
copilot-swe-agent[bot]
2026-02-13 11:25:30 +00:00
parent fe32302adb
commit 4897cb6655
9 changed files with 38 additions and 23 deletions
+1 -1
View File
@@ -64,7 +64,7 @@ async def test_notification(request: Request):
# Add request_time to request.state # Add request_time to request.state
import datetime import datetime
request.state.request_time = datetime.datetime.utcnow().isoformat() request.state.request_time = datetime.datetime.now(datetime.timezone.utc).isoformat()
""" """
Send a test notification through all configured notification channels Send a test notification through all configured notification channels
""" """
+6 -3
View File
@@ -4,6 +4,9 @@ from sqlalchemy import Boolean, Column, DateTime, ForeignKey, Integer, String, T
from app.database import Base from app.database import Base
# Foreign key constants
_FILES_ID_FK = "files.id"
class DocumentMetadata(Base): class DocumentMetadata(Base):
__tablename__ = "documents" __tablename__ = "documents"
@@ -50,7 +53,7 @@ class FileRecord(Base):
is_duplicate = Column(Boolean, default=False, nullable=False, index=True) is_duplicate = Column(Boolean, default=False, nullable=False, index=True)
# If this is a duplicate, record the ID of the original file for reference # If this is a duplicate, record the ID of the original file for reference
duplicate_of_id = Column(Integer, ForeignKey("files.id"), nullable=True) duplicate_of_id = Column(Integer, ForeignKey(_FILES_ID_FK), nullable=True)
# Timestamp when we inserted this record # Timestamp when we inserted this record
created_at = Column(DateTime(timezone=True), server_default=func.now()) created_at = Column(DateTime(timezone=True), server_default=func.now())
@@ -65,7 +68,7 @@ class FileProcessingStep(Base):
__tablename__ = "file_processing_steps" __tablename__ = "file_processing_steps"
id = Column(Integer, primary_key=True, index=True) id = Column(Integer, primary_key=True, index=True)
file_id = Column(Integer, ForeignKey("files.id"), nullable=False, index=True) file_id = Column(Integer, ForeignKey(_FILES_ID_FK), nullable=False, index=True)
step_name = Column(String, nullable=False, index=True) # e.g., "hash_file", "upload_to_dropbox" step_name = Column(String, nullable=False, index=True) # e.g., "hash_file", "upload_to_dropbox"
status = Column(String, nullable=False) # "pending", "in_progress", "success", "failure", "skipped" status = Column(String, nullable=False) # "pending", "in_progress", "success", "failure", "skipped"
started_at = Column(DateTime(timezone=True), nullable=True) # When step started started_at = Column(DateTime(timezone=True), nullable=True) # When step started
@@ -80,7 +83,7 @@ class FileProcessingStep(Base):
class ProcessingLog(Base): class ProcessingLog(Base):
__tablename__ = "processing_logs" __tablename__ = "processing_logs"
id = Column(Integer, primary_key=True, index=True) id = Column(Integer, primary_key=True, index=True)
file_id = Column(Integer, ForeignKey("files.id"), nullable=True) # Optional file association file_id = Column(Integer, ForeignKey(_FILES_ID_FK), nullable=True) # Optional file association
task_id = Column(String, index=True) # Celery task ID task_id = Column(String, index=True) # Celery task ID
step_name = Column(String) # e.g., "OCR", "convert_to_pdf", "upload_s3" step_name = Column(String) # e.g., "OCR", "convert_to_pdf", "upload_s3"
status = Column(String) # "pending", "in_progress", "success", "failure" status = Column(String) # "pending", "in_progress", "success", "failure"
+3 -3
View File
@@ -6,7 +6,7 @@ that have been stuck in "in_progress" state for too long and mark them as failed
""" """
import logging import logging
from datetime import datetime from datetime import datetime, timezone
from app.celery_app import celery from app.celery_app import celery
from app.database import SessionLocal from app.database import SessionLocal
@@ -37,12 +37,12 @@ def monitor_stalled_steps():
if stalled_count > 0: if stalled_count > 0:
logger.warning( logger.warning(
f"[{datetime.utcnow().isoformat()}] " f"[{datetime.now(timezone.utc).isoformat()}] "
f"Recovered {stalled_count} stalled step(s). " f"Recovered {stalled_count} stalled step(s). "
f"Marked as failed due to timeout." f"Marked as failed due to timeout."
) )
else: else:
logger.debug(f"[{datetime.utcnow().isoformat()}] No stalled steps found.") logger.debug(f"[{datetime.now(timezone.utc).isoformat()}] No stalled steps found.")
return {"recovered": stalled_count} return {"recovered": stalled_count}
+7 -4
View File
@@ -20,6 +20,9 @@ from app.utils import log_task_progress
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
# Constants
_LOGO_FILENAME = "logo.png"
def get_email_template(template_name="default.html"): def get_email_template(template_name="default.html"):
""" """
@@ -87,16 +90,16 @@ def attach_logo(msg):
"""Attach the DocuElevate logo to the email with proper Content-ID.""" """Attach the DocuElevate logo to the email with proper Content-ID."""
try: try:
# Try to find logo in workdir first (for customization) # Try to find logo in workdir first (for customization)
custom_logo_path = os.path.join(settings.workdir, "templates", "email", "logo.png") custom_logo_path = os.path.join(settings.workdir, "templates", "email", _LOGO_FILENAME)
if os.path.exists(custom_logo_path): if os.path.exists(custom_logo_path):
logo_path = custom_logo_path logo_path = custom_logo_path
else: else:
# Use built-in logo # Use built-in logo
app_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) app_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
logo_path = os.path.join(app_dir, "static", "logo.png") logo_path = os.path.join(app_dir, "static", _LOGO_FILENAME)
# Fallback to logo in frontend/static if app/static doesn't exist # Fallback to logo in frontend/static if app/static doesn't exist
if not os.path.exists(logo_path): if not os.path.exists(logo_path):
logo_path = os.path.join(app_dir, "..", "frontend", "static", "logo.png") logo_path = os.path.join(app_dir, "..", "frontend", "static", _LOGO_FILENAME)
if os.path.exists(logo_path): if os.path.exists(logo_path):
with open(logo_path, "rb") as img: with open(logo_path, "rb") as img:
@@ -106,7 +109,7 @@ def attach_logo(msg):
mimetype = "image/svg+xml" if logo_path.endswith(".svg") else "image/png" mimetype = "image/svg+xml" if logo_path.endswith(".svg") else "image/png"
logo_attach = MIMEImage(logo_data, mimetype) logo_attach = MIMEImage(logo_data, mimetype)
logo_attach.add_header("Content-ID", "<logo>") logo_attach.add_header("Content-ID", "<logo>")
logo_attach.add_header("Content-Disposition", "inline", filename="logo.png") logo_attach.add_header("Content-Disposition", "inline", filename=_LOGO_FILENAME)
msg.attach(logo_attach) msg.attach(logo_attach)
logger.info(f"Logo attached from {logo_path}") logger.info(f"Logo attached from {logo_path}")
return True return True
+4 -1
View File
@@ -20,6 +20,9 @@ from app.utils import log_task_progress
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
# Google OAuth constants
_GOOGLE_TOKEN_URL = "https://oauth2.googleapis.com/token"
def get_drive_service_oauth(): def get_drive_service_oauth():
""" """
@@ -40,7 +43,7 @@ def get_drive_service_oauth():
credentials = OAuthCredentials( credentials = OAuthCredentials(
None, # No access token initially, will be refreshed None, # No access token initially, will be refreshed
refresh_token=settings.google_drive_refresh_token, refresh_token=settings.google_drive_refresh_token,
token_uri="https://oauth2.googleapis.com/token", token_uri=_GOOGLE_TOKEN_URL,
client_id=settings.google_drive_client_id, client_id=settings.google_drive_client_id,
client_secret=settings.google_drive_client_secret, client_secret=settings.google_drive_client_secret,
# Use only drive.file scope # Use only drive.file scope
+2 -2
View File
@@ -1,7 +1,7 @@
import logging import logging
import threading import threading
from collections import defaultdict from collections import defaultdict
from datetime import datetime from datetime import datetime, timezone
from app.database import SessionLocal from app.database import SessionLocal
from app.models import FileProcessingStep, ProcessingLog from app.models import FileProcessingStep, ProcessingLog
@@ -106,7 +106,7 @@ def log_task_progress(task_id, step_name, status, message=None, file_id=None, de
.first() .first()
) )
now = datetime.utcnow() now = datetime.now(timezone.utc)
if not step_record: if not step_record:
# Create new step record # Create new step record
+2 -2
View File
@@ -6,7 +6,7 @@ This prevents files from getting stuck in "pending" state when processing crashe
""" """
import logging import logging
from datetime import datetime, timedelta from datetime import datetime, timedelta, timezone
from typing import Optional from typing import Optional
from sqlalchemy.orm import Session from sqlalchemy.orm import Session
@@ -54,7 +54,7 @@ def mark_stalled_steps_as_failed(
if timeout_seconds is None: if timeout_seconds is None:
timeout_seconds = get_step_timeout() timeout_seconds = get_step_timeout()
now = datetime.utcnow() now = datetime.now(timezone.utc)
cutoff_time = now - timedelta(seconds=timeout_seconds) cutoff_time = now - timedelta(seconds=timeout_seconds)
# Query for stalled steps # Query for stalled steps
+7 -4
View File
@@ -13,6 +13,9 @@ from app.views.base import APIRouter, get_db, logger, require_login, templates
router = APIRouter() router = APIRouter()
# Error message constants
_FILE_NOT_FOUND = "File not found"
@router.get("/files") @router.get("/files")
@require_login @require_login
@@ -433,7 +436,7 @@ def preview_original_file(request: Request, file_id: int, db: Session = Depends(
file_record = db.query(FileRecord).filter(FileRecord.id == file_id).first() file_record = db.query(FileRecord).filter(FileRecord.id == file_id).first()
if not file_record: if not file_record:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="File not found") raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=_FILE_NOT_FOUND)
if not file_record.original_file_path or not os.path.exists(file_record.original_file_path): if not file_record.original_file_path or not os.path.exists(file_record.original_file_path):
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Original file not found on disk") raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Original file not found on disk")
@@ -460,7 +463,7 @@ def preview_processed_file(request: Request, file_id: int, db: Session = Depends
file_record = db.query(FileRecord).filter(FileRecord.id == file_id).first() file_record = db.query(FileRecord).filter(FileRecord.id == file_id).first()
if not file_record: if not file_record:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="File not found") raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=_FILE_NOT_FOUND)
if not file_record.processed_file_path or not os.path.exists(file_record.processed_file_path): if not file_record.processed_file_path or not os.path.exists(file_record.processed_file_path):
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Processed file not found on disk") raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Processed file not found on disk")
@@ -487,7 +490,7 @@ def get_original_text(request: Request, file_id: int, db: Session = Depends(get_
file_record = db.query(FileRecord).filter(FileRecord.id == file_id).first() file_record = db.query(FileRecord).filter(FileRecord.id == file_id).first()
if not file_record: if not file_record:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="File not found") raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=_FILE_NOT_FOUND)
if not file_record.original_file_path or not os.path.exists(file_record.original_file_path): if not file_record.original_file_path or not os.path.exists(file_record.original_file_path):
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Original file not found on disk") raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Original file not found on disk")
@@ -527,7 +530,7 @@ def get_processed_text(request: Request, file_id: int, db: Session = Depends(get
file_record = db.query(FileRecord).filter(FileRecord.id == file_id).first() file_record = db.query(FileRecord).filter(FileRecord.id == file_id).first()
if not file_record: if not file_record:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="File not found") raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=_FILE_NOT_FOUND)
if not file_record.processed_file_path or not os.path.exists(file_record.processed_file_path): if not file_record.processed_file_path or not os.path.exists(file_record.processed_file_path):
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Processed file not found on disk") raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Processed file not found on disk")
+6 -3
View File
@@ -14,6 +14,9 @@ from app.views.base import APIRouter, get_db, logger, require_login, templates
router = APIRouter() router = APIRouter()
# Date format constant
_DATE_DISPLAY_FORMAT = "%B %d, %Y"
@router.get("/", include_in_schema=False) @router.get("/", include_in_schema=False)
async def serve_index(request: Request, db: Session = Depends(get_db)): async def serve_index(request: Request, db: Session = Depends(get_db)):
@@ -82,7 +85,7 @@ async def serve_about(request: Request):
async def serve_privacy(request: Request): async def serve_privacy(request: Request):
"""Serve the privacy policy page.""" """Serve the privacy policy page."""
# Pass the current date for the "Last Updated" field # Pass the current date for the "Last Updated" field
current_date = date.today().strftime("%B %d, %Y") current_date = date.today().strftime(_DATE_DISPLAY_FORMAT)
return templates.TemplateResponse("privacy.html", {"request": request, "current_date": current_date}) return templates.TemplateResponse("privacy.html", {"request": request, "current_date": current_date})
@@ -148,12 +151,12 @@ Please visit http://www.apache.org/licenses/LICENSE-2.0 for the complete license
@router.get("/cookies", include_in_schema=False) @router.get("/cookies", include_in_schema=False)
async def serve_cookies(request: Request): async def serve_cookies(request: Request):
"""Serve the cookie policy page.""" """Serve the cookie policy page."""
current_date = date.today().strftime("%B %d, %Y") current_date = date.today().strftime(_DATE_DISPLAY_FORMAT)
return templates.TemplateResponse("cookies.html", {"request": request, "current_date": current_date}) return templates.TemplateResponse("cookies.html", {"request": request, "current_date": current_date})
@router.get("/terms", include_in_schema=False) @router.get("/terms", include_in_schema=False)
async def serve_terms(request: Request): async def serve_terms(request: Request):
"""Serve the terms of service page.""" """Serve the terms of service page."""
current_date = date.today().strftime("%B %d, %Y") current_date = date.today().strftime(_DATE_DISPLAY_FORMAT)
return templates.TemplateResponse("terms.html", {"request": request, "current_date": current_date}) return templates.TemplateResponse("terms.html", {"request": request, "current_date": current_date})