From e530d452e597f56d8f522e7b822529084f9ed01e Mon Sep 17 00:00:00 2001 From: albinmedoc Date: Tue, 30 Jul 2024 15:36:16 +0200 Subject: [PATCH] =?UTF-8?q?feat:=20=F0=9F=9A=80=20Allow=20uploading=20imag?= =?UTF-8?q?es=20for=20onboarding=20pages?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../wizarr_backend/api/routes/__init__.py | 3 +- .../wizarr_backend/api/routes/image_api.py | 85 +++++++++++++++++++ .../components/Modals/EditOnboarding.vue | 24 +++++- 3 files changed, 110 insertions(+), 2 deletions(-) create mode 100644 apps/wizarr-backend/wizarr_backend/api/routes/image_api.py diff --git a/apps/wizarr-backend/wizarr_backend/api/routes/__init__.py b/apps/wizarr-backend/wizarr_backend/api/routes/__init__.py index 6027bb9b..d42008bd 100644 --- a/apps/wizarr-backend/wizarr_backend/api/routes/__init__.py +++ b/apps/wizarr-backend/wizarr_backend/api/routes/__init__.py @@ -13,6 +13,7 @@ from .authentication_api import api as authentication_api # REVIEW - This is almost completed from .backup_api import api as backup_api from .discord_api import api as discord_api +from .image_api import api as image_api from .invitations_api import api as invitations_api # REVIEW - This is almost completed from .libraries_api import api as libraries_api from .notifications_api import api as notifications_api @@ -112,6 +113,7 @@ def handle_request_exception(error): api.add_namespace(discord_api) api.add_namespace(emby_api) api.add_namespace(healthcheck_api) +api.add_namespace(image_api) api.add_namespace(invitations_api) api.add_namespace(jellyfin_api) api.add_namespace(libraries_api) @@ -137,4 +139,3 @@ def handle_request_exception(error): # TODO: Tasks API # TODO: API API -# TODO: HTML API diff --git a/apps/wizarr-backend/wizarr_backend/api/routes/image_api.py b/apps/wizarr-backend/wizarr_backend/api/routes/image_api.py new file mode 100644 index 00000000..0def422d --- /dev/null +++ b/apps/wizarr-backend/wizarr_backend/api/routes/image_api.py @@ -0,0 +1,85 @@ +import os +from json import dumps, loads +from uuid import uuid4 +from flask import send_from_directory, current_app, request +from flask_jwt_extended import jwt_required +from flask_restx import Namespace, Resource, reqparse +from werkzeug.utils import secure_filename +from werkzeug.datastructures import FileStorage + +api = Namespace("Image", description="Image related operations", path="/image") + +# Define the file upload parser +file_upload_parser = reqparse.RequestParser() +file_upload_parser.add_argument('file', location='files', + type=FileStorage, required=True, + help='Image file') + +@api.route("") +class ImageListApi(Resource): + """API resource for all images""" + + @jwt_required() + @api.doc(security="jwt") + @api.expect(file_upload_parser) + def post(self): + """Upload image""" + # Check if the post request has the file part + if 'file' not in request.files: + return {"message": "No file part"}, 400 + file = request.files['file'] + # If the user does not select a file, the browser submits an + # empty file without a filename. + if file.filename == '': + return {"message": "No selected file"}, 400 + if file: + # Extract the file extension + file_extension = os.path.splitext(secure_filename(file.filename))[1].lower() + if file_extension not in ['.png', '.jpg', '.jpeg']: + return {"message": "Unsupported file format"}, 400 + + upload_folder = current_app.config['UPLOAD_FOLDER'] + if not os.path.exists(upload_folder): + os.makedirs(upload_folder) + # Generate a unique filename using UUID + filename = f"{uuid4()}{file_extension}" + + # Check if the file exists and generate a new UUID if it does + while os.path.exists(os.path.join(upload_folder, filename)): + filename = f"{uuid4()}{file_extension}" + file_path = os.path.join(upload_folder, filename) + file.save(file_path) + return {"message": f"File {filename} uploaded successfully", "filename": filename}, 201 + + +@api.route("/") +class ImageAPI(Resource): + """API resource for a single image""" + + @api.response(404, "Image not found") + @api.response(500, "Internal server error") + def get(self, filename): + """Get image""" + # Assuming images are stored in a directory specified by UPLOAD_FOLDER config + upload_folder = current_app.config['UPLOAD_FOLDER'] + image_path = os.path.join(upload_folder, filename) + if os.path.exists(image_path): + return send_from_directory(upload_folder, filename) + else: + return {"message": "Image not found"}, 404 + + @jwt_required() + @api.doc(description="Delete a single image") + @api.response(404, "Image not found") + @api.response(500, "Internal server error") + def delete(self, filename): + """Delete image""" + upload_folder = current_app.config['UPLOAD_FOLDER'] + image_path = os.path.join(upload_folder, filename) + + # Check if the file exists + if not os.path.exists(image_path): + return {"message": "Image not found"}, 404 + + os.remove(image_path) + return {"message": "Image deleted successfully"}, 200 diff --git a/apps/wizarr-frontend/src/modules/settings/components/Modals/EditOnboarding.vue b/apps/wizarr-frontend/src/modules/settings/components/Modals/EditOnboarding.vue index 801e77cb..015e637f 100644 --- a/apps/wizarr-frontend/src/modules/settings/components/Modals/EditOnboarding.vue +++ b/apps/wizarr-frontend/src/modules/settings/components/Modals/EditOnboarding.vue @@ -1,5 +1,5 @@