Add audiobooks

This commit is contained in:
2024-06-15 11:53:53 +04:00
parent fe424182df
commit 563560c5e3
20 changed files with 420 additions and 8 deletions

View File

@@ -1,2 +1,4 @@
from .games import *
from .movies import *
from .audiobooks import *
from .users import *

View File

@@ -0,0 +1,45 @@
from time import strftime
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy.future import select
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()
async def get_audiobook(db: AsyncSession, audiobook_id: int):
return await db.get(mdl.Audiobook, audiobook_id)
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)
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 (value and value is not None and 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

View File

@@ -32,6 +32,7 @@ async def edit_game(db: AsyncSession,
for key, value in vars(game_info).items():
if (value and value is not None and getattr(game, key) != value):
setattr(game, key, value)
setattr(game, "update_date", strftime("%Y-%m-%d %H:%M:%S"))
await db.commit()
return game

45
database/crud/movies.py Normal file
View File

@@ -0,0 +1,45 @@
from time import strftime
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy.future import select
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()
async def get_movie(db: AsyncSession, movie_id: int):
return await db.get(mdl.Movie, movie_id)
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)
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 (value and value is not None and 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

View File

@@ -1,2 +1,4 @@
from .games import Game as Game
from .movies import Movie as Movie
from .audiobooks import Audiobook as Audiobook
from .users import User as User

View File

@@ -0,0 +1,26 @@
from sqlalchemy import Column, ForeignKey, Integer, String
from ..database import Base
class Audiobook(Base):
__tablename__ = "audiobooks"
id = Column(Integer, primary_key=True)
title = Column(String, nullable=False, unique=True)
cover = Column(String)
description = Column(String)
author = Column(String)
torrent_file = Column(String, nullable=False)
upload_date = Column(String, nullable=False)
fragment = Column(String)
update_date = Column(String, nullable=False)
language = Column(String)
release_date = Column(String)
download_size = Column(String)
duration = Column(String)
reader = Column(String)
owner_id = Column(Integer, ForeignKey("users.id"))

View File

@@ -1,5 +1,4 @@
from sqlalchemy import Column, ForeignKey, Integer, String
from sqlalchemy.orm import relationship
from ..database import Base

28
database/models/movies.py Normal file
View File

@@ -0,0 +1,28 @@
from sqlalchemy import Column, ForeignKey, Integer, String
from ..database import Base
class Movie(Base):
__tablename__ = "movies"
id = Column(Integer, primary_key=True)
title = Column(String, nullable=False, unique=True)
cover = Column(String)
age = Column(String)
description = Column(String)
torrent_file = Column(String, nullable=False)
upload_date = Column(String, nullable=False)
trailer = Column(String)
update_date = Column(String, nullable=False)
language = Column(String)
subtitles = Column(String)
release_date = Column(String)
download_size = Column(String)
director = Column(String)
duration = Column(String)
country = Column(String)
owner_id = Column(Integer, ForeignKey("users.id"))

View File

@@ -1,2 +1,4 @@
from .games import *
from .movies import *
from .audiobooks import *
from .users import *

View File

@@ -0,0 +1,51 @@
from typing import Optional
from pydantic import BaseModel, ConfigDict, Field
class AudiobookCardBase(BaseModel):
title: str = Field(examples=["Марсианин"])
cover: Optional[str] = \
Field(default=None, examples=["cover_filename.jpg"])
description: Optional[str] = \
Field(default=None,
examples=["Главный герой оказался в сложнейшей ситуации."
" Его жизнь висела на волоске и зависела от"
" нескольких совершенно нелогичных факторов."
" Дело в том, что его бросили на Марсе в крайне"
" затруднительном для дальнейшей жизни положении."
" Рассчитывать стоит лишь на себя и на чудо,"
" ведь ультрасовременный скафандр оказался прошит"
" антенной, а до прибытия следующей экспедиции"
" остается целая вечность."])
author: Optional[str] = \
Field(default=None, examples=["Вейр Энди"])
class AudiobookCard(AudiobookCardBase):
id: int = Field(examples=[1])
class AudiobookBase(AudiobookCardBase):
torrent_file: str = Field(examples=["torrent_filename.torrent"])
fragment: Optional[str] = \
Field(default=None, examples=[
"fragment.mp3"])
language: Optional[str] = Field(default=None, examples=["рус"])
release_date: Optional[str] = Field(default=None, examples=["2015"])
download_size: Optional[str] = Field(default=None, examples=["300Mb"])
duration: Optional[str] = Field(default=None, examples=["12:38"])
reader: Optional[str] = Field(default=None, examples=["Дмитрий Хазанович"])
class AudiobookCreate(AudiobookBase):
pass
class Audiobook(AudiobookBase):
id: int = Field(examples=[1])
update_date: str = Field(examples=["2024-06-14 12:00:00"])
upload_date: str = Field(examples=["2024-06-14 12:00:00"])
owner_id: int = Field(examples=[1])
model_config = ConfigDict(from_attributes=True)

View File

@@ -1,10 +1,9 @@
from typing import Optional
from fastapi import Body
from pydantic import BaseModel, ConfigDict, Field
class GameCardBase(BaseModel):
title: str = Field(examples=["DwarfFortress", "RimWorld"])
title: str = Field(examples=["DwarfFortress"])
cover: Optional[str] = \
Field(default=None, examples=["cover_filename.jpg"])
description: Optional[str] = \

View File

@@ -0,0 +1,55 @@
from typing import Optional
from pydantic import BaseModel, ConfigDict, Field
class MovieCardBase(BaseModel):
title: str = Field(examples=["Интерстеллар"])
cover: Optional[str] = \
Field(default=None, examples=["cover_filename.jpg"])
description: Optional[str] = \
Field(default=None,
examples=["Когда засуха, пыльные бури и вымирание"
" растений приводят человечество к"
" продовольственному кризису, коллектив"
" исследователей и учёных отправляется"
" сквозь червоточину (которая предположительно"
" соединяет области пространства-времени"
" через большое расстояние) в путешествие,"
" чтобы превзойти прежние ограничения для"
" космических путешествий человека и найти"
" планету с подходящими для человечества условиями."])
age: Optional[str] = \
Field(default=None, examples=["18+"])
class MovieCard(MovieCardBase):
id: int = Field(examples=[1])
class MovieBase(MovieCardBase):
torrent_file: str = Field(examples=["torrent_filename.torrent"])
trailer: Optional[str] = \
Field(default=None, examples=[
"https://www.youtube.com/watch?v=6ybBuTETr3U"])
language: Optional[str] = Field(default=None, examples=["рус"])
subtitles: Optional[str] = Field(default=None, examples=["Отсутствуют"])
release_date: Optional[str] = Field(default=None, examples=["2014"])
download_size: Optional[str] = Field(default=None, examples=["32Gb"])
director: Optional[str] = Field(default=None, examples=["Кристофер Нолан"])
duration: Optional[str] = Field(default=None, examples=["02:37:58"])
country: Optional[str] = \
Field(default=None, examples=["США, Великобритания, Канада"])
class MovieCreate(MovieBase):
pass
class Movie(MovieBase):
id: int = Field(examples=[1])
update_date: str = Field(examples=["2024-06-11 12:00:00"])
upload_date: str = Field(examples=["2024-06-11 12:00:00"])
owner_id: int = Field(examples=[1])
model_config = ConfigDict(from_attributes=True)

View File

@@ -85,3 +85,25 @@ async def save_image(cover: UploadFile, type: Literal["cover", "screenshot"]):
buf, format=cover.content_type.upper().replace("IMAGE/", ""))
await preview_file.write(buf.getbuffer())
return hash_filename
async def save_audio_fragment(fragment: UploadFile):
if (fragment.filename is None):
raise ValueError("Filename not found")
if (fragment.content_type is None):
raise ValueError("File content type unknown")
hash_filename = create_hash_name(fragment.filename)
file_extension = mimetypes.guess_extension(fragment.content_type)
if (file_extension is None):
raise NameError("File extension not found")
else:
hash_filename += file_extension
async with aiofiles.open(Path() / "content" / "audio"
/ hash_filename, 'wb') as file:
fragment_data = await fragment.read()
if (isinstance(fragment_data, str)):
raise ValueError("Invalid audio file")
await file.write(fragment_data)
return hash_filename

View File

@@ -15,6 +15,8 @@ app = FastAPI(
)
app.include_router(startup_router)
app.include_router(games_router)
app.include_router(movies_router)
app.include_router(audiobooks_router)
app.include_router(files_router)
app.include_router(auth_router)
app.mount("/content", StaticFiles(directory="content"), name="content")

View File

@@ -1,4 +1,6 @@
from .games import games_router as games_router
from .movies import movies_router as movies_router
from .audiobooks import audiobooks_router as audiobooks_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

61
routes/audiobooks.py Normal file
View File

@@ -0,0 +1,61 @@
from sqlalchemy.ext.asyncio import AsyncSession
from fastapi import APIRouter, Depends, HTTPException, status
import database as db
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.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[db.AudiobookCard])
async def get_audiobooks_cards(db_session: AsyncSession = Depends(db.get_session)):
return await db.get_audiobooks(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.put("/{audiobook_id}", response_model=db.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)
if (audiobook_db is None):
raise HTTPException(status.HTTP_404_NOT_FOUND,
detail=f"Audiobook with id={audiobook_id} not found")
if (user.id != audiobook_db.owner_id):
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)
@audiobooks_router.delete("/{audiobook_id}", response_model=db.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)
if (audiobook_db is None):
raise HTTPException(status.HTTP_404_NOT_FOUND,
detail=f"Audiobook with id={audiobook_id} not found")
if (user.id != audiobook_db.owner_id):
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)

View File

@@ -12,7 +12,7 @@ async def upload_torrent(torrent: UploadFile):
return await save_torrent_file(torrent)
except Exception as ex:
print(ex)
raise HTTPException(500)
raise HTTPException(500, detail=str(ex))
@files_router.post("/cover", response_model=str)
@@ -21,4 +21,13 @@ async def upload_cover(cover: UploadFile):
return await save_image(cover, "cover")
except Exception as ex:
print(ex)
raise HTTPException(500)
raise HTTPException(500, detail=str(ex))
@files_router.post("/audio", response_model=str)
async def upload_audio_fragment(fragment: UploadFile):
try:
return await save_audio_fragment(fragment)
except Exception as ex:
print(ex)
raise HTTPException(500, detail=str(ex))

View File

@@ -51,7 +51,6 @@ 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)
print(game_db)
if (game_db is None):
raise HTTPException(status.HTTP_404_NOT_FOUND,
detail=f"Game with id={game_id} not found")

61
routes/movies.py Normal file
View File

@@ -0,0 +1,61 @@
from sqlalchemy.ext.asyncio import AsyncSession
from fastapi import APIRouter, Depends, HTTPException, status
import database as db
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.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[db.MovieCard])
async def get_movies_cards(db_session: AsyncSession = Depends(db.get_session)):
return await db.get_movies(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.put("/{movie_id}", response_model=db.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)
if (movie_db is None):
raise HTTPException(status.HTTP_404_NOT_FOUND,
detail=f"Movie with id={movie_id} not found")
if (user.id != movie_db.owner_id):
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)
@movies_router.delete("/{movie_id}", response_model=db.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)
if (movie_db is None):
raise HTTPException(status.HTTP_404_NOT_FOUND,
detail=f"Movie with id={movie_id} not found")
if (user.id != movie_db.owner_id):
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)

View File

@@ -12,7 +12,8 @@ def create_folders():
Path() / "content" / "images" / "cover" / "preview",
Path() / "content" / "images" / "screenshot" / "full_size",
Path() / "content" / "images" / "screenshot" / "preview",
Path() / "content" / "torrent"
Path() / "content" / "torrent",
Path() / "content" / "audio"
]
for path in need_paths:
path.mkdir(parents=True, exist_ok=True)