import os from PIL import Image as PILImage, UnidentifiedImageError from flask import Blueprint, request, jsonify, send_file from tinydb import Query from config.paths import get_user_image_dir from db.db import image_db from models.image import Image image_api = Blueprint('image_api', __name__) UPLOAD_FOLDER = get_user_image_dir("user123") ALLOWED_EXTENSIONS = {'jpg', 'jpeg', 'png'} IMAGE_TYPE_PROFILE = 1 IMAGE_TYPE_ICON = 2 MAX_DIMENSION = 512 def allowed_file(filename): return '.' in filename and filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS @image_api.route('/image/upload', methods=['POST']) def upload(): if 'file' not in request.files: return jsonify({'error': 'No file part in the request'}), 400 file = request.files['file'] if file.filename == '': return jsonify({'error': 'No selected file'}), 400 image_type = request.form.get('type', None) if not image_type: return jsonify({'error': 'Image type is required'}), 400 try: image_type = int(image_type) if image_type not in [IMAGE_TYPE_PROFILE, IMAGE_TYPE_ICON]: return jsonify({'error': 'Invalid image type. Must be 1 or 2'}), 400 except ValueError: return jsonify({'error': 'Image type must be an integer'}), 400 perm = request.form.get('permanent', "false").lower() == 'true' if not file or not allowed_file(file.filename): return jsonify({'error': 'Invalid file type or no file selected'}), 400 try: pil_image = PILImage.open(file.stream) original_format = pil_image.format # store before convert pil_image.verify() # quick integrity check file.stream.seek(0) pil_image = PILImage.open(file.stream).convert('RGBA' if pil_image.mode in ('RGBA', 'LA') else 'RGB') except UnidentifiedImageError: return jsonify({'error': 'Uploaded file is not a valid image'}), 400 except Exception: return jsonify({'error': 'Failed to process image'}), 400 if original_format not in ('JPEG', 'PNG'): return jsonify({'error': 'Only JPEG and PNG images are allowed'}), 400 # Resize preserving aspect ratio (in-place thumbnail) pil_image.thumbnail((MAX_DIMENSION, MAX_DIMENSION), PILImage.Resampling.LANCZOS) format_extension_map = {'JPEG': '.jpg', 'PNG': '.png'} extension = format_extension_map.get(original_format, '.png') image_record = Image(extension=extension, permanent=perm, type=image_type, user="user123") filename = image_record.id + extension filepath = os.path.abspath(os.path.join(UPLOAD_FOLDER, filename)) try: os.makedirs(UPLOAD_FOLDER, exist_ok=True) # Save with appropriate format save_params = {} if pil_image.format == 'JPEG': save_params['quality'] = 90 save_params['optimize'] = True pil_image.save(filepath, format=pil_image.format, **save_params) except Exception: return jsonify({'error': 'Failed to save processed image'}), 500 image_db.insert(image_record.to_dict()) return jsonify({'message': 'Image uploaded successfully', 'filename': filename, 'id': image_record.id}), 200 @image_api.route('/image/request/', methods=['GET']) def request_image(id): ImageQuery = Query() image: Image = Image.from_dict(image_db.get(ImageQuery.id == id)) if not image: return jsonify({'error': 'Image not found'}), 404 filename = f"{image.id}{image.extension}" filepath = os.path.abspath(os.path.join(get_user_image_dir(image.user), filename)) if not os.path.exists(filepath): return jsonify({'error': 'File not found'}), 404 return send_file(filepath) @image_api.route('/image/list', methods=['GET']) def list_images(): image_type = request.args.get('type', type=int) ImageQuery = Query() if image_type is not None: if image_type not in [IMAGE_TYPE_PROFILE, IMAGE_TYPE_ICON]: return jsonify({'error': 'Invalid image type'}), 400 images = image_db.search(ImageQuery.type == image_type) else: images = image_db.all() image_ids = [img['id'] for img in images] return jsonify({'ids': image_ids, 'count': len(image_ids)}), 200