From a51a128fcba9668d76cc447991eeaf70d33fcd13 Mon Sep 17 00:00:00 2001 From: swve Date: Sat, 20 Jan 2024 00:41:37 +0100 Subject: [PATCH] feat: implement backend avatar update --- apps/api/.gitignore | 2 +- apps/api/src/routers/users.py | 17 ++++++- .../src/services/blocks/utils/upload_files.py | 3 +- .../courses/activities/uploads/pdfs.py | 1 + .../courses/activities/uploads/videos.py | 1 + apps/api/src/services/courses/courses.py | 2 +- apps/api/src/services/courses/thumbnails.py | 6 +-- apps/api/src/services/orgs/logos.py | 1 + apps/api/src/services/users/avatars.py | 16 +++++++ apps/api/src/services/users/users.py | 48 ++++++++++++++++++- apps/api/src/services/utils/upload_content.py | 25 ++++++---- 11 files changed, 103 insertions(+), 19 deletions(-) create mode 100644 apps/api/src/services/users/avatars.py diff --git a/apps/api/.gitignore b/apps/api/.gitignore index 010e2064..2a59b52b 100644 --- a/apps/api/.gitignore +++ b/apps/api/.gitignore @@ -10,7 +10,7 @@ __pycache__/ .vscode/ # Learnhouse -content/org_* +content/* # Flyio fly.toml diff --git a/apps/api/src/routers/users.py b/apps/api/src/routers/users.py index 808f77b5..dee50ca1 100644 --- a/apps/api/src/routers/users.py +++ b/apps/api/src/routers/users.py @@ -1,5 +1,5 @@ from typing import Literal -from fastapi import APIRouter, Depends, Request +from fastapi import APIRouter, Depends, Request, UploadFile from sqlmodel import Session from src.security.auth import get_current_user from src.core.events.database import get_db_session @@ -22,6 +22,7 @@ from src.services.users.users import ( read_user_by_id, read_user_by_uuid, update_user, + update_user_avatar, update_user_password, ) @@ -137,6 +138,20 @@ async def api_update_user( return await update_user(request, db_session, user_id, current_user, user_object) +@router.put("/update_avatar/{user_id}", response_model=UserRead, tags=["users"]) +async def api_update_avatar_user( + *, + request: Request, + db_session: Session = Depends(get_db_session), + current_user: PublicUser = Depends(get_current_user), + avatar_file: UploadFile | None = None, +) -> UserRead: + """ + Update User + """ + return await update_user_avatar(request, db_session, current_user, avatar_file) + + @router.put("/change_password/{user_id}", response_model=UserRead, tags=["users"]) async def api_update_user_password( *, diff --git a/apps/api/src/services/blocks/utils/upload_files.py b/apps/api/src/services/blocks/utils/upload_files.py index 76ebd1c7..32bd8024 100644 --- a/apps/api/src/services/blocks/utils/upload_files.py +++ b/apps/api/src/services/blocks/utils/upload_files.py @@ -50,7 +50,8 @@ async def upload_file_and_return_file_object( await upload_content( f"courses/{course_uuid}/activities/{activity_uuid}/dynamic/blocks/{type_of_block}/{block_id}", - org_uuid=org_uuid, + type_of_dir='orgs', + uuid=org_uuid, file_binary=file_binary, file_and_format=f"{file_id}.{file_format}", ) diff --git a/apps/api/src/services/courses/activities/uploads/pdfs.py b/apps/api/src/services/courses/activities/uploads/pdfs.py index 3d4f5ef6..48576048 100644 --- a/apps/api/src/services/courses/activities/uploads/pdfs.py +++ b/apps/api/src/services/courses/activities/uploads/pdfs.py @@ -8,6 +8,7 @@ async def upload_pdf(pdf_file, activity_uuid, org_uuid, course_uuid): try: await upload_content( f"courses/{course_uuid}/activities/{activity_uuid}/documentpdf", + "orgs", org_uuid, contents, f"documentpdf.{pdf_format}", diff --git a/apps/api/src/services/courses/activities/uploads/videos.py b/apps/api/src/services/courses/activities/uploads/videos.py index 2da6c35e..bfc48476 100644 --- a/apps/api/src/services/courses/activities/uploads/videos.py +++ b/apps/api/src/services/courses/activities/uploads/videos.py @@ -10,6 +10,7 @@ async def upload_video(video_file, activity_uuid, org_uuid, course_uuid): await upload_content( f"courses/{course_uuid}/activities/{activity_uuid}/video", org_uuid, + org_uuid, contents, f"video.{video_format}", ) diff --git a/apps/api/src/services/courses/courses.py b/apps/api/src/services/courses/courses.py index cc007a56..a420d0b0 100644 --- a/apps/api/src/services/courses/courses.py +++ b/apps/api/src/services/courses/courses.py @@ -213,7 +213,7 @@ async def update_course_thumbnail( if thumbnail_file and thumbnail_file.filename: name_in_disk = f"{course_uuid}_thumbnail_{uuid4()}.{thumbnail_file.filename.split('.')[-1]}" await upload_thumbnail( - thumbnail_file, name_in_disk, org.org_uuid, course.course_uuid + thumbnail_file, name_in_disk, 'users', course.course_uuid ) # Update course diff --git a/apps/api/src/services/courses/thumbnails.py b/apps/api/src/services/courses/thumbnails.py index 46b59172..b00a0049 100644 --- a/apps/api/src/services/courses/thumbnails.py +++ b/apps/api/src/services/courses/thumbnails.py @@ -1,13 +1,13 @@ - from src.services.utils.upload_content import upload_content -async def upload_thumbnail(thumbnail_file, name_in_disk, org_id, course_id): +async def upload_thumbnail(thumbnail_file, name_in_disk, org_uuid, course_id): contents = thumbnail_file.file.read() try: await upload_content( f"courses/{course_id}/thumbnails", - org_id, + "orgs", + org_uuid, contents, f"{name_in_disk}", ) diff --git a/apps/api/src/services/orgs/logos.py b/apps/api/src/services/orgs/logos.py index 09fe048a..6895f090 100644 --- a/apps/api/src/services/orgs/logos.py +++ b/apps/api/src/services/orgs/logos.py @@ -10,6 +10,7 @@ async def upload_org_logo(logo_file, org_uuid): await upload_content( "logos", org_uuid, + org_uuid, contents, name_in_disk, ) diff --git a/apps/api/src/services/users/avatars.py b/apps/api/src/services/users/avatars.py new file mode 100644 index 00000000..5fe24528 --- /dev/null +++ b/apps/api/src/services/users/avatars.py @@ -0,0 +1,16 @@ +from src.services.utils.upload_content import upload_content + + +async def upload_avatar(avatar_file, name_in_disk, user_uuid): + contents = avatar_file.file.read() + try: + await upload_content( + f"avatars", + "users", + user_uuid, + contents, + f"{name_in_disk}", + ) + + except Exception: + return {"message": "There was an error uploading the file"} diff --git a/apps/api/src/services/users/users.py b/apps/api/src/services/users/users.py index bce3301b..3beca002 100644 --- a/apps/api/src/services/users/users.py +++ b/apps/api/src/services/users/users.py @@ -1,13 +1,14 @@ from datetime import datetime from typing import Literal from uuid import uuid4 -from fastapi import HTTPException, Request, status +from fastapi import HTTPException, Request, UploadFile, status from sqlmodel import Session, select +from src.services.users.avatars import upload_avatar from src.db.roles import Role, RoleRead from src.security.rbac.rbac import ( authorization_verify_based_on_roles_and_authorship, authorization_verify_if_user_is_anon, -) +) from src.db.organizations import Organization, OrganizationRead from src.db.users import ( AnonymousUser, @@ -195,6 +196,49 @@ async def update_user( return user +async def update_user_avatar( + request: Request, + db_session: Session, + current_user: PublicUser | AnonymousUser, + avatar_file: UploadFile | None = None, +): + # Get user + statement = select(User).where(User.id == current_user.id) + user = db_session.exec(statement).first() + + if not user: + raise HTTPException( + status_code=400, + detail="User does not exist", + ) + + # RBAC check + await rbac_check(request, current_user, "update", user.user_uuid, db_session) + + # Upload thumbnail + if avatar_file and avatar_file.filename: + name_in_disk = f"{user.user_uuid}_avatar_{uuid4()}.{avatar_file.filename.split('.')[-1]}" + await upload_avatar(avatar_file, name_in_disk, user.user_uuid) + + # Update course + if name_in_disk: + user.avatar_image = name_in_disk + else: + raise HTTPException( + status_code=500, + detail="Issue with Avatar upload", + ) + + # Update user in database + db_session.add(user) + db_session.commit() + db_session.refresh(user) + + user = UserRead.from_orm(user) + + return user + + async def update_user_password( request: Request, db_session: Session, diff --git a/apps/api/src/services/utils/upload_content.py b/apps/api/src/services/utils/upload_content.py index 33200231..04448346 100644 --- a/apps/api/src/services/utils/upload_content.py +++ b/apps/api/src/services/utils/upload_content.py @@ -1,3 +1,4 @@ +from typing import Literal import boto3 from botocore.exceptions import ClientError import os @@ -6,7 +7,11 @@ from config.config import get_learnhouse_config async def upload_content( - directory: str, org_uuid: str, file_binary: bytes, file_and_format: str + directory: str, + type_of_dir: Literal["orgs", "users"], + uuid: str, # org_uuid or user_uuid + file_binary: bytes, + file_and_format: str, ): # Get Learnhouse Config learnhouse_config = get_learnhouse_config() @@ -16,12 +21,12 @@ async def upload_content( if content_delivery == "filesystem": # create folder for activity - if not os.path.exists(f"content/{org_uuid}/{directory}"): + if not os.path.exists(f"content/{type_of_dir}/{uuid}/{directory}"): # create folder for activity - os.makedirs(f"content/{org_uuid}/{directory}") + os.makedirs(f"content/{type_of_dir}/{uuid}/{directory}") # upload file to server with open( - f"content/{org_uuid}/{directory}/{file_and_format}", + f"content/{type_of_dir}/{uuid}/{directory}/{file_and_format}", "wb", ) as f: f.write(file_binary) @@ -37,13 +42,13 @@ async def upload_content( ) # Create folder for activity - if not os.path.exists(f"content/{org_uuid}/{directory}"): + if not os.path.exists(f"content/{type_of_dir}/{uuid}/{directory}"): # create folder for activity - os.makedirs(f"content/{org_uuid}/{directory}") + os.makedirs(f"content/{type_of_dir}/{uuid}/{directory}") # Upload file to server with open( - f"content/{org_uuid}/{directory}/{file_and_format}", + f"content/{type_of_dir}/{uuid}/{directory}/{file_and_format}", "wb", ) as f: f.write(file_binary) @@ -52,9 +57,9 @@ async def upload_content( print("Uploading to s3 using boto3...") try: s3.upload_file( - f"content/{org_uuid}/{directory}/{file_and_format}", + f"content/{type_of_dir}/{uuid}/{directory}/{file_and_format}", "learnhouse-media", - f"content/{org_uuid}/{directory}/{file_and_format}", + f"content/{type_of_dir}/{uuid}/{directory}/{file_and_format}", ) except ClientError as e: print(e) @@ -63,7 +68,7 @@ async def upload_content( try: s3.head_object( Bucket="learnhouse-media", - Key=f"content/{org_uuid}/{directory}/{file_and_format}", + Key=f"content/{type_of_dir}/{uuid}/{directory}/{file_and_format}", ) print("File upload successful!") except Exception as e: