From b9e22fcc4c58c6128535d8bae834700d8ae1daa6 Mon Sep 17 00:00:00 2001 From: StepanovPlaton Date: Wed, 26 Jun 2024 23:13:47 +0400 Subject: [PATCH] Code refactoring. Add support genres and actors to routes --- cli_commands.py | 8 +- database/__init__.py | 7 +- database/crud/__init__.py | 15 +++- database/crud/audiobook_genres.py | 19 +++++ database/crud/audiobooks.py | 67 +++++++-------- database/crud/game_genres.py | 20 +++++ database/crud/games.py | 68 ++++++++-------- database/crud/movie_actors.py | 19 +++++ database/crud/movie_genres.py | 19 +++++ database/crud/movies.py | 77 ++++++++++-------- database/crud/users.py | 49 +++++++---- database/database.py | 121 ++++++++++++++++++++++------ database/models/audiobook_genres.py | 2 +- database/models/audiobooks.py | 1 + database/models/game_genres.py | 2 +- database/models/movie_actors.py | 4 +- database/models/movie_genres.py | 4 +- database/models/movies.py | 1 + main.py | 9 +++ routes/__init__.py | 7 ++ routes/audiobook_genres.py | 22 +++++ routes/audiobooks.py | 52 ++++++------ routes/auth.py | 31 ++++--- routes/game_genres.py | 22 +++++ routes/games.py | 53 ++++++------ routes/movie_actors.py | 22 +++++ routes/movie_genres.py | 22 +++++ routes/movies.py | 52 ++++++------ 28 files changed, 545 insertions(+), 250 deletions(-) create mode 100644 database/crud/audiobook_genres.py create mode 100644 database/crud/game_genres.py create mode 100644 database/crud/movie_actors.py create mode 100644 database/crud/movie_genres.py create mode 100644 routes/audiobook_genres.py create mode 100644 routes/game_genres.py create mode 100644 routes/movie_actors.py create mode 100644 routes/movie_genres.py diff --git a/cli_commands.py b/cli_commands.py index a17ee80..a2149d9 100644 --- a/cli_commands.py +++ b/cli_commands.py @@ -2,14 +2,14 @@ from asyncio import run as aiorun import typer -import database +from database import Database cli = typer.Typer() @cli.command(name="create") -def create_database(): aiorun(database.create_all()) +def create_database(): aiorun(Database.create_all()) @cli.command(name="drop") -def drop_database(): aiorun(database.drop_all()) +def drop_database(): aiorun(Database.drop_all()) @cli.command(name="recreate") -def recreate_database(): aiorun(database.recreate_all()) +def recreate_database(): aiorun(Database.recreate_all()) diff --git a/database/__init__.py b/database/__init__.py index d9ce308..2a724f5 100644 --- a/database/__init__.py +++ b/database/__init__.py @@ -1,7 +1,4 @@ from .crud import * from .schemas import * -from .database import get_session as get_session, \ - drop_all as drop_all, \ - create_all as create_all, \ - recreate_all as recreate_all -from .crud import * \ No newline at end of file +from .database import Database +from .crud import * diff --git a/database/crud/__init__.py b/database/crud/__init__.py index 4d11ab3..5bde6c5 100644 --- a/database/crud/__init__.py +++ b/database/crud/__init__.py @@ -1,4 +1,11 @@ -from .games import * -from .movies import * -from .audiobooks import * -from .users import * +from .games import GamesCRUD as GamesCRUD +from .game_genres import GameGenresCRUD as GameGenresCRUD + +from .movies import MoviesCRUD as MoviesCRUD +from .movie_actors import MovieActorsCRUD as MovieActorsCRUD +from .movie_genres import MovieGenresCRUD as MovieGenresCRUD + +from .audiobooks import AudiobooksCRUD as AudiobooksCRUD +from .audiobook_genres import AudiobookGenresCRUD as AudiobookGenresCRUD + +from .users import UsersCRUD as UsersCRUD diff --git a/database/crud/audiobook_genres.py b/database/crud/audiobook_genres.py new file mode 100644 index 0000000..d59a0cb --- /dev/null +++ b/database/crud/audiobook_genres.py @@ -0,0 +1,19 @@ +from time import strftime +from sqlalchemy.ext.asyncio import AsyncSession + +from database.database import Database, EntityCRUD + +from .. import models as mdl +from .. import schemas as sch + + +class AudiobookGenresCRUD(EntityCRUD[mdl.AudiobookGenre]): + @staticmethod + async def get_all(db: AsyncSession): + return await Database.get_all(db, mdl.AudiobookGenre) + + @staticmethod + async def add(db: AsyncSession, + info: sch.AudiobookGenreCreate): + audiobook_genre = mdl.AudiobookGenre(**info.model_dump()) + return await Database.add(db, audiobook_genre) diff --git a/database/crud/audiobooks.py b/database/crud/audiobooks.py index 8f9aa63..949d164 100644 --- a/database/crud/audiobooks.py +++ b/database/crud/audiobooks.py @@ -1,45 +1,46 @@ from time import strftime from sqlalchemy.ext.asyncio import AsyncSession -from sqlalchemy.future import select + +from database.crud.audiobook_genres import AudiobookGenresCRUD +from database.database import Database, EntityCRUD from .. import models as mdl from .. import schemas as sch -from ..database import add_transaction -async def get_audiobooks(db: AsyncSession): - return (await db.execute(select(mdl.Audiobook))).scalars().all() +class AudiobooksCRUD(EntityCRUD[mdl.Audiobook]): + @staticmethod + async def get(db: AsyncSession, id: int): + return await Database.get(db, mdl.Audiobook, id) + @staticmethod + async def get_all(db: AsyncSession): + return await Database.get_all(db, mdl.Audiobook) -async def get_audiobook(db: AsyncSession, audiobook_id: int): - return await db.get(mdl.Audiobook, audiobook_id) + @staticmethod + async def change_genres(db: AsyncSession, audiobook: mdl.Audiobook, info: sch.AudiobookCreate): + audiobook_genres = await AudiobookGenresCRUD.get_all(db) + if (info.genres): + audiobook.genres = [ + genre for genre in audiobook_genres if genre.id in info.genres] + @staticmethod + async def add(db: AsyncSession, + info: sch.AudiobookCreate, + owner_id: int): + audiobook = mdl.Audiobook(**info.model_dump(), + update_date=strftime("%Y-%m-%d %H:%M:%S"), + owner_id=owner_id) + await AudiobooksCRUD.change_genres(db, audiobook, info) + return await Database.add(db, audiobook) -async def add_audiobook(db: AsyncSession, - audiobook_info: sch.AudiobookCreate, - user_id: int): - audiobook = mdl.Audiobook(**audiobook_info.model_dump(), - update_date=strftime("%Y-%m-%d %H:%M:%S"), - upload_date=strftime("%Y-%m-%d %H:%M:%S"), - owner_id=user_id) - return await add_transaction(db, audiobook) + @staticmethod + async def change(db: AsyncSession, + id: int, + info: sch.AudiobookCreate): + return await Database.change(db, mdl.Audiobook, id, info, AudiobooksCRUD.change_genres) - -async def edit_audiobook(db: AsyncSession, - audiobook_id: int, - audiobook_info: sch.AudiobookCreate): - audiobook = await db.get(mdl.Audiobook, audiobook_id) - for key, value in vars(audiobook_info).items(): - if (getattr(audiobook, key) != value): - setattr(audiobook, key, value) - setattr(audiobook, "update_date", strftime("%Y-%m-%d %H:%M:%S")) - await db.commit() - return audiobook - - -async def delete_audiobook(db: AsyncSession, - audiobook_id: int): - audiobook = await get_audiobook(db, audiobook_id) - await db.delete(audiobook) - await db.commit() - return audiobook + @staticmethod + async def delete(db: AsyncSession, + id: int): + return await Database.delete(db, mdl.Audiobook, id) diff --git a/database/crud/game_genres.py b/database/crud/game_genres.py new file mode 100644 index 0000000..6112bed --- /dev/null +++ b/database/crud/game_genres.py @@ -0,0 +1,20 @@ +from time import strftime +from sqlalchemy.ext.asyncio import AsyncSession + +from database.database import Database, EntityCRUD + +from .. import models as mdl +from .. import schemas as sch + + +class GameGenresCRUD(EntityCRUD[mdl.GameGenre]): + + @staticmethod + async def get_all(db: AsyncSession): + return await Database.get_all(db, mdl.GameGenre) + + @staticmethod + async def add(db: AsyncSession, + info: sch.GameGenreCreate): + game_genre = mdl.GameGenre(**info.model_dump()) + return await Database.add(db, game_genre) diff --git a/database/crud/games.py b/database/crud/games.py index 09f0d22..dce795e 100644 --- a/database/crud/games.py +++ b/database/crud/games.py @@ -1,46 +1,46 @@ from time import strftime from sqlalchemy.ext.asyncio import AsyncSession -from sqlalchemy.future import select -from sqlalchemy.orm import selectinload + +from database.crud.game_genres import GameGenresCRUD +from database.database import Database, EntityCRUD from .. import models as mdl from .. import schemas as sch -from ..database import add_transaction -async def get_games(db: AsyncSession): - return (await db.execute(select(mdl.Game))).scalars().all() +class GamesCRUD(EntityCRUD[mdl.Game]): + @staticmethod + async def get(db: AsyncSession, id: int): + return await Database.get(db, mdl.Game, id) + @staticmethod + async def get_all(db: AsyncSession): + return await Database.get_all(db, mdl.Game) -async def get_game(db: AsyncSession, game_id: int): - return await db.get(mdl.Game, game_id) + @staticmethod + async def change_genres(db: AsyncSession, game: mdl.Game, info: sch.GameCreate): + game_genres = await GameGenresCRUD.get_all(db) + if (info.genres): + game.genres = [ + genre for genre in game_genres if genre.id in info.genres] + @staticmethod + async def add(db: AsyncSession, + info: sch.GameCreate, + owner_id: int): + game = mdl.Game(**info.model_dump(), + update_date=strftime("%Y-%m-%d %H:%M:%S"), + owner_id=owner_id) + await GamesCRUD.change_genres(db, game, info) + return await Database.add(db, game) -async def add_game(db: AsyncSession, - game_info: sch.GameCreate, - user_id: int): - game = mdl.Game(**game_info.model_dump(), - update_date=strftime("%Y-%m-%d %H:%M:%S"), - upload_date=strftime("%Y-%m-%d %H:%M:%S"), - owner_id=user_id) - return await add_transaction(db, game) + @staticmethod + async def change(db: AsyncSession, + id: int, + info: sch.GameCreate): + return await Database.change(db, mdl.Game, id, info, GamesCRUD.change_genres) - -async def edit_game(db: AsyncSession, - game_id: int, - game_info: sch.GameCreate): - game = await db.get(mdl.Game, game_id) - for key, value in vars(game_info).items(): - if (getattr(game, key) != value): - setattr(game, key, value) - setattr(game, "update_date", strftime("%Y-%m-%d %H:%M:%S")) - await db.commit() - return game - - -async def delete_game(db: AsyncSession, - game_id: int): - game = await get_game(db, game_id) - await db.delete(game) - await db.commit() - return game + @staticmethod + async def delete(db: AsyncSession, + id: int): + return await Database.delete(db, mdl.Game, id) diff --git a/database/crud/movie_actors.py b/database/crud/movie_actors.py new file mode 100644 index 0000000..7fa38db --- /dev/null +++ b/database/crud/movie_actors.py @@ -0,0 +1,19 @@ +from time import strftime +from sqlalchemy.ext.asyncio import AsyncSession + +from database.database import Database, EntityCRUD + +from .. import models as mdl +from .. import schemas as sch + + +class MovieActorsCRUD(EntityCRUD[mdl.MovieActor]): + @staticmethod + async def get_all(db: AsyncSession): + return await Database.get_all(db, mdl.MovieActor) + + @staticmethod + async def add(db: AsyncSession, + info: sch.MovieActorCreate): + movie_actor = mdl.MovieActor(**info.model_dump()) + return await Database.add(db, movie_actor) diff --git a/database/crud/movie_genres.py b/database/crud/movie_genres.py new file mode 100644 index 0000000..76e96f7 --- /dev/null +++ b/database/crud/movie_genres.py @@ -0,0 +1,19 @@ +from time import strftime +from sqlalchemy.ext.asyncio import AsyncSession + +from database.database import Database, EntityCRUD + +from .. import models as mdl +from .. import schemas as sch + + +class MovieGenresCRUD(EntityCRUD[mdl.MovieGenre]): + @staticmethod + async def get_all(db: AsyncSession): + return await Database.get_all(db, mdl.MovieGenre) + + @staticmethod + async def add(db: AsyncSession, + info: sch.MovieGenreCreate): + movie_genre = mdl.MovieGenre(**info.model_dump()) + return await Database.add(db, movie_genre) diff --git a/database/crud/movies.py b/database/crud/movies.py index 0f4c9c6..243aaa8 100644 --- a/database/crud/movies.py +++ b/database/crud/movies.py @@ -1,45 +1,58 @@ from time import strftime from sqlalchemy.ext.asyncio import AsyncSession -from sqlalchemy.future import select + +from database.crud.movie_actors import MovieActorsCRUD +from database.crud.movie_genres import MovieGenresCRUD +from database.database import Database, EntityCRUD from .. import models as mdl from .. import schemas as sch -from ..database import add_transaction -async def get_movies(db: AsyncSession): - return (await db.execute(select(mdl.Movie))).scalars().all() +class MoviesCRUD(EntityCRUD[mdl.Movie]): + @staticmethod + async def get(db: AsyncSession, id: int): + return await Database.get(db, mdl.Movie, id) + @staticmethod + async def get_all(db: AsyncSession): + return await Database.get_all(db, mdl.Movie) -async def get_movie(db: AsyncSession, movie_id: int): - return await db.get(mdl.Movie, movie_id) + @staticmethod + async def change_actors(db: AsyncSession, movie: mdl.Movie, info: sch.MovieCreate): + movie_actors = await MovieActorsCRUD.get_all(db) + if (info.actors): + movie.actors = [ + actor for actor in movie_actors if actor.id in info.actors] + @staticmethod + async def change_genres(db: AsyncSession, movie: mdl.Movie, info: sch.MovieCreate): + movie_genres = await MovieGenresCRUD.get_all(db) + if (info.genres): + movie.genres = [ + genre for genre in movie_genres if genre.id in info.genres] -async def add_movie(db: AsyncSession, - movie_info: sch.MovieCreate, - user_id: int): - movie = mdl.Movie(**movie_info.model_dump(), - update_date=strftime("%Y-%m-%d %H:%M:%S"), - upload_date=strftime("%Y-%m-%d %H:%M:%S"), - owner_id=user_id) - return await add_transaction(db, movie) + @staticmethod + async def add(db: AsyncSession, + info: sch.MovieCreate, + owner_id: int): + movie = mdl.Movie(**info.model_dump(), + update_date=strftime("%Y-%m-%d %H:%M:%S"), + owner_id=owner_id) + await MoviesCRUD.change_genres(db, movie, info) + await MoviesCRUD.change_actors(db, movie, info) + return await Database.add(db, movie) + @staticmethod + async def change(db: AsyncSession, + id: int, + info: sch.MovieCreate): + async def additional_change(db: AsyncSession, movie: mdl.Movie, info: sch.MovieCreate): + await MoviesCRUD.change_genres(db, movie, info) + await MoviesCRUD.change_actors(db, movie, info) + return await Database.change(db, mdl.Movie, id, info, additional_change) -async def edit_movie(db: AsyncSession, - movie_id: int, - movie_info: sch.MovieCreate): - movie = await db.get(mdl.Movie, movie_id) - for key, value in vars(movie_info).items(): - if (getattr(movie, key) != value): - setattr(movie, key, value) - setattr(movie, "update_date", strftime("%Y-%m-%d %H:%M:%S")) - await db.commit() - return movie - - -async def delete_movie(db: AsyncSession, - movie_id: int): - movie = await get_movie(db, movie_id) - await db.delete(movie) - await db.commit() - return movie + @staticmethod + async def delete(db: AsyncSession, + id: int): + return await Database.delete(db, mdl.Movie, id) diff --git a/database/crud/users.py b/database/crud/users.py index c36a62a..2537ea2 100644 --- a/database/crud/users.py +++ b/database/crud/users.py @@ -1,26 +1,45 @@ from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy.future import select +from database.database import Database, EntityCRUD + from .. import models as mdl from .. import schemas as sch -from ..database import add_transaction -async def get_user(db: AsyncSession, username: str): - return (await db.execute(select(mdl.User).where(mdl.User.name == username))).scalar() +class UsersCRUD(EntityCRUD[mdl.User]): + @staticmethod + async def get(db: AsyncSession, username: str): + return (await db.execute(select(mdl.User).where(mdl.User.name == username))).scalar() + @staticmethod + async def get_all(db: AsyncSession): + return await Database.get_all(db, mdl.User) -async def add_user(db: AsyncSession, - user_data: sch.UserCreate, hash_of_password: str): - user_data_db = \ - {k: v for k, v in user_data.model_dump().items() - if k != "password"} - user = mdl.User(**user_data_db, - hash_of_password=hash_of_password) - return await add_transaction(db, user) + @staticmethod + async def add(db: AsyncSession, + info: sch.UserCreate, + hash_of_password: str): + user_data_db = \ + {k: v for k, v in info.model_dump().items() + if k != "password"} + user = mdl.User(**user_data_db, + hash_of_password=hash_of_password) + return await Database.add(db, user) + @staticmethod + async def change(db: AsyncSession, + id: int, + info: sch.UserCreate): + return await Database.change(db, mdl.User, id, info) -async def check_email(db: AsyncSession, email: str): - users = (await db.execute(select(mdl.User) - .where(mdl.User.email == email))).scalars().all() - return True if len(users) == 0 else False + @staticmethod + async def delete_user(db: AsyncSession, + id: int): + return await Database.delete(db, mdl.User, id) + + @staticmethod + async def check_email(db: AsyncSession, email: str): + users = (await db.execute(select(mdl.User) + .where(mdl.User.email == email))).scalars().all() + return True if len(users) == 0 else False diff --git a/database/database.py b/database/database.py index 017a098..87a8a0d 100644 --- a/database/database.py +++ b/database/database.py @@ -1,4 +1,8 @@ +from abc import ABC, abstractmethod +from time import strftime +from typing import Any, Callable, Coroutine, Generic, Type from sqlalchemy.orm import sessionmaker +from sqlalchemy.future import select from sqlalchemy.ext.declarative import declarative_base from sqlalchemy.ext.asyncio import AsyncSession, create_async_engine @@ -15,32 +19,103 @@ async_session = sessionmaker( # type: ignore Base = declarative_base() -async def get_session() -> AsyncSession: # type: ignore - async with async_session() as session: # type: ignore - yield session # type: ignore +class Database: + @staticmethod + async def get_session() -> AsyncSession: # type: ignore + async with async_session() as session: # type: ignore + yield session # type: ignore + + @staticmethod + async def drop_all(): + async with engine.begin() as conn: + await conn.run_sync(Base.metadata.drop_all) + + @staticmethod + async def create_all(): + async with engine.begin() as conn: + await conn.run_sync(Base.metadata.create_all) + + @staticmethod + async def recreate_all(): + await Database.drop_all() + await Database.create_all() + + @staticmethod + async def get[T](db: AsyncSession, typeof_entity: Type[T], entity_id: int) -> T | None: + return await db.get(typeof_entity, entity_id) + + @staticmethod + async def get_all[T](db: AsyncSession, typeof_entity: Type[T]) -> list[T]: + return list((await db.execute(select(typeof_entity))).scalars().all()) + + @staticmethod + async def add[T](db: AsyncSession, entity: T) -> T: + try: + db.add(entity) + await db.commit() + await db.refresh(entity) + return entity + except Exception as ex: + await db.rollback() + raise ex + + @staticmethod + async def change[T, U](db: AsyncSession, typeof_entity: Type[T], + entity_id: int, info: U, + additional_change: Callable[[AsyncSession, T, U], Coroutine[Any, Any, None]] | None = None) -> T: + try: + entity = await db.get(typeof_entity, entity_id) + if (entity is None): + raise ValueError(f"Can't change entity. " + + f"{str(typeof_entity)} with id={entity_id} not found") + for key, value in vars(info).items(): + try: + if (getattr(entity, key) != value): + setattr(entity, key, value) + except: + ... + setattr(entity, "update_date", strftime("%Y-%m-%d %H:%M:%S")) + if (additional_change): + await additional_change(db, entity, info) + await db.commit() + return entity + except Exception as ex: + await db.rollback() + raise ex + + @staticmethod + async def delete[T](db: AsyncSession, typeof_entity: Type[T], entity_id: int) -> T: + try: + entity = await db.get(typeof_entity, entity_id) + if (entity is None): + raise ValueError(f"Can't delete entity. " + + f"{str(typeof_entity)} with id={entity_id} not found") + await db.delete(entity) + await db.commit() + return entity + except Exception as ex: + await db.rollback() + raise ex -async def drop_all(): - async with engine.begin() as conn: - await conn.run_sync(Base.metadata.drop_all) +class EntityCRUD[T](ABC): + @staticmethod + @abstractmethod + async def get(db: AsyncSession, id: int) -> T | None: ... + @staticmethod + @abstractmethod + async def get_all(db: AsyncSession) -> list[T]: ... -async def create_all(): - async with engine.begin() as conn: - await conn.run_sync(Base.metadata.create_all) + @staticmethod + @abstractmethod + async def add(db: AsyncSession, entity: T, + owner_id: int | None = None) -> T: ... + @staticmethod + @abstractmethod + async def change(db: AsyncSession, entity_id: int, info: object) -> T: ... -async def recreate_all(): - await drop_all() - await create_all() - - -async def add_transaction[T](db: AsyncSession, entity: T) -> T: - try: - db.add(entity) - await db.commit() - await db.refresh(entity) - return entity - except Exception as ex: - await db.rollback() - raise ex + @staticmethod + @abstractmethod + async def delete(db: AsyncSession, entity_id: int) -> T: ... diff --git a/database/models/audiobook_genres.py b/database/models/audiobook_genres.py index 696319f..757d22e 100644 --- a/database/models/audiobook_genres.py +++ b/database/models/audiobook_genres.py @@ -11,7 +11,7 @@ class AudiobookGenre(Base): genre = Column(String, nullable=False, unique=True) audiobooks = relationship("Audiobook", secondary="audiobook_to_genre", - lazy="selectin") + lazy="selectin", viewonly=True) class AudiobookToGenre(Base): diff --git a/database/models/audiobooks.py b/database/models/audiobooks.py index 0af432b..f33b8d4 100644 --- a/database/models/audiobooks.py +++ b/database/models/audiobooks.py @@ -27,3 +27,4 @@ class Audiobook(Base): lazy="selectin") owner_id = Column(Integer, ForeignKey("users.id")) + owner = relationship("User", lazy="selectin", viewonly=True) diff --git a/database/models/game_genres.py b/database/models/game_genres.py index 3aa405f..f85f12f 100644 --- a/database/models/game_genres.py +++ b/database/models/game_genres.py @@ -11,7 +11,7 @@ class GameGenre(Base): genre = Column(String, nullable=False, unique=True) games = relationship("Game", secondary="game_to_genre", - lazy="selectin") + lazy="selectin", viewonly=True) class GameToGenre(Base): diff --git a/database/models/movie_actors.py b/database/models/movie_actors.py index 10ccf58..69618aa 100644 --- a/database/models/movie_actors.py +++ b/database/models/movie_actors.py @@ -10,8 +10,8 @@ class MovieActor(Base): id = Column(Integer, primary_key=True) actor = Column(String, nullable=False, unique=True) - movies = relationship("Movies", secondary="movie_to_actor", - lazy="selectin") + movies = relationship("Movie", secondary="movie_to_actor", + lazy="selectin", viewonly=True) class MovieToActor(Base): diff --git a/database/models/movie_genres.py b/database/models/movie_genres.py index eed9d57..f94e181 100644 --- a/database/models/movie_genres.py +++ b/database/models/movie_genres.py @@ -10,8 +10,8 @@ class MovieGenre(Base): id = Column(Integer, primary_key=True) genre = Column(String, nullable=False, unique=True) - movies = relationship("Movies", secondary="movie_to_genre", - lazy="selectin") + movies = relationship("Movie", secondary="movie_to_genre", + lazy="selectin", viewonly=True) class MovieToGenre(Base): diff --git a/database/models/movies.py b/database/models/movies.py index 99eddbc..55cc9fe 100644 --- a/database/models/movies.py +++ b/database/models/movies.py @@ -32,3 +32,4 @@ class Movie(Base): lazy="selectin") owner_id = Column(Integer, ForeignKey("users.id")) + owner = relationship("User", lazy="selectin", viewonly=True) diff --git a/main.py b/main.py index 2f5b996..5322ceb 100644 --- a/main.py +++ b/main.py @@ -13,10 +13,19 @@ app = FastAPI( "url": "https://github.com/StepanovPlaton" }, ) + app.include_router(startup_router) + app.include_router(games_router) +app.include_router(game_genres_router) + app.include_router(movies_router) +app.include_router(movie_actors_router) +app.include_router(movie_genres_router) + app.include_router(audiobooks_router) +app.include_router(audiobook_genres_router) + app.include_router(files_router) app.include_router(auth_router) app.mount("/content", StaticFiles(directory="content"), name="content") diff --git a/routes/__init__.py b/routes/__init__.py index 4a8d60e..6129aa2 100644 --- a/routes/__init__.py +++ b/routes/__init__.py @@ -1,6 +1,13 @@ from .games import games_router as games_router +from .game_genres import game_genres_router as game_genres_router + from .movies import movies_router as movies_router +from .movie_actors import movie_actors_router as movie_actors_router +from .movie_genres import movie_genres_router as movie_genres_router + from .audiobooks import audiobooks_router as audiobooks_router +from .audiobook_genres import audiobook_genres_router as audiobook_genres_router + from .files import files_router as files_router from .startup import startup_router as startup_router from .auth import auth_router as auth_router diff --git a/routes/audiobook_genres.py b/routes/audiobook_genres.py new file mode 100644 index 0000000..524fc4c --- /dev/null +++ b/routes/audiobook_genres.py @@ -0,0 +1,22 @@ +from sqlalchemy.ext.asyncio import AsyncSession +from fastapi import APIRouter, Depends, HTTPException, status + +from database import * + +from file_handler import * +from routes.auth import get_user + +audiobook_genres_router = APIRouter( + prefix="/genres/audiobooks", tags=["Audiobooks", "Genres"]) + + +@audiobook_genres_router.get("", response_model=list[AudiobookGenre]) +async def get_audiobook_genres(db_session: AsyncSession = Depends(Database.get_session)): + return await AudiobookGenresCRUD.get_all(db_session) + + +@audiobook_genres_router.post("", response_model=AudiobookGenre) +async def add_audiobook_genre(genre: AudiobookGenreCreate, + user: User = Depends(get_user), + db_session: AsyncSession = Depends(Database.get_session)): + return await AudiobookGenresCRUD.add(db_session, genre) diff --git a/routes/audiobooks.py b/routes/audiobooks.py index d3c716f..f79b14b 100644 --- a/routes/audiobooks.py +++ b/routes/audiobooks.py @@ -1,41 +1,41 @@ from sqlalchemy.ext.asyncio import AsyncSession from fastapi import APIRouter, Depends, HTTPException, status -import database as db +from database import * from file_handler import * from routes.auth import get_user audiobooks_router = APIRouter(prefix="/audiobooks", tags=["Audiobooks"]) -@audiobooks_router.get("", response_model=list[db.Audiobook]) -async def get_audiobooks(db_session: AsyncSession = Depends(db.get_session)): - return await db.get_audiobooks(db_session) +@audiobooks_router.get("/{audiobook_id}", response_model=Audiobook) +async def get_audiobook(audiobook_id: int, db_session: AsyncSession = Depends(Database.get_session)): + return await AudiobooksCRUD.get(db_session, audiobook_id) -@audiobooks_router.post("", response_model=db.Audiobook) -async def add_audiobook(audiobook: db.AudiobookCreate, - user: db.User = Depends(get_user), - db_session: AsyncSession = Depends(db.get_session)): - return await db.add_audiobook(db_session, audiobook, user.id) +@audiobooks_router.get("/cards", response_model=list[AudiobookCard]) +async def get_audiobooks_cards(db_session: AsyncSession = Depends(Database.get_session)): + return await AudiobooksCRUD.get_all(db_session) -@audiobooks_router.get("/cards", response_model=list[db.AudiobookCard]) -async def get_audiobooks_cards(db_session: AsyncSession = Depends(db.get_session)): - return await db.get_audiobooks(db_session) +@audiobooks_router.get("", response_model=list[Audiobook]) +async def get_audiobooks(db_session: AsyncSession = Depends(Database.get_session)): + return await AudiobooksCRUD.get_all(db_session) -@audiobooks_router.get("/{audiobook_id}", response_model=db.Audiobook) -async def get_audiobook(audiobook_id: int, db_session: AsyncSession = Depends(db.get_session)): - return await db.get_audiobook(db_session, audiobook_id) +@audiobooks_router.post("", response_model=Audiobook) +async def add_audiobook(audiobook: AudiobookCreate, + user: User = Depends(get_user), + db_session: AsyncSession = Depends(Database.get_session)): + return await AudiobooksCRUD.add(db_session, audiobook, user.id) -@audiobooks_router.put("/{audiobook_id}", response_model=db.Audiobook) +@audiobooks_router.put("/{audiobook_id}", response_model=Audiobook) async def edit_audiobook(audiobook_id: int, - audiobook: db.AudiobookCreate, - user: db.User = Depends(get_user), - db_session: AsyncSession = Depends(db.get_session)): - audiobook_db = await db.get_audiobook(db_session, audiobook_id) + audiobook: AudiobookCreate, + user: User = Depends(get_user), + db_session: AsyncSession = Depends(Database.get_session)): + audiobook_db = await AudiobooksCRUD.get(db_session, audiobook_id) if (audiobook_db is None): raise HTTPException(status.HTTP_404_NOT_FOUND, detail=f"Audiobook with id={audiobook_id} not found") @@ -43,14 +43,14 @@ async def edit_audiobook(audiobook_id: int, raise HTTPException(status.HTTP_401_UNAUTHORIZED, detail=f"Audiobook can only be edited " "by the owner (creator)") - return await db.edit_audiobook(db_session, audiobook_id, audiobook) + return await AudiobooksCRUD.change(db_session, audiobook_id, audiobook) -@audiobooks_router.delete("/{audiobook_id}", response_model=db.Audiobook) +@audiobooks_router.delete("/{audiobook_id}", response_model=Audiobook) async def delete_audiobook(audiobook_id: int, - user: db.User = Depends(get_user), - db_session: AsyncSession = Depends(db.get_session)): - audiobook_db = await db.get_audiobook(db_session, audiobook_id) + user: User = Depends(get_user), + db_session: AsyncSession = Depends(Database.get_session)): + audiobook_db = await AudiobooksCRUD.get(db_session, audiobook_id) if (audiobook_db is None): raise HTTPException(status.HTTP_404_NOT_FOUND, detail=f"Audiobook with id={audiobook_id} not found") @@ -58,4 +58,4 @@ async def delete_audiobook(audiobook_id: int, raise HTTPException(status.HTTP_401_UNAUTHORIZED, detail=f"Audiobook can only be deleted " "by the owner (creator)") - return await db.delete_audiobook(db_session, audiobook_id) + return await AudiobooksCRUD.delete(db_session, audiobook_id) diff --git a/routes/auth.py b/routes/auth.py index 6354d37..c811618 100644 --- a/routes/auth.py +++ b/routes/auth.py @@ -1,13 +1,12 @@ -from typing import Annotated, Any from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm from passlib.context import CryptContext from datetime import datetime, timedelta, timezone from fastapi import APIRouter, Depends, status, HTTPException from sqlalchemy.ext.asyncio import AsyncSession from pydantic import BaseModel -from jose import JWTError, jwt +from jose import jwt -import database as db +from database import * from env import Env SECRET_KEY = Env.get_strict("JWT_SECRET_KEY", str) @@ -36,7 +35,7 @@ def get_hash(password): return crypt.hash(password) async def get_user(token: str = Depends(oauth2_scheme), - db_session: AsyncSession = Depends(db.get_session)): + db_session: AsyncSession = Depends(Database.get_session)): credentials_exception = HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, detail="Could not validate credentials", @@ -48,13 +47,13 @@ async def get_user(token: str = Depends(oauth2_scheme), except Exception as e: print(e) raise credentials_exception - user = await db.get_user(db_session, token_data.username) + user = await UsersCRUD.get(db_session, token_data.username) if user is None: raise credentials_exception return user -def create_token(user: db.User): +def create_token(user: User): access_token_expires = \ timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES) expire = datetime.now(timezone.utc) + access_token_expires @@ -70,33 +69,33 @@ def create_token(user: db.User): @auth_router.post("/registration") async def registration_user( - user_data: db.UserCreate, - db_session: AsyncSession = Depends(db.get_session) + user_data: UserCreate, + db_session: AsyncSession = Depends(Database.get_session) ) -> Token: - if (not await db.check_email(db_session, user_data.email)): + if (not await UsersCRUD.check_email(db_session, user_data.email)): raise HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, detail="This email is occupied by another user", headers={"WWW-Authenticate": "Bearer"}, ) - elif (await db.get_user(db_session, user_data.name) is not None): + elif (await UsersCRUD.get(db_session, user_data.name) is not None): raise HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, detail="User with the same name already exists", headers={"WWW-Authenticate": "Bearer"}, ) else: - user = await db.add_user(db_session, user_data, - get_hash(user_data.password)) + user = await UsersCRUD.add(db_session, user_data, + get_hash(user_data.password)) return create_token(user) @auth_router.post("") async def login_user( auth_data: OAuth2PasswordRequestForm = Depends(), - db_session: AsyncSession = Depends(db.get_session) + db_session: AsyncSession = Depends(Database.get_session) ) -> Token: - user = await db.get_user(db_session, auth_data.username) + user = await UsersCRUD.get(db_session, auth_data.username) if (user is None): raise HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, @@ -112,6 +111,6 @@ async def login_user( return create_token(user) -@auth_router.get("/me", response_model=db.UserOpenData) -async def read_me(user: db.User = Depends(get_user)): +@auth_router.get("/me", response_model=UserOpenData) +async def read_me(user: User = Depends(get_user)): return user diff --git a/routes/game_genres.py b/routes/game_genres.py new file mode 100644 index 0000000..15d86e9 --- /dev/null +++ b/routes/game_genres.py @@ -0,0 +1,22 @@ +from sqlalchemy.ext.asyncio import AsyncSession +from fastapi import APIRouter, Depends, HTTPException, status + +from database import * + +from file_handler import * +from routes.auth import get_user + +game_genres_router = APIRouter( + prefix="/genres/games", tags=["Games", "Genres"]) + + +@game_genres_router.get("", response_model=list[GameGenre]) +async def get_game_genres(db_session: AsyncSession = Depends(Database.get_session)): + return await GameGenresCRUD.get_all(db_session) + + +@game_genres_router.post("", response_model=GameGenre) +async def add_game_genre(genre: GameGenreCreate, + user: User = Depends(get_user), + db_session: AsyncSession = Depends(Database.get_session)): + return await GameGenresCRUD.add(db_session, genre) diff --git a/routes/games.py b/routes/games.py index fde1619..f1a6fcd 100644 --- a/routes/games.py +++ b/routes/games.py @@ -1,41 +1,42 @@ from sqlalchemy.ext.asyncio import AsyncSession from fastapi import APIRouter, Depends, HTTPException, status -import database as db +from database import * + from file_handler import * from routes.auth import get_user games_router = APIRouter(prefix="/games", tags=["Games"]) -@games_router.get("", response_model=list[db.Game]) -async def get_games(db_session: AsyncSession = Depends(db.get_session)): - return await db.get_games(db_session) +@games_router.get("/{game_id}", response_model=Game) +async def get_game(game_id: int, db_session: AsyncSession = Depends(Database.get_session)): + return await GamesCRUD.get(db_session, game_id) -@games_router.post("", response_model=db.Game) -async def add_game(game: db.GameCreate, - user: db.User = Depends(get_user), - db_session: AsyncSession = Depends(db.get_session)): - return await db.add_game(db_session, game, user.id) +@games_router.get("/cards", response_model=list[GameCard]) +async def get_games_cards(db_session: AsyncSession = Depends(Database.get_session)): + return await GamesCRUD.get_all(db_session) -@games_router.get("/cards", response_model=list[db.GameCard]) -async def get_games_cards(db_session: AsyncSession = Depends(db.get_session)): - return await db.get_games(db_session) +@games_router.get("", response_model=list[Game]) +async def get_games(db_session: AsyncSession = Depends(Database.get_session)): + return await GamesCRUD.get_all(db_session) -@games_router.get("/{game_id}", response_model=db.Game) -async def get_game(game_id: int, db_session: AsyncSession = Depends(db.get_session)): - return await db.get_game(db_session, game_id) +@games_router.post("", response_model=Game) +async def add_game(game: GameCreate, + user: User = Depends(get_user), + db_session: AsyncSession = Depends(Database.get_session)): + return await GamesCRUD.add(db_session, game, user.id) -@games_router.put("/{game_id}", response_model=db.Game) +@games_router.put("/{game_id}", response_model=Game) async def edit_game(game_id: int, - game: db.GameCreate, - user: db.User = Depends(get_user), - db_session: AsyncSession = Depends(db.get_session)): - game_db = await db.get_game(db_session, game_id) + game: GameCreate, + user: User = Depends(get_user), + db_session: AsyncSession = Depends(Database.get_session)): + game_db = await GamesCRUD.get(db_session, game_id) if (game_db is None): raise HTTPException(status.HTTP_404_NOT_FOUND, detail=f"Game with id={game_id} not found") @@ -43,14 +44,14 @@ async def edit_game(game_id: int, raise HTTPException(status.HTTP_401_UNAUTHORIZED, detail=f"Game can only be edited " "by the owner (creator)") - return await db.edit_game(db_session, game_id, game) + return await GamesCRUD.change(db_session, game_id, game) -@games_router.delete("/{game_id}", response_model=db.Game) +@games_router.delete("/{game_id}", response_model=Game) async def delete_game(game_id: int, - user: db.User = Depends(get_user), - db_session: AsyncSession = Depends(db.get_session)): - game_db = await db.get_game(db_session, game_id) + user: User = Depends(get_user), + db_session: AsyncSession = Depends(Database.get_session)): + game_db = await GamesCRUD.get(db_session, game_id) if (game_db is None): raise HTTPException(status.HTTP_404_NOT_FOUND, detail=f"Game with id={game_id} not found") @@ -58,4 +59,4 @@ async def delete_game(game_id: int, raise HTTPException(status.HTTP_401_UNAUTHORIZED, detail=f"Game can only be deleted " "by the owner (creator)") - return await db.delete_game(db_session, game_id) + return await GamesCRUD.delete(db_session, game_id) diff --git a/routes/movie_actors.py b/routes/movie_actors.py new file mode 100644 index 0000000..e767915 --- /dev/null +++ b/routes/movie_actors.py @@ -0,0 +1,22 @@ +from sqlalchemy.ext.asyncio import AsyncSession +from fastapi import APIRouter, Depends, HTTPException, status + +from database import * + +from file_handler import * +from routes.auth import get_user + +movie_actors_router = APIRouter( + prefix="/actors", tags=["Movies", "Actors"]) + + +@movie_actors_router.get("", response_model=list[MovieActor]) +async def get_movie_actors(db_session: AsyncSession = Depends(Database.get_session)): + return await MovieActorsCRUD.get_all(db_session) + + +@movie_actors_router.post("", response_model=MovieActor) +async def add_movie_actor(actor: MovieActorCreate, + user: User = Depends(get_user), + db_session: AsyncSession = Depends(Database.get_session)): + return await MovieActorsCRUD.add(db_session, actor) diff --git a/routes/movie_genres.py b/routes/movie_genres.py new file mode 100644 index 0000000..fc2419d --- /dev/null +++ b/routes/movie_genres.py @@ -0,0 +1,22 @@ +from sqlalchemy.ext.asyncio import AsyncSession +from fastapi import APIRouter, Depends, HTTPException, status + +from database import * + +from file_handler import * +from routes.auth import get_user + +movie_genres_router = APIRouter( + prefix="/genres/movies", tags=["Movies", "Genres"]) + + +@movie_genres_router.get("", response_model=list[MovieGenre]) +async def get_movie_genres(db_session: AsyncSession = Depends(Database.get_session)): + return await MovieGenresCRUD.get_all(db_session) + + +@movie_genres_router.post("", response_model=MovieGenre) +async def add_movie_genre(genre: MovieGenreCreate, + user: User = Depends(get_user), + db_session: AsyncSession = Depends(Database.get_session)): + return await MovieGenresCRUD.add(db_session, genre) diff --git a/routes/movies.py b/routes/movies.py index 3909eb4..56246c0 100644 --- a/routes/movies.py +++ b/routes/movies.py @@ -1,41 +1,41 @@ from sqlalchemy.ext.asyncio import AsyncSession from fastapi import APIRouter, Depends, HTTPException, status -import database as db +from database import * from file_handler import * from routes.auth import get_user movies_router = APIRouter(prefix="/movies", tags=["Movies"]) -@movies_router.get("", response_model=list[db.Movie]) -async def get_movies(db_session: AsyncSession = Depends(db.get_session)): - return await db.get_movies(db_session) +@movies_router.get("/{movie_id}", response_model=Movie) +async def get_movie(movie_id: int, db_session: AsyncSession = Depends(Database.get_session)): + return await MoviesCRUD.get(db_session, movie_id) -@movies_router.post("", response_model=db.Movie) -async def add_movie(movie: db.MovieCreate, - user: db.User = Depends(get_user), - db_session: AsyncSession = Depends(db.get_session)): - return await db.add_movie(db_session, movie, user.id) +@movies_router.get("/cards", response_model=list[MovieCard]) +async def get_movies_cards(db_session: AsyncSession = Depends(Database.get_session)): + return await MoviesCRUD.get_all(db_session) -@movies_router.get("/cards", response_model=list[db.MovieCard]) -async def get_movies_cards(db_session: AsyncSession = Depends(db.get_session)): - return await db.get_movies(db_session) +@movies_router.get("", response_model=list[Movie]) +async def get_movies(db_session: AsyncSession = Depends(Database.get_session)): + return await MoviesCRUD.get_all(db_session) -@movies_router.get("/{movie_id}", response_model=db.Movie) -async def get_movie(movie_id: int, db_session: AsyncSession = Depends(db.get_session)): - return await db.get_movie(db_session, movie_id) +@movies_router.post("", response_model=Movie) +async def add_movie(movie: MovieCreate, + user: User = Depends(get_user), + db_session: AsyncSession = Depends(Database.get_session)): + return await MoviesCRUD.add(db_session, movie, user.id) -@movies_router.put("/{movie_id}", response_model=db.Movie) +@movies_router.put("/{movie_id}", response_model=Movie) async def edit_movie(movie_id: int, - movie: db.MovieCreate, - user: db.User = Depends(get_user), - db_session: AsyncSession = Depends(db.get_session)): - movie_db = await db.get_movie(db_session, movie_id) + movie: MovieCreate, + user: User = Depends(get_user), + db_session: AsyncSession = Depends(Database.get_session)): + movie_db = await MoviesCRUD.get(db_session, movie_id) if (movie_db is None): raise HTTPException(status.HTTP_404_NOT_FOUND, detail=f"Movie with id={movie_id} not found") @@ -43,14 +43,14 @@ async def edit_movie(movie_id: int, raise HTTPException(status.HTTP_401_UNAUTHORIZED, detail=f"Movie can only be edited " "by the owner (creator)") - return await db.edit_movie(db_session, movie_id, movie) + return await MoviesCRUD.change(db_session, movie_id, movie) -@movies_router.delete("/{movie_id}", response_model=db.Movie) +@movies_router.delete("/{movie_id}", response_model=Movie) async def delete_movie(movie_id: int, - user: db.User = Depends(get_user), - db_session: AsyncSession = Depends(db.get_session)): - movie_db = await db.get_movie(db_session, movie_id) + user: User = Depends(get_user), + db_session: AsyncSession = Depends(Database.get_session)): + movie_db = await MoviesCRUD.get(db_session, movie_id) if (movie_db is None): raise HTTPException(status.HTTP_404_NOT_FOUND, detail=f"Movie with id={movie_id} not found") @@ -58,4 +58,4 @@ async def delete_movie(movie_id: int, raise HTTPException(status.HTTP_401_UNAUTHORIZED, detail=f"Movie can only be deleted " "by the owner (creator)") - return await db.delete_movie(db_session, movie_id) + return await MoviesCRUD.delete(db_session, movie_id)