diff --git a/onlylegs/__init__.py b/onlylegs/__init__.py deleted file mode 100644 index d424fcb..0000000 --- a/onlylegs/__init__.py +++ /dev/null @@ -1,140 +0,0 @@ -""" -Onlylegs Gallery -This is the main app file, it loads all the other files and sets up the app -""" -import os -import logging - -from flask_assets import Bundle -from flask_migrate import init as migrate_init - -from flask import Flask, render_template, abort -from werkzeug.exceptions import HTTPException -from werkzeug.security import generate_password_hash - -from onlylegs.extensions import db, migrate, login_manager, assets, compress, cache -from onlylegs.config import INSTANCE_DIR, MIGRATIONS_DIR -from onlylegs.models import User -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.api import media as api_media, group as api_group, account as api_account -from onlylegs import auth as view_auth -from onlylegs import filters -from onlylegs import gwagwa - - -def create_app(): # pylint: disable=R0914 - """ - Create and configure the main app - """ - app = Flask(__name__, instance_path=INSTANCE_DIR) - app.config.from_pyfile("config.py") - - # DATABASE - db.init_app(app) - migrate.init_app(app, db, directory=MIGRATIONS_DIR) - - # If database file doesn't exist, create it - if not os.path.exists(os.path.join(INSTANCE_DIR, "gallery.sqlite3")): - print("Creating database") - with app.app_context(): - db.create_all() - - register_user = User( - username=app.config["ADMIN_CONF"]["username"], - email=app.config["ADMIN_CONF"]["email"], - password=generate_password_hash("changeme!", method="sha256"), - ) - db.session.add(register_user) - db.session.commit() - - print( - """ -#################################################### -# DEFAULT ADMIN USER GENERATED WITH GIVEN USERNAME # -# THE DEFAULT PASSWORD "changeme!" HAS BEEN USED, # -# PLEASE UPDATE IT IN THE SETTINGS! # -#################################################### - """ - ) - - # Check if migrations directory exists, if not create it - with app.app_context(): - if not os.path.exists(MIGRATIONS_DIR): - print("Creating migrations directory") - migrate_init(directory=MIGRATIONS_DIR) - - # LOGIN MANAGER - # can also set session_protection to "strong" - # this would protect against session hijacking - login_manager.init_app(app) - login_manager.login_view = "onlylegs.index" - - @login_manager.user_loader - def load_user(user_id): - return User.query.filter_by(alt_id=user_id).first() - - @login_manager.unauthorized_handler - def unauthorized(): - error = 401 - msg = "You are not authorized to view this page!!!!" - return render_template("error.html", error=error, msg=msg), error - - # ERROR HANDLERS - @app.errorhandler(Exception) - def error_page(err): - """ - Error handlers, if the error is not a HTTP error, return 500 - """ - if not isinstance(err, HTTPException): - abort(500) - return ( - render_template("error.html", error=err.code, msg=err.description), - err.code, - ) - - # ASSETS - assets.init_app(app) - - scripts = Bundle( - "js/*.js", output="gen/js.js", depends="js/*.js" - ) # filter jsmin is broken :c - styles = Bundle( - "sass/style.sass", - filters="libsass, cssmin", - output="gen/styles.css", - depends="sass/**/*.sass", - ) - - assets.register("scripts", scripts) - assets.register("styles", styles) - - # BLUEPRINTS - app.register_blueprint(view_auth.blueprint) - app.register_blueprint(view_index.blueprint) - app.register_blueprint(view_image.blueprint) - app.register_blueprint(view_group.blueprint) - app.register_blueprint(view_profile.blueprint) - app.register_blueprint(view_settings.blueprint) - - # APIS - app.register_blueprint(api_media.blueprint) - app.register_blueprint(api_group.blueprint) - app.register_blueprint(api_account.blueprint) - - # FILTERS - app.register_blueprint(filters.blueprint) - - # CACHE AND COMPRESS - cache.init_app(app) - compress.init_app(app) - - # Yupee! We got there :3 - print("Done!") - logging.info("Gallery started successfully!") - return app diff --git a/onlylegs/api.py b/onlylegs/api.py new file mode 100644 index 0000000..9bcf477 --- /dev/null +++ b/onlylegs/api.py @@ -0,0 +1,293 @@ +""" +Onlylegs - API endpoints +""" +import os +import pathlib +import re +import logging +from uuid import uuid4 + +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 Users, Pictures, Albums, AlbumJunction +from onlylegs.utils.metadata import yoink +from onlylegs.utils.generate_image import generate_thumbnail + + +blueprint = Blueprint("api", __name__, url_prefix="/api") + + +@blueprint.route("/account/picture/", methods=["POST"]) +@login_required +def account_picture(user_id): + """ + Returns the profile of a user + """ + user = db.get_or_404(Users, user_id) + file = request.files.get("file", None) + + # If no image is uploaded, return 404 error + if not file: + return jsonify({"error": "No file uploaded"}), 400 + if 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("/account/username/", methods=["POST"]) +@login_required +def account_username(user_id): + """ + Returns the profile of a user + """ + user = db.get_or_404(Users, 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 + if 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 + + +@blueprint.route("/media/", methods=["GET"]) +def media(path): + """ + Returns image from media folder + r for resolution, thumb for thumbnail etc + e for extension, jpg, png etc + """ + res = request.args.get("r", default=None).strip() + ext = request.args.get("e", default=None).strip() + + # 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) + + # Generate thumbnail, if None is returned a server error occured + thumb = generate_thumbnail(path, res, ext) + if not thumb: + abort(500) + + response = send_from_directory(os.path.dirname(thumb), os.path.basename(thumb)) + response.headers["Cache-Control"] = "public, max-age=31536000" + response.headers["Expires"] = "31536000" + + return response + + +@blueprint.route("/media/upload", methods=["POST"]) +@login_required +def upload(): + """ + Uploads an image to the server and saves it to the database + """ + form_file = request.files.get("file", None) + 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 = yoink(img_path) # Get EXIF data + img_colors = ColorThief(img_path).get_palette(color_count=3) # Get color palette + + # Save to database + query = Pictures( + 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("/media/delete/", methods=["POST"]) +@login_required +def delete_image(image_id): + """ + Deletes an image from the server and database + """ + post = db.get_or_404(Pictures, 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) + + AlbumJunction.query.filter_by(picture_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 + + +@blueprint.route("/group/create", methods=["POST"]) +@login_required +def create_group(): + """ + Creates a group + """ + group_name = request.form.get("name", "").strip() + group_description = request.form.get("description", "").strip() + group_author = current_user.id + + new_group = Albums( + name=group_name, + description=group_description, + author_id=group_author, + ) + + db.session.add(new_group) + db.session.commit() + + return jsonify({"message": "Group created", "id": new_group.id}) + + +@blueprint.route("/group/modify", methods=["POST"]) +@login_required +def modify_group(): + """ + Changes the images in a group + """ + group_id = request.form.get("group", "").strip() + image_id = request.form.get("image", "").strip() + action = request.form.get("action", "").strip() + + group = db.get_or_404(Albums, group_id) + db.get_or_404(Pictures, 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 + + junction_exist = AlbumJunction.query.filter_by( + album_id=group_id, picture_id=image_id + ).first() + + if action == "add" and not junction_exist: + db.session.add(AlbumJunction(album_id=group_id, picture_id=image_id)) + elif request.form["action"] == "remove": + AlbumJunction.query.filter_by(album_id=group_id, picture_id=image_id).delete() + + db.session.commit() + return jsonify({"message": "Group modified"}) + + +@blueprint.route("/group/delete", methods=["POST"]) +def delete_group(): + """ + Deletes a group + """ + group_id = request.form.get("group", "").strip() + group = db.get_or_404(Albums, group_id) + + if group.author_id != current_user.id: + return jsonify({"message": "You are not the owner of this group"}), 403 + + AlbumJunction.query.filter_by(album_id=group_id).delete() + db.session.delete(group) + db.session.commit() + + flash(["Group yeeted!", "1"]) + return jsonify({"message": "Group deleted"}) diff --git a/onlylegs/api/__init__.py b/onlylegs/api/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/onlylegs/api/account.py b/onlylegs/api/account.py deleted file mode 100644 index fb54c25..0000000 --- a/onlylegs/api/account.py +++ /dev/null @@ -1,93 +0,0 @@ -""" -Onlylegs - API endpoints -""" -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/", 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 - if 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/", 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 - if 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 diff --git a/onlylegs/api/group.py b/onlylegs/api/group.py deleted file mode 100644 index 9be39a5..0000000 --- a/onlylegs/api/group.py +++ /dev/null @@ -1,78 +0,0 @@ -""" -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"}) diff --git a/onlylegs/api/media.py b/onlylegs/api/media.py deleted file mode 100644 index 46f59f5..0000000 --- a/onlylegs/api/media.py +++ /dev/null @@ -1,148 +0,0 @@ -""" -Onlylegs - API endpoints -Media upload and retrieval -""" -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.metadata import yoink -from onlylegs.utils.generate_image import generate_thumbnail - - -blueprint = Blueprint("media_api", __name__, url_prefix="/api/media") - - -@blueprint.route("/", methods=["GET"]) -def media(path): - """ - Returns image from media 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) - - # 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) - - # Generate thumbnail, if None is returned a server error occured - thumb = generate_thumbnail(path, res, ext) - if not thumb: - abort(500) - - response = send_from_directory(os.path.dirname(thumb), os.path.basename(thumb)) - response.headers["Cache-Control"] = "public, max-age=31536000" - response.headers["Expires"] = "31536000" - - return response - - -@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 = yoink(img_path) # 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/", 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 diff --git a/onlylegs/app.py b/onlylegs/app.py new file mode 100644 index 0000000..9cbfb05 --- /dev/null +++ b/onlylegs/app.py @@ -0,0 +1,134 @@ +""" +Onlylegs Gallery +This is the main app file, it loads all the other files and sets up the app +""" +import os +import logging + +from flask_assets import Bundle +from flask_migrate import init as migrate_init + +from flask import Flask, render_template, abort +from werkzeug.exceptions import HTTPException +from werkzeug.security import generate_password_hash + +from onlylegs.extensions import db, migrate, login_manager, assets, compress, cache +from onlylegs.config import INSTANCE_DIR, MIGRATIONS_DIR +from onlylegs.models import Users +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 +from onlylegs import auth as view_auth +from onlylegs import filters + +app = Flask(__name__, instance_path=INSTANCE_DIR) +app.config.from_pyfile("config.py") + +# DATABASE +db.init_app(app) +migrate.init_app(app, db, directory=MIGRATIONS_DIR) + +# If database file doesn't exist, create it +if not os.path.exists(os.path.join(INSTANCE_DIR, "gallery.sqlite3")): + print("Creating database") + with app.app_context(): + db.create_all() + + register_user = Users( + username=app.config["ADMIN_CONF"]["username"], + email=app.config["ADMIN_CONF"]["email"], + password=generate_password_hash("changeme!", method="sha256"), + ) + db.session.add(register_user) + db.session.commit() + + print( + """ +#################################################### +# DEFAULT ADMIN USER GENERATED WITH GIVEN USERNAME # +# THE DEFAULT PASSWORD "changeme!" HAS BEEN USED, # +# PLEASE UPDATE IT IN THE SETTINGS! # +#################################################### + """ + ) + +# Check if migrations directory exists, if not create it +with app.app_context(): + if not os.path.exists(MIGRATIONS_DIR): + print("Creating migrations directory") + migrate_init(directory=MIGRATIONS_DIR) + +# LOGIN MANAGER +# can also set session_protection to "strong" +# this would protect against session hijacking +login_manager.init_app(app) +login_manager.login_view = "onlylegs.index" + + +@login_manager.user_loader +def load_user(user_id): + return Users.query.filter_by(alt_id=user_id).first() + + +@login_manager.unauthorized_handler +def unauthorized(): + error = 401 + msg = "You are not authorized to view this page!!!!" + return render_template("error.html", error=error, msg=msg), error + + +# ERROR HANDLERS +@app.errorhandler(Exception) +def error_page(err): + """ + Error handlers, if the error is not a HTTP error, return 500 + """ + if not isinstance(err, HTTPException): + abort(500) + return ( + render_template("error.html", error=err.code, msg=err.description), + err.code, + ) + + +# ASSETS +assets.init_app(app) + +scripts = Bundle( + "js/*.js", output="gen/js.js", depends="js/*.js" +) # filter jsmin is broken :c +styles = Bundle( + "sass/style.sass", + filters="libsass, cssmin", + output="gen/styles.css", + depends="sass/**/*.sass", +) + +assets.register("scripts", scripts) +assets.register("styles", styles) + +# BLUEPRINTS +app.register_blueprint(view_auth.blueprint) +app.register_blueprint(view_index.blueprint) +app.register_blueprint(view_image.blueprint) +app.register_blueprint(view_group.blueprint) +app.register_blueprint(view_profile.blueprint) +app.register_blueprint(view_settings.blueprint) + +app.register_blueprint(api.blueprint) + +# FILTERS +app.register_blueprint(filters.blueprint) + +# CACHE AND COMPRESS +cache.init_app(app) +compress.init_app(app) + +# Yupee! We got there :3 +print("Done!") +logging.info("Gallery started successfully!") diff --git a/onlylegs/auth.py b/onlylegs/auth.py index 6b5884f..c3d0698 100644 --- a/onlylegs/auth.py +++ b/onlylegs/auth.py @@ -11,7 +11,7 @@ from werkzeug.security import check_password_hash, generate_password_hash from flask_login import login_user, logout_user, login_required from onlylegs.extensions import db -from onlylegs.models import User +from onlylegs.models import Users blueprint = Blueprint("auth", __name__, url_prefix="/auth") @@ -28,7 +28,7 @@ def login(): password = request.form["password"].strip() remember = bool(request.form["remember-me"]) - user = User.query.filter_by(username=username).first() + user = Users.query.filter_by(username=username).first() if not user or not check_password_hash(user.password, password): logging.error("Login attempt from %s", request.remote_addr) @@ -77,7 +77,7 @@ def register(): elif password_repeat != password: error.append("Passwords do not match!") - user_exists = User.query.filter_by(username=username).first() + user_exists = Users.query.filter_by(username=username).first() if user_exists: error.append("User already exists!") @@ -86,7 +86,7 @@ def register(): print(error) return jsonify(error), 400 - register_user = User( + register_user = Users( username=username, email=email, password=generate_password_hash(password, method="sha256"), diff --git a/onlylegs/extensions.py b/onlylegs/extensions.py index 90ef9ab..9e3eb84 100644 --- a/onlylegs/extensions.py +++ b/onlylegs/extensions.py @@ -13,4 +13,4 @@ migrate = Migrate() login_manager = LoginManager() assets = Environment() compress = Compress() -cache = Cache(config={'CACHE_TYPE': 'simple', "CACHE_DEFAULT_TIMEOUT": 300}) +cache = Cache(config={"CACHE_TYPE": "simple", "CACHE_DEFAULT_TIMEOUT": 300}) diff --git a/onlylegs/filters.py b/onlylegs/filters.py index d8f2dd4..765003b 100644 --- a/onlylegs/filters.py +++ b/onlylegs/filters.py @@ -3,7 +3,7 @@ OnlyLegs filters Custom Jinja2 filters """ from flask import Blueprint -from onlylegs.utils.colour import contrast +from onlylegs.utils import colour as colour_utils blueprint = Blueprint("filters", __name__) @@ -16,7 +16,5 @@ def colour_contrast(colour): a css variable based on the contrast of text required to be readable "color: var(--fg-white);" or "color: var(--fg-black);" """ - bright = "var(--fg-white)" - dark = "var(--fg-black)" - - return "color: RGB(" + contrast(colour, dark, bright) + ");" + colour_obj = colour_utils.Colour(colour) + return "rgb(var(--fg-black));" if colour_obj.is_light() else "rgb(var(--fg-white));" diff --git a/onlylegs/gwagwa.py b/onlylegs/gwagwa.py deleted file mode 100644 index 6fb58f6..0000000 --- a/onlylegs/gwagwa.py +++ /dev/null @@ -1,4 +0,0 @@ -""" -Gwa Gwa! -""" -print("Gwa Gwa!") diff --git a/onlylegs/langs/gb.json b/onlylegs/langs/gb.json deleted file mode 100644 index d3f0895..0000000 --- a/onlylegs/langs/gb.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "IMAGES_UPLOADED": "%s images uploaded!", - "DONT USE THIS": "variable:format(data), jinja2 doesnt use the same method as Django does, odd" -} \ No newline at end of file diff --git a/onlylegs/models.py b/onlylegs/models.py index af3eb6f..6970a9b 100644 --- a/onlylegs/models.py +++ b/onlylegs/models.py @@ -6,18 +6,18 @@ from flask_login import UserMixin from onlylegs.extensions import db -class GroupJunction(db.Model): # pylint: disable=too-few-public-methods, C0103 +class AlbumJunction(db.Model): # pylint: disable=too-few-public-methods, C0103 """ - Junction table for posts and groups - Joins with posts and groups + Junction table for picturess and albums + Joins with picturess and albums """ - __tablename__ = "group_junction" + __tablename__ = "album_junction" id = db.Column(db.Integer, primary_key=True) - group_id = db.Column(db.Integer, db.ForeignKey("group.id")) - post_id = db.Column(db.Integer, db.ForeignKey("post.id")) + album_id = db.Column(db.Integer, db.ForeignKey("albums.id")) + picture_id = db.Column(db.Integer, db.ForeignKey("pictures.id")) date_added = db.Column( db.DateTime, @@ -26,16 +26,15 @@ class GroupJunction(db.Model): # pylint: disable=too-few-public-methods, C0103 ) -class Post(db.Model): # pylint: disable=too-few-public-methods, C0103 +class Pictures(db.Model): # pylint: disable=too-few-public-methods, C0103 """ - Post table + Pictures table """ - __tablename__ = "post" + __tablename__ = "pictures" id = db.Column(db.Integer, primary_key=True) - - author_id = db.Column(db.Integer, db.ForeignKey("user.id")) + author_id = db.Column(db.Integer, db.ForeignKey("users.id")) filename = db.Column(db.String, unique=True, nullable=False) mimetype = db.Column(db.String, nullable=False) @@ -51,37 +50,37 @@ class Post(db.Model): # pylint: disable=too-few-public-methods, C0103 server_default=db.func.now(), # pylint: disable=E1102 ) - junction = db.relationship("GroupJunction", backref="posts") + album_fk = db.relationship("AlbumJunction", backref="pictures") -class Group(db.Model): # pylint: disable=too-few-public-methods, C0103 +class Albums(db.Model): # pylint: disable=too-few-public-methods, C0103 """ - Group table + albums table """ - __tablename__ = "group" + __tablename__ = "albums" id = db.Column(db.Integer, primary_key=True) name = db.Column(db.String, nullable=False) description = db.Column(db.String, nullable=False) - author_id = db.Column(db.Integer, db.ForeignKey("user.id")) + author_id = db.Column(db.Integer, db.ForeignKey("users.id")) created_at = db.Column( db.DateTime, nullable=False, server_default=db.func.now(), # pylint: disable=E1102 ) - junction = db.relationship("GroupJunction", backref="groups") + album_fk = db.relationship("AlbumJunction", backref="albums") -class User(db.Model, UserMixin): # pylint: disable=too-few-public-methods, C0103 +class Users(db.Model, UserMixin): # pylint: disable=too-few-public-methods, C0103 """ - User table + Users table """ - __tablename__ = "user" + __tablename__ = "users" # Gallery used information id = db.Column(db.Integer, primary_key=True) @@ -100,8 +99,8 @@ class User(db.Model, UserMixin): # pylint: disable=too-few-public-methods, C010 server_default=db.func.now(), # pylint: disable=E1102 ) - posts = db.relationship("Post", backref="author") - groups = db.relationship("Group", backref="author") + pictures_fk = db.relationship("Pictures", backref="author") + albums_fk = db.relationship("Albums", backref="author") def get_id(self): return str(self.alt_id) diff --git a/onlylegs/static/fonts/Rubik.ttf b/onlylegs/static/fonts/Rubik.ttf deleted file mode 100644 index 547f069..0000000 Binary files a/onlylegs/static/fonts/Rubik.ttf and /dev/null differ diff --git a/onlylegs/static/fonts/font.css b/onlylegs/static/fonts/font.css deleted file mode 100644 index bb8f61b..0000000 --- a/onlylegs/static/fonts/font.css +++ /dev/null @@ -1,7 +0,0 @@ -@font-face { - font-family: 'Rubik'; - src: url('./Rubik.ttf') format('truetype'); - font-style: normal; - font-display: block; - font-weight: 300 900; -} \ No newline at end of file diff --git a/onlylegs/static/logo-black.svg b/onlylegs/static/logo-black.svg deleted file mode 100644 index 559ad4d..0000000 --- a/onlylegs/static/logo-black.svg +++ /dev/null @@ -1,60 +0,0 @@ - - - - - - - - - - - diff --git a/onlylegs/static/logo-white.svg b/onlylegs/static/logo-white.svg deleted file mode 100644 index a50b3f3..0000000 --- a/onlylegs/static/logo-white.svg +++ /dev/null @@ -1,62 +0,0 @@ - - - - - - - - - - - diff --git a/onlylegs/static/manifest.json b/onlylegs/static/manifest.json deleted file mode 100644 index ab9009a..0000000 --- a/onlylegs/static/manifest.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "name": "OnlyLegs", - "short_name": "OnlyLegs", - "start_url": "/", - "display": "standalone", - "background_color": "#151515", - "theme_color": "#151515", - "description": "A gallery built for fast and simple image management!", - "icons": [ - { - "src": "icon.png", - "sizes": "621x621", - "type": "image/png" - } - ], - "splash_pages": null - } - \ No newline at end of file diff --git a/onlylegs/static/sass/style.sass b/onlylegs/static/sass/style.sass index 3302ef4..daf7094 100644 --- a/onlylegs/static/sass/style.sass +++ b/onlylegs/static/sass/style.sass @@ -23,9 +23,8 @@ * box-sizing: border-box - font-family: $font - scrollbar-color: RGB($primary) transparent + font-family: $font ::-webkit-scrollbar width: 0.5rem diff --git a/onlylegs/templates/base.html b/onlylegs/templates/base.html index 59c3b06..f39b915 100644 --- a/onlylegs/templates/base.html +++ b/onlylegs/templates/base.html @@ -1,37 +1,32 @@ + {{ config.WEBSITE_CONF.name }} + - {{ config.WEBSITE_CONF.name }} + + - - + + + - - - + + - - + + + + - + - - + + - - {% assets "scripts" %} {% endassets %} {% assets "styles" %} {% endassets %} {% block head %}{% endblock %} @@ -70,14 +65,14 @@ {% if current_user.picture %} - - + + Profile picture + > {% else %} @@ -109,12 +104,12 @@ - - - + + +
diff --git a/onlylegs/templates/group.html b/onlylegs/templates/group.html index 07ef0e8..c942713 100644 --- a/onlylegs/templates/group.html +++ b/onlylegs/templates/group.html @@ -4,8 +4,8 @@ {% block head %} {% if images %} - - + + {% endif %} @@ -47,7 +47,7 @@ const formData = new FormData(); formData.append("group", formID); - fetch('{{ url_for('group_api.delete_group') }}', { + fetch('{{ url_for('api.delete_group') }}', { method: 'POST', body: formData }).then(response => { @@ -143,7 +143,7 @@ formData.append("image", formImage); formData.append("action", formAction); - fetch('{{ url_for('group_api.modify_group') }}', { + fetch('{{ url_for('api.modify_group') }}', { method: 'POST', body: formData }).then(response => { @@ -185,9 +185,9 @@ .navigation-item.selected { color: {{ text_colour }} !important; } - .banner-header, - .banner-info, - .banner-subtitle { + .banner .banner-content .banner-header, + .banner .banner-content .banner-info, + .banner .banner-content .banner-subtitle { color: {{ text_colour }} !important; } .banner-content .link { @@ -215,10 +215,10 @@ {% if images %}