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,