mirror of
https://github.com/Derpy-Leggies/OnlyLegs.git
synced 2025-01-21 05:41:42 +00:00
Michał
3008a55899
Renamed upload route to file as its more approprete Fixed random CSS issues that occur on older browsers or Safari
310 lines
9.7 KiB
Python
310 lines
9.7 KiB
Python
"""
|
|
Onlylegs - API endpoints
|
|
Used intermally by the frontend and possibly by other applications
|
|
"""
|
|
from uuid import uuid4
|
|
import os
|
|
import pathlib
|
|
import io
|
|
import logging
|
|
from datetime import datetime as dt
|
|
|
|
from flask import (Blueprint, send_from_directory, send_file,
|
|
abort, flash, jsonify, request, g, current_app)
|
|
from werkzeug.utils import secure_filename
|
|
|
|
from colorthief import ColorThief
|
|
from PIL import Image, ImageOps, ImageFilter
|
|
|
|
from sqlalchemy.orm import sessionmaker
|
|
from gallery.auth import login_required
|
|
|
|
from gallery import db
|
|
from gallery.utils import metadata as mt
|
|
|
|
|
|
blueprint = Blueprint('api', __name__, url_prefix='/api')
|
|
db_session = sessionmaker(bind=db.engine)
|
|
db_session = db_session()
|
|
|
|
|
|
@blueprint.route('/file/<file_name>', methods=['GET'])
|
|
def get_file(file_name):
|
|
"""
|
|
Returns a file from the uploads folder
|
|
t is the type of file (thumb, etc)
|
|
w and h are the width and height of the image for resizing
|
|
f is whether to apply filters to the image, such as blurring NSFW images
|
|
b is whether to force blur the image, even if it's not NSFW
|
|
"""
|
|
# Get args
|
|
type = request.args.get('t', default=None, type=str) # Type of file (thumb, etc)
|
|
width = request.args.get('w', default=0, type=int) # Width of image
|
|
height = request.args.get('h', default=0, type=int) # Height of image
|
|
filtered = request.args.get('f', default=False, type=bool) # Whether to apply filters
|
|
blur = request.args.get('b', default=False, type=bool) # Whether to force blur
|
|
|
|
file_name = secure_filename(file_name) # Sanitize file name
|
|
|
|
# If type is thumb(nail), return from database instead of file system
|
|
# as it's faster than generating a new thumbnail on every request
|
|
if type == 'thumb':
|
|
thumb = db_session.query(db.Thumbnails).filter_by(file_name=file_name).first()
|
|
|
|
# If no thumbnail exists, return 404
|
|
if not thumb:
|
|
abort(404)
|
|
|
|
return send_file(thumb.data, mimetype='image/' + thumb.file_ext)
|
|
|
|
# if no args are passed, return the raw file
|
|
if not request.args:
|
|
if not os.path.exists(os.path.join(current_app.config['UPLOAD_FOLDER'], file_name)):
|
|
abort(404)
|
|
|
|
return send_from_directory(current_app.config['UPLOAD_FOLDER'], file_name)
|
|
|
|
# If only width is passed, set height to width
|
|
if width and not height:
|
|
height = width
|
|
# If only height is passed, set width to height
|
|
elif not width and height:
|
|
width = height
|
|
# If neither are passed, return 400 as one is required for resizing
|
|
elif not width and not height:
|
|
abort(400)
|
|
|
|
buff = io.BytesIO() # Image Buffer
|
|
|
|
# Open image and set extension
|
|
try:
|
|
img = Image.open(os.path.join(current_app.config['UPLOAD_FOLDER'], file_name))
|
|
# FileNotFound is raised if the file doesn't exist
|
|
except FileNotFoundError:
|
|
logging.error('File not found: %s', file_name)
|
|
abort(404)
|
|
# OSError is raised if the file is broken or corrupted
|
|
except OSError as err:
|
|
logging.error('Possibly broken image %s, error: %s', file_name, err)
|
|
abort(500)
|
|
|
|
img_ext = pathlib.Path(file_name).suffix.replace('.', '').lower() # Get file extension
|
|
img_ext = current_app.config['ALLOWED_EXTENSIONS'][img_ext] # Convert to MIME type
|
|
img_icc = img.info.get("icc_profile") # Get ICC profile
|
|
|
|
img.thumbnail((width, height), Image.LANCZOS) # Resize image
|
|
img = ImageOps.exif_transpose(img) # Rotate image based on EXIF data
|
|
|
|
# If has NSFW tag, blur image, etc.
|
|
if filtered:
|
|
pass
|
|
|
|
# If forced to blur, blur image
|
|
if blur:
|
|
img = img.filter(ImageFilter.GaussianBlur(20))
|
|
|
|
try:
|
|
img.save(buff, img_ext, icc_profile=img_icc)
|
|
except OSError:
|
|
# This usually happens when saving a JPEG with an ICC profile,
|
|
# so we convert to RGB and try again
|
|
img = img.convert('RGB')
|
|
img.save(buff, img_ext, icc_profile=img_icc)
|
|
except Exception as err:
|
|
logging.error('Could not resize image %s, error: %s', file_name, err)
|
|
abort(500)
|
|
|
|
img.close() # Close image to free memory, learned the hard way
|
|
buff.seek(0) # Reset buffer to start
|
|
|
|
return send_file(buff, mimetype='image/' + img_ext)
|
|
|
|
|
|
@blueprint.route('/upload', methods=['POST'])
|
|
@login_required
|
|
def upload():
|
|
"""
|
|
Uploads an image to the server and saves it to the database
|
|
"""
|
|
form_file = request.files['file']
|
|
form = request.form
|
|
|
|
# If no image is uploaded, return 404 error
|
|
if not form_file:
|
|
return abort(404)
|
|
|
|
# Get file extension, generate random name and set file path
|
|
img_ext = pathlib.Path(form_file.filename).suffix.replace('.', '').lower()
|
|
img_name = "GWAGWA_"+str(uuid4())
|
|
img_path = os.path.join(current_app.config['UPLOAD_FOLDER'], img_name+'.'+img_ext)
|
|
|
|
# Check if file extension is allowed
|
|
if img_ext not in current_app.config['ALLOWED_EXTENSIONS'].keys():
|
|
logging.info('File extension not allowed: %s', img_ext)
|
|
abort(403)
|
|
|
|
# Save file
|
|
try:
|
|
form_file.save(img_path)
|
|
except Exception as err:
|
|
logging.error('Could not save file: %s', err)
|
|
abort(500)
|
|
|
|
img_exif = mt.Metadata(img_path).yoink() # Get EXIF data
|
|
img_colors = ColorThief(img_path).get_palette(color_count=3) # Get color palette
|
|
|
|
# Save to database
|
|
try:
|
|
query = db.Posts(author_id=g.user.id,
|
|
created_at=dt.utcnow(),
|
|
file_name=img_name+'.'+img_ext,
|
|
file_type=img_ext,
|
|
image_exif=img_exif,
|
|
image_colours=img_colors,
|
|
post_description=form['description'],
|
|
post_alt=form['alt'])
|
|
|
|
db_session.add(query)
|
|
db_session.commit()
|
|
except Exception as err:
|
|
logging.error('Could not save to database: %s', err)
|
|
abort(500)
|
|
|
|
return 'Gwa Gwa' # Return something so the browser doesn't show an error
|
|
|
|
|
|
@blueprint.route('/delete/<int:image_id>', methods=['POST'])
|
|
@login_required
|
|
def delete_image(image_id):
|
|
"""
|
|
Deletes an image from the server and database
|
|
"""
|
|
img = db_session.query(db.Posts).filter_by(id=image_id).first()
|
|
|
|
if img is None:
|
|
abort(404)
|
|
if img.author_id != g.user.id:
|
|
abort(403)
|
|
|
|
try:
|
|
os.remove(os.path.join(current_app.config['UPLOAD_FOLDER'],img.file_name))
|
|
except FileNotFoundError:
|
|
# File was already deleted or doesn't exist
|
|
logging.warning('File not found: %s, already deleted or never existed', img.file_name)
|
|
except Exception as err:
|
|
logging.error('Could not remove file: %s', err)
|
|
abort(500)
|
|
|
|
try:
|
|
db_session.query(db.Posts).filter_by(id=image_id).delete()
|
|
|
|
groups = db_session.query(db.GroupJunction).filter_by(post_id=image_id).all()
|
|
for group in groups:
|
|
db_session.delete(group)
|
|
|
|
db_session.commit()
|
|
except Exception as err:
|
|
logging.error('Could not remove from database: %s', err)
|
|
abort(500)
|
|
|
|
logging.info('Removed image (%s) %s', image_id, img.file_name)
|
|
flash(['Image was all in Le Head!', 1])
|
|
return 'Gwa Gwa'
|
|
|
|
|
|
@blueprint.route('/group/create', methods=['POST'])
|
|
@login_required
|
|
def create_group():
|
|
"""
|
|
Creates a group
|
|
"""
|
|
new_group = db.Groups(name=request.form['name'],
|
|
description=request.form['description'],
|
|
author_id=g.user.id,
|
|
created_at=dt.utcnow())
|
|
|
|
db_session.add(new_group)
|
|
db_session.commit()
|
|
|
|
return ':3'
|
|
|
|
|
|
@blueprint.route('/group/modify', methods=['POST'])
|
|
@login_required
|
|
def modify_group():
|
|
"""
|
|
Changes the images in a group
|
|
"""
|
|
group_id = request.form['group']
|
|
image_id = request.form['image']
|
|
|
|
group = db_session.query(db.Groups).filter_by(id=group_id).first()
|
|
|
|
if group is None:
|
|
abort(404)
|
|
elif group.author_id != g.user.id:
|
|
abort(403)
|
|
|
|
if request.form['action'] == 'add':
|
|
if db_session.query(db.GroupJunction).filter_by(group_id=group_id, post_id=image_id).first() is None:
|
|
db_session.add(db.GroupJunction(group_id=group_id, post_id=image_id, date_added=dt.utcnow()))
|
|
elif request.form['action'] == 'remove':
|
|
db_session.query(db.GroupJunction).filter_by(group_id=group_id, post_id=image_id).delete()
|
|
|
|
db_session.commit()
|
|
|
|
return ':3'
|
|
|
|
|
|
@blueprint.route('/metadata/<int:img_id>', methods=['GET'])
|
|
def metadata(img_id):
|
|
"""
|
|
Yoinks metadata from an image
|
|
"""
|
|
img = db_session.query(db.Posts).filter_by(id=img_id).first()
|
|
|
|
if img is None:
|
|
abort(404)
|
|
|
|
img_path = os.path.join(current_app.config['UPLOAD_FOLDER'], img.file_name)
|
|
exif = mt.Metadata(img_path).yoink()
|
|
|
|
return jsonify(exif)
|
|
|
|
|
|
@blueprint.route('/logfile')
|
|
@login_required
|
|
def logfile():
|
|
"""
|
|
Gets the log file and returns it as a JSON object
|
|
"""
|
|
log_dict = {}
|
|
|
|
with open('only.log', encoding='utf-8') as file:
|
|
for i, line in enumerate(file):
|
|
line = line.split(' : ')
|
|
|
|
event = line[0].strip().split(' ')
|
|
event_data = {
|
|
'date': event[0],
|
|
'time': event[1],
|
|
'severity': event[2],
|
|
'owner': event[3]
|
|
}
|
|
|
|
message = line[1].strip()
|
|
try:
|
|
message_data = {
|
|
'code': int(message[1:4]),
|
|
'message': message[5:].strip()
|
|
}
|
|
except ValueError:
|
|
message_data = {'code': 0, 'message': message}
|
|
except Exception as err:
|
|
logging.error('Could not parse log file: %s', err)
|
|
abort(500)
|
|
|
|
log_dict[i] = {'event': event_data, 'message': message_data}
|
|
|
|
return jsonify(log_dict)
|