diff --git a/app/core/email.py b/app/core/email.py index 12ec5bd..478abf5 100644 --- a/app/core/email.py +++ b/app/core/email.py @@ -6,14 +6,12 @@ "EmailSendError", ] -import base64 from datetime import datetime from os import getenv -from pathlib import Path from typing import Optional, Union import resend -from flask import current_app, render_template +from flask import render_template from redis import Redis from resend import Attachment, RemoteAttachment, Tag from resend.http_client_requests import RequestsClient @@ -274,40 +272,3 @@ def send_password_reset_email( text=text, tags=[{"name": "category", "value": "password_reset"}], ) - - -def _build_logo_attachment() -> Attachment: - """ - Load the Drssed logo from static/emails/logo.png and return a - Resend-compatible inline attachment dict. - - The content_id "logo" matches the cid:logo reference in HTML templates. - """ - logo_path = _get_static_folder() / "emails" / "logo.png" - - with open(logo_path, "rb") as f: - logo_bytes = f.read() - - return { - "filename": "logo.png", - "content": base64.b64encode(logo_bytes).decode(), - "content_id": "logo", - } - - -def _get_static_folder() -> Path: - folder = current_app.static_folder - - if not folder: - raise RuntimeError("⚠️ Flask static_folder is not configured") - - return Path(folder) - - -def _get_templates_folder() -> str: - folder = current_app.template_folder - - if not folder: - raise RuntimeError("⚠️ Flask template_folder is not configured") - - return str(folder) diff --git a/app/routes/clothing.py b/app/routes/clothing.py index 5b8c9ba..4081381 100644 --- a/app/routes/clothing.py +++ b/app/routes/clothing.py @@ -28,7 +28,6 @@ def delete_clothing_piece(clothing_id: str): @limiter.limit("60 per minute") @authorize_request def patch_clothing_piece(clothing_id: str): - request.headers["Authorization"] data = request.get_json() # NOTE: Default to private entries until social features implemented diff --git a/app/services/image.py b/app/services/image.py index e6d960d..bd61f91 100644 --- a/app/services/image.py +++ b/app/services/image.py @@ -1,6 +1,5 @@ __all__ = [ "move_preview_image_to_permanent", - "save_outfit_preview", "load_clothing_image_by_id", "generate_outfit_preview", "delete_outfit_preview", @@ -12,7 +11,6 @@ from urllib.parse import urljoin from PIL import Image -from werkzeug.datastructures import FileStorage from app.core.logging import get_logger @@ -47,35 +45,6 @@ def move_preview_image_to_permanent( raise -def save_outfit_preview(outfit_id: str, preview_file: FileStorage) -> str: - """ - Returns: public_url - """ - mimetype = (preview_file.mimetype or "").lower() - if mimetype not in ("image/png", "image/webp", "image/jpeg", "image/jpg"): - raise ValueError("Invalid preview mimetype. Must be png/webp/jpeg.") - - preview_file.stream.seek(0) - img = Image.open(preview_file.stream).convert("RGBA") - - img.thumbnail((1024, 1024), Image.Resampling.LANCZOS) - - alpha = img.getchannel("A") - bbox = alpha.getbbox() - if bbox: - img = img.crop(bbox) - - path = f"app/static/outfit_collages/{outfit_id}.webp" - img.save(path, "WEBP") - - return str( - urljoin( - os.getenv("API_BASE_URL", ""), - f"static/outfit_collages/{outfit_id}.webp", - ) - ) - - def load_clothing_image_by_id(image_id: str) -> Image.Image: image_path = os.path.join("app/static/clothing_images", f"{image_id}.webp") diff --git a/app/utils/exceptions/__init__.py b/app/utils/exceptions/__init__.py index 9790e6d..3b007ab 100644 --- a/app/utils/exceptions/__init__.py +++ b/app/utils/exceptions/__init__.py @@ -20,9 +20,7 @@ from app.utils.exceptions.clothing import ( ClothingColorMissingError, ClothingIDMissingError, - ClothingImageInvalidError, ClothingImageMissingError, - ClothingNameMissingError, ClothingNameTooLongError, ClothingNameTooShortError, ClothingSubCategoryMissingError, @@ -32,7 +30,6 @@ ) from app.utils.exceptions.outfits import ( OutfitClothingIDInvalidError, - OutfitClothingIDsMissingError, OutfitFavoriteMissingError, OutfitIDMissingError, OutfitLimitInvalidError, @@ -40,11 +37,9 @@ OutfitNameTooLongError, OutfitNameTooShortError, OutfitOffsetInvalidError, - OutfitPreviewInvalidError, OutfitPublicMissingError, OutfitSceneInvalidError, OutfitSceneMissingError, - OutfitSeasonsInvalidError, OutfitTagsInvalidError, ) from app.utils.exceptions.user import ( @@ -62,9 +57,7 @@ UsernameTooShortError, ) from app.utils.exceptions.validation import ( - FileTooLargeError, ImageUnclearError, - UnsupportedFileTypeError, ) from app.utils.exceptions.wear import ( WearAlreadyLoggedError, @@ -95,27 +88,21 @@ "ClothingValidationError", "ClothingNotFoundError", "ClothingConflictError", - "UnsupportedFileTypeError", - "FileTooLargeError", "ImageUnclearError", - "ClothingNameMissingError", "ClothingSubCategoryMissingError", "ClothingColorMissingError", "ClothingIDMissingError", "ClothingImageMissingError", "ClothingNameTooShortError", "ClothingNameTooLongError", - "ClothingImageInvalidError", "SeasonsInvalidError", "ClothingTagsInvalidError", "ClothingWarmthLevelInvalidError", "OutfitValidationError", "OutfitNotFoundError", "OutfitIDMissingError", - "OutfitClothingIDsMissingError", "OutfitNameMissingError", "OutfitClothingIDInvalidError", - "OutfitSeasonsInvalidError", "OutfitTagsInvalidError", "OutfitLimitInvalidError", "OutfitOffsetInvalidError", @@ -125,7 +112,6 @@ "OutfitFavoriteMissingError", "OutfitSceneMissingError", "OutfitSceneInvalidError", - "OutfitPreviewInvalidError", "WearValidationError", "WearNotFoundError", "WearAlreadyLoggedError", diff --git a/app/utils/exceptions/clothing.py b/app/utils/exceptions/clothing.py index 790e81a..87a98c8 100644 --- a/app/utils/exceptions/clothing.py +++ b/app/utils/exceptions/clothing.py @@ -1,4 +1,4 @@ -from app.utils.exceptions.base import ClothingConflictError, ClothingValidationError +from app.utils.exceptions.base import ClothingValidationError class ClothingIDMissingError(ClothingValidationError): @@ -6,11 +6,6 @@ def __init__(self, message="Clothing ID is missing"): super().__init__(message) -class ClothingNameMissingError(ClothingValidationError): - def __init__(self, message="Clothing name is missing"): - super().__init__(message) - - class ClothingSubCategoryMissingError(ClothingValidationError): def __init__(self, message="Clothing category is missing"): super().__init__(message) @@ -36,11 +31,6 @@ def __init__(self, message="Clothing name has to be at most 50 characters long") super().__init__(message) -class ClothingImageInvalidError(ClothingConflictError): - def __init__(self, message="Clothing image is invalid"): - super().__init__(message) - - class SeasonsInvalidError(ClothingValidationError): def __init__(self, message="Clothing seasons are invalid"): super().__init__(message) diff --git a/app/utils/exceptions/outfits.py b/app/utils/exceptions/outfits.py index b327526..adf41e8 100644 --- a/app/utils/exceptions/outfits.py +++ b/app/utils/exceptions/outfits.py @@ -6,11 +6,6 @@ def __init__(self, message="Outfit ID is missing"): super().__init__(message) -class OutfitClothingIDsMissingError(OutfitValidationError): - def __init__(self, message="Outfit clothing ID(s) are missing"): - super().__init__(message) - - class OutfitNameMissingError(OutfitValidationError): def __init__(self, message="Outfit name is missing"): super().__init__(message) @@ -21,11 +16,6 @@ def __init__(self, message="Outfit clothing ID is invalid"): super().__init__(message) -class OutfitSeasonsInvalidError(OutfitValidationError): - def __init__(self, message="Outfit seasons are invalid"): - super().__init__(message) - - class OutfitTagsInvalidError(OutfitValidationError): def __init__(self, message="Outfit tags are invalid"): super().__init__(message) @@ -69,8 +59,3 @@ def __init__(self, message="Outfit scene is missing."): class OutfitSceneInvalidError(OutfitValidationError): def __init__(self, message="Outfit scene is invalid."): super().__init__(message) - - -class OutfitPreviewInvalidError(OutfitValidationError): - def __init__(self, message="Outfit preview is invalid."): - super().__init__(message) diff --git a/app/utils/exceptions/validation.py b/app/utils/exceptions/validation.py index 50c5338..b6afeb3 100644 --- a/app/utils/exceptions/validation.py +++ b/app/utils/exceptions/validation.py @@ -1,16 +1,6 @@ from app.utils.exceptions.base import ValidationError -class UnsupportedFileTypeError(ValidationError): - def __init__(self, message="Unsupported file type [Supported: .png, .jpg, .jpeg]"): - super().__init__(message) - - -class FileTooLargeError(ValidationError): - def __init__(self, message="File is too large (max 2MB)"): - super().__init__(message) - - class ImageUnclearError(ValidationError): def __init__(self, message="Image is unclear"): super().__init__(message) diff --git a/app/utils/helpers.py b/app/utils/helpers.py index 0cfa37b..9f1e54e 100644 --- a/app/utils/helpers.py +++ b/app/utils/helpers.py @@ -1,31 +1,15 @@ __all__ = ["helper"] from datetime import datetime, timezone -from decimal import Decimal -from typing import Any, Sequence, cast from flask import g, has_request_context, request from app.core.logging import get_logger -from ..models.outfit import CanvasPlacement - logger = get_logger() class HelperFunctions: - @staticmethod - def ensure_dict(result: Any) -> dict: - """ - :param result: The object to check - :return: Result as dictionary - :raises TypeError: If result is not a dictionary - """ - if not isinstance(result, dict): - raise TypeError(f"Expected a dictionary, but got {type(result).__name__}") - - return result - @staticmethod def build_paginated_response(items, limit, offset, total): """ @@ -56,17 +40,6 @@ def get_request_context(self) -> dict: return context - def _parse_canvas_row(self, row: Sequence) -> CanvasPlacement: - clothing_id, x, y, z, scale, rotation = row - return CanvasPlacement( - clothing_id=cast(str, clothing_id), - x=float(cast(Decimal, x)), - y=float(cast(Decimal, y)), - z=int(cast(Decimal, z)), - scale=float(cast(Decimal, scale)), - rotation=float(cast(Decimal, rotation)), - ) - helper = HelperFunctions() diff --git a/app/utils/old_exceptions.py b/app/utils/old_exceptions.py deleted file mode 100755 index cf8bd15..0000000 --- a/app/utils/old_exceptions.py +++ /dev/null @@ -1,106 +0,0 @@ -# ! Internal: - - -class DatabaseError(Exception): - def __init__(self, message="Database error occurred"): - self.message = message - super().__init__(self.message) - - -class InvalidTokenError(Exception): - def __init__(self, message="Invalid token"): - self.message = message - super().__init__(self.message) - - -# External: - - -class PermissionError(Exception): - def __init__(self, message="Permission denied"): - self.message = message - super().__init__(self.message) - - -class EmailAlreadyInUseError(Exception): - def __init__(self, message="Email already in use"): - self.message = message - super().__init__(self.message) - - -class EmailInvalidError(Exception): - def __init__(self, message="Email is invalid"): - self.message = message - super().__init__(self.message) - - -class UsernameAlreadySetError(Exception): - def __init__(self, message="Username already set"): - self.message = message - super().__init__(self.message) - - -class UsernameAlreadyInUseError(Exception): - def __init__(self, message="Username already in use"): - self.message = message - super().__init__(self.message) - - -class UsernameTooShortError(Exception): - def __init__(self, message="Username has to be at least 3 characters long"): - self.message = message - super().__init__(self.message) - - -class UsernameTooLongError(Exception): - def __init__(self, message="Username has to be at most 32 characters long"): - self.message = message - super().__init__(self.message) - - -class PasswordTooShortError(Exception): - def __init__(self, message="Password has twaso be at least 8 characters long"): - self.message = message - super().__init__(self.message) - - -class WrongSignInCredentialsError(Exception): - def __init__(self, message="Wrong email/username or password"): - self.message = message - super().__init__(self.message) - - -class UserNotFoundError(Exception): - def __init__(self, message="User not found"): - self.message = message - super().__init__(self.message) - - -class UserProfilePictureNotFoundError(Exception): - def __init__(self, message="User profile picture not set"): - self.message = message - super().__init__(self.message) - - -class ClothingNotFoundError(Exception): - def __init__(self, message="Clothing not found"): - self.message = message - super().__init__(self.message) - - -class OutfitNameTooShortError(Exception): - def __init__(self, message="Outfit name has to be at least 3 characters long"): - self.message = message - super().__init__(self.message) - - -class OutfitNameTooLongError(Exception): - def __init__(self, message="Outfit name has to be at most 50 characters long"): - self.message = message - super().__init__(self.message) - - -class OutfitNotFoundError(Exception): - def __init__(self, message="Outfit not found"): - self.message = message - super().__init__(self.message) diff --git a/requirements.txt b/requirements.txt index 93d1282..5b72b89 100644 --- a/requirements.txt +++ b/requirements.txt @@ -60,7 +60,6 @@ torchvision==0.20.1 tqdm==4.67.0 typing_extensions==4.14.1 urllib3==2.2.3 -waitress==3.0.1 Werkzeug==3.1.3 wrapt==1.16.0 transformers==4.37.2