From 4e539865c96a1d44c3252b3bcd5f1ede25e22bfd Mon Sep 17 00:00:00 2001 From: swve Date: Mon, 12 Dec 2022 11:08:54 +0100 Subject: [PATCH] feat: init files upload/get backend code --- src/main.py | 3 +- src/routers/courses/elements.py | 1 - src/routers/files.py | 40 +++++++++ src/services/courses/elements/video.py | 2 +- src/services/courses/files/photos.py | 4 - src/services/courses/files/videos.py | 0 src/services/files/pictures.py | 111 +++++++++++++++++++++++ src/services/files/videos.py | 117 +++++++++++++++++++++++++ 8 files changed, 271 insertions(+), 7 deletions(-) create mode 100644 src/routers/files.py delete mode 100644 src/services/courses/files/photos.py delete mode 100644 src/services/courses/files/videos.py create mode 100644 src/services/files/pictures.py create mode 100644 src/services/files/videos.py diff --git a/src/main.py b/src/main.py index c291b269..ffa2f84f 100644 --- a/src/main.py +++ b/src/main.py @@ -1,5 +1,5 @@ from fastapi import APIRouter -from src.routers import users, auth, houses, orgs, roles +from src.routers import users, auth, houses, orgs, roles, files from src.routers.courses import chapters, collections, courses,elements @@ -12,6 +12,7 @@ global_router.include_router(auth.router, prefix="/auth", tags=["auth"]) global_router.include_router(houses.router, prefix="/houses", tags=["houses"]) global_router.include_router(orgs.router, prefix="/orgs", tags=["orgs"]) global_router.include_router(roles.router, prefix="/roles", tags=["roles"]) +global_router.include_router(files.router, prefix="/files", tags=["files"]) global_router.include_router(courses.router, prefix="/courses", tags=["courses"]) global_router.include_router(chapters.router, prefix="/chapters", tags=["chapters"]) global_router.include_router(elements.router, prefix="/elements", tags=["elements"]) diff --git a/src/routers/courses/elements.py b/src/routers/courses/elements.py index 32e6ae9d..39226a4d 100644 --- a/src/routers/courses/elements.py +++ b/src/routers/courses/elements.py @@ -47,7 +47,6 @@ async def api_delete_element(element_id: str, current_user: PublicUser = Depends # Video Element - @router.post("/video") async def api_create_video_element(name: str = Form() , coursechapter_id: str = Form(), current_user: PublicUser = Depends(get_current_user), video_file: UploadFile | None = None): """ diff --git a/src/routers/files.py b/src/routers/files.py new file mode 100644 index 00000000..2cecd884 --- /dev/null +++ b/src/routers/files.py @@ -0,0 +1,40 @@ +from fastapi import APIRouter, Depends +from src.dependencies.auth import get_current_user +from fastapi import HTTPException, status, UploadFile + +from src.services.files.pictures import create_picture_file, get_picture_file +from src.services.files.videos import create_video_file, get_video_file +from src.services.users import PublicUser + +router = APIRouter() + + +@router.post("/picture") +async def api_create_picture_file(file_object: UploadFile, current_user: PublicUser = Depends(get_current_user)): + """ + Create new picture file + """ + return await create_picture_file(file_object, "ed_123") + + +@router.post("/video") +async def api_create_video_file(file_object: UploadFile, current_user: PublicUser = Depends(get_current_user)): + """ + Create new video file + """ + return await create_video_file(file_object, "ed_123") + +@router.get("/picture") +async def api_get_picture_file(file_id :str ,current_user: PublicUser = Depends(get_current_user)): + """ + Get picture file + """ + return await get_picture_file(file_id, current_user) + +@router.get("/video") +async def api_get_video_file(file_id :str ,current_user: PublicUser = Depends(get_current_user)): + """ + Get video file + """ + return await get_video_file(file_id, current_user) + diff --git a/src/services/courses/elements/video.py b/src/services/courses/elements/video.py index b997da7d..6b2677fe 100644 --- a/src/services/courses/elements/video.py +++ b/src/services/courses/elements/video.py @@ -1,7 +1,7 @@ from pydantic import BaseModel from src.services.database import check_database, learnhouseDB from src.services.security import verify_user_rights_with_roles -from src.services.courses.elements.uploads import upload_video +from src.services.courses.elements.uploads.videos import upload_video from src.services.users import PublicUser from src.services.courses.elements.elements import ElementInDB from fastapi import HTTPException, status, UploadFile diff --git a/src/services/courses/files/photos.py b/src/services/courses/files/photos.py deleted file mode 100644 index d440e5ea..00000000 --- a/src/services/courses/files/photos.py +++ /dev/null @@ -1,4 +0,0 @@ -from uuid import uuid4 - - - \ No newline at end of file diff --git a/src/services/courses/files/videos.py b/src/services/courses/files/videos.py deleted file mode 100644 index e69de29b..00000000 diff --git a/src/services/files/pictures.py b/src/services/files/pictures.py new file mode 100644 index 00000000..ad5b4701 --- /dev/null +++ b/src/services/files/pictures.py @@ -0,0 +1,111 @@ +from uuid import uuid4 +from pydantic import BaseModel +from src.services.database import check_database, learnhouseDB, learnhouseDB +from fastapi import HTTPException, status, UploadFile +from fastapi.responses import StreamingResponse + +from src.services.users import PublicUser + + +class PhotoFile(BaseModel): + file_id: str + file_format: str + file_name: str + file_size: int + file_type: str + element_id: str + + +async def create_picture_file(picture_file: UploadFile, element_id: str): + await check_database() + photos = learnhouseDB["files"] + + # generate file_id + file_id = str(f"file_{uuid4()}") + + # get file format + file_format = picture_file.filename.split(".")[-1] + + # validate file format + if file_format not in ["jpg", "jpeg", "png", "gif"]: + raise HTTPException( + status_code=status.HTTP_409_CONFLICT, detail="Picture file format not supported") + + # create file + file = await picture_file.read() + + + # get file size + file_size = len(file) + + # get file type + file_type = picture_file.content_type + + # get file name + file_name = picture_file.filename + + # create file object + uploadable_file = PhotoFile( + file_id=file_id, + file_format=file_format, + file_name=file_name, + file_size=file_size, + file_type=file_type, + element_id=element_id + ) + + # upload file to server + with open(f"content/uploads/files/pictures/{element_id}/{file_id}.{file_format}", 'wb') as f: + f.write(file) + f.close() + + # insert file object into database + photo_file_in_db = photos.insert_one(uploadable_file.dict()) + + if not photo_file_in_db: + raise HTTPException( + status_code=status.HTTP_409_CONFLICT, detail="Photo file could not be created") + + return uploadable_file + + +async def get_picture_object(file_id: str): + await check_database() + photos = learnhouseDB["files"] + + photo_file = photos.find_one({"file_id": file_id}) + + if photo_file: + photo_file = PhotoFile(**photo_file) + return photo_file + + else: + raise HTTPException( + status_code=status.HTTP_409_CONFLICT, detail="Photo file does not exist") + + +async def get_picture_file(file_id: str, current_user: PublicUser): + await check_database() + photos = learnhouseDB["files"] + + photo_file = photos.find_one({"file_id": file_id}) + + # check media type + if photo_file.format not in ["jpg", "jpeg", "png", "gif"]: + raise HTTPException( + status_code=status.HTTP_409_CONFLICT, detail="Photo file format not supported") + + # TODO : check if user has access to file + + if photo_file: + # stream file + photo_file = PhotoFile(**photo_file) + file_format = photo_file.file_format + element_id = photo_file.element_id + file = open( + f"content/uploads/files/pictures/{element_id}/{file_id}.{file_format}", 'rb') + return StreamingResponse(file, media_type=photo_file.file_type) + + else: + raise HTTPException( + status_code=status.HTTP_409_CONFLICT, detail="Photo file does not exist") diff --git a/src/services/files/videos.py b/src/services/files/videos.py new file mode 100644 index 00000000..6acec817 --- /dev/null +++ b/src/services/files/videos.py @@ -0,0 +1,117 @@ +from uuid import uuid4 +from pydantic import BaseModel +import os +from src.services.database import check_database, learnhouseDB, learnhouseDB +from fastapi import HTTPException, status, UploadFile +from fastapi.responses import StreamingResponse + +from src.services.users import PublicUser + + +class VideoFile(BaseModel): + file_id: str + file_format: str + file_name: str + file_size: int + file_type: str + element_id: str + + +async def create_video_file(video_file: UploadFile, element_id: str): + await check_database() + files = learnhouseDB["files"] + + # generate file_id + file_id = str(f"file_{uuid4()}") + + # get file format + file_format = video_file.filename.split(".")[-1] + + # validate file format + if file_format not in ["mp4", "webm", "ogg"]: + raise HTTPException( + status_code=status.HTTP_409_CONFLICT, detail="Video file format not supported") + + # create file + file = await video_file.read() + + # get file size + file_size = len(file) + + # get file type + file_type = video_file.content_type + + # get file name + file_name = video_file.filename + + # create file object + uploadable_file = VideoFile( + file_id=file_id, + file_format=file_format, + file_name=file_name, + file_size=file_size, + file_type=file_type, + element_id=element_id + ) + + # create folder for element + os.mkdir(f"content/uploads/files/videos/{element_id}") + + # upload file to server + with open(f"content/uploads/files/videos/{element_id}/{file_id}.{file_format}", 'wb') as f: + f.write(file) + f.close() + + # insert file object into database + video_file_in_db = files.insert_one(uploadable_file.dict()) + + if not video_file_in_db: + raise HTTPException( + status_code=status.HTTP_409_CONFLICT, detail="Video file could not be created") + + return uploadable_file + + +async def get_video_object(file_id: str, current_user: PublicUser): + await check_database() + photos = learnhouseDB["files"] + + video_file = photos.find_one({"file_id": file_id}) + + if video_file: + video_file = VideoFile(**video_file) + return video_file + + else: + raise HTTPException( + status_code=status.HTTP_409_CONFLICT, detail="Photo file does not exist") + + +async def get_video_file(file_id: str, current_user: PublicUser): + await check_database() + photos = learnhouseDB["files"] + + video_file = photos.find_one({"file_id": file_id}) + + # check media type + if video_file.format not in ["mp4", "webm", "ogg"]: + raise HTTPException( + status_code=status.HTTP_409_CONFLICT, detail="Video file format not supported") + + # TODO : check if user has access to file + + if video_file: + # stream file + video_file = VideoFile(**video_file) + file_format = video_file.file_format + element_id = video_file.element_id + + def iterfile(): # + # + with open(f"content/uploads/files/videos/{element_id}/{file_id}.{file_format}", mode="rb") as file_like: + yield from file_like + return StreamingResponse(iterfile(), media_type=video_file.file_type) + + else: + raise HTTPException( + status_code=status.HTTP_409_CONFLICT, detail="Video file does not exist")