fix: critical bug fixes - routes UUID, image paths, favorites loading, bottom nav debounce
This commit is contained in:
79
backend/app/services/image_handler.py
Normal file
79
backend/app/services/image_handler.py
Normal file
@ -0,0 +1,79 @@
|
||||
import os
|
||||
import shutil
|
||||
from uuid import uuid4
|
||||
from fastapi import UploadFile
|
||||
from typing import Optional
|
||||
from datetime import datetime
|
||||
from sqlmodel import Session, select
|
||||
from app.models.coupon import Coupon
|
||||
|
||||
# Use absolute path relative to THIS file's location (app/services/image_handler.py)
|
||||
# Going up 3 levels: services -> app -> backend -> uploads
|
||||
_BASE_DIR = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||
UPLOAD_BASE = os.path.join(_BASE_DIR, "uploads")
|
||||
|
||||
|
||||
def save_image(image: UploadFile, subfolder: str) -> str:
|
||||
"""Saves an image to the local filesystem and returns its relative URL."""
|
||||
upload_dir = os.path.join(UPLOAD_BASE, subfolder)
|
||||
os.makedirs(upload_dir, exist_ok=True)
|
||||
|
||||
# Generate unique filename
|
||||
ext = os.path.splitext(image.filename or "")[1].lower()
|
||||
if not ext or ext not in ['.jpg', '.jpeg', '.png', '.gif', '.webp', '.avif']:
|
||||
ext = ".jpg" # Default extension if missing or invalid
|
||||
|
||||
filename = f"{uuid4()}{ext}"
|
||||
path = os.path.join(upload_dir, filename)
|
||||
|
||||
# Reset file pointer to start before reading
|
||||
image.file.seek(0)
|
||||
|
||||
# Save file
|
||||
with open(path, "wb") as buffer:
|
||||
shutil.copyfileobj(image.file, buffer)
|
||||
|
||||
print(f"DEBUG: Saved image to {path}")
|
||||
return f"/uploads/{subfolder}/{filename}"
|
||||
|
||||
|
||||
def delete_image(image_url: Optional[str]):
|
||||
"""Deletes an image from the filesystem if it's stored locally."""
|
||||
if not image_url:
|
||||
return
|
||||
|
||||
# We only delete if it points to our local uploads folder
|
||||
if image_url.startswith("/uploads/"):
|
||||
# Build absolute path
|
||||
relative = image_url.lstrip("/") # e.g. "uploads/businesses/uuid.jpg"
|
||||
file_path = os.path.join(_BASE_DIR, relative)
|
||||
if os.path.exists(file_path):
|
||||
try:
|
||||
os.remove(file_path)
|
||||
print(f"DEBUG: Deleted file {file_path}")
|
||||
except Exception as e:
|
||||
print(f"ERROR: Failed to delete {file_path}: {e}")
|
||||
else:
|
||||
print(f"DEBUG: File not found for deletion: {file_path}")
|
||||
|
||||
|
||||
def cleanup_expired_coupons(session: Session):
|
||||
"""Finds and deletes expired coupons and their associated images."""
|
||||
try:
|
||||
now = datetime.now()
|
||||
# Coupons where valid_until is passed
|
||||
statement = select(Coupon).where(Coupon.valid_until < now)
|
||||
expired = session.exec(statement).all()
|
||||
|
||||
count = 0
|
||||
for coupon in expired:
|
||||
if coupon.image_url:
|
||||
delete_image(coupon.image_url)
|
||||
session.delete(coupon)
|
||||
count += 1
|
||||
|
||||
if count > 0:
|
||||
session.commit()
|
||||
print(f"DEBUG: Cleaned up {count} expired coupons and their images.")
|
||||
except Exception as e:
|
||||
print(f"ERROR: Cleanup failed: {e}")
|
||||
Reference in New Issue
Block a user