From 03d3a28b21496f193bbba064a4f05eea2c5aedd8 Mon Sep 17 00:00:00 2001 From: Tanya Date: Fri, 2 Jan 2026 14:46:57 -0500 Subject: [PATCH] feat: Add date validation for photo import process This commit introduces a new function, `validate_date_taken`, to ensure that the date taken for photos is valid before being saved. The function checks for valid date objects, prevents future dates, and filters out dates that are too old. The photo import process is updated to utilize this validation, enhancing data integrity and preventing corrupted date data from being stored. Additionally, the `date_added` field is explicitly set to the current UTC time to ensure validity. --- backend/services/photo_service.py | 55 +++++++++++++++++++++++++++++-- 1 file changed, 53 insertions(+), 2 deletions(-) diff --git a/backend/services/photo_service.py b/backend/services/photo_service.py index 4d943c8..cdc919a 100644 --- a/backend/services/photo_service.py +++ b/backend/services/photo_service.py @@ -6,7 +6,7 @@ import hashlib import os from pathlib import Path from datetime import datetime, date -from typing import Callable, Optional, Tuple +from typing import Callable, Optional, Tuple, Union from PIL import Image from sqlalchemy.orm import Session @@ -15,6 +15,45 @@ from backend.config import SUPPORTED_IMAGE_FORMATS, SUPPORTED_VIDEO_FORMATS from backend.db.models import Photo +def validate_date_taken(date_taken: Optional[Union[date, datetime]]) -> Optional[date]: + """Validate and normalize date_taken value. + + Ensures the date is: + - A valid date object (not datetime, not invalid type) + - Not in the future + - Not too old (before 1900) + + Returns: + Valid date object or None if invalid + """ + if date_taken is None: + return None + + try: + # Convert datetime to date if needed + if isinstance(date_taken, datetime): + date_taken = date_taken.date() + elif not isinstance(date_taken, date): + # Invalid type + return None + + # Validate date is reasonable + if date_taken > date.today(): + # Date in the future is invalid + return None + elif date_taken < date(1900, 1, 1): + # Date too old is likely invalid + return None + + return date_taken + except (ValueError, TypeError, AttributeError) as e: + # If any validation fails, return None + import logging + logger = logging.getLogger(__name__) + logger.warning(f"Invalid date_taken value: {date_taken}, error: {e}") + return None + + def extract_exif_date(image_path: str) -> Optional[date]: """Extract date taken from photo EXIF data - returns Date (not DateTime) to match desktop schema. @@ -299,6 +338,8 @@ def import_photo_from_path( date_taken = extract_video_date(photo_path) else: date_taken = extract_photo_date(photo_path) + # Validate date_taken before setting + date_taken = validate_date_taken(date_taken) if date_taken: existing.date_taken = date_taken db.commit() @@ -319,6 +360,8 @@ def import_photo_from_path( date_taken = extract_video_date(photo_path) else: date_taken = extract_photo_date(photo_path) + # Validate date_taken before setting + date_taken = validate_date_taken(date_taken) if date_taken: existing_by_path.date_taken = date_taken db.commit() @@ -331,15 +374,23 @@ def import_photo_from_path( else: date_taken = extract_photo_date(photo_path) + # Validate date_taken - ensure it's a valid date object or None + # This prevents corrupted date data from being saved + date_taken = validate_date_taken(date_taken) + # For videos, mark as processed immediately (we don't process videos for faces) # For images, start as unprocessed processed = media_type == "video" + # Explicitly set date_added to current UTC time to ensure it's always valid + date_added = datetime.utcnow() + # Create new photo record with file_hash and media_type photo = Photo( path=photo_path, filename=filename, - date_taken=date_taken, + date_added=date_added, # Explicitly set to ensure valid DateTime + date_taken=date_taken, # Validated date or None processed=processed, file_hash=file_hash, media_type=media_type,