mirror of
https://github.com/Derpy-Leggies/OnlyLegs.git
synced 2025-06-29 03:26:16 +00:00
Move APIs to their own folder
This commit is contained in:
parent
789441e636
commit
60e7078e13
13 changed files with 325 additions and 295 deletions
|
@ -15,8 +15,9 @@ from werkzeug.security import generate_password_hash
|
||||||
from onlylegs.extensions import db, migrate, login_manager, assets, compress, cache
|
from onlylegs.extensions import db, migrate, login_manager, assets, compress, cache
|
||||||
from onlylegs.config import INSTANCE_DIR, MIGRATIONS_DIR
|
from onlylegs.config import INSTANCE_DIR, MIGRATIONS_DIR
|
||||||
from onlylegs.models import User
|
from onlylegs.models import User
|
||||||
from onlylegs.views import index, image, group, settings, profile
|
from onlylegs.views import index as view_index, image as view_image, group as view_group, settings as view_settings, profile as view_profile
|
||||||
from onlylegs import api, auth
|
from onlylegs.api import media as api_media, group as api_group, account as api_account
|
||||||
|
from onlylegs import auth as view_auth
|
||||||
from onlylegs import gwagwa
|
from onlylegs import gwagwa
|
||||||
|
|
||||||
|
|
||||||
|
@ -107,13 +108,17 @@ def create_app(): # pylint: disable=R0914
|
||||||
assets.register("styles", styles)
|
assets.register("styles", styles)
|
||||||
|
|
||||||
# BLUEPRINTS
|
# BLUEPRINTS
|
||||||
app.register_blueprint(auth.blueprint)
|
app.register_blueprint(view_auth.blueprint)
|
||||||
app.register_blueprint(api.blueprint)
|
app.register_blueprint(view_index.blueprint)
|
||||||
app.register_blueprint(index.blueprint)
|
app.register_blueprint(view_image.blueprint)
|
||||||
app.register_blueprint(image.blueprint)
|
app.register_blueprint(view_group.blueprint)
|
||||||
app.register_blueprint(group.blueprint)
|
app.register_blueprint(view_profile.blueprint)
|
||||||
app.register_blueprint(profile.blueprint)
|
app.register_blueprint(view_settings.blueprint)
|
||||||
app.register_blueprint(settings.blueprint)
|
|
||||||
|
# APIS
|
||||||
|
app.register_blueprint(api_media.blueprint)
|
||||||
|
app.register_blueprint(api_group.blueprint)
|
||||||
|
app.register_blueprint(api_account.blueprint)
|
||||||
|
|
||||||
# CACHE AND COMPRESS
|
# CACHE AND COMPRESS
|
||||||
cache.init_app(app)
|
cache.init_app(app)
|
||||||
|
|
268
onlylegs/api.py
268
onlylegs/api.py
|
@ -1,268 +0,0 @@
|
||||||
"""
|
|
||||||
Onlylegs - API endpoints
|
|
||||||
"""
|
|
||||||
from uuid import uuid4
|
|
||||||
import os
|
|
||||||
import pathlib
|
|
||||||
import re
|
|
||||||
import logging
|
|
||||||
|
|
||||||
from flask import Blueprint, send_from_directory, abort, flash, request, current_app
|
|
||||||
from flask_login import login_required, current_user
|
|
||||||
|
|
||||||
from colorthief import ColorThief
|
|
||||||
|
|
||||||
from onlylegs.extensions import db
|
|
||||||
from onlylegs.models import Post, Group, GroupJunction, User
|
|
||||||
from onlylegs.utils import metadata as mt
|
|
||||||
from onlylegs.utils.generate_image import generate_thumbnail
|
|
||||||
|
|
||||||
|
|
||||||
blueprint = Blueprint("api", __name__, url_prefix="/api")
|
|
||||||
|
|
||||||
|
|
||||||
@blueprint.route("/media/<path:path>", methods=["GET"])
|
|
||||||
def media(path):
|
|
||||||
"""
|
|
||||||
Returns a file from the uploads folder
|
|
||||||
r for resolution, thumb for thumbnail etc
|
|
||||||
e for extension, jpg, png etc
|
|
||||||
"""
|
|
||||||
res = request.args.get("r", default=None, type=str)
|
|
||||||
ext = request.args.get("e", default=None, type=str)
|
|
||||||
# path = secure_filename(path)
|
|
||||||
|
|
||||||
# if no args are passed, return the raw file
|
|
||||||
if not res and not ext:
|
|
||||||
if not os.path.exists(os.path.join(current_app.config["MEDIA_FOLDER"], path)):
|
|
||||||
abort(404)
|
|
||||||
return send_from_directory(current_app.config["MEDIA_FOLDER"], path)
|
|
||||||
|
|
||||||
thumb = generate_thumbnail(path, res, ext)
|
|
||||||
if not thumb:
|
|
||||||
abort(404)
|
|
||||||
|
|
||||||
return send_from_directory(os.path.dirname(thumb), os.path.basename(thumb))
|
|
||||||
|
|
||||||
|
|
||||||
@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 OSError as err:
|
|
||||||
logging.info("Error saving file %s because of %s", img_path, 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
|
|
||||||
query = Post(
|
|
||||||
author_id=current_user.id,
|
|
||||||
filename=img_name + "." + img_ext,
|
|
||||||
mimetype=img_ext,
|
|
||||||
exif=img_exif,
|
|
||||||
colours=img_colors,
|
|
||||||
description=form["description"],
|
|
||||||
alt=form["alt"],
|
|
||||||
)
|
|
||||||
|
|
||||||
db.session.add(query)
|
|
||||||
db.session.commit()
|
|
||||||
|
|
||||||
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
|
|
||||||
"""
|
|
||||||
post = db.get_or_404(Post, image_id)
|
|
||||||
|
|
||||||
# Check if image exists and if user is allowed to delete it (author)
|
|
||||||
if post.author_id != current_user.id:
|
|
||||||
abort(403)
|
|
||||||
|
|
||||||
# Delete file
|
|
||||||
try:
|
|
||||||
os.remove(os.path.join(current_app.config["UPLOAD_FOLDER"], post.filename))
|
|
||||||
except FileNotFoundError:
|
|
||||||
logging.warning(
|
|
||||||
"File not found: %s, already deleted or never existed", post.filename
|
|
||||||
)
|
|
||||||
|
|
||||||
# Delete cached files
|
|
||||||
cache_name = post.filename.rsplit(".")[0]
|
|
||||||
for cache_file in pathlib.Path(current_app.config["CACHE_FOLDER"]).glob(cache_name + "*"):
|
|
||||||
os.remove(cache_file)
|
|
||||||
|
|
||||||
GroupJunction.query.filter_by(post_id=image_id).delete()
|
|
||||||
db.session.delete(post)
|
|
||||||
db.session.commit()
|
|
||||||
|
|
||||||
logging.info("Removed image (%s) %s", image_id, post.filename)
|
|
||||||
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 = Group(
|
|
||||||
name=request.form["name"],
|
|
||||||
description=request.form["description"],
|
|
||||||
author_id=current_user.id,
|
|
||||||
)
|
|
||||||
|
|
||||||
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"]
|
|
||||||
action = request.form["action"]
|
|
||||||
|
|
||||||
group = db.get_or_404(Group, group_id)
|
|
||||||
db.get_or_404(Post, image_id) # Check if image exists
|
|
||||||
|
|
||||||
if group.author_id != current_user.id:
|
|
||||||
abort(403)
|
|
||||||
|
|
||||||
if (
|
|
||||||
action == "add"
|
|
||||||
and not GroupJunction.query.filter_by(
|
|
||||||
group_id=group_id, post_id=image_id
|
|
||||||
).first()
|
|
||||||
):
|
|
||||||
db.session.add(GroupJunction(group_id=group_id, post_id=image_id))
|
|
||||||
elif request.form["action"] == "remove":
|
|
||||||
GroupJunction.query.filter_by(group_id=group_id, post_id=image_id).delete()
|
|
||||||
|
|
||||||
db.session.commit()
|
|
||||||
return ":3"
|
|
||||||
|
|
||||||
|
|
||||||
@blueprint.route("/group/delete", methods=["POST"])
|
|
||||||
def delete_group():
|
|
||||||
"""
|
|
||||||
Deletes a group
|
|
||||||
"""
|
|
||||||
group_id = request.form["group"]
|
|
||||||
group = db.get_or_404(Group, group_id)
|
|
||||||
|
|
||||||
if group.author_id != current_user.id:
|
|
||||||
abort(403)
|
|
||||||
|
|
||||||
GroupJunction.query.filter_by(group_id=group_id).delete()
|
|
||||||
db.session.delete(group)
|
|
||||||
db.session.commit()
|
|
||||||
|
|
||||||
flash(["Group yeeted!", "1"])
|
|
||||||
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))
|
|
||||||
# Delete cached files
|
|
||||||
cache_name = user.picture.rsplit(".")[0]
|
|
||||||
for cache_file in pathlib.Path(current_app.config["CACHE_FOLDER"]).glob(cache_name + "*"):
|
|
||||||
os.remove(cache_file)
|
|
||||||
|
|
||||||
# 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
|
|
0
onlylegs/api/__init__.py
Normal file
0
onlylegs/api/__init__.py
Normal file
92
onlylegs/api/account.py
Normal file
92
onlylegs/api/account.py
Normal file
|
@ -0,0 +1,92 @@
|
||||||
|
"""
|
||||||
|
Onlylegs - API endpoints
|
||||||
|
"""
|
||||||
|
from uuid import uuid4
|
||||||
|
import os
|
||||||
|
import pathlib
|
||||||
|
import re
|
||||||
|
import logging
|
||||||
|
|
||||||
|
from flask import Blueprint, jsonify, request, current_app
|
||||||
|
from flask_login import login_required, current_user
|
||||||
|
|
||||||
|
from colorthief import ColorThief
|
||||||
|
|
||||||
|
from onlylegs.extensions import db
|
||||||
|
from onlylegs.models import User
|
||||||
|
|
||||||
|
|
||||||
|
blueprint = Blueprint("account_api", __name__, url_prefix="/api/account")
|
||||||
|
|
||||||
|
|
||||||
|
@blueprint.route("/picture/<int:user_id>", methods=["POST"])
|
||||||
|
@login_required
|
||||||
|
def account_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 jsonify({"error": "No file uploaded"}), 400
|
||||||
|
elif user.id != current_user.id:
|
||||||
|
return jsonify({"error": "You are not allowed to do this, go away"}), 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)
|
||||||
|
return jsonify({"error": "File extension not allowed"}), 403
|
||||||
|
|
||||||
|
if user.picture:
|
||||||
|
# Delete cached files and old image
|
||||||
|
os.remove(os.path.join(current_app.config["PFP_FOLDER"], user.picture))
|
||||||
|
cache_name = user.picture.rsplit(".")[0]
|
||||||
|
for cache_file in pathlib.Path(current_app.config["CACHE_FOLDER"]).glob(cache_name + "*"):
|
||||||
|
os.remove(cache_file)
|
||||||
|
|
||||||
|
# Save file
|
||||||
|
try:
|
||||||
|
file.save(img_path)
|
||||||
|
except OSError as err:
|
||||||
|
logging.info("Error saving file %s because of %s", img_path, err)
|
||||||
|
return jsonify({"error": "Error saving file"}), 500
|
||||||
|
|
||||||
|
img_colors = ColorThief(img_path).get_color()
|
||||||
|
|
||||||
|
# Save to database
|
||||||
|
user.colour = img_colors
|
||||||
|
user.picture = str(img_name + "." + img_ext)
|
||||||
|
db.session.commit()
|
||||||
|
|
||||||
|
return jsonify({"message": "File uploaded"}), 200
|
||||||
|
|
||||||
|
|
||||||
|
@blueprint.route("/username/<int:user_id>", methods=["POST"])
|
||||||
|
@login_required
|
||||||
|
def account_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):
|
||||||
|
return jsonify({"error": "Username is invalid"}), 400
|
||||||
|
elif user.id != current_user.id:
|
||||||
|
return jsonify({"error": "You are not allowed to do this, go away"}), 403
|
||||||
|
|
||||||
|
# Save to database
|
||||||
|
user.username = new_name
|
||||||
|
db.session.commit()
|
||||||
|
|
||||||
|
return jsonify({"message": "Username changed"}), 200
|
73
onlylegs/api/group.py
Normal file
73
onlylegs/api/group.py
Normal file
|
@ -0,0 +1,73 @@
|
||||||
|
"""
|
||||||
|
Onlylegs - API endpoints
|
||||||
|
"""
|
||||||
|
from flask import Blueprint, flash, jsonify, request
|
||||||
|
from flask_login import login_required, current_user
|
||||||
|
|
||||||
|
from onlylegs.extensions import db
|
||||||
|
from onlylegs.models import Post, Group, GroupJunction
|
||||||
|
|
||||||
|
|
||||||
|
blueprint = Blueprint("group_api", __name__, url_prefix="/api/group")
|
||||||
|
|
||||||
|
|
||||||
|
@blueprint.route("/create", methods=["POST"])
|
||||||
|
@login_required
|
||||||
|
def create_group():
|
||||||
|
"""
|
||||||
|
Creates a group
|
||||||
|
"""
|
||||||
|
new_group = Group(
|
||||||
|
name=request.form["name"],
|
||||||
|
description=request.form["description"],
|
||||||
|
author_id=current_user.id,
|
||||||
|
)
|
||||||
|
|
||||||
|
db.session.add(new_group)
|
||||||
|
db.session.commit()
|
||||||
|
|
||||||
|
return jsonify({"message": "Group created", "id": new_group.id})
|
||||||
|
|
||||||
|
|
||||||
|
@blueprint.route("/modify", methods=["POST"])
|
||||||
|
@login_required
|
||||||
|
def modify_group():
|
||||||
|
"""
|
||||||
|
Changes the images in a group
|
||||||
|
"""
|
||||||
|
group_id = request.form["group"]
|
||||||
|
image_id = request.form["image"]
|
||||||
|
action = request.form["action"]
|
||||||
|
|
||||||
|
group = db.get_or_404(Group, group_id)
|
||||||
|
db.get_or_404(Post, image_id) # Check if image exists
|
||||||
|
|
||||||
|
if group.author_id != current_user.id:
|
||||||
|
return jsonify({"message": "You are not the owner of this group"}), 403
|
||||||
|
|
||||||
|
if (action == "add" and not GroupJunction.query.filter_by(group_id=group_id, post_id=image_id).first()):
|
||||||
|
db.session.add(GroupJunction(group_id=group_id, post_id=image_id))
|
||||||
|
elif request.form["action"] == "remove":
|
||||||
|
GroupJunction.query.filter_by(group_id=group_id, post_id=image_id).delete()
|
||||||
|
|
||||||
|
db.session.commit()
|
||||||
|
return jsonify({"message": "Group modified"})
|
||||||
|
|
||||||
|
|
||||||
|
@blueprint.route("/delete", methods=["POST"])
|
||||||
|
def delete_group():
|
||||||
|
"""
|
||||||
|
Deletes a group
|
||||||
|
"""
|
||||||
|
group_id = request.form["group"]
|
||||||
|
group = db.get_or_404(Group, group_id)
|
||||||
|
|
||||||
|
if group.author_id != current_user.id:
|
||||||
|
return jsonify({"message": "You are not the owner of this group"}), 403
|
||||||
|
|
||||||
|
GroupJunction.query.filter_by(group_id=group_id).delete()
|
||||||
|
db.session.delete(group)
|
||||||
|
db.session.commit()
|
||||||
|
|
||||||
|
flash(["Group yeeted!", "1"])
|
||||||
|
return jsonify({"message": "Group deleted"})
|
128
onlylegs/api/media.py
Normal file
128
onlylegs/api/media.py
Normal file
|
@ -0,0 +1,128 @@
|
||||||
|
"""
|
||||||
|
Onlylegs - API endpoints
|
||||||
|
Media upload and retrieval
|
||||||
|
"""
|
||||||
|
import os
|
||||||
|
from uuid import uuid4
|
||||||
|
import os
|
||||||
|
import pathlib
|
||||||
|
import logging
|
||||||
|
|
||||||
|
from flask import Blueprint, flash, abort, send_from_directory, jsonify, request, current_app
|
||||||
|
from flask_login import login_required, current_user
|
||||||
|
|
||||||
|
from colorthief import ColorThief
|
||||||
|
|
||||||
|
from onlylegs.extensions import db
|
||||||
|
from onlylegs.models import Post, GroupJunction
|
||||||
|
from onlylegs.utils import metadata as mt
|
||||||
|
from onlylegs.utils.generate_image import generate_thumbnail
|
||||||
|
|
||||||
|
|
||||||
|
blueprint = Blueprint("media_api", __name__, url_prefix="/api/media")
|
||||||
|
|
||||||
|
|
||||||
|
@blueprint.route("/<path:path>", methods=["GET"])
|
||||||
|
def media(path):
|
||||||
|
"""
|
||||||
|
Returns a file from the uploads folder
|
||||||
|
r for resolution, thumb for thumbnail etc
|
||||||
|
e for extension, jpg, png etc
|
||||||
|
"""
|
||||||
|
res = request.args.get("r", default=None, type=str)
|
||||||
|
ext = request.args.get("e", default=None, type=str)
|
||||||
|
# path = secure_filename(path)
|
||||||
|
|
||||||
|
# if no args are passed, return the raw file
|
||||||
|
if not res and not ext:
|
||||||
|
if not os.path.exists(os.path.join(current_app.config["MEDIA_FOLDER"], path)):
|
||||||
|
abort(404)
|
||||||
|
return send_from_directory(current_app.config["MEDIA_FOLDER"], path)
|
||||||
|
|
||||||
|
thumb = generate_thumbnail(path, res, ext)
|
||||||
|
if not thumb:
|
||||||
|
abort(500)
|
||||||
|
|
||||||
|
return send_from_directory(os.path.dirname(thumb), os.path.basename(thumb))
|
||||||
|
|
||||||
|
|
||||||
|
@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 not form_file:
|
||||||
|
return jsonify({"message": "No file"}), 400
|
||||||
|
|
||||||
|
# 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)
|
||||||
|
return jsonify({"message": "File extension not allowed"}), 403
|
||||||
|
|
||||||
|
# Save file
|
||||||
|
try:
|
||||||
|
form_file.save(img_path)
|
||||||
|
except OSError as err:
|
||||||
|
logging.info("Error saving file %s because of %s", img_path, err)
|
||||||
|
return jsonify({"message": "Error saving file"}), 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
|
||||||
|
query = Post(
|
||||||
|
author_id=current_user.id,
|
||||||
|
filename=img_name + "." + img_ext,
|
||||||
|
mimetype=img_ext,
|
||||||
|
exif=img_exif,
|
||||||
|
colours=img_colors,
|
||||||
|
description=form["description"],
|
||||||
|
alt=form["alt"],
|
||||||
|
)
|
||||||
|
|
||||||
|
db.session.add(query)
|
||||||
|
db.session.commit()
|
||||||
|
|
||||||
|
return jsonify({"message": "File uploaded"}), 200
|
||||||
|
|
||||||
|
|
||||||
|
@blueprint.route("/delete/<int:image_id>", methods=["POST"])
|
||||||
|
@login_required
|
||||||
|
def delete_image(image_id):
|
||||||
|
"""
|
||||||
|
Deletes an image from the server and database
|
||||||
|
"""
|
||||||
|
post = db.get_or_404(Post, image_id)
|
||||||
|
|
||||||
|
# Check if image exists and if user is allowed to delete it (author)
|
||||||
|
if post.author_id != current_user.id:
|
||||||
|
logging.info("User %s tried to delete image %s", current_user.id, image_id)
|
||||||
|
return jsonify({"message": "You are not allowed to delete this image, heck off"}), 403
|
||||||
|
|
||||||
|
# Delete file
|
||||||
|
try:
|
||||||
|
os.remove(os.path.join(current_app.config["UPLOAD_FOLDER"], post.filename))
|
||||||
|
except FileNotFoundError:
|
||||||
|
logging.warning("File not found: %s, already deleted or never existed", post.filename)
|
||||||
|
|
||||||
|
# Delete cached files
|
||||||
|
cache_name = post.filename.rsplit(".")[0]
|
||||||
|
for cache_file in pathlib.Path(current_app.config["CACHE_FOLDER"]).glob(cache_name + "*"):
|
||||||
|
os.remove(cache_file)
|
||||||
|
|
||||||
|
GroupJunction.query.filter_by(post_id=image_id).delete()
|
||||||
|
db.session.delete(post)
|
||||||
|
db.session.commit()
|
||||||
|
|
||||||
|
logging.info("Removed image (%s) %s", image_id, post.filename)
|
||||||
|
flash(["Image was all in Le Head!", "1"])
|
||||||
|
return jsonify({"message": "Image deleted"}), 200
|
|
@ -51,7 +51,7 @@
|
||||||
const formData = new FormData();
|
const formData = new FormData();
|
||||||
formData.append("group", formID);
|
formData.append("group", formID);
|
||||||
|
|
||||||
fetch('{{ url_for('api.delete_group') }}', {
|
fetch('{{ url_for('group_api.delete_group') }}', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
body: formData
|
body: formData
|
||||||
}).then(response => {
|
}).then(response => {
|
||||||
|
@ -147,7 +147,7 @@
|
||||||
formData.append("image", formImage);
|
formData.append("image", formImage);
|
||||||
formData.append("action", formAction);
|
formData.append("action", formAction);
|
||||||
|
|
||||||
fetch('{{ url_for('api.modify_group') }}', {
|
fetch('{{ url_for('group_api.modify_group') }}', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
body: formData
|
body: formData
|
||||||
}).then(response => {
|
}).then(response => {
|
||||||
|
@ -221,7 +221,7 @@
|
||||||
{% block content %}
|
{% block content %}
|
||||||
{% if images %}
|
{% if images %}
|
||||||
<div class="banner">
|
<div class="banner">
|
||||||
<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 %}"/>
|
<img src="{{ url_for('media_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>
|
||||||
|
@ -267,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.media', path='uploads/' + 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('media_api.media', path='uploads/' + image.filename) }}?r=thumb" onload="this.classList.add('loaded');" id="lazy-load"/>
|
||||||
</a>
|
</a>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -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.media', path='uploads/' + image.filename) }}"/>
|
<meta property="og:image" content="{{ url_for('media_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">
|
||||||
|
@ -50,7 +50,7 @@
|
||||||
function deleteConfirm() {
|
function deleteConfirm() {
|
||||||
popupDissmiss();
|
popupDissmiss();
|
||||||
|
|
||||||
fetch('{{ url_for('api.delete_image', image_id=image['id']) }}', {
|
fetch('{{ url_for('media_api.delete_image', image_id=image['id']) }}', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json'
|
'Content-Type': 'application/json'
|
||||||
|
@ -82,7 +82,7 @@
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="background">
|
<div class="background">
|
||||||
<img src="{{ url_for('api.media', path='uploads/' + image.filename) }}?r=prev" alt="{{ image.alt }}" onload="imgFade(this)" style="opacity:0;"/>
|
<img src="{{ url_for('media_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.media', path='uploads/' + image.filename) }}?r=prev"
|
src="{{ url_for('media_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.media', path='uploads/' + image.filename) }}" download onclick="addNotification('Download started!', 4)"><i class="ph ph-file-arrow-down"></i></a>
|
<a class="pill-item" href="{{ url_for('media_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>
|
||||||
|
|
|
@ -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.media', path='uploads/' + 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('media_api.media', path='uploads/' + image.filename) }}?r=thumb" onload="this.classList.add('loaded');" id="lazy-load"/>
|
||||||
</a>
|
</a>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -91,7 +91,7 @@
|
||||||
{% if current_user.picture %}
|
{% if current_user.picture %}
|
||||||
<span class="nav-pfp">
|
<span class="nav-pfp">
|
||||||
<img
|
<img
|
||||||
src="{{ url_for('api.media', path='pfp/' + current_user.picture) }}?r=icon"
|
src="{{ url_for('media_api.media', path='pfp/' + current_user.picture) }}?r=icon"
|
||||||
alt="Profile picture"
|
alt="Profile picture"
|
||||||
onload="imgFade(this)"
|
onload="imgFade(this)"
|
||||||
style="opacity:0;"
|
style="opacity:0;"
|
||||||
|
|
|
@ -67,7 +67,7 @@
|
||||||
formData.append("name", formName);
|
formData.append("name", formName);
|
||||||
formData.append("description", formDescription);
|
formData.append("description", formDescription);
|
||||||
|
|
||||||
fetch('{{ url_for('api.create_group') }}', {
|
fetch('{{ url_for('group_api.create_group') }}', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
body: formData
|
body: formData
|
||||||
}).then(response => {
|
}).then(response => {
|
||||||
|
@ -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.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 %}/>
|
<img data-src="{{ url_for('media_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"/>
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
{% extends 'layout.html' %}
|
{% extends 'layout.html' %}
|
||||||
{% block head %}
|
{% block head %}
|
||||||
{% if user.picture %}
|
{% if user.picture %}
|
||||||
<meta property="og:image" content="{{ url_for('api.media', path='pfp/' + user.picture) }}"/>
|
<meta property="og:image" content="{{ url_for('media_api.media', path='pfp/' + user.picture) }}"/>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if user.colour %}
|
{% if user.colour %}
|
||||||
<meta name="theme-color" content="rgb({{ user.colour.0 }}, {{ user.colour.1 }}, {{ user.colour.2 }})"/>
|
<meta name="theme-color" content="rgb({{ user.colour.0 }}, {{ user.colour.1 }}, {{ user.colour.2 }})"/>
|
||||||
|
@ -35,7 +35,7 @@
|
||||||
{% if user.picture %}
|
{% if user.picture %}
|
||||||
<img
|
<img
|
||||||
class="banner-picture"
|
class="banner-picture"
|
||||||
src="{{ url_for('api.media', path='pfp/' + user.picture) }}?r=pfp"
|
src="{{ url_for('media_api.media', path='pfp/' + user.picture) }}?r=pfp"
|
||||||
alt="Profile picture"
|
alt="Profile picture"
|
||||||
onload="imgFade(this)"
|
onload="imgFade(this)"
|
||||||
style="opacity:0;"
|
style="opacity:0;"
|
||||||
|
@ -73,7 +73,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="{{ image.alt }}" data-src="{{ url_for('api.media', path='uploads/' + image.filename) }}?r=thumb" onload="this.classList.add('loaded');" id="lazy-load"/>
|
<img fetchpriority="low" alt="{{ image.alt }}" data-src="{{ url_for('media_api.media', path='uploads/' + image.filename) }}?r=thumb" onload="this.classList.add('loaded');" id="lazy-load"/>
|
||||||
</a>
|
</a>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -16,13 +16,13 @@
|
||||||
|
|
||||||
<div class="settings-content" id="profileSettings">
|
<div class="settings-content" id="profileSettings">
|
||||||
<h2>Profile Settings</h2>
|
<h2>Profile Settings</h2>
|
||||||
<form method="POST" action="{{ url_for('api.user_picture', user_id=current_user.id) }}" enctype="multipart/form-data">
|
<form method="POST" action="{{ url_for('account_api.account_picture', user_id=current_user.id) }}" enctype="multipart/form-data">
|
||||||
<h3>Profile Picture</h3>
|
<h3>Profile Picture</h3>
|
||||||
<input type="file" name="file" tab-index="-1"/>
|
<input type="file" name="file" tab-index="-1"/>
|
||||||
<input type="submit" value="Upload" class="btn-block">
|
<input type="submit" value="Upload" class="btn-block">
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
<form method="POST" action="{{ url_for('api.user_username', user_id=current_user.id) }}" enctype="multipart/form-data">
|
<form method="POST" action="{{ url_for('account_api.account_username', user_id=current_user.id) }}" enctype="multipart/form-data">
|
||||||
<h3>Username</h3>
|
<h3>Username</h3>
|
||||||
<input type="text" name="name" class="input-block" value="{{ current_user.username }}" />
|
<input type="text" name="name" class="input-block" value="{{ current_user.username }}" />
|
||||||
<input type="submit" value="Upload" class="btn-block"/>
|
<input type="submit" value="Upload" class="btn-block"/>
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue