feat: add avatars to members
This commit is contained in:
parent
437c93463d
commit
66a5dfb433
3 changed files with 60 additions and 3 deletions
|
@ -2,6 +2,7 @@ from pydantic import Field
|
||||||
from quart import Blueprint, g
|
from quart import Blueprint, g
|
||||||
from quart_schema import validate_request, validate_response
|
from quart_schema import validate_request, validate_response
|
||||||
|
|
||||||
|
from foxnouns import tasks
|
||||||
from foxnouns.auth import require_auth
|
from foxnouns.auth import require_auth
|
||||||
from foxnouns.db import Member
|
from foxnouns.db import Member
|
||||||
from foxnouns.db.aio import async_session
|
from foxnouns.db.aio import async_session
|
||||||
|
@ -54,4 +55,7 @@ async def create_member(data: MemberCreateModel):
|
||||||
# we have to do it manually.
|
# we have to do it manually.
|
||||||
await member.awaitable_attrs.user
|
await member.awaitable_attrs.user
|
||||||
|
|
||||||
|
if data.avatar:
|
||||||
|
tasks.process_member_avatar.delay(member.id, data.avatar)
|
||||||
|
|
||||||
return FullMemberModel.model_validate(member)
|
return FullMemberModel.model_validate(member)
|
||||||
|
|
|
@ -18,6 +18,8 @@ class MemberPatchModel(BasePatchModel):
|
||||||
)
|
)
|
||||||
bio: str | None = Field(max_length=1024, default=None)
|
bio: str | None = Field(max_length=1024, default=None)
|
||||||
|
|
||||||
|
avatar: str | None = Field(max_length=1_000_000, default=None)
|
||||||
|
|
||||||
names: list[FieldEntry] = Field(default=[])
|
names: list[FieldEntry] = Field(default=[])
|
||||||
pronouns: list[PronounEntry] = Field(default=[])
|
pronouns: list[PronounEntry] = Field(default=[])
|
||||||
fields: list[ProfileField] = Field(default=[])
|
fields: list[ProfileField] = Field(default=[])
|
||||||
|
|
|
@ -8,7 +8,7 @@ from celery.utils.log import get_task_logger
|
||||||
from minio import Minio
|
from minio import Minio
|
||||||
from sqlalchemy import select, update
|
from sqlalchemy import select, update
|
||||||
|
|
||||||
from foxnouns.db import User
|
from foxnouns.db import Member, User
|
||||||
from foxnouns.db.sync import session
|
from foxnouns.db.sync import session
|
||||||
from foxnouns.settings import MINIO, REDIS_URL
|
from foxnouns.settings import MINIO, REDIS_URL
|
||||||
|
|
||||||
|
@ -44,7 +44,7 @@ def convert_avatar(uri: str) -> bytes:
|
||||||
|
|
||||||
|
|
||||||
@app.task
|
@app.task
|
||||||
def process_user_avatar(user_id: int, avatar: str):
|
def process_user_avatar(user_id: int, avatar: str) -> None:
|
||||||
with session() as conn:
|
with session() as conn:
|
||||||
user = conn.scalar(select(User).where(User.id == user_id))
|
user = conn.scalar(select(User).where(User.id == user_id))
|
||||||
if not user:
|
if not user:
|
||||||
|
@ -71,7 +71,7 @@ def process_user_avatar(user_id: int, avatar: str):
|
||||||
|
|
||||||
|
|
||||||
@app.task
|
@app.task
|
||||||
def delete_user_avatar(user_id: int):
|
def delete_user_avatar(user_id: int) -> None:
|
||||||
with session() as conn:
|
with session() as conn:
|
||||||
user = conn.scalar(select(User).where(User.id == user_id))
|
user = conn.scalar(select(User).where(User.id == user_id))
|
||||||
if not user:
|
if not user:
|
||||||
|
@ -87,3 +87,54 @@ def delete_user_avatar(user_id: int):
|
||||||
with session() as conn:
|
with session() as conn:
|
||||||
conn.execute(update(User).values(avatar=None).where(User.id == user_id))
|
conn.execute(update(User).values(avatar=None).where(User.id == user_id))
|
||||||
conn.commit()
|
conn.commit()
|
||||||
|
|
||||||
|
|
||||||
|
@app.task
|
||||||
|
def process_member_avatar(member_id: int, avatar: str) -> None:
|
||||||
|
with session() as conn:
|
||||||
|
member = conn.scalar(select(Member).where(Member.id == member_id))
|
||||||
|
if not member:
|
||||||
|
raise ValueError(
|
||||||
|
"process_member_avatar was passed the ID of a nonexistent member"
|
||||||
|
)
|
||||||
|
|
||||||
|
img = convert_avatar(avatar)
|
||||||
|
hash = hashlib.new("sha256", data=img).hexdigest()
|
||||||
|
old_hash = member.avatar
|
||||||
|
|
||||||
|
minio.put_object(
|
||||||
|
bucket,
|
||||||
|
f"members/{member_id}/avatars/{hash}.webp",
|
||||||
|
BytesIO(img),
|
||||||
|
len(img),
|
||||||
|
"image/webp",
|
||||||
|
)
|
||||||
|
|
||||||
|
with session() as conn:
|
||||||
|
conn.execute(update(Member).values(avatar=hash).where(Member.id == member_id))
|
||||||
|
conn.commit()
|
||||||
|
|
||||||
|
if old_hash:
|
||||||
|
minio.remove_object(bucket, f"members/{member_id}/avatars/{old_hash}.webp")
|
||||||
|
|
||||||
|
|
||||||
|
@app.task
|
||||||
|
def delete_member_avatar(member_id: int) -> None:
|
||||||
|
with session() as conn:
|
||||||
|
member = conn.scalar(select(Member).where(Member.id == member_id))
|
||||||
|
if not member:
|
||||||
|
raise ValueError(
|
||||||
|
"delete_member_avatar was passed the ID of a nonexistent member"
|
||||||
|
)
|
||||||
|
if not member.avatar:
|
||||||
|
logger.info(
|
||||||
|
"delete_member_avatar was called for a member with a null avatar (%d)",
|
||||||
|
member_id,
|
||||||
|
)
|
||||||
|
return
|
||||||
|
|
||||||
|
minio.remove_object(bucket, f"members/{member_id}/avatars/{member.avatar}.webp")
|
||||||
|
|
||||||
|
with session() as conn:
|
||||||
|
conn.execute(update(Member).values(avatar=None).where(Member.id == member_id))
|
||||||
|
conn.commit()
|
||||||
|
|
Loading…
Reference in a new issue