From a45c2dfee26cef84071c30cbb018bd311afe4283 Mon Sep 17 00:00:00 2001 From: StepanovPlaton Date: Fri, 10 May 2024 14:20:46 +0400 Subject: [PATCH] Add file upload --- .gitignore | 1 + database/crud/games.py | 4 +++- database/models.py | 2 +- database/schemas.py | 2 +- file_handler.py | 42 ++++++++++++++++++++++++++++++++++++++++++ main.py | 2 ++ requirements.txt | 3 ++- routes/__init__.py | 4 +++- routes/files.py | 20 ++++++++++++++++++++ routes/games.py | 15 +++++++++++---- routes/startup.py | 16 ++++++++++++++++ 11 files changed, 102 insertions(+), 9 deletions(-) create mode 100644 file_handler.py create mode 100644 routes/files.py create mode 100644 routes/startup.py diff --git a/.gitignore b/.gitignore index 99a2b6e..78f4009 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ +content dev_database.db # Byte-compiled / optimized / DLL files diff --git a/database/crud/games.py b/database/crud/games.py index 1693ed5..088f906 100644 --- a/database/crud/games.py +++ b/database/crud/games.py @@ -5,7 +5,9 @@ from .. import models as mdl from .. import schemas as sch from ..database import add_transaction -async def add_game(db: AsyncSession, game_info: sch.GameCreate, user_id: int): +async def add_game(db: AsyncSession, + game_info: sch.GameCreate, + user_id: int): game = mdl.Game(**game_info.model_dump(), owner_id=user_id) return await add_transaction(db, game) diff --git a/database/models.py b/database/models.py index af6f4e0..0022ccd 100644 --- a/database/models.py +++ b/database/models.py @@ -10,7 +10,7 @@ class Game(Base): id = Column(Integer, primary_key=True) cover = Column(String) - title = Column(String, nullable=False) + title = Column(String, nullable=False, unique=True) description = Column(String) torrent_file = Column(String, nullable=False) language = Column(String) diff --git a/database/schemas.py b/database/schemas.py index 0222b00..ac0b0c6 100644 --- a/database/schemas.py +++ b/database/schemas.py @@ -2,8 +2,8 @@ from pydantic import BaseModel class GameBase(BaseModel): - cover: str | None = None title: str + cover: str | None = None description: str | None = None torrent_file: str language: str | None = None diff --git a/file_handler.py b/file_handler.py new file mode 100644 index 0000000..8c37f11 --- /dev/null +++ b/file_handler.py @@ -0,0 +1,42 @@ +import hashlib +from io import BytesIO +import mimetypes +from pathlib import Path +from typing import Literal +import aiofiles +from fastapi import UploadFile +from PIL import Image + +def create_hash_name(filename: str): + return str(hashlib.sha1(filename.encode()).hexdigest()) + +async def save_torrent_file(torrent: UploadFile): + hash_filename = create_hash_name(torrent.filename)+".torrent" + async with aiofiles.open(Path() / "content" / "torrent" + / hash_filename, 'wb') as file: + await file.write(await torrent.read()) + return hash_filename + +async def save_image(cover: UploadFile, type: Literal["cover", "screenshot"]): + hash_filename = create_hash_name(cover.filename) \ + + mimetypes.guess_extension(cover.content_type) + async with aiofiles.open(Path() / "content" / "images" / type / "full_size" + / hash_filename, 'wb') as full_size_file, \ + aiofiles.open(Path() / "content" / "images" / type / + "preview" / hash_filename, 'wb') as preview_file: + cover_data = await cover.read() + await full_size_file.write(cover_data) + image = Image.open(BytesIO(cover_data)) + compressed_coefficient = (image.size[0] * image.size[1]) / (1280*720/4) + if(compressed_coefficient < 1): compressed_coefficient = 1 + compressed_image = image.resize( + ( int(image.size[0] / compressed_coefficient), + int(image.size[1] / compressed_coefficient) ) + ) + + buf = BytesIO() + compressed_image.save(buf, format= + cover.content_type.upper().replace("IMAGE/", "")) + await preview_file.write(buf.getbuffer()) + return hash_filename + \ No newline at end of file diff --git a/main.py b/main.py index 9507378..b0ac94c 100644 --- a/main.py +++ b/main.py @@ -6,7 +6,9 @@ import cli_commands from routes import * app = FastAPI() +app.include_router(startup_router) app.include_router(games_router) +app.include_router(files_router) cli = typer.Typer() cli.add_typer(cli_commands.cli, name="database") diff --git a/requirements.txt b/requirements.txt index a85bec0..401e5c7 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,4 +2,5 @@ fastapi==0.111.0 pydantic==2.7.1 SQLAlchemy==2.0.30 aiosqlite==0.20.0 -typer==0.12.3 \ No newline at end of file +typer==0.12.3 +aiofiles==23.2.1 \ No newline at end of file diff --git a/routes/__init__.py b/routes/__init__.py index 45686f9..c74089f 100644 --- a/routes/__init__.py +++ b/routes/__init__.py @@ -1 +1,3 @@ -from .games import router as games_router \ No newline at end of file +from .games import router as games_router +from .files import router as files_router +from .startup import router as startup_router \ No newline at end of file diff --git a/routes/files.py b/routes/files.py new file mode 100644 index 0000000..6ef7e2c --- /dev/null +++ b/routes/files.py @@ -0,0 +1,20 @@ +from fastapi import APIRouter, Depends, HTTPException, UploadFile + +from database import * +from file_handler import * + +router = APIRouter(prefix="/files", tags=["Files"]) + +@router.post("/torrent", response_model=str) +async def upload_torrent(torrent: UploadFile): + try: return await save_torrent_file(torrent) + except Exception as ex: + print(ex) + raise HTTPException(500) + +@router.post("/cover", response_model=str) +async def upload_cover(cover: UploadFile): + try: return await save_image(cover, "cover") + except Exception as ex: + print(ex) + raise HTTPException(500) diff --git a/routes/games.py b/routes/games.py index 815dcac..bdbb622 100644 --- a/routes/games.py +++ b/routes/games.py @@ -1,6 +1,7 @@ -from fastapi import APIRouter, Depends, HTTPException +from fastapi import APIRouter, Depends, HTTPException, UploadFile from database import * +from file_handler import * router = APIRouter(prefix="/games", tags=["Games"]) @@ -14,7 +15,13 @@ async def get_game(game_id: int, db: AsyncSession = Depends(get_session)): return await crud.get_game(db, game_id) @router.post("/", response_model=Game) -async def add_game(game: GameCreate, user_id: int, db:AsyncSession = Depends(get_session)): - try: return await crud.add_game(db, game, user_id) - except Exception as ex: raise HTTPException(500) +async def add_game(game: GameCreate, + user_id: int, + db:AsyncSession = Depends(get_session)): + try: + torrent_filename = save_torrent_file(torrent, game.title) + cover_filename = save_image(cover, game.title, "cover") + return await crud.add_game(db, game, user_id) + except Exception as ex: + raise HTTPException(500) \ No newline at end of file diff --git a/routes/startup.py b/routes/startup.py new file mode 100644 index 0000000..f28ef95 --- /dev/null +++ b/routes/startup.py @@ -0,0 +1,16 @@ +from fastapi import APIRouter +from pathlib import Path + +router = APIRouter() + +@router.on_event("startup") +def startup(): + need_paths = [ + Path() / "content" / "images" / "cover" / "full_size", + Path() / "content" / "images" / "cover" / "preview", + Path() / "content" / "images" / "screenshot" / "full_size", + Path() / "content" / "images" / "screenshot" / "preview", + Path() / "content" / "torrent" + ] + for path in need_paths: + path.mkdir(parents=True, exist_ok=True) \ No newline at end of file