Add support for different image directories

This changed the url for images, from file to media
This commit is contained in:
Michał Gdula 2023-04-20 20:45:57 +00:00
parent 8e54a3b4b6
commit 87a8254c73
7 changed files with 93 additions and 27 deletions

View file

@ -4,16 +4,16 @@ Onlylegs - API endpoints
from uuid import uuid4 from uuid import uuid4
import os import os
import pathlib import pathlib
import re
import logging import logging
from flask import Blueprint, send_from_directory, abort, flash, request, current_app from flask import Blueprint, send_from_directory, abort, flash, request, current_app
from werkzeug.utils import secure_filename
from flask_login import login_required, current_user from flask_login import login_required, current_user
from colorthief import ColorThief from colorthief import ColorThief
from onlylegs.extensions import db from onlylegs.extensions import db
from onlylegs.models import Post, Group, GroupJunction from onlylegs.models import Post, Group, GroupJunction, User
from onlylegs.utils import metadata as mt from onlylegs.utils import metadata as mt
from onlylegs.utils.generate_image import generate_thumbnail from onlylegs.utils.generate_image import generate_thumbnail
@ -21,25 +21,24 @@ from onlylegs.utils.generate_image import generate_thumbnail
blueprint = Blueprint("api", __name__, url_prefix="/api") blueprint = Blueprint("api", __name__, url_prefix="/api")
@blueprint.route("/file/<file_name>", methods=["GET"]) @blueprint.route("/media/<path:path>", methods=["GET"])
def file(file_name): def media(path):
""" """
Returns a file from the uploads folder Returns a file from the uploads folder
r for resolution, 400x400 or thumb for thumbnail r for resolution, thumb for thumbnail etc
e for extension, jpg, png etc
""" """
res = request.args.get("r", default=None, type=str) # Type of file (thumb, etc) res = request.args.get("r", default=None, type=str)
ext = request.args.get("e", default=None, type=str) # File extension ext = request.args.get("e", default=None, type=str)
file_name = secure_filename(file_name) # Sanitize file name # path = secure_filename(path)
# if no args are passed, return the raw file # if no args are passed, return the raw file
if not res and not ext: if not res and not ext:
if not os.path.exists( if not os.path.exists(os.path.join(current_app.config["MEDIA_FOLDER"], path)):
os.path.join(current_app.config["UPLOAD_FOLDER"], file_name)
):
abort(404) abort(404)
return send_from_directory(current_app.config["UPLOAD_FOLDER"], file_name) return send_from_directory(current_app.config["MEDIA_FOLDER"], path)
thumb = generate_thumbnail(file_name, res, ext) thumb = generate_thumbnail(path, res, ext)
if not thumb: if not thumb:
abort(404) abort(404)
@ -197,3 +196,69 @@ def delete_group():
flash(["Group yeeted!", "1"]) flash(["Group yeeted!", "1"])
return ":3" return ":3"
@blueprint.route("/user/picture/<int:user_id>", methods=["POST"])
def user_picture(user_id):
"""
Returns the profile of a user
"""
user = db.get_or_404(User, user_id)
file = request.files["file"]
# If no image is uploaded, return 404 error
if not file:
return abort(404)
elif user.id != current_user.id:
return abort(403)
# Get file extension, generate random name and set file path
img_ext = pathlib.Path(file.filename).suffix.replace(".", "").lower()
img_name = str(user.id)
img_path = os.path.join(current_app.config["PFP_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)
if user.picture:
os.remove(os.path.join(current_app.config["PFP_FOLDER"], user.picture))
# Save file
try:
file.save(img_path)
except OSError as err:
logging.info("Error saving file %s because of %s", img_path, err)
abort(500)
img_colors = ColorThief(img_path).get_color() # Get color palette
# Save to database
user.colour = img_colors
user.picture = str(img_name + "." + img_ext)
db.session.commit()
return "Gwa Gwa" # Return something so the browser doesn't show an error
@blueprint.route("/user/username/<int:user_id>", methods=["POST"])
def user_username(user_id):
"""
Returns the profile of a user
"""
user = db.get_or_404(User, user_id)
new_name = request.form["name"]
username_regex = re.compile(r"\b[A-Za-z0-9._-]+\b")
# Validate the form
if not new_name or not username_regex.match(new_name):
abort(400)
elif user.id != current_user.id:
return abort(403)
# Save to database
user.username = new_name
db.session.commit()
return "Gwa Gwa" # Return something so the browser doesn't show an error

View file

@ -36,6 +36,7 @@ WEBSITE_CONF = conf["website"]
UPLOAD_FOLDER = os.path.join(user_dir, "media", "uploads") UPLOAD_FOLDER = os.path.join(user_dir, "media", "uploads")
CACHE_FOLDER = os.path.join(user_dir, "media", "cache") CACHE_FOLDER = os.path.join(user_dir, "media", "cache")
PFP_FOLDER = os.path.join(user_dir, "media", "pfp") PFP_FOLDER = os.path.join(user_dir, "media", "pfp")
MEDIA_FOLDER = os.path.join(user_dir, "media")
# Database # Database
INSTANCE_DIR = instance_dir INSTANCE_DIR = instance_dir

View file

@ -209,8 +209,7 @@
.navigation { .navigation {
background-color: rgb({{ images.0.colours.0.0 }}, {{ images.0.colours.0.1 }}, {{ images.0.colours.0.2 }}) !important; background-color: rgb({{ images.0.colours.0.0 }}, {{ images.0.colours.0.1 }}, {{ images.0.colours.0.2 }}) !important;
} }
.navigation-item > svg { .navigation-item > i {
fill: {{ text_colour }} !important;
color: {{ text_colour }} !important; color: {{ text_colour }} !important;
} }
.navigation-item.selected::before { .navigation-item.selected::before {
@ -222,7 +221,7 @@
{% block content %} {% block content %}
{% if images %} {% if images %}
<div class="banner"> <div class="banner">
<img src="{{ url_for('api.file', file_name=images.0.filename ) }}?r=prev" onload="imgFade(this)" style="opacity:0;" alt="{% if images.0.alt %}{{ images.0.alt }}{% else %}Group Banner{% endif %}"/> <img src="{{ url_for('api.media', path='uploads/' + images.0.filename ) }}?r=prev" onload="imgFade(this)" style="opacity:0;" alt="{% if images.0.alt %}{{ images.0.alt }}{% else %}Group Banner{% endif %}"/>
<span class="banner-filter"></span> <span class="banner-filter"></span>
<div class="banner-content"> <div class="banner-content">
<p class="banner-info"><a href="{{ url_for('profile.profile', id=group.author.id) }}" class="link">By {{ group.author.username }}</a></p> <p class="banner-info"><a href="{{ url_for('profile.profile', id=group.author.id) }}" class="link">By {{ group.author.username }}</a></p>
@ -268,7 +267,7 @@
<div class="image-filter"> <div class="image-filter">
<p class="image-title"><span class="time">{{ image.created_at }}</span></p> <p class="image-title"><span class="time">{{ image.created_at }}</span></p>
</div> </div>
<img alt="{% if image.alt %}{{ image.alt }}{% else %}Image Thumbnail{% endif %}" data-src="{{ url_for('api.file', file_name=image.filename) }}?r=thumb" onload="this.classList.add('loaded');" id="lazy-load"/> <img alt="{% if image.alt %}{{ image.alt }}{% else %}Image Thumbnail{% endif %}" data-src="{{ url_for('api.media', path='uploads/' + image.filename) }}?r=thumb" onload="this.classList.add('loaded');" id="lazy-load"/>
</a> </a>
{% endfor %} {% endfor %}
</div> </div>

View file

@ -2,7 +2,7 @@
{% block page_index %} {% block page_index %}
{% if return_page %}?page={{ return_page }}{% endif %}{% endblock %} {% if return_page %}?page={{ return_page }}{% endif %}{% endblock %}
{% block head %} {% block head %}
<meta property="og:image" content="{{ url_for('api.file', file_name=image.filename) }}"/> <meta property="og:image" content="{{ url_for('api.media', path='uploads/' + image.filename) }}"/>
<meta name="theme-color" content="rgb({{ image.colours.0.0 }}{{ image.colours.0.1 }}{{ image.colours.0.2 }})"/> <meta name="theme-color" content="rgb({{ image.colours.0.0 }}{{ image.colours.0.1 }}{{ image.colours.0.2 }})"/>
<script type="text/javascript"> <script type="text/javascript">
@ -82,7 +82,7 @@
{% endblock %} {% endblock %}
{% block content %} {% block content %}
<div class="background"> <div class="background">
<img src="{{ url_for('api.file', file_name=image.filename) }}?r=prev" alt="{{ image.alt }}" onload="imgFade(this)" style="opacity:0;"/> <img src="{{ url_for('api.media', path='uploads/' + image.filename) }}?r=prev" alt="{{ image.alt }}" onload="imgFade(this)" style="opacity:0;"/>
<span></span> <span></span>
</div> </div>
@ -90,7 +90,7 @@
<div class="image-block"> <div class="image-block">
<div class="image-container"> <div class="image-container">
<img <img
src="{{ url_for('api.file', file_name=image.filename) }}?r=prev" src="{{ url_for('api.media', path='uploads/' + image.filename) }}?r=prev"
alt="{{ image.alt }}" alt="{{ image.alt }}"
onload="imgFade(this)" onload="imgFade(this)"
style="opacity: 0;" style="opacity: 0;"
@ -107,7 +107,7 @@
<div> <div>
<button class="pill-item" onclick="fullscreen()" id="fullscreenImage"><i class="ph ph-info"></i></button> <button class="pill-item" onclick="fullscreen()" id="fullscreenImage"><i class="ph ph-info"></i></button>
<button class="pill-item" onclick="imageShare()"><i class="ph ph-export"></i></button> <button class="pill-item" onclick="imageShare()"><i class="ph ph-export"></i></button>
<a class="pill-item" href="{{ url_for('api.file', file_name=image.filename) }}" download onclick="addNotification('Download started!', 4)"><i class="ph ph-file-arrow-down"></i></a> <a class="pill-item" href="{{ url_for('api.media', path='uploads/' + image.filename) }}" download onclick="addNotification('Download started!', 4)"><i class="ph ph-file-arrow-down"></i></a>
</div> </div>
{% if current_user.id == image.author.id %} {% if current_user.id == image.author.id %}
<div> <div>

View file

@ -35,7 +35,7 @@
<div class="image-filter"> <div class="image-filter">
<p class="image-title"><span class="time">{{ image.created_at }}</span></p> <p class="image-title"><span class="time">{{ image.created_at }}</span></p>
</div> </div>
<img fetchpriority="low" alt="{% if image.alt %}{{ image.alt }}{% else %}Image Thumbnail{% endif %}" data-src="{{ url_for('api.file', file_name=image.filename) }}?r=thumb" onload="this.classList.add('loaded');" id="lazy-load"/> <img fetchpriority="low" alt="{% if image.alt %}{{ image.alt }}{% else %}Image Thumbnail{% endif %}" data-src="{{ url_for('api.media', path='uploads/' + image.filename) }}?r=thumb" onload="this.classList.add('loaded');" id="lazy-load"/>
</a> </a>
{% endfor %} {% endfor %}
</div> </div>

View file

@ -126,7 +126,7 @@
<div class="images size-{{ group.images|length }}"> <div class="images size-{{ group.images|length }}">
{% if group.images|length > 0 %} {% if group.images|length > 0 %}
{% for image in group.images %} {% for image in group.images %}
<img data-src="{{ url_for('api.file', file_name=image.filename) }}?r=thumb" onload="this.classList.add('loaded');" id="lazy-load" class="data-{{ loop.index }}" {% if image.alt %}{{ image.alt }}{% else %}Image Thumbnail{% endif %}/> <img data-src="{{ url_for('api.media', path='uploads/' + image.filename) }}?r=thumb" onload="this.classList.add('loaded');" id="lazy-load" class="data-{{ loop.index }}" {% if image.alt %}{{ image.alt }}{% else %}Image Thumbnail{% endif %}/>
{% endfor %} {% endfor %}
{% else %} {% else %}
<img src="{{ url_for('static', filename='error.png') }}" class="loaded" alt="Error thumbnail"/> <img src="{{ url_for('static', filename='error.png') }}" class="loaded" alt="Error thumbnail"/>

View file

@ -4,11 +4,11 @@ Tools for generating images and thumbnails
import os import os
from PIL import Image, ImageOps from PIL import Image, ImageOps
from onlylegs.config import UPLOAD_FOLDER, CACHE_FOLDER from onlylegs.config import MEDIA_FOLDER, CACHE_FOLDER
from werkzeug.utils import secure_filename from werkzeug.utils import secure_filename
def generate_thumbnail(file_name, resolution, ext=None): def generate_thumbnail(file_path, resolution, ext=None):
""" """
Image thumbnail generator Image thumbnail generator
Uses PIL to generate a thumbnail of the image and saves it to the cache directory Uses PIL to generate a thumbnail of the image and saves it to the cache directory
@ -21,6 +21,7 @@ def generate_thumbnail(file_name, resolution, ext=None):
os.makedirs(CACHE_FOLDER) os.makedirs(CACHE_FOLDER)
# no sussy business # no sussy business
file_name = os.path.basename(file_path)
file_name, file_ext = secure_filename(file_name).rsplit(".") file_name, file_ext = secure_filename(file_name).rsplit(".")
if not ext: if not ext:
ext = file_ext.strip(".") ext = file_ext.strip(".")
@ -44,11 +45,11 @@ def generate_thumbnail(file_name, resolution, ext=None):
return os.path.join(CACHE_FOLDER, f"{file_name}_{res_x}x{res_y}.{ext}") return os.path.join(CACHE_FOLDER, f"{file_name}_{res_x}x{res_y}.{ext}")
# Check if image exists in the uploads directory # Check if image exists in the uploads directory
if not os.path.exists(os.path.join(UPLOAD_FOLDER, f"{file_name}.{file_ext}")): if not os.path.exists(os.path.join(MEDIA_FOLDER, file_path, f"{file_name}.{file_ext}")):
return None return None
# Open image and rotate it based on EXIF data and get ICC profile so colors are correct # Open image and rotate it based on EXIF data and get ICC profile so colors are correct
image = Image.open(os.path.join(UPLOAD_FOLDER, f"{file_name}.{file_ext}")) image = Image.open(os.path.join(MEDIA_FOLDER, file_path, f"{file_name}.{file_ext}"))
image_icc = image.info.get("icc_profile") image_icc = image.info.get("icc_profile")
img_x, img_y = image.size img_x, img_y = image.size