Добавление авторизации и фронтэнда

This commit is contained in:
2025-12-18 18:52:09 +03:00
parent 2c24f66de0
commit 756e941f99
55 changed files with 2314 additions and 577 deletions
+3
View File
@@ -1,5 +1,7 @@
"""Модуль объединения роутеров"""
from fastapi import APIRouter
from .auth import router as auth_router
from .authors import router as authors_router
from .books import router as books_router
from .genres import router as genres_router
@@ -9,6 +11,7 @@ from .misc import router as misc_router
api_router = APIRouter()
# Подключение всех маршрутов
api_router.include_router(auth_router)
api_router.include_router(authors_router)
api_router.include_router(books_router)
api_router.include_router(genres_router)
+156
View File
@@ -0,0 +1,156 @@
"""Модуль работы с авторизацией и аутентификацией пользователей"""
from datetime import timedelta
from typing import Annotated
from fastapi import APIRouter, Depends, HTTPException, status
from fastapi.security import OAuth2PasswordRequestForm
from sqlmodel import Session, select
from library_service.models.db import Role, User
from library_service.models.dto import Token, UserCreate, UserRead, UserUpdate
from library_service.settings import get_session
from library_service.auth import (ACCESS_TOKEN_EXPIRE_MINUTES, RequireAdmin,
RequireAuth, authenticate_user, get_password_hash,
create_access_token, create_refresh_token)
router = APIRouter(prefix="/auth", tags=["authentication"])
@router.post(
"/register",
response_model=UserRead,
status_code=status.HTTP_201_CREATED,
summary="Регистрация нового пользователя",
description="Создает нового пользователя в системе",
)
def register(user_data: UserCreate, session: Session = Depends(get_session)):
"""Эндпоинт регистрации пользователя"""
# Проверка если username существует
existing_user = session.exec(
select(User).where(User.username == user_data.username)
).first()
if existing_user:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail="Username already registered",
)
# Проверка если email существует
existing_email = session.exec(
select(User).where(User.email == user_data.email)
).first()
if existing_email:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST, detail="Email already registered"
)
# Создание пользователя
db_user = User(
**user_data.model_dump(exclude={"password"}),
hashed_password=get_password_hash(user_data.password)
)
# Назначение роли по умолчанию
default_role = session.exec(select(Role).where(Role.name == "user")).first()
if default_role:
db_user.roles.append(default_role)
session.add(db_user)
session.commit()
session.refresh(db_user)
return UserRead(**db_user.model_dump(), roles=[role.name for role in db_user.roles])
@router.post(
"/token",
response_model=Token,
summary="Получение токена",
description="Аутентификация и получение JWT токена",
)
def login(
form_data: Annotated[OAuth2PasswordRequestForm, Depends()],
session: Session = Depends(get_session),
):
"""Эндпоинт аутентификации и получения JWT токена"""
user = authenticate_user(session, form_data.username, form_data.password)
if not user:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Incorrect username or password",
headers={"WWW-Authenticate": "Bearer"},
)
access_token_expires = timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES)
access_token = create_access_token(
data={"sub": user.username, "user_id": user.id},
expires_delta=access_token_expires,
)
refresh_token = create_refresh_token(
data={"sub": user.username, "user_id": user.id}
)
return Token(
access_token=access_token, refresh_token=refresh_token, token_type="bearer"
)
@router.get(
"/me",
response_model=UserRead,
summary="Текущий пользователь",
description="Получить информацию о текущем авторизованном пользователе",
)
def read_users_me(current_user: RequireAuth):
"""Эндпоинт получения информации о себе"""
return UserRead(
**current_user.model_dump(), roles=[role.name for role in current_user.roles]
)
@router.put(
"/me",
response_model=UserRead,
summary="Обновить профиль",
description="Обновить информацию текущего пользователя",
)
def update_user_me(
user_update: UserUpdate,
current_user: RequireAuth,
session: Session = Depends(get_session),
):
"""Эндпоинт обновления пользователя"""
if user_update.email:
current_user.email = user_update.email
if user_update.full_name:
current_user.full_name = user_update.full_name
if user_update.password:
current_user.hashed_password = get_password_hash(user_update.password)
session.add(current_user)
session.commit()
session.refresh(current_user)
return UserRead(
**current_user.model_dump(), roles=[role.name for role in current_user.roles]
)
@router.get(
"/users",
response_model=list[UserRead],
summary="Список пользователей",
description="Получить список всех пользователей (только для админов)",
)
def read_users(
admin: RequireAdmin,
session: Session = Depends(get_session),
skip: int = 0,
limit: int = 100,
):
"""Эндпоинт получения списка всех пользователей"""
users = session.exec(select(User).offset(skip).limit(limit)).all()
return [
UserRead(**user.model_dump(), roles=[role.name for role in user.roles])
for user in users
]
+18 -16
View File
@@ -1,28 +1,28 @@
from fastapi import APIRouter, Path, Depends, HTTPException
"""Модуль работы с авторами"""
from fastapi import APIRouter, Depends, HTTPException, Path
from sqlmodel import Session, select
from library_service.auth import RequireAuth
from library_service.settings import get_session
from library_service.models.db import Author, AuthorBookLink, Book, AuthorWithBooks
from library_service.models.dto import (
AuthorCreate,
AuthorUpdate,
AuthorRead,
AuthorList,
BookRead,
)
from library_service.models.db import Author, AuthorBookLink, Book
from library_service.models.dto import (BookRead, AuthorWithBooks,
AuthorCreate, AuthorList, AuthorRead, AuthorUpdate)
router = APIRouter(prefix="/authors", tags=["authors"])
# Create an author
@router.post(
"/",
response_model=AuthorRead,
summary="Создать автора",
description="Добавляет автора в систему",
)
def create_author(author: AuthorCreate, session: Session = Depends(get_session)):
def create_author(
current_user: RequireAuth,
author: AuthorCreate,
session: Session = Depends(get_session),
):
"""Эндпоинт создания автора"""
db_author = Author(**author.model_dump())
session.add(db_author)
session.commit()
@@ -30,7 +30,6 @@ def create_author(author: AuthorCreate, session: Session = Depends(get_session))
return AuthorRead(**db_author.model_dump())
# Read authors
@router.get(
"/",
response_model=AuthorList,
@@ -38,6 +37,7 @@ def create_author(author: AuthorCreate, session: Session = Depends(get_session))
description="Возвращает список всех авторов в системе",
)
def read_authors(session: Session = Depends(get_session)):
"""Эндпоинт чтения списка авторов"""
authors = session.exec(select(Author)).all()
return AuthorList(
authors=[AuthorRead(**author.model_dump()) for author in authors],
@@ -45,7 +45,6 @@ def read_authors(session: Session = Depends(get_session)):
)
# Read an author with their books
@router.get(
"/{author_id}",
response_model=AuthorWithBooks,
@@ -56,6 +55,7 @@ def get_author(
author_id: int = Path(..., description="ID автора (целое число, > 0)", gt=0),
session: Session = Depends(get_session),
):
"""Эндпоинт чтения конкретного автора"""
author = session.get(Author, author_id)
if not author:
raise HTTPException(status_code=404, detail="Author not found")
@@ -72,7 +72,6 @@ def get_author(
return AuthorWithBooks(**author_data)
# Update an author
@router.put(
"/{author_id}",
response_model=AuthorRead,
@@ -80,10 +79,12 @@ def get_author(
description="Обновляет информацию об авторе в системе",
)
def update_author(
current_user: RequireAuth,
author: AuthorUpdate,
author_id: int = Path(..., description="ID автора (целое число, > 0)", gt=0),
session: Session = Depends(get_session),
):
"""Эндпоинт обновления автора"""
db_author = session.get(Author, author_id)
if not db_author:
raise HTTPException(status_code=404, detail="Author not found")
@@ -97,7 +98,6 @@ def update_author(
return AuthorRead(**db_author.model_dump())
# Delete an author
@router.delete(
"/{author_id}",
response_model=AuthorRead,
@@ -105,9 +105,11 @@ def update_author(
description="Удаляет автора из системы",
)
def delete_author(
current_user: RequireAuth,
author_id: int = Path(..., description="ID автора (целое число, > 0)", gt=0),
session: Session = Depends(get_session),
):
"""Эндпоинт удаления автора"""
author = session.get(Author, author_id)
if not author:
raise HTTPException(status_code=404, detail="Author not found")
+70 -17
View File
@@ -1,29 +1,32 @@
from fastapi import APIRouter, Path, Depends, HTTPException
from sqlmodel import Session, select
"""Модуль работы с книгами"""
from typing import List
from library_service.models.db.links import BookWithAuthorsAndGenres
from fastapi import APIRouter, Depends, HTTPException, Path, Query
from sqlmodel import Session, select, col, func
from library_service.auth import RequireAuth
from library_service.settings import get_session
from library_service.models.db import Author, Book, BookWithAuthors, AuthorBookLink
from library_service.models.dto import (
AuthorRead,
BookList,
BookRead,
BookCreate,
BookUpdate,
from library_service.models.db import Author, AuthorBookLink, Book
from library_service.models.dto import AuthorRead, BookCreate, BookList, BookRead, BookUpdate
from library_service.models.dto.combined import (
BookWithAuthorsAndGenres,
BookFilteredList
)
router = APIRouter(prefix="/books", tags=["books"])
# Create a book
@router.post(
"/",
response_model=Book,
summary="Создать книгу",
description="Добавляет книгу в систему",
)
def create_book(book: BookCreate, session: Session = Depends(get_session)):
def create_book(
current_user: RequireAuth, book: BookCreate, session: Session = Depends(get_session)
):
"""Эндпоинт создания книги"""
db_book = Book(**book.model_dump())
session.add(db_book)
session.commit()
@@ -31,7 +34,6 @@ def create_book(book: BookCreate, session: Session = Depends(get_session)):
return BookRead(**db_book.model_dump())
# Read books
@router.get(
"/",
response_model=BookList,
@@ -39,13 +41,13 @@ def create_book(book: BookCreate, session: Session = Depends(get_session)):
description="Возвращает список всех книг в системе",
)
def read_books(session: Session = Depends(get_session)):
"""Эндпоинт чтения списка книг"""
books = session.exec(select(Book)).all()
return BookList(
books=[BookRead(**book.model_dump()) for book in books], total=len(books)
)
# Read a book with their authors and genres
@router.get(
"/{book_id}",
response_model=BookWithAuthorsAndGenres,
@@ -56,6 +58,7 @@ def get_book(
book_id: int = Path(..., description="ID книги (целое число, > 0)", gt=0),
session: Session = Depends(get_session),
):
"""Эндпоинт чтения конкретной книги"""
book = session.get(Book, book_id)
if not book:
raise HTTPException(status_code=404, detail="Book not found")
@@ -76,10 +79,9 @@ def get_book(
book_data["authors"] = author_reads
book_data["genres"] = genre_reads
return BookWithAuthors(**book_data)
return BookWithAuthorsAndGenres(**book_data)
# Update a book
@router.put(
"/{book_id}",
response_model=Book,
@@ -87,10 +89,12 @@ def get_book(
description="Обновляет информацию о книге в системе",
)
def update_book(
current_user: RequireAuth,
book: BookUpdate,
book_id: int = Path(..., description="ID книги (целое число, > 0)", gt=0),
session: Session = Depends(get_session),
):
"""Эндпоинт обновления книги"""
db_book = session.get(Book, book_id)
if not db_book:
raise HTTPException(status_code=404, detail="Book not found")
@@ -102,7 +106,6 @@ def update_book(
return db_book
# Delete a book
@router.delete(
"/{book_id}",
response_model=BookRead,
@@ -110,9 +113,11 @@ def update_book(
description="Удаляет книгу их системы",
)
def delete_book(
current_user: RequireAuth,
book_id: int = Path(..., description="ID книги (целое число, > 0)", gt=0),
session: Session = Depends(get_session),
):
"""Эндпоинт удаления книги"""
book = session.get(Book, book_id)
if not book:
raise HTTPException(status_code=404, detail="Book not found")
@@ -122,3 +127,51 @@ def delete_book(
session.delete(book)
session.commit()
return book_read
@router.get(
"/filter",
response_model=BookFilteredList,
summary="Фильтрация книг",
description="Фильтрация списка книг по названию, авторам и жанрам с пагинацией"
)
def filter_books(
session: Session = Depends(get_session),
q: str | None = Query(None, min_length=3, max_length=50, description="Поиск"),
author_ids: List[int] | None = Query(None, description="Список ID авторов"),
genre_ids: List[int] | None = Query(None, description="Список ID жанров"),
page: int = Query(1, gt=0, description="Номер страницы"),
size: int = Query(20, gt=0, lt=101, description="Количество элементов на странице"),
):
"""Эндпоинт получения отфильтрованного списка книг"""
statement = select(Book).distinct()
if q:
statement = statement.where(
(col(Book.title).ilike(f"%{q}%")) | (col(Book.description).ilike(f"%{q}%"))
)
if author_ids:
statement = statement.join(AuthorBookLink).where(AuthorBookLink.author_id.in_(author_ids))
if genre_ids:
statement = statement.join(GenreBookLink).where(GenreBookLink.genre_id.in_(genre_ids))
total_statement = select(func.count()).select_from(statement.subquery())
total = session.exec(total_statement).one()
offset = (page - 1) * size
statement = statement.offset(offset).limit(size)
results = session.exec(statement).all()
books_with_data = []
for db_book in results:
books_with_data.append(
BookWithAuthorsAndGenres(
**db_book.model_dump(),
authors=[AuthorRead(**a.model_dump()) for a in db_book.authors],
genres=[GenreRead(**g.model_dump()) for g in db_book.genres]
)
)
return BookFilteredList(books=books_with_data, total=total)
+22 -16
View File
@@ -1,28 +1,28 @@
from fastapi import APIRouter, Path, Depends, HTTPException
"""Модуль работы с жанрами"""
from fastapi import APIRouter, Depends, HTTPException, Path
from sqlmodel import Session, select
from library_service.auth import RequireAuth
from library_service.models.db import Book, Genre, GenreBookLink
from library_service.models.dto import BookRead, GenreCreate, GenreList, GenreRead, GenreUpdate, GenreWithBooks
from library_service.settings import get_session
from library_service.models.db import Genre, GenreBookLink, Book, GenreWithBooks
from library_service.models.dto import (
GenreCreate,
GenreUpdate,
GenreRead,
GenreList,
BookRead,
)
router = APIRouter(prefix="/genres", tags=["genres"])
# Create a genre
# Создание жанра
@router.post(
"/",
response_model=GenreRead,
summary="Создать жанр",
description="Добавляет жанр книг в систему",
)
def create_genre(genre: GenreCreate, session: Session = Depends(get_session)):
def create_genre(
current_user: RequireAuth,
genre: GenreCreate,
session: Session = Depends(get_session),
):
"""Эндпоинт создания жанра"""
db_genre = Genre(**genre.model_dump())
session.add(db_genre)
session.commit()
@@ -30,7 +30,7 @@ def create_genre(genre: GenreCreate, session: Session = Depends(get_session)):
return GenreRead(**db_genre.model_dump())
# Read genres
# Чтение жанров
@router.get(
"/",
response_model=GenreList,
@@ -38,13 +38,14 @@ def create_genre(genre: GenreCreate, session: Session = Depends(get_session)):
description="Возвращает список всех жанров в системе",
)
def read_genres(session: Session = Depends(get_session)):
"""Эндпоинт чтения списка жанров"""
genres = session.exec(select(Genre)).all()
return GenreList(
genres=[GenreRead(**genre.model_dump()) for genre in genres], total=len(genres)
)
# Read a genre with their books
# Чтение жанра с его книгами
@router.get(
"/{genre_id}",
response_model=GenreWithBooks,
@@ -55,6 +56,7 @@ def get_genre(
genre_id: int = Path(..., description="ID жанра (целое число, > 0)", gt=0),
session: Session = Depends(get_session),
):
"""Эндпоинт чтения конкретного жанра"""
genre = session.get(Genre, genre_id)
if not genre:
raise HTTPException(status_code=404, detail="Genre not found")
@@ -71,7 +73,7 @@ def get_genre(
return GenreWithBooks(**genre_data)
# Update a genre
# Обновление жанра
@router.put(
"/{genre_id}",
response_model=GenreRead,
@@ -79,10 +81,12 @@ def get_genre(
description="Обновляет информацию о жанре в системе",
)
def update_genre(
current_user: RequireAuth,
genre: GenreUpdate,
genre_id: int = Path(..., description="ID жанра (целое число, > 0)", gt=0),
session: Session = Depends(get_session),
):
"""Эндпоинт обновления жанра"""
db_genre = session.get(Genre, genre_id)
if not db_genre:
raise HTTPException(status_code=404, detail="Genre not found")
@@ -96,7 +100,7 @@ def update_genre(
return GenreRead(**db_genre.model_dump())
# Delete a genre
# Удаление жанра
@router.delete(
"/{genre_id}",
response_model=GenreRead,
@@ -104,9 +108,11 @@ def update_genre(
description="Удаляет автора из системы",
)
def delete_genre(
current_user: RequireAuth,
genre_id: int = Path(..., description="ID жанра (целое число, > 0)", gt=0),
session: Session = Depends(get_session),
):
"""Эндпоинт удаления жанра"""
genre = session.get(Genre, genre_id)
if not genre:
raise HTTPException(status_code=404, detail="Genre not found")
+34 -13
View File
@@ -1,21 +1,22 @@
from fastapi import APIRouter, Path, Request
from fastapi.params import Depends
from fastapi.responses import FileResponse, HTMLResponse, JSONResponse, RedirectResponse
from fastapi.templating import Jinja2Templates
from pathlib import Path
"""Модуль прочих эндпоинтов"""
from datetime import datetime
from pathlib import Path
from typing import Dict
from fastapi import APIRouter, Request
from fastapi.params import Depends
from fastapi.responses import FileResponse, JSONResponse, RedirectResponse
from fastapi.templating import Jinja2Templates
from library_service.settings import get_app
# Загрузка шаблонов
templates = Jinja2Templates(directory=Path(__file__).parent.parent / "templates")
router = APIRouter(tags=["misc"])
templates = Jinja2Templates(directory=Path(__file__).parent.parent / "templates")
# Форматированная информация о приложении
def get_info(app) -> Dict:
"""Форматированная информация о приложении"""
return {
"status": "ok",
"app_info": {
@@ -27,29 +28,49 @@ def get_info(app) -> Dict:
}
# Эндпоинт главной страницы
@router.get("/", include_in_schema=False)
async def root(request: Request, app=Depends(get_app)):
"""Эндпоинт главной страницы"""
return templates.TemplateResponse(request, "index.html", get_info(app))
# Редирект иконки вкладки
@router.get("/api", include_in_schema=False)
async def root(request: Request, app=Depends(get_app)):
"""Страница с сылками на документацию API"""
return templates.TemplateResponse(request, "api.html", get_info(app))
@router.get("/favicon.ico", include_in_schema=False)
def redirect_favicon():
"""Редирект иконки вкладки"""
return RedirectResponse("/favicon.svg")
# Эндпоинт иконки вкладки
@router.get("/favicon.svg", include_in_schema=False)
async def favicon():
return FileResponse("library_service/favicon.svg", media_type="image/svg+xml")
"""Эндпоинт иконки вкладки"""
return FileResponse(
"library_service/static/favicon.svg", media_type="image/svg+xml"
)
@router.get("/static/{path:path}", include_in_schema=False)
async def serve_static(path: str):
"""Статические файлы"""
static_dir = Path(__file__).parent.parent / "static"
file_path = static_dir / path
if not file_path.is_file() or not file_path.is_relative_to(static_dir):
return JSONResponse(status_code=404, content={"error": "File not found"})
return FileResponse(file_path)
# Эндпоинт информации об API
@router.get(
"/api/info",
summary="Информация о сервисе",
description="Возвращает информацию о системе",
)
async def api_info(app=Depends(get_app)):
"""Эндпоинт информации об API"""
return JSONResponse(content=get_info(app))
+121 -119
View File
@@ -1,15 +1,82 @@
"""Модуль работы со связями"""
from typing import Dict, List
from fastapi import APIRouter, Depends, HTTPException
from sqlmodel import Session, select
from typing import List, Dict
from library_service.settings import get_session
from library_service.models.db import Author, Book, Genre, AuthorBookLink, GenreBookLink
from library_service.auth import RequireAuth
from library_service.models.db import Author, AuthorBookLink, Book, Genre, GenreBookLink
from library_service.models.dto import AuthorRead, BookRead, GenreRead
from library_service.settings import get_session
router = APIRouter(tags=["relations"])
# Add author to book
def check_entity_exists(session, model, entity_id, entity_name):
"""Проверка существования связи между сущностями в БД"""
entity = session.get(model, entity_id)
if not entity:
raise HTTPException(status_code=404, detail=f"{entity_name} not found")
return entity
def add_relationship(session, link_model, id1, field1, id2, field2, detail):
"""Создание связи между сущностями в БД"""
existing_link = session.exec(
select(link_model)
.where(getattr(link_model, field1) == id1)
.where(getattr(link_model, field2) == id2)
).first()
if existing_link:
raise HTTPException(status_code=400, detail=detail)
link = link_model(**{field1: id1, field2: id2})
session.add(link)
session.commit()
session.refresh(link)
return link
def remove_relationship(session, link_model, id1, field1, id2, field2):
"""Удаление связи между сущностями в БД"""
link = session.exec(
select(link_model)
.where(getattr(link_model, field1) == id1)
.where(getattr(link_model, field2) == id2)
).first()
if not link:
raise HTTPException(status_code=404, detail="Relationship not found")
session.delete(link)
session.commit()
return {"message": "Relationship removed successfully"}
def get_related(
session,
main_model,
main_id,
main_name,
related_model,
link_model,
link_main_field,
link_related_field,
read_model
):
"""Получение связанных в БД сущностей"""
check_entity_exists(session, main_model, main_id, main_name)
related = session.exec(
select(related_model).join(link_model)
.where(getattr(link_model, link_main_field) == main_id)
).all()
return [read_model(**obj.model_dump()) for obj in related]
@router.post(
"/relationships/author-book",
response_model=AuthorBookLink,
@@ -17,33 +84,19 @@ router = APIRouter(tags=["relations"])
description="Добавляет связь между автором и книгой в систему",
)
def add_author_to_book(
author_id: int, book_id: int, session: Session = Depends(get_session)
current_user: RequireAuth,
author_id: int,
book_id: int,
session: Session = Depends(get_session),
):
author = session.get(Author, author_id)
if not author:
raise HTTPException(status_code=404, detail="Author not found")
"""Эндпоинт добавления автора к книге"""
check_entity_exists(session, Author, author_id, "Author")
check_entity_exists(session, Book, book_id, "Book")
book = session.get(Book, book_id)
if not book:
raise HTTPException(status_code=404, detail="Book not found")
existing_link = session.exec(
select(AuthorBookLink)
.where(AuthorBookLink.author_id == author_id)
.where(AuthorBookLink.book_id == book_id)
).first()
if existing_link:
raise HTTPException(status_code=400, detail="Relationship already exists")
link = AuthorBookLink(author_id=author_id, book_id=book_id)
session.add(link)
session.commit()
session.refresh(link)
return link
return add_relationship(session, AuthorBookLink,
author_id, "author_id", book_id, "book_id", "Relationship already exists")
# Remove author from book
@router.delete(
"/relationships/author-book",
response_model=Dict[str, str],
@@ -51,23 +104,16 @@ def add_author_to_book(
description="Удаляет связь между автором и книгой в системе",
)
def remove_author_from_book(
author_id: int, book_id: int, session: Session = Depends(get_session)
current_user: RequireAuth,
author_id: int,
book_id: int,
session: Session = Depends(get_session),
):
link = session.exec(
select(AuthorBookLink)
.where(AuthorBookLink.author_id == author_id)
.where(AuthorBookLink.book_id == book_id)
).first()
if not link:
raise HTTPException(status_code=404, detail="Relationship not found")
session.delete(link)
session.commit()
return {"message": "Relationship removed successfully"}
"""Эндпоинт удаления автора из книги"""
return remove_relationship(session, AuthorBookLink,
author_id, "author_id", book_id, "book_id")
# Get author's books
@router.get(
"/authors/{author_id}/books/",
response_model=List[BookRead],
@@ -75,18 +121,12 @@ def remove_author_from_book(
description="Возвращает все книги в системе, написанные автором",
)
def get_books_for_author(author_id: int, session: Session = Depends(get_session)):
author = session.get(Author, author_id)
if not author:
raise HTTPException(status_code=404, detail="Author not found")
books = session.exec(
select(Book).join(AuthorBookLink).where(AuthorBookLink.author_id == author_id)
).all()
return [BookRead(**book.model_dump()) for book in books]
"""Эндпоинт получения книг, написанных автором"""
return get_related(session,
Author, author_id, "Author", Book,
AuthorBookLink, "author_id", "book_id", BookRead)
# Get book's authors
@router.get(
"/books/{book_id}/authors/",
response_model=List[AuthorRead],
@@ -94,18 +134,12 @@ def get_books_for_author(author_id: int, session: Session = Depends(get_session)
description="Возвращает всех авторов книги в системе",
)
def get_authors_for_book(book_id: int, session: Session = Depends(get_session)):
book = session.get(Book, book_id)
if not book:
raise HTTPException(status_code=404, detail="Book not found")
authors = session.exec(
select(Author).join(AuthorBookLink).where(AuthorBookLink.book_id == book_id)
).all()
return [AuthorRead(**author.model_dump()) for author in authors]
"""Эндпоинт получения авторов книги"""
return get_related(session,
Book, book_id, "Book", Author,
AuthorBookLink, "book_id", "author_id", AuthorRead)
# Add genre to book
@router.post(
"/relationships/genre-book",
response_model=GenreBookLink,
@@ -113,33 +147,19 @@ def get_authors_for_book(book_id: int, session: Session = Depends(get_session)):
description="Добавляет связь между книгой и жанром в систему",
)
def add_genre_to_book(
genre_id: int, book_id: int, session: Session = Depends(get_session)
current_user: RequireAuth,
genre_id: int,
book_id: int,
session: Session = Depends(get_session),
):
genre = session.get(Genre, genre_id)
if not genre:
raise HTTPException(status_code=404, detail="Genre not found")
"""Эндпоинт добавления жанра к книге"""
check_entity_exists(session, Genre, genre_id, "Genre")
check_entity_exists(session, Book, book_id, "Book")
book = session.get(Book, book_id)
if not book:
raise HTTPException(status_code=404, detail="Book not found")
existing_link = session.exec(
select(GenreBookLink)
.where(GenreBookLink.genre_id == genre_id)
.where(GenreBookLink.book_id == book_id)
).first()
if existing_link:
raise HTTPException(status_code=400, detail="Relationship already exists")
link = GenreBookLink(genre_id=genre_id, book_id=book_id)
session.add(link)
session.commit()
session.refresh(link)
return link
return add_relationship(session, GenreBookLink,
genre_id, "genre_id", book_id, "book_id", "Relationship already exists")
# Remove author from book
@router.delete(
"/relationships/genre-book",
response_model=Dict[str, str],
@@ -147,55 +167,37 @@ def add_genre_to_book(
description="Удаляет связь между жанром и книгой в системе",
)
def remove_genre_from_book(
genre_id: int, book_id: int, session: Session = Depends(get_session)
current_user: RequireAuth,
genre_id: int,
book_id: int,
session: Session = Depends(get_session),
):
link = session.exec(
select(GenreBookLink)
.where(GenreBookLink.genre_id == genre_id)
.where(GenreBookLink.book_id == book_id)
).first()
if not link:
raise HTTPException(status_code=404, detail="Relationship not found")
session.delete(link)
session.commit()
return {"message": "Relationship removed successfully"}
"""Эндпоинт удаления жанра из книги"""
return remove_relationship(session, GenreBookLink,
genre_id, "genre_id", book_id, "book_id")
# Get genre's books
@router.get(
"/genres/{author_id}/books/",
"/genres/{genre_id}/books/",
response_model=List[BookRead],
summary="Получить книги, написанные в жанре",
description="Возвращает все книги в системе в этом жанре",
)
def get_books_for_genre(genre_id: int, session: Session = Depends(get_session)):
genre = session.get(Genre, genre_id)
if not genre:
raise HTTPException(status_code=404, detail="Genre not found")
books = session.exec(
select(Book).join(GenreBookLink).where(GenreBookLink.author_id == genre_id)
).all()
return [BookRead(**book.model_dump()) for book in books]
"""Эндпоинт получения книг с жанром"""
return get_related(session,
Genre, genre_id, "Genre", Book,
GenreBookLink, "genre_id", "book_id", BookRead)
# Get book's genres
@router.get(
"/books/{book_id}/genres/",
response_model=List[GenreRead],
summary="Получить жанры книги",
description="Возвращает все жанры книги в системе",
)
def get_authors_for_book(book_id: int, session: Session = Depends(get_session)):
book = session.get(Book, book_id)
if not book:
raise HTTPException(status_code=404, detail="Book not found")
genres = session.exec(
select(Genre).join(GenreBookLink).where(GenreBookLink.book_id == book_id)
).all()
return [GenreRead(**author.model_dump()) for genre in genres]
def get_genres_for_book(book_id: int, session: Session = Depends(get_session)):
"""Эндпоинт получения жанров книги"""
return get_related(session,
Book, book_id, "Book", Genre,
GenreBookLink, "book_id", "genre_id", GenreRead)