From db77f62459b92b11661c712de88e1b99b2929428 Mon Sep 17 00:00:00 2001 From: profitroll Date: Thu, 22 Jun 2023 13:06:10 +0200 Subject: [PATCH 1/6] Fixed incorrect exceptions codes --- classes/exceptions.py | 94 ++++++++++++++++++++++++++++++++++++------- 1 file changed, 79 insertions(+), 15 deletions(-) diff --git a/classes/exceptions.py b/classes/exceptions.py index 1bec769..73c739a 100644 --- a/classes/exceptions.py +++ b/classes/exceptions.py @@ -1,7 +1,9 @@ from typing import Literal +from fastapi import HTTPException -class AlbumNotFoundError(Exception): + +class AlbumNotFoundError(HTTPException): """Raises HTTP 404 if no album with this ID found.""" def __init__(self, id: str): @@ -16,7 +18,7 @@ class AlbumNotFoundError(Exception): } -class AlbumNameNotFoundError(Exception): +class AlbumNameNotFoundError(HTTPException): """Raises HTTP 404 if no album with this name found.""" def __init__(self, name: str): @@ -29,9 +31,15 @@ class AlbumNameNotFoundError(Exception): } }, } + super().__init__( + status_code=404, + detail=self.openapi["content"]["application/json"]["example"][ + "detail" + ].format(name=self.name), + ) -class AlbumAlreadyExistsError(Exception): +class AlbumAlreadyExistsError(HTTPException): """Raises HTTP 409 if album with this name already exists.""" def __init__(self, name: str): @@ -44,9 +52,15 @@ class AlbumAlreadyExistsError(Exception): } }, } + super().__init__( + status_code=409, + detail=self.openapi["content"]["application/json"]["example"][ + "detail" + ].format(name=self.name), + ) -class AlbumIncorrectError(Exception): +class AlbumIncorrectError(HTTPException): """Raises HTTP 406 if album's title or name is invalid.""" def __init__(self, place: Literal["name", "title"], error: str) -> None: @@ -56,13 +70,19 @@ class AlbumIncorrectError(Exception): "description": "Album Name/Title Invalid", "content": { "application/json": { - "example": {"detail": "Album {name/title} invalid: {error}"} + "example": {"detail": "Album {place} invalid: {error}"} } }, } + super().__init__( + status_code=406, + detail=self.openapi["content"]["application/json"]["example"][ + "detail" + ].format(place=self.place, error=self.error), + ) -class PhotoNotFoundError(Exception): +class PhotoNotFoundError(HTTPException): """Raises HTTP 404 if no photo with this ID found.""" def __init__(self, id: str): @@ -75,9 +95,15 @@ class PhotoNotFoundError(Exception): } }, } + super().__init__( + status_code=404, + detail=self.openapi["content"]["application/json"]["example"][ + "detail" + ].format(id=self.id), + ) -class PhotoSearchQueryEmptyError(Exception): +class PhotoSearchQueryEmptyError(HTTPException): """Raises HTTP 422 if no photo search query provided.""" def __init__(self): @@ -91,9 +117,13 @@ class PhotoSearchQueryEmptyError(Exception): } }, } + super().__init__( + status_code=422, + detail=self.openapi["content"]["application/json"]["example"]["detail"], + ) -class VideoNotFoundError(Exception): +class VideoNotFoundError(HTTPException): """Raises HTTP 404 if no video with this ID found.""" def __init__(self, id: str): @@ -106,9 +136,15 @@ class VideoNotFoundError(Exception): } }, } + super().__init__( + status_code=404, + detail=self.openapi["content"]["application/json"]["example"][ + "detail" + ].format(id=self.id), + ) -class VideoSearchQueryEmptyError(Exception): +class VideoSearchQueryEmptyError(HTTPException): """Raises HTTP 422 if no video search query provided.""" def __init__(self): @@ -122,9 +158,13 @@ class VideoSearchQueryEmptyError(Exception): } }, } + super().__init__( + status_code=422, + detail=self.openapi["content"]["application/json"]["example"]["detail"], + ) -class SearchPageInvalidError(Exception): +class SearchPageInvalidError(HTTPException): """Raises HTTP 400 if page or page size are not in valid range.""" def __init__(self): @@ -138,9 +178,13 @@ class SearchPageInvalidError(Exception): } }, } + super().__init__( + status_code=400, + detail=self.openapi["content"]["application/json"]["example"]["detail"], + ) -class SearchTokenInvalidError(Exception): +class SearchTokenInvalidError(HTTPException): """Raises HTTP 401 if search token is not valid.""" def __init__(self): @@ -150,9 +194,13 @@ class SearchTokenInvalidError(Exception): "application/json": {"example": {"detail": "Invalid search token."}} }, } + super().__init__( + status_code=401, + detail=self.openapi["content"]["application/json"]["example"]["detail"], + ) -class UserEmailCodeInvalid(Exception): +class UserEmailCodeInvalid(HTTPException): """Raises HTTP 400 if email confirmation code is not valid.""" def __init__(self): @@ -164,9 +212,13 @@ class UserEmailCodeInvalid(Exception): } }, } + super().__init__( + status_code=400, + detail=self.openapi["content"]["application/json"]["example"]["detail"], + ) -class UserAlreadyExists(Exception): +class UserAlreadyExists(HTTPException): """Raises HTTP 409 if user with this name already exists.""" def __init__(self): @@ -178,9 +230,13 @@ class UserAlreadyExists(Exception): } }, } + super().__init__( + status_code=409, + detail=self.openapi["content"]["application/json"]["example"]["detail"], + ) -class AccessTokenInvalidError(Exception): +class AccessTokenInvalidError(HTTPException): """Raises HTTP 401 if access token is not valid.""" def __init__(self): @@ -190,9 +246,13 @@ class AccessTokenInvalidError(Exception): "application/json": {"example": {"detail": "Invalid access token."}} }, } + super().__init__( + status_code=401, + detail=self.openapi["content"]["application/json"]["example"]["detail"], + ) -class UserCredentialsInvalid(Exception): +class UserCredentialsInvalid(HTTPException): """Raises HTTP 401 if user credentials are not valid.""" def __init__(self): @@ -202,3 +262,7 @@ class UserCredentialsInvalid(Exception): "application/json": {"example": {"detail": "Invalid credentials."}} }, } + super().__init__( + status_code=401, + detail=self.openapi["content"]["application/json"]["example"]["detail"], + ) From 47435c6128b2821b1dc50df6fc2612c2908b45c3 Mon Sep 17 00:00:00 2001 From: profitroll Date: Thu, 22 Jun 2023 13:16:12 +0200 Subject: [PATCH 2/6] Async I/O implemented --- extensions/pages.py | 25 +++++++++++++++++-------- extensions/photos.py | 11 ++++++----- extensions/videos.py | 9 +++++---- 3 files changed, 28 insertions(+), 17 deletions(-) diff --git a/extensions/pages.py b/extensions/pages.py index b712a84..b88d117 100644 --- a/extensions/pages.py +++ b/extensions/pages.py @@ -1,3 +1,4 @@ +import aiofiles from os import path from modules.app import app from fastapi.responses import HTMLResponse, Response @@ -5,27 +6,35 @@ from fastapi.responses import HTMLResponse, Response @app.get("/pages/matter.css", include_in_schema=False) async def page_matter(): - with open(path.join("pages", "matter.css"), "r", encoding="utf-8") as f: - output = f.read() + async with aiofiles.open( + path.join("pages", "matter.css"), "r", encoding="utf-8" + ) as f: + output = await f.read() return Response(content=output) @app.get("/pages/{page}/{file}", include_in_schema=False) async def page_assets(page: str, file: str): - with open(path.join("pages", page, file), "r", encoding="utf-8") as f: - output = f.read() + async with aiofiles.open( + path.join("pages", page, file), "r", encoding="utf-8" + ) as f: + output = await f.read() return Response(content=output) @app.get("/", include_in_schema=False) async def page_home(): - with open(path.join("pages", "home", "index.html"), "r", encoding="utf-8") as f: - output = f.read() + async with aiofiles.open( + path.join("pages", "home", "index.html"), "r", encoding="utf-8" + ) as f: + output = await f.read() return HTMLResponse(content=output) @app.get("/register", include_in_schema=False) async def page_register(): - with open(path.join("pages", "register", "index.html"), "r", encoding="utf-8") as f: - output = f.read() + async with aiofiles.open( + path.join("pages", "register", "index.html"), "r", encoding="utf-8" + ) as f: + output = await f.read() return HTMLResponse(content=output) diff --git a/extensions/photos.py b/extensions/photos.py index 3077ada..448ccad 100644 --- a/extensions/photos.py +++ b/extensions/photos.py @@ -1,3 +1,4 @@ +import aiofiles import re import pickle from secrets import token_urlsafe @@ -130,7 +131,7 @@ async def photo_upload( ".".join(base_name) + f"_{int(datetime.now().timestamp())}." + extension ) - with open( + async with aiofiles.open( path.join("data", "users", current_user.user, "albums", album, filename), "wb" ) as f: f.write(await file.read()) @@ -266,8 +267,8 @@ if configGet("media_token_access") is True: mime = Magic(mime=True).from_file(image_path) - with open(image_path, "rb") as f: - image_file = f.read() + async with aiofiles.open(image_path, "rb") as f: + image_file = await f.read() return Response(image_file, media_type=mime) @@ -293,8 +294,8 @@ async def photo_get( mime = Magic(mime=True).from_file(image_path) - with open(image_path, "rb") as f: - image_file = f.read() + async with aiofiles.open(image_path, "rb") as f: + image_file = await f.read() return Response(image_file, media_type=mime) diff --git a/extensions/videos.py b/extensions/videos.py index e19d2d7..bd0402a 100644 --- a/extensions/videos.py +++ b/extensions/videos.py @@ -1,3 +1,4 @@ +import aiofiles import re import pickle from secrets import token_urlsafe @@ -59,10 +60,10 @@ async def video_upload( ".".join(base_name) + f"_{int(datetime.now().timestamp())}." + extension ) - with open( + async with aiofiles.open( path.join("data", "users", current_user.user, "albums", album, filename), "wb" ) as f: - f.write(await file.read()) + await f.write(await file.read()) # Hashing and duplicates check should be here @@ -112,8 +113,8 @@ async def video_get( mime = Magic(mime=True).from_file(video_path) - with open(video_path, "rb") as f: - video_file = f.read() + with aiofiles.open(video_path, "rb") as f: + video_file = await f.read() return Response(video_file, media_type=mime) From 83dd4b67468d099956d8e98277f180637ea77c01 Mon Sep 17 00:00:00 2001 From: profitroll Date: Thu, 22 Jun 2023 13:17:53 +0200 Subject: [PATCH 3/6] Sorted imports with isort --- classes/models.py | 1 + extensions/exceptions.py | 5 +-- extensions/pages.py | 6 ++-- extensions/photos.py | 69 ++++++++++++++++------------------------ extensions/security.py | 8 ++--- extensions/users.py | 25 ++++++--------- extensions/videos.py | 29 +++++++++-------- modules/app.py | 3 +- modules/database.py | 3 +- modules/hasher.py | 5 +-- modules/mailer.py | 3 +- modules/security.py | 7 ++-- modules/utils.py | 5 +-- photos_api.py | 10 +++--- 14 files changed, 82 insertions(+), 97 deletions(-) diff --git a/classes/models.py b/classes/models.py index 5e072b8..9ce232a 100644 --- a/classes/models.py +++ b/classes/models.py @@ -1,4 +1,5 @@ from typing import List, Union + from pydantic import BaseModel diff --git a/extensions/exceptions.py b/extensions/exceptions.py index 2bfda00..613310b 100644 --- a/extensions/exceptions.py +++ b/extensions/exceptions.py @@ -1,7 +1,5 @@ from fastapi import Request from fastapi.responses import UJSONResponse -from modules.app import app -from classes.exceptions import * from starlette.status import ( HTTP_400_BAD_REQUEST, HTTP_401_UNAUTHORIZED, @@ -11,6 +9,9 @@ from starlette.status import ( HTTP_422_UNPROCESSABLE_ENTITY, ) +from classes.exceptions import * +from modules.app import app + @app.exception_handler(AlbumNotFoundError) async def album_not_found_exception_handler(request: Request, exc: AlbumNotFoundError): diff --git a/extensions/pages.py b/extensions/pages.py index b88d117..d32dad6 100644 --- a/extensions/pages.py +++ b/extensions/pages.py @@ -1,8 +1,10 @@ -import aiofiles from os import path -from modules.app import app + +import aiofiles from fastapi.responses import HTMLResponse, Response +from modules.app import app + @app.get("/pages/matter.css", include_in_schema=False) async def page_matter(): diff --git a/extensions/photos.py b/extensions/photos.py index 448ccad..145b241 100644 --- a/extensions/photos.py +++ b/extensions/photos.py @@ -1,54 +1,41 @@ -import aiofiles -import re import pickle +import re +from datetime import datetime, timedelta, timezone +from os import makedirs, path, remove, system from secrets import token_urlsafe from shutil import move from threading import Thread from typing import Union from uuid import uuid4 -from magic import Magic -from datetime import datetime, timedelta, timezone -from os import makedirs, path, remove, system -from pydantic import ValidationError -from classes.exceptions import ( - AccessTokenInvalidError, - AlbumNameNotFoundError, - PhotoNotFoundError, - PhotoSearchQueryEmptyError, - SearchPageInvalidError, - SearchTokenInvalidError, -) -from classes.models import Photo, PhotoPublic, SearchResultsPhoto -from modules.exif_reader import extract_location -from modules.hasher import get_phash, get_duplicates -from modules.scheduler import scheduler -from modules.security import ( - ALGORITHM, - SECRET_KEY, - TokenData, - User, - create_access_token, - get_current_active_user, - get_user, -) -from modules.app import app -from modules.database import col_photos, col_albums, col_tokens -from pymongo import DESCENDING -from bson.objectid import ObjectId +import aiofiles from bson.errors import InvalidId -from plum.exceptions import UnpackError -from jose import JWTError, jwt - -from fastapi import UploadFile, Security -from fastapi.responses import UJSONResponse, Response +from bson.objectid import ObjectId +from fastapi import Security, UploadFile from fastapi.exceptions import HTTPException -from starlette.status import ( - HTTP_204_NO_CONTENT, - HTTP_401_UNAUTHORIZED, - HTTP_409_CONFLICT, -) +from fastapi.responses import Response, UJSONResponse +from jose import JWTError, jwt +from magic import Magic +from plum.exceptions import UnpackError +from pydantic import ValidationError +from pymongo import DESCENDING +from starlette.status import (HTTP_204_NO_CONTENT, HTTP_401_UNAUTHORIZED, + HTTP_409_CONFLICT) +from classes.exceptions import (AccessTokenInvalidError, + AlbumNameNotFoundError, PhotoNotFoundError, + PhotoSearchQueryEmptyError, + SearchPageInvalidError, + SearchTokenInvalidError) +from classes.models import Photo, PhotoPublic, SearchResultsPhoto +from modules.app import app +from modules.database import col_albums, col_photos, col_tokens +from modules.exif_reader import extract_location +from modules.hasher import get_duplicates, get_phash +from modules.scheduler import scheduler +from modules.security import (ALGORITHM, SECRET_KEY, TokenData, User, + create_access_token, get_current_active_user, + get_user) from modules.utils import configGet, logWrite diff --git a/extensions/security.py b/extensions/security.py index 8590fc6..7b7db55 100644 --- a/extensions/security.py +++ b/extensions/security.py @@ -1,12 +1,10 @@ from datetime import timedelta -from classes.exceptions import UserCredentialsInvalid -from modules.app import app from fastapi import Depends -from fastapi.security import ( - OAuth2PasswordRequestForm, -) +from fastapi.security import OAuth2PasswordRequestForm +from classes.exceptions import UserCredentialsInvalid +from modules.app import app from modules.security import ( ACCESS_TOKEN_EXPIRE_DAYS, Token, diff --git a/extensions/users.py b/extensions/users.py index 81820df..6371d8f 100644 --- a/extensions/users.py +++ b/extensions/users.py @@ -1,27 +1,19 @@ from datetime import datetime, timedelta +from uuid import uuid1 + +from fastapi import Depends, Form +from fastapi.responses import Response, UJSONResponse +from starlette.status import HTTP_204_NO_CONTENT + from classes.exceptions import ( UserAlreadyExists, UserCredentialsInvalid, UserEmailCodeInvalid, ) -from modules.database import ( - col_users, - col_albums, - col_photos, - col_emails, - col_videos, - col_emails, -) from modules.app import app -from modules.utils import configGet, logWrite -from modules.scheduler import scheduler +from modules.database import col_albums, col_emails, col_photos, col_users, col_videos from modules.mailer import mail_sender - -from uuid import uuid1 -from fastapi import Depends, Form -from fastapi.responses import Response, UJSONResponse -from starlette.status import HTTP_204_NO_CONTENT - +from modules.scheduler import scheduler from modules.security import ( User, get_current_active_user, @@ -29,6 +21,7 @@ from modules.security import ( get_user, verify_password, ) +from modules.utils import configGet, logWrite async def send_confirmation(user: str, email: str): diff --git a/extensions/videos.py b/extensions/videos.py index bd0402a..773fa64 100644 --- a/extensions/videos.py +++ b/extensions/videos.py @@ -1,12 +1,20 @@ -import aiofiles -import re import pickle +import re +from datetime import datetime, timezone +from os import makedirs, path, remove from secrets import token_urlsafe from shutil import move from typing import Union + +import aiofiles +from bson.errors import InvalidId +from bson.objectid import ObjectId +from fastapi import Security, UploadFile +from fastapi.responses import Response, UJSONResponse from magic import Magic -from datetime import datetime, timezone -from os import makedirs, path, remove +from pymongo import DESCENDING +from starlette.status import HTTP_204_NO_CONTENT + from classes.exceptions import ( AlbumNameNotFoundError, SearchPageInvalidError, @@ -14,17 +22,10 @@ from classes.exceptions import ( VideoNotFoundError, VideoSearchQueryEmptyError, ) -from classes.models import Video, SearchResultsVideo, VideoPublic -from modules.security import User, get_current_active_user +from classes.models import SearchResultsVideo, Video, VideoPublic from modules.app import app -from modules.database import col_videos, col_albums, col_tokens -from bson.objectid import ObjectId -from bson.errors import InvalidId -from pymongo import DESCENDING - -from fastapi import UploadFile, Security -from fastapi.responses import UJSONResponse, Response -from starlette.status import HTTP_204_NO_CONTENT +from modules.database import col_albums, col_tokens, col_videos +from modules.security import User, get_current_active_user video_post_responses = {404: AlbumNameNotFoundError("name").openapi} diff --git a/modules/app.py b/modules/app.py index c7b8ca7..0117169 100644 --- a/modules/app.py +++ b/modules/app.py @@ -1,6 +1,5 @@ from fastapi import FastAPI -from fastapi.openapi.docs import get_swagger_ui_html, get_redoc_html - +from fastapi.openapi.docs import get_redoc_html, get_swagger_ui_html app = FastAPI(title="END PLAY Photos", docs_url=None, redoc_url=None, version="0.2") diff --git a/modules/database.py b/modules/database.py index 46a18e4..964cf54 100644 --- a/modules/database.py +++ b/modules/database.py @@ -1,5 +1,6 @@ +from pymongo import GEOSPHERE, MongoClient + from modules.utils import configGet -from pymongo import MongoClient, GEOSPHERE db_config = configGet("database") diff --git a/modules/hasher.py b/modules/hasher.py index 3ad56e9..afd2389 100644 --- a/modules/hasher.py +++ b/modules/hasher.py @@ -1,8 +1,9 @@ -from modules.database import col_photos +import cv2 import numpy as np from numpy.typing import NDArray from scipy import spatial -import cv2 + +from modules.database import col_photos def hash_array_to_hash_hex(hash_array): diff --git a/modules/mailer.py b/modules/mailer.py index d637fb6..0ff0927 100644 --- a/modules/mailer.py +++ b/modules/mailer.py @@ -1,6 +1,7 @@ from smtplib import SMTP, SMTP_SSL -from traceback import print_exc from ssl import create_default_context +from traceback import print_exc + from modules.utils import configGet, logWrite try: diff --git a/modules/security.py b/modules/security.py index 8703ce6..5ae9191 100644 --- a/modules/security.py +++ b/modules/security.py @@ -1,16 +1,13 @@ from datetime import datetime, timedelta, timezone from typing import List, Union -from modules.database import col_users from fastapi import Depends, HTTPException, Security, status -from fastapi.security import ( - OAuth2PasswordBearer, - SecurityScopes, -) +from fastapi.security import OAuth2PasswordBearer, SecurityScopes from jose import JWTError, jwt from passlib.context import CryptContext from pydantic import BaseModel, ValidationError +from modules.database import col_users with open("secret_key", "r", encoding="utf-8") as f: SECRET_KEY = f.read() diff --git a/modules/utils.py b/modules/utils.py index 7163ffa..1cf907e 100644 --- a/modules/utils.py +++ b/modules/utils.py @@ -1,6 +1,7 @@ -from typing import Any, Union -from ujson import loads, dumps, JSONDecodeError from traceback import print_exc +from typing import Any, Union + +from ujson import JSONDecodeError, dumps, loads # Print to stdout and then to log diff --git a/photos_api.py b/photos_api.py index e359c90..714924b 100644 --- a/photos_api.py +++ b/photos_api.py @@ -1,10 +1,12 @@ from os import makedirs, path -from modules.app import app -from modules.utils import * -from modules.scheduler import scheduler -from modules.extensions_loader import dynamic_import_from_src + from fastapi.responses import FileResponse +from modules.app import app +from modules.extensions_loader import dynamic_import_from_src +from modules.scheduler import scheduler +from modules.utils import * + makedirs(path.join("data", "users"), exist_ok=True) From d2f3d7e687a385559d39cb2e564fb78de7f00bef Mon Sep 17 00:00:00 2001 From: profitroll Date: Thu, 22 Jun 2023 13:25:36 +0200 Subject: [PATCH 4/6] Added aiofiles to requirements --- requirements.txt | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/requirements.txt b/requirements.txt index 8019647..a9f3be1 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,10 +1,11 @@ -fastapi[all]==0.97.0 -pymongo==4.4.0 -ujson~=5.8.0 -scipy~=1.10.1 -python-magic~=0.4.27 -opencv-python~=4.7.0.72 -python-jose[cryptography]~=3.3.0 -passlib~=1.7.4 +aiofiles==23.1.0 apscheduler~=3.10.1 -exif==1.6.0 \ No newline at end of file +exif==1.6.0 +fastapi[all]==0.97.0 +opencv-python~=4.7.0.72 +passlib~=1.7.4 +pymongo==4.4.0 +python-jose[cryptography]~=3.3.0 +python-magic~=0.4.27 +scipy~=1.10.1 +ujson~=5.8.0 \ No newline at end of file From b46f3fb0fd2d05fef0d32bd3136faed56e9933b7 Mon Sep 17 00:00:00 2001 From: profitroll Date: Thu, 22 Jun 2023 13:26:01 +0200 Subject: [PATCH 5/6] Imports cleanup --- extensions/exceptions.py | 16 +++++++++++++++- extensions/photos.py | 30 ++++++++++++++++++------------ extensions/videos.py | 1 - 3 files changed, 33 insertions(+), 14 deletions(-) diff --git a/extensions/exceptions.py b/extensions/exceptions.py index 613310b..d8e7364 100644 --- a/extensions/exceptions.py +++ b/extensions/exceptions.py @@ -9,7 +9,21 @@ from starlette.status import ( HTTP_422_UNPROCESSABLE_ENTITY, ) -from classes.exceptions import * +from classes.exceptions import ( + AlbumNotFoundError, + AlbumAlreadyExistsError, + AlbumIncorrectError, + PhotoNotFoundError, + PhotoSearchQueryEmptyError, + VideoNotFoundError, + VideoSearchQueryEmptyError, + SearchPageInvalidError, + SearchTokenInvalidError, + AccessTokenInvalidError, + UserEmailCodeInvalid, + UserAlreadyExists, + UserCredentialsInvalid, +) from modules.app import app diff --git a/extensions/photos.py b/extensions/photos.py index 145b241..6855f8c 100644 --- a/extensions/photos.py +++ b/extensions/photos.py @@ -1,4 +1,3 @@ -import pickle import re from datetime import datetime, timedelta, timezone from os import makedirs, path, remove, system @@ -12,30 +11,37 @@ import aiofiles from bson.errors import InvalidId from bson.objectid import ObjectId from fastapi import Security, UploadFile -from fastapi.exceptions import HTTPException from fastapi.responses import Response, UJSONResponse from jose import JWTError, jwt from magic import Magic from plum.exceptions import UnpackError from pydantic import ValidationError from pymongo import DESCENDING -from starlette.status import (HTTP_204_NO_CONTENT, HTTP_401_UNAUTHORIZED, - HTTP_409_CONFLICT) +from starlette.status import HTTP_204_NO_CONTENT, HTTP_409_CONFLICT -from classes.exceptions import (AccessTokenInvalidError, - AlbumNameNotFoundError, PhotoNotFoundError, - PhotoSearchQueryEmptyError, - SearchPageInvalidError, - SearchTokenInvalidError) +from classes.exceptions import ( + AccessTokenInvalidError, + AlbumNameNotFoundError, + PhotoNotFoundError, + PhotoSearchQueryEmptyError, + SearchPageInvalidError, + SearchTokenInvalidError, +) from classes.models import Photo, PhotoPublic, SearchResultsPhoto from modules.app import app from modules.database import col_albums, col_photos, col_tokens from modules.exif_reader import extract_location from modules.hasher import get_duplicates, get_phash from modules.scheduler import scheduler -from modules.security import (ALGORITHM, SECRET_KEY, TokenData, User, - create_access_token, get_current_active_user, - get_user) +from modules.security import ( + ALGORITHM, + SECRET_KEY, + TokenData, + User, + create_access_token, + get_current_active_user, + get_user, +) from modules.utils import configGet, logWrite diff --git a/extensions/videos.py b/extensions/videos.py index 773fa64..bacf746 100644 --- a/extensions/videos.py +++ b/extensions/videos.py @@ -1,4 +1,3 @@ -import pickle import re from datetime import datetime, timezone from os import makedirs, path, remove From 19e0531a248ba4de61349dea8d0ff87be16a4da8 Mon Sep 17 00:00:00 2001 From: profitroll Date: Thu, 22 Jun 2023 13:51:04 +0200 Subject: [PATCH 6/6] Fixed mime types of photo/video get --- extensions/photos.py | 12 ++++++++++-- extensions/videos.py | 16 ++++++++++++---- 2 files changed, 22 insertions(+), 6 deletions(-) diff --git a/extensions/photos.py b/extensions/photos.py index 6855f8c..1ca11c0 100644 --- a/extensions/photos.py +++ b/extensions/photos.py @@ -266,10 +266,18 @@ if configGet("media_token_access") is True: return Response(image_file, media_type=mime) -photo_get_responses = {404: PhotoNotFoundError("id").openapi} +photo_get_responses = { + 200: {"content": {"image/*": {}}}, + 404: PhotoNotFoundError("id").openapi, +} -@app.get("/photos/{id}", description="Get a photo by id", responses=photo_get_responses) +@app.get( + "/photos/{id}", + description="Get a photo by id", + responses=photo_get_responses, + response_class=Response, +) async def photo_get( id: str, current_user: User = Security(get_current_active_user, scopes=["photos.read"]), diff --git a/extensions/videos.py b/extensions/videos.py index bacf746..12fd20b 100644 --- a/extensions/videos.py +++ b/extensions/videos.py @@ -92,10 +92,18 @@ async def video_upload( ) -video_get_responses = {404: VideoNotFoundError("id").openapi} +video_get_responses = { + 200: {"content": {"video/*": {}}}, + 404: VideoNotFoundError("id").openapi, +} -@app.get("/videos/{id}", description="Get a video by id", responses=video_get_responses) +@app.get( + "/videos/{id}", + description="Get a video by id", + responses=video_get_responses, + response_class=Response, +) async def video_get( id: str, current_user: User = Security(get_current_active_user, scopes=["videos.read"]), @@ -113,10 +121,10 @@ async def video_get( mime = Magic(mime=True).from_file(video_path) - with aiofiles.open(video_path, "rb") as f: + async with aiofiles.open(video_path, "rb") as f: video_file = await f.read() - return Response(video_file, media_type=mime) + return Response(content=video_file, media_type=mime) video_move_responses = {404: VideoNotFoundError("id").openapi}