initial commit
This commit is contained in:
107
api/image_api.py
Normal file
107
api/image_api.py
Normal file
@@ -0,0 +1,107 @@
|
||||
import os
|
||||
import uuid
|
||||
from io import BytesIO
|
||||
|
||||
from flask import Blueprint, request, jsonify, send_file
|
||||
from db.db import image_db
|
||||
from models.image import Image
|
||||
from tinydb import Query
|
||||
from PIL import Image as PILImage, UnidentifiedImageError
|
||||
|
||||
image_api = Blueprint('image_api', __name__)
|
||||
UPLOAD_FOLDER = './resources/images'
|
||||
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')
|
||||
|
||||
_id = str(uuid.uuid4())
|
||||
filename = _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_record = Image(image_type, extension, permanent=perm, id=_id)
|
||||
image_db.insert(image_record.to_dict())
|
||||
|
||||
return jsonify({'message': 'Image uploaded successfully', 'filename': filename, 'id': _id}), 200
|
||||
|
||||
@image_api.route('/image/request/<id>', methods=['GET'])
|
||||
def request_image(id):
|
||||
ImageQuery = Query()
|
||||
image = 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(UPLOAD_FOLDER, 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
|
||||
Reference in New Issue
Block a user