diff --git a/foxnouns/app.py b/foxnouns/app.py index 9479fe2..af561a8 100644 --- a/foxnouns/app.py +++ b/foxnouns/app.py @@ -6,8 +6,10 @@ from . import blueprints from .db.aio import async_session from .db.util import validate_token from .exceptions import ErrorCode, ExpectedError +from .settings import SECRET_KEY, BASE_DOMAIN -app = Quart(__name__) +app = Quart(__name__, host_matching=True, static_host=BASE_DOMAIN) +app.secret_key = SECRET_KEY app = cors( app, allow_origin="*", diff --git a/foxnouns/settings.py b/foxnouns/settings.py index 17e3daf..a5e352a 100644 --- a/foxnouns/settings.py +++ b/foxnouns/settings.py @@ -24,6 +24,7 @@ with env.prefixed("MINIO_"): "SECRET_KEY": env("SECRET_KEY"), "BUCKET": env("BUCKET"), "SECURE": env.bool("SECURE", True), + "REGION": env("REGION", "auto"), } # The base domain the API is served on. This must be set. diff --git a/foxnouns/tasks.py b/foxnouns/tasks.py index 9a56dcc..052d1f4 100644 --- a/foxnouns/tasks.py +++ b/foxnouns/tasks.py @@ -21,11 +21,16 @@ minio = Minio( access_key=MINIO["ACCESS_KEY"], secret_key=MINIO["SECRET_KEY"], secure=MINIO["SECURE"], + region=MINIO["REGION"], ) bucket = MINIO["BUCKET"] def convert_avatar(uri: str) -> bytes: + """Converts a base64 data URI into a WebP image. + Images are resized and cropped to 512x512 and exported with quality 95. + Only PNG, WebP, and JPEG images are allowed as input.""" + if not uri.startswith("data:image/"): raise ValueError("Not a data URI") @@ -45,6 +50,9 @@ def convert_avatar(uri: str) -> bytes: @app.task def process_user_avatar(user_id: int, avatar: str) -> None: + """Processes an avatar string, uploads it to S3, and updates the user's avatar hash. + Also deletes the old avatar if one was already set.""" + with session() as conn: user = conn.scalar(select(User).where(User.id == user_id)) if not user: @@ -66,12 +74,14 @@ def process_user_avatar(user_id: int, avatar: str) -> None: conn.execute(update(User).values(avatar=hash).where(User.id == user_id)) conn.commit() - if old_hash: + if old_hash and old_hash != hash: minio.remove_object(bucket, f"users/{user_id}/avatars/{old_hash}.webp") @app.task def delete_user_avatar(user_id: int) -> None: + """Deletes a user's avatar.""" + with session() as conn: user = conn.scalar(select(User).where(User.id == user_id)) if not user: @@ -91,6 +101,9 @@ def delete_user_avatar(user_id: int) -> None: @app.task def process_member_avatar(member_id: int, avatar: str) -> None: + """Processes an avatar string, uploads it to S3, and updates the member's avatar hash. + Also deletes the old avatar if one was already set.""" + with session() as conn: member = conn.scalar(select(Member).where(Member.id == member_id)) if not member: @@ -114,12 +127,14 @@ def process_member_avatar(member_id: int, avatar: str) -> None: conn.execute(update(Member).values(avatar=hash).where(Member.id == member_id)) conn.commit() - if old_hash: + if old_hash and old_hash != hash: minio.remove_object(bucket, f"members/{member_id}/avatars/{old_hash}.webp") @app.task def delete_member_avatar(member_id: int) -> None: + """Deletes a member's avatar.""" + with session() as conn: member = conn.scalar(select(Member).where(Member.id == member_id)) if not member: