mirror of
https://github.com/wowlikon/LiB.git
synced 2026-02-04 04:31:09 +00:00
Добавление аналитики
This commit is contained in:
@@ -5,15 +5,19 @@ 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
|
||||
from .loans import router as loans_router
|
||||
from .relationships import router as relationships_router
|
||||
from .misc import router as misc_router
|
||||
|
||||
|
||||
api_router = APIRouter()
|
||||
|
||||
|
||||
# Подключение всех маршрутов
|
||||
api_router.include_router(misc_router)
|
||||
api_router.include_router(auth_router, prefix="/api")
|
||||
api_router.include_router(authors_router, prefix="/api")
|
||||
api_router.include_router(books_router, prefix="/api")
|
||||
api_router.include_router(genres_router, prefix="/api")
|
||||
api_router.include_router(loans_router, prefix="/api")
|
||||
api_router.include_router(relationships_router, prefix="/api")
|
||||
|
||||
@@ -9,10 +9,11 @@ from sqlmodel import Session, select
|
||||
from library_service.models.db import Role, User
|
||||
from library_service.models.dto import Token, UserCreate, UserRead, UserUpdate, UserList, RoleRead, RoleList
|
||||
from library_service.settings import get_session
|
||||
from library_service.auth import (ACCESS_TOKEN_EXPIRE_MINUTES, RequireAdmin, RequireAuth,
|
||||
from library_service.auth import (ACCESS_TOKEN_EXPIRE_MINUTES, RequireAuth, RequireAdmin, RequireStaff,
|
||||
authenticate_user, get_password_hash, decode_token,
|
||||
create_access_token, create_refresh_token)
|
||||
|
||||
|
||||
router = APIRouter(prefix="/auth", tags=["authentication"])
|
||||
|
||||
|
||||
@@ -24,8 +25,7 @@ router = APIRouter(prefix="/auth", tags=["authentication"])
|
||||
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()
|
||||
@@ -35,7 +35,6 @@ def register(user_data: UserCreate, session: Session = Depends(get_session)):
|
||||
detail="Username already registered",
|
||||
)
|
||||
|
||||
# Проверка если email существует
|
||||
existing_email = session.exec(
|
||||
select(User).where(User.email == user_data.email)
|
||||
).first()
|
||||
@@ -70,7 +69,7 @@ def login(
|
||||
form_data: Annotated[OAuth2PasswordRequestForm, Depends()],
|
||||
session: Session = Depends(get_session),
|
||||
):
|
||||
"""Эндпоинт аутентификации и получения JWT токена"""
|
||||
"""Аутентифицирует пользователя и возвращает JWT токены"""
|
||||
user = authenticate_user(session, form_data.username, form_data.password)
|
||||
if not user:
|
||||
raise HTTPException(
|
||||
@@ -103,7 +102,7 @@ def refresh_token(
|
||||
refresh_token: str = Body(..., embed=True),
|
||||
session: Session = Depends(get_session),
|
||||
):
|
||||
"""Эндпоинт для обновления токенов."""
|
||||
"""Обновляет пару токенов (access и refresh)"""
|
||||
try:
|
||||
token_data = decode_token(refresh_token, expected_type="refresh")
|
||||
except HTTPException:
|
||||
@@ -149,7 +148,7 @@ def refresh_token(
|
||||
description="Получить информацию о текущем авторизованном пользователе",
|
||||
)
|
||||
def get_my_profile(current_user: RequireAuth):
|
||||
"""Эндпоинт получения информации о себе"""
|
||||
"""Возвращает информацию о текущем пользователе"""
|
||||
return UserRead(
|
||||
**current_user.model_dump(), roles=[role.name for role in current_user.roles]
|
||||
)
|
||||
@@ -166,7 +165,7 @@ def update_user_me(
|
||||
current_user: RequireAuth,
|
||||
session: Session = Depends(get_session),
|
||||
):
|
||||
"""Эндпоинт обновления пользователя"""
|
||||
"""Обновляет профиль текущего пользователя"""
|
||||
if user_update.email:
|
||||
current_user.email = user_update.email
|
||||
if user_update.full_name:
|
||||
@@ -190,12 +189,12 @@ def update_user_me(
|
||||
description="Получить список всех пользователей (только для админов)",
|
||||
)
|
||||
def read_users(
|
||||
admin: RequireAdmin,
|
||||
current_user: RequireStaff,
|
||||
skip: int = 0,
|
||||
limit: int = 100,
|
||||
session: Session = Depends(get_session),
|
||||
):
|
||||
"""Эндпоинт получения списка всех пользователей"""
|
||||
"""Возвращает список всех пользователей"""
|
||||
users = session.exec(select(User).offset(skip).limit(limit)).all()
|
||||
return UserList(
|
||||
users=[
|
||||
@@ -218,7 +217,7 @@ def add_role_to_user(
|
||||
admin: RequireAdmin,
|
||||
session: Session = Depends(get_session),
|
||||
):
|
||||
"""Эндпоинт добавления роли пользователю"""
|
||||
"""Добавляет роль пользователю"""
|
||||
user = session.get(User, user_id)
|
||||
if not user:
|
||||
raise HTTPException(
|
||||
@@ -259,7 +258,7 @@ def remove_role_from_user(
|
||||
admin: RequireAdmin,
|
||||
session: Session = Depends(get_session),
|
||||
):
|
||||
"""Эндпоинт удаления роли у пользователя"""
|
||||
"""Удаляет роль у пользователя"""
|
||||
user = session.get(User, user_id)
|
||||
if not user:
|
||||
raise HTTPException(
|
||||
@@ -298,7 +297,7 @@ def get_roles(
|
||||
auth: RequireAuth,
|
||||
session: Session = Depends(get_session),
|
||||
):
|
||||
"""Эндпоинт получения списа ролей"""
|
||||
"""Возвращает список ролей в системе"""
|
||||
user_roles = [role.name for role in auth.roles]
|
||||
exclude = {"payroll"} if "admin" in user_roles else set()
|
||||
roles = session.exec(select(Role)).all()
|
||||
|
||||
@@ -2,12 +2,13 @@
|
||||
from fastapi import APIRouter, Depends, HTTPException, Path
|
||||
from sqlmodel import Session, select
|
||||
|
||||
from library_service.auth import RequireAuth
|
||||
from library_service.auth import RequireStaff
|
||||
from library_service.settings import get_session
|
||||
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"])
|
||||
|
||||
|
||||
@@ -18,11 +19,11 @@ router = APIRouter(prefix="/authors", tags=["authors"])
|
||||
description="Добавляет автора в систему",
|
||||
)
|
||||
def create_author(
|
||||
current_user: RequireAuth,
|
||||
current_user: RequireStaff,
|
||||
author: AuthorCreate,
|
||||
session: Session = Depends(get_session),
|
||||
):
|
||||
"""Эндпоинт создания автора"""
|
||||
"""Создает нового автора в системе"""
|
||||
db_author = Author(**author.model_dump())
|
||||
session.add(db_author)
|
||||
session.commit()
|
||||
@@ -37,7 +38,7 @@ def create_author(
|
||||
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],
|
||||
@@ -55,7 +56,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")
|
||||
@@ -79,12 +80,12 @@ def get_author(
|
||||
description="Обновляет информацию об авторе в системе",
|
||||
)
|
||||
def update_author(
|
||||
current_user: RequireAuth,
|
||||
current_user: RequireStaff,
|
||||
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")
|
||||
@@ -105,11 +106,11 @@ def update_author(
|
||||
description="Удаляет автора из системы",
|
||||
)
|
||||
def delete_author(
|
||||
current_user: RequireAuth,
|
||||
current_user: RequireStaff,
|
||||
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")
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
"""Модуль работы с книгами"""
|
||||
from datetime import datetime
|
||||
from typing import List
|
||||
|
||||
from fastapi import APIRouter, Depends, HTTPException, Path, Query
|
||||
from sqlmodel import Session, select, col, func
|
||||
|
||||
from library_service.auth import RequireAuth
|
||||
from library_service.auth import RequireStaff
|
||||
from library_service.settings import get_session
|
||||
from library_service.models.db import Author, AuthorBookLink, Book, GenreBookLink, Genre
|
||||
from library_service.models.enums import BookStatus
|
||||
from library_service.models.db import Author, AuthorBookLink, Book, GenreBookLink, Genre, BookUserLink
|
||||
from library_service.models.dto import AuthorRead, BookCreate, BookList, BookRead, BookUpdate, GenreRead
|
||||
from library_service.models.dto.combined import (
|
||||
BookWithAuthorsAndGenres,
|
||||
@@ -17,6 +19,19 @@ from library_service.models.dto.combined import (
|
||||
router = APIRouter(prefix="/books", tags=["books"])
|
||||
|
||||
|
||||
def close_active_loan(session: Session, book_id: int) -> None:
|
||||
"""Закрывает активную выдачу книги при изменении статуса"""
|
||||
active_loan = session.exec(
|
||||
select(BookUserLink)
|
||||
.where(BookUserLink.book_id == book_id)
|
||||
.where(BookUserLink.returned_at == None) # noqa: E711
|
||||
).first()
|
||||
|
||||
if active_loan:
|
||||
active_loan.returned_at = datetime.utcnow()
|
||||
session.add(active_loan)
|
||||
|
||||
|
||||
@router.get(
|
||||
"/filter",
|
||||
response_model=BookFilteredList,
|
||||
@@ -31,7 +46,7 @@ def filter_books(
|
||||
page: int = Query(1, gt=0, description="Номер страницы"),
|
||||
size: int = Query(20, gt=0, lt=101, description="Количество элементов на странице"),
|
||||
):
|
||||
"""Эндпоинт получения отфильтрованного списка книг"""
|
||||
"""Возвращает отфильтрованный список книг с пагинацией"""
|
||||
statement = select(Book).distinct()
|
||||
|
||||
if q:
|
||||
@@ -72,9 +87,11 @@ def filter_books(
|
||||
description="Добавляет книгу в систему",
|
||||
)
|
||||
def create_book(
|
||||
current_user: RequireAuth, book: BookCreate, session: Session = Depends(get_session)
|
||||
book: BookCreate,
|
||||
current_user: RequireStaff,
|
||||
session: Session = Depends(get_session)
|
||||
):
|
||||
"""Эндпоинт создания книги"""
|
||||
"""Создает новую книгу в системе"""
|
||||
db_book = Book(**book.model_dump())
|
||||
session.add(db_book)
|
||||
session.commit()
|
||||
@@ -89,7 +106,7 @@ def create_book(
|
||||
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)
|
||||
@@ -106,7 +123,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")
|
||||
@@ -137,22 +154,39 @@ def get_book(
|
||||
description="Обновляет информацию о книге в системе",
|
||||
)
|
||||
def update_book(
|
||||
current_user: RequireAuth,
|
||||
book: BookUpdate,
|
||||
book_id: int = Path(..., description="ID книги (целое число, > 0)", gt=0),
|
||||
current_user: RequireStaff,
|
||||
book_update: BookUpdate,
|
||||
book_id: int = Path(..., 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")
|
||||
|
||||
db_book.title = book.title or db_book.title
|
||||
db_book.description = book.description or db_book.description
|
||||
db_book.status = book.status or db_book.status
|
||||
if book_update.status is not None:
|
||||
if book_update.status == BookStatus.BORROWED:
|
||||
raise HTTPException(
|
||||
status_code=400,
|
||||
detail="Статус 'borrowed' устанавливается только через выдачу книги"
|
||||
)
|
||||
|
||||
if db_book.status == BookStatus.BORROWED:
|
||||
close_active_loan(session, book_id)
|
||||
|
||||
db_book.status = book_update.status
|
||||
|
||||
if book_update.title is not None or book_update.description is not None:
|
||||
if book_update.title is not None:
|
||||
db_book.title = book_update.title
|
||||
if book_update.description is not None:
|
||||
db_book.description = book_update.description
|
||||
|
||||
session.add(db_book)
|
||||
session.commit()
|
||||
session.refresh(db_book)
|
||||
return db_book
|
||||
|
||||
return BookRead(**db_book.model_dump())
|
||||
|
||||
|
||||
@router.delete(
|
||||
@@ -162,11 +196,11 @@ def update_book(
|
||||
description="Удаляет книгу их системы",
|
||||
)
|
||||
def delete_book(
|
||||
current_user: RequireAuth,
|
||||
current_user: RequireStaff,
|
||||
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")
|
||||
|
||||
@@ -2,11 +2,12 @@
|
||||
from fastapi import APIRouter, Depends, HTTPException, Path
|
||||
from sqlmodel import Session, select
|
||||
|
||||
from library_service.auth import RequireAuth
|
||||
from library_service.auth import RequireStaff
|
||||
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
|
||||
|
||||
|
||||
router = APIRouter(prefix="/genres", tags=["genres"])
|
||||
|
||||
|
||||
@@ -17,11 +18,11 @@ router = APIRouter(prefix="/genres", tags=["genres"])
|
||||
description="Добавляет жанр книг в систему",
|
||||
)
|
||||
def create_genre(
|
||||
current_user: RequireAuth,
|
||||
current_user: RequireStaff,
|
||||
genre: GenreCreate,
|
||||
session: Session = Depends(get_session),
|
||||
):
|
||||
"""Эндпоинт создания жанра"""
|
||||
"""Создает новый жанр в системе"""
|
||||
db_genre = Genre(**genre.model_dump())
|
||||
session.add(db_genre)
|
||||
session.commit()
|
||||
@@ -36,7 +37,7 @@ def create_genre(
|
||||
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)
|
||||
@@ -53,7 +54,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")
|
||||
@@ -73,16 +74,16 @@ def get_genre(
|
||||
@router.put(
|
||||
"/{genre_id}",
|
||||
response_model=GenreRead,
|
||||
summary="Обновляет информацию о жанре",
|
||||
summary="Обновить информацию о жанре",
|
||||
description="Обновляет информацию о жанре в системе",
|
||||
)
|
||||
def update_genre(
|
||||
current_user: RequireAuth,
|
||||
current_user: RequireStaff,
|
||||
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")
|
||||
@@ -100,14 +101,14 @@ def update_genre(
|
||||
"/{genre_id}",
|
||||
response_model=GenreRead,
|
||||
summary="Удалить жанр",
|
||||
description="Удаляет автора из системы",
|
||||
description="Удаляет жанр из системы",
|
||||
)
|
||||
def delete_genre(
|
||||
current_user: RequireAuth,
|
||||
current_user: RequireStaff,
|
||||
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")
|
||||
|
||||
@@ -0,0 +1,506 @@
|
||||
"""Модуль работы с выдачей и бронированием книг"""
|
||||
from datetime import datetime, timedelta
|
||||
from typing import Dict, List
|
||||
|
||||
from fastapi import APIRouter, Depends, HTTPException, Path, Query, status
|
||||
from fastapi.responses import JSONResponse
|
||||
from sqlmodel import Session, select, col, func
|
||||
from sqlalchemy import cast, Date
|
||||
|
||||
from library_service.auth import RequireAuth, RequireStaff, RequireAdmin, is_user_staff
|
||||
from library_service.settings import get_session
|
||||
from library_service.models.db import Book, User, BookUserLink
|
||||
from library_service.models.dto import LoanCreate, LoanRead, LoanList, LoanUpdate
|
||||
from library_service.models.enums import BookStatus
|
||||
|
||||
|
||||
router = APIRouter(prefix="/loans", tags=["loans"])
|
||||
|
||||
|
||||
@router.post(
|
||||
"/",
|
||||
response_model=LoanRead,
|
||||
summary="Создать выдачу/бронь",
|
||||
description="Создает запись о выдаче или бронировании книги",
|
||||
)
|
||||
def create_loan(
|
||||
current_user: RequireAuth,
|
||||
loan: LoanCreate,
|
||||
session: Session = Depends(get_session),
|
||||
):
|
||||
"""Создает выдачу или бронирование книги"""
|
||||
is_staff = is_user_staff(current_user)
|
||||
|
||||
if not is_staff and loan.user_id != current_user.id:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_403_FORBIDDEN,
|
||||
detail="You can only create loans for yourself"
|
||||
)
|
||||
|
||||
book = session.get(Book, loan.book_id)
|
||||
if not book:
|
||||
raise HTTPException(status_code=404, detail="Book not found")
|
||||
|
||||
if book.status != BookStatus.ACTIVE:
|
||||
raise HTTPException(
|
||||
status_code=400,
|
||||
detail=f"Book is not available for loan (status: {book.status})"
|
||||
)
|
||||
|
||||
target_user = session.get(User, loan.user_id)
|
||||
if not target_user:
|
||||
raise HTTPException(status_code=404, detail="User not found")
|
||||
|
||||
db_loan = BookUserLink(
|
||||
book_id=loan.book_id,
|
||||
user_id=loan.user_id,
|
||||
due_date=loan.due_date,
|
||||
borrowed_at=datetime.utcnow()
|
||||
)
|
||||
|
||||
book.status = BookStatus.RESERVED
|
||||
|
||||
session.add(db_loan)
|
||||
session.add(book)
|
||||
session.commit()
|
||||
session.refresh(db_loan)
|
||||
|
||||
return LoanRead(**db_loan.model_dump())
|
||||
|
||||
|
||||
@router.get(
|
||||
"/",
|
||||
response_model=LoanList,
|
||||
summary="Получить список выдач",
|
||||
description="Возвращает список выдач. Читатели видят только свои. Сотрудники видят все.",
|
||||
)
|
||||
def read_loans(
|
||||
current_user: RequireAuth,
|
||||
session: Session = Depends(get_session),
|
||||
user_id: int | None = Query(None, description="Фильтр по user_ID"),
|
||||
book_id: int | None = Query(None, description="Фильтр по book_ID"),
|
||||
active_only: bool = Query(False, description="Только не возвращенные выдачи"),
|
||||
page: int = Query(1, gt=0, description="Номер страницы"),
|
||||
size: int = Query(20, gt=0, lt=101, description="Элементов на странице"),
|
||||
):
|
||||
"""Возвращает список выдач с фильтрацией и пагинацией"""
|
||||
is_staff = is_user_staff(current_user)
|
||||
|
||||
statement = select(BookUserLink)
|
||||
|
||||
if not is_staff:
|
||||
statement = statement.where(BookUserLink.user_id == current_user.id)
|
||||
elif user_id is not None:
|
||||
statement = statement.where(BookUserLink.user_id == user_id)
|
||||
|
||||
if book_id is not None:
|
||||
statement = statement.where(BookUserLink.book_id == book_id)
|
||||
|
||||
if active_only:
|
||||
statement = statement.where(BookUserLink.returned_at == None) # noqa: E711
|
||||
|
||||
total_statement = select(func.count()).select_from(statement.subquery())
|
||||
total = session.exec(total_statement).one()
|
||||
|
||||
offset = (page - 1) * size
|
||||
statement = statement.order_by(col(BookUserLink.borrowed_at).desc())
|
||||
statement = statement.offset(offset).limit(size)
|
||||
|
||||
loans = session.exec(statement).all()
|
||||
|
||||
return LoanList(
|
||||
loans=[LoanRead(**loan.model_dump()) for loan in loans],
|
||||
total=total
|
||||
)
|
||||
|
||||
|
||||
@router.get(
|
||||
"/analytics",
|
||||
summary="Аналитика выдач и возвратов",
|
||||
description="Возвращает аналитику выдач и возвратов. Только для админов.",
|
||||
)
|
||||
def get_loans_analytics(
|
||||
current_user: RequireAdmin,
|
||||
days: int = Query(30, ge=1, le=365, description="Количество дней для анализа"),
|
||||
session: Session = Depends(get_session),
|
||||
):
|
||||
"""Возвращает аналитику по выдачам и возвратам книг"""
|
||||
end_date = datetime.utcnow()
|
||||
start_date = end_date - timedelta(days=days)
|
||||
total_loans = session.exec(
|
||||
select(func.count(BookUserLink.id))
|
||||
.where(BookUserLink.borrowed_at >= start_date)
|
||||
).one()
|
||||
|
||||
active_loans = session.exec(
|
||||
select(func.count(BookUserLink.id))
|
||||
.where(BookUserLink.borrowed_at >= start_date)
|
||||
.where(BookUserLink.returned_at == None) # noqa: E711
|
||||
).one()
|
||||
|
||||
returned_loans = session.exec(
|
||||
select(func.count(BookUserLink.id))
|
||||
.where(BookUserLink.borrowed_at >= start_date)
|
||||
.where(BookUserLink.returned_at != None) # noqa: E711
|
||||
).one()
|
||||
|
||||
overdue_loans = session.exec(
|
||||
select(func.count(BookUserLink.id))
|
||||
.where(BookUserLink.returned_at == None) # noqa: E711
|
||||
.where(BookUserLink.due_date < end_date)
|
||||
).one()
|
||||
|
||||
daily_loans = {}
|
||||
daily_returns = {}
|
||||
|
||||
loans_by_date = session.exec(
|
||||
select(
|
||||
cast(BookUserLink.borrowed_at, Date).label("date"),
|
||||
func.count(BookUserLink.id).label("count")
|
||||
)
|
||||
.where(BookUserLink.borrowed_at >= start_date)
|
||||
.group_by(cast(BookUserLink.borrowed_at, Date))
|
||||
.order_by(cast(BookUserLink.borrowed_at, Date))
|
||||
).all()
|
||||
|
||||
returns_by_date = session.exec(
|
||||
select(
|
||||
cast(BookUserLink.returned_at, Date).label("date"),
|
||||
func.count(BookUserLink.id).label("count")
|
||||
)
|
||||
.where(BookUserLink.returned_at >= start_date)
|
||||
.where(BookUserLink.returned_at != None) # noqa: E711
|
||||
.group_by(cast(BookUserLink.returned_at, Date))
|
||||
.order_by(cast(BookUserLink.returned_at, Date))
|
||||
).all()
|
||||
|
||||
for row in loans_by_date:
|
||||
date_str = str(row[0]) if isinstance(row, tuple) else str(row.date)
|
||||
count = row[1] if isinstance(row, tuple) else row.count
|
||||
daily_loans[date_str] = count
|
||||
|
||||
for row in returns_by_date:
|
||||
date_str = str(row[0]) if isinstance(row, tuple) else str(row.date)
|
||||
count = row[1] if isinstance(row, tuple) else row.count
|
||||
daily_returns[date_str] = count
|
||||
|
||||
top_books = session.exec(
|
||||
select(
|
||||
BookUserLink.book_id,
|
||||
func.count(BookUserLink.id).label("loan_count")
|
||||
)
|
||||
.where(BookUserLink.borrowed_at >= start_date)
|
||||
.group_by(BookUserLink.book_id)
|
||||
.order_by(func.count(BookUserLink.id).desc())
|
||||
.limit(10)
|
||||
).all()
|
||||
|
||||
top_books_data = []
|
||||
for row in top_books:
|
||||
book_id = row[0] if isinstance(row, tuple) else row.book_id
|
||||
loan_count = row[1] if isinstance(row, tuple) else row.loan_count
|
||||
book = session.get(Book, book_id)
|
||||
if book:
|
||||
top_books_data.append({
|
||||
"book_id": book_id,
|
||||
"title": book.title,
|
||||
"loan_count": loan_count
|
||||
})
|
||||
|
||||
reserved_count = session.exec(
|
||||
select(func.count(Book.id))
|
||||
.where(Book.status == BookStatus.RESERVED)
|
||||
).one()
|
||||
|
||||
borrowed_count = session.exec(
|
||||
select(func.count(Book.id))
|
||||
.where(Book.status == BookStatus.BORROWED)
|
||||
).one()
|
||||
|
||||
return JSONResponse(content={
|
||||
"summary": {
|
||||
"total_loans": total_loans,
|
||||
"active_loans": active_loans,
|
||||
"returned_loans": returned_loans,
|
||||
"overdue_loans": overdue_loans,
|
||||
"reserved_books": reserved_count,
|
||||
"borrowed_books": borrowed_count,
|
||||
},
|
||||
"daily_loans": daily_loans,
|
||||
"daily_returns": daily_returns,
|
||||
"top_books": top_books_data,
|
||||
"period_days": days,
|
||||
"start_date": start_date.isoformat(),
|
||||
"end_date": end_date.isoformat(),
|
||||
})
|
||||
|
||||
|
||||
@router.get(
|
||||
"/{loan_id}",
|
||||
response_model=LoanRead,
|
||||
summary="Получить выдачу по ID",
|
||||
description="Возвращает выдачу по ID",
|
||||
)
|
||||
def get_loan(
|
||||
current_user: RequireAuth,
|
||||
loan_id: int = Path(..., description="Loan ID (integer, > 0)", gt=0),
|
||||
session: Session = Depends(get_session),
|
||||
):
|
||||
"""Возвращает информацию о выдаче по ID"""
|
||||
loan = session.get(BookUserLink, loan_id)
|
||||
|
||||
if not loan:
|
||||
raise HTTPException(status_code=404, detail="Loan not found")
|
||||
|
||||
is_staff = is_user_staff(current_user)
|
||||
|
||||
if not is_staff and loan.user_id != current_user.id:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_403_FORBIDDEN,
|
||||
detail="Access denied to this loan"
|
||||
)
|
||||
|
||||
return LoanRead(**loan.model_dump())
|
||||
|
||||
|
||||
@router.put(
|
||||
"/{loan_id}",
|
||||
response_model=LoanRead,
|
||||
summary="Обновить выдачу",
|
||||
description="Обновляет информацию о выдаче. Сотрудники могут обновлять любые, читатели только свои.",
|
||||
)
|
||||
def update_loan(
|
||||
current_user: RequireAuth,
|
||||
loan_update: LoanUpdate,
|
||||
loan_id: int = Path(..., description="Loan ID (integer, > 0)", gt=0),
|
||||
session: Session = Depends(get_session),
|
||||
):
|
||||
"""Обновляет информацию о выдаче"""
|
||||
db_loan = session.get(BookUserLink, loan_id)
|
||||
if not db_loan:
|
||||
raise HTTPException(status_code=404, detail="Loan not found")
|
||||
|
||||
is_staff = is_user_staff(current_user)
|
||||
|
||||
if not is_staff and db_loan.user_id != current_user.id:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_403_FORBIDDEN,
|
||||
detail="You can only update your own loans"
|
||||
)
|
||||
|
||||
book = session.get(Book, db_loan.book_id)
|
||||
if not book:
|
||||
raise HTTPException(status_code=404, detail="Book not found")
|
||||
|
||||
if loan_update.user_id is not None:
|
||||
if not is_staff:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_403_FORBIDDEN,
|
||||
detail="Only staff can change loan user"
|
||||
)
|
||||
new_user = session.get(User, loan_update.user_id)
|
||||
if not new_user:
|
||||
raise HTTPException(status_code=404, detail="User not found")
|
||||
db_loan.user_id = loan_update.user_id
|
||||
|
||||
if loan_update.due_date is not None:
|
||||
db_loan.due_date = loan_update.due_date
|
||||
|
||||
if loan_update.returned_at is not None:
|
||||
if db_loan.returned_at is not None:
|
||||
raise HTTPException(
|
||||
status_code=400,
|
||||
detail="Loan is already returned"
|
||||
)
|
||||
db_loan.returned_at = loan_update.returned_at
|
||||
book.status = BookStatus.ACTIVE
|
||||
|
||||
session.add(db_loan)
|
||||
session.add(book)
|
||||
session.commit()
|
||||
session.refresh(db_loan)
|
||||
|
||||
return LoanRead(**db_loan.model_dump())
|
||||
|
||||
|
||||
@router.post(
|
||||
"/{loan_id}/confirm",
|
||||
response_model=LoanRead,
|
||||
summary="Подтвердить бронь",
|
||||
description="Подтверждает бронирование и меняет статус книги на BORROWED",
|
||||
)
|
||||
def confirm_loan(
|
||||
current_user: RequireStaff,
|
||||
loan_id: int = Path(..., description="Loan ID (integer, > 0)", gt=0),
|
||||
session: Session = Depends(get_session),
|
||||
):
|
||||
"""Подтверждает бронирование и меняет статус книги на BORROWED"""
|
||||
loan = session.get(BookUserLink, loan_id)
|
||||
if not loan:
|
||||
raise HTTPException(status_code=404, detail="Loan not found")
|
||||
|
||||
if loan.returned_at:
|
||||
raise HTTPException(status_code=400, detail="Loan is already returned")
|
||||
|
||||
book = session.get(Book, loan.book_id)
|
||||
if not book:
|
||||
raise HTTPException(status_code=404, detail="Book not found")
|
||||
|
||||
if book.status not in [BookStatus.RESERVED, BookStatus.ACTIVE]:
|
||||
raise HTTPException(
|
||||
status_code=400,
|
||||
detail=f"Cannot confirm loan for book with status: {book.status}"
|
||||
)
|
||||
|
||||
book.status = BookStatus.BORROWED
|
||||
|
||||
session.add(loan)
|
||||
session.add(book)
|
||||
session.commit()
|
||||
session.refresh(loan)
|
||||
|
||||
return LoanRead(**loan.model_dump())
|
||||
|
||||
|
||||
@router.post(
|
||||
"/{loan_id}/return",
|
||||
response_model=LoanRead,
|
||||
summary="Вернуть книгу",
|
||||
description="Возвращает книгу и закрывает выдачу",
|
||||
)
|
||||
def return_loan(
|
||||
current_user: RequireStaff,
|
||||
loan_id: int = Path(..., description="Loan ID (integer, > 0)", gt=0),
|
||||
session: Session = Depends(get_session),
|
||||
):
|
||||
"""Возвращает книгу и закрывает выдачу"""
|
||||
loan = session.get(BookUserLink, loan_id)
|
||||
if not loan:
|
||||
raise HTTPException(status_code=404, detail="Loan not found")
|
||||
|
||||
if loan.returned_at:
|
||||
raise HTTPException(status_code=400, detail="Loan is already returned")
|
||||
|
||||
loan.returned_at = datetime.utcnow()
|
||||
|
||||
book = session.get(Book, loan.book_id)
|
||||
if book:
|
||||
book.status = BookStatus.ACTIVE
|
||||
session.add(book)
|
||||
|
||||
session.add(loan)
|
||||
session.commit()
|
||||
session.refresh(loan)
|
||||
|
||||
return LoanRead(**loan.model_dump())
|
||||
|
||||
|
||||
@router.delete(
|
||||
"/{loan_id}",
|
||||
response_model=LoanRead,
|
||||
summary="Удалить выдачу/бронь",
|
||||
description="Удаляет выдачу/бронь. Работает только для статуса RESERVED.",
|
||||
)
|
||||
def delete_loan(
|
||||
current_user: RequireAuth,
|
||||
loan_id: int = Path(..., description="Loan ID (integer, > 0)", gt=0),
|
||||
session: Session = Depends(get_session),
|
||||
):
|
||||
"""Удаляет выдачу или бронирование (только для RESERVED статуса)"""
|
||||
loan = session.get(BookUserLink, loan_id)
|
||||
if not loan:
|
||||
raise HTTPException(status_code=404, detail="Loan not found")
|
||||
|
||||
is_staff = is_user_staff(current_user)
|
||||
|
||||
if not is_staff and loan.user_id != current_user.id:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_403_FORBIDDEN,
|
||||
detail="You can only delete your own loans"
|
||||
)
|
||||
|
||||
book = session.get(Book, loan.book_id)
|
||||
|
||||
if book and book.status != BookStatus.RESERVED:
|
||||
raise HTTPException(
|
||||
status_code=400,
|
||||
detail="Can only delete reservations. Use update endpoint to return borrowed books"
|
||||
)
|
||||
|
||||
loan_read = LoanRead(**loan.model_dump())
|
||||
session.delete(loan)
|
||||
|
||||
if book:
|
||||
book.status = BookStatus.ACTIVE
|
||||
session.add(book)
|
||||
|
||||
session.commit()
|
||||
|
||||
return loan_read
|
||||
|
||||
|
||||
@router.get(
|
||||
"/book/{book_id}/active",
|
||||
response_model=LoanRead | None,
|
||||
summary="Получить активную выдачу книги",
|
||||
description="Возвращает активную выдачу для указанной книги",
|
||||
)
|
||||
def get_active_loan_for_book(
|
||||
current_user: RequireStaff,
|
||||
book_id: int = Path(..., description="Book ID (integer, > 0)", gt=0),
|
||||
session: Session = Depends(get_session),
|
||||
):
|
||||
"""Возвращает активную выдачу для указанной книги"""
|
||||
loan = session.exec(
|
||||
select(BookUserLink)
|
||||
.where(BookUserLink.book_id == book_id)
|
||||
.where(BookUserLink.returned_at == None) # noqa: E711
|
||||
).first()
|
||||
|
||||
if not loan:
|
||||
return None
|
||||
|
||||
return LoanRead(**loan.model_dump())
|
||||
|
||||
|
||||
@router.post(
|
||||
"/issue",
|
||||
response_model=LoanRead,
|
||||
summary="Выдать книгу напрямую",
|
||||
description="Только для администраторов. Создает выдачу и устанавливает статус книги на BORROWED.",
|
||||
)
|
||||
def issue_book_directly(
|
||||
current_user: RequireAdmin,
|
||||
loan: LoanCreate,
|
||||
session: Session = Depends(get_session),
|
||||
):
|
||||
"""Выдает книгу напрямую без бронирования (только для администраторов)"""
|
||||
book = session.get(Book, loan.book_id)
|
||||
if not book:
|
||||
raise HTTPException(status_code=404, detail="Book not found")
|
||||
|
||||
if book.status != BookStatus.ACTIVE:
|
||||
raise HTTPException(
|
||||
status_code=400,
|
||||
detail=f"Book is not available (status: {book.status})"
|
||||
)
|
||||
|
||||
target_user = session.get(User, loan.user_id)
|
||||
if not target_user:
|
||||
raise HTTPException(status_code=404, detail="User not found")
|
||||
|
||||
db_loan = BookUserLink(
|
||||
book_id=loan.book_id,
|
||||
user_id=loan.user_id,
|
||||
due_date=loan.due_date,
|
||||
borrowed_at=datetime.utcnow()
|
||||
)
|
||||
|
||||
book.status = BookStatus.BORROWED
|
||||
|
||||
session.add(db_loan)
|
||||
session.add(book)
|
||||
session.commit()
|
||||
session.refresh(db_loan)
|
||||
|
||||
return LoanRead(**db_loan.model_dump())
|
||||
@@ -18,7 +18,7 @@ templates = Jinja2Templates(directory=Path(__file__).parent.parent / "templates"
|
||||
|
||||
|
||||
def get_info(app) -> Dict:
|
||||
"""Форматированная информация о приложении"""
|
||||
"""Возвращает информацию о приложении"""
|
||||
return {
|
||||
"status": "ok",
|
||||
"app_info": {
|
||||
@@ -32,103 +32,115 @@ def get_info(app) -> Dict:
|
||||
|
||||
@router.get("/", include_in_schema=False)
|
||||
async def root(request: Request):
|
||||
"""Эндпоинт главной страницы"""
|
||||
"""Рендерит главную страницу"""
|
||||
return templates.TemplateResponse(request, "index.html")
|
||||
|
||||
|
||||
@router.get("/genre/create", include_in_schema=False)
|
||||
async def create_genre(request: Request):
|
||||
"""Эндпоинт страницы создания жанра"""
|
||||
"""Рендерит страницу создания жанра"""
|
||||
return templates.TemplateResponse(request, "create_genre.html")
|
||||
|
||||
|
||||
@router.get("/genre/{genre_id}/edit", include_in_schema=False)
|
||||
async def edit_genre(request: Request, genre_id: int):
|
||||
"""Эндпоинт страницы редактирования жанра"""
|
||||
"""Рендерит страницу редактирования жанра"""
|
||||
return templates.TemplateResponse(request, "edit_genre.html")
|
||||
|
||||
|
||||
@router.get("/authors", include_in_schema=False)
|
||||
async def authors(request: Request):
|
||||
"""Эндпоинт страницы выбора автора"""
|
||||
"""Рендерит страницу списка авторов"""
|
||||
return templates.TemplateResponse(request, "authors.html")
|
||||
|
||||
|
||||
@router.get("/author/create", include_in_schema=False)
|
||||
async def create_author(request: Request):
|
||||
"""Эндпоинт страницы создания автора"""
|
||||
"""Рендерит страницу создания автора"""
|
||||
return templates.TemplateResponse(request, "create_author.html")
|
||||
|
||||
|
||||
@router.get("/author/{author_id}/edit", include_in_schema=False)
|
||||
async def edit_author(request: Request, author_id: int):
|
||||
"""Эндпоинт страницы редактирования автора"""
|
||||
"""Рендерит страницу редактирования автора"""
|
||||
return templates.TemplateResponse(request, "edit_author.html")
|
||||
|
||||
|
||||
@router.get("/author/{author_id}", include_in_schema=False)
|
||||
async def author(request: Request, author_id: int):
|
||||
"""Эндпоинт страницы автора"""
|
||||
"""Рендерит страницу просмотра автора"""
|
||||
return templates.TemplateResponse(request, "author.html")
|
||||
|
||||
|
||||
@router.get("/books", include_in_schema=False)
|
||||
async def books(request: Request):
|
||||
"""Эндпоинт страницы выбора книг"""
|
||||
"""Рендерит страницу списка книг"""
|
||||
return templates.TemplateResponse(request, "books.html")
|
||||
|
||||
|
||||
@router.get("/book/create", include_in_schema=False)
|
||||
async def create_book(request: Request):
|
||||
"""Эндпоинт страницы создания книги"""
|
||||
"""Рендерит страницу создания книги"""
|
||||
return templates.TemplateResponse(request, "create_book.html")
|
||||
|
||||
|
||||
@router.get("/book/{book_id}/edit", include_in_schema=False)
|
||||
async def edit_book(request: Request, book_id: int):
|
||||
"""Эндпоинт страницы редактирования книги"""
|
||||
"""Рендерит страницу редактирования книги"""
|
||||
return templates.TemplateResponse(request, "edit_book.html")
|
||||
|
||||
|
||||
@router.get("/book/{book_id}", include_in_schema=False)
|
||||
async def book(request: Request, book_id: int):
|
||||
"""Эндпоинт страницы книги"""
|
||||
"""Рендерит страницу просмотра книги"""
|
||||
return templates.TemplateResponse(request, "book.html")
|
||||
|
||||
|
||||
@router.get("/auth", include_in_schema=False)
|
||||
async def auth(request: Request):
|
||||
"""Эндпоинт страницы авторизации"""
|
||||
"""Рендерит страницу авторизации"""
|
||||
return templates.TemplateResponse(request, "auth.html")
|
||||
|
||||
|
||||
@router.get("/profile", include_in_schema=False)
|
||||
async def profile(request: Request):
|
||||
"""Эндпоинт страницы профиля"""
|
||||
"""Рендерит страницу профиля пользователя"""
|
||||
return templates.TemplateResponse(request, "profile.html")
|
||||
|
||||
|
||||
@router.get("/users", include_in_schema=False)
|
||||
async def users(request: Request):
|
||||
"""Эндпоинт страницы управления пользователями"""
|
||||
"""Рендерит страницу управления пользователями"""
|
||||
return templates.TemplateResponse(request, "users.html")
|
||||
|
||||
|
||||
@router.get("/my-books", include_in_schema=False)
|
||||
async def my_books(request: Request):
|
||||
"""Рендерит страницу моих книг пользователя"""
|
||||
return templates.TemplateResponse(request, "my_books.html")
|
||||
|
||||
|
||||
@router.get("/analytics", include_in_schema=False)
|
||||
async def analytics(request: Request):
|
||||
"""Рендерит страницу аналитики выдач"""
|
||||
return templates.TemplateResponse(request, "analytics.html")
|
||||
|
||||
|
||||
@router.get("/api", include_in_schema=False)
|
||||
async def api(request: Request, app=Depends(lambda: get_app())):
|
||||
"""Страница с сылками на документацию API"""
|
||||
"""Рендерит страницу с ссылками на документацию API"""
|
||||
return templates.TemplateResponse(request, "api.html", get_info(app))
|
||||
|
||||
|
||||
@router.get("/favicon.ico", include_in_schema=False)
|
||||
def redirect_favicon():
|
||||
"""Редирект иконки вкладки"""
|
||||
"""Редиректит на favicon.svg"""
|
||||
return RedirectResponse("/favicon.svg")
|
||||
|
||||
|
||||
@router.get("/favicon.svg", include_in_schema=False)
|
||||
async def favicon():
|
||||
"""Эндпоинт иконки вкладки"""
|
||||
"""Возвращает иконку сайта"""
|
||||
return FileResponse(
|
||||
"library_service/static/favicon.svg", media_type="image/svg+xml"
|
||||
)
|
||||
@@ -140,7 +152,7 @@ async def favicon():
|
||||
description="Возвращает общую информацию о системе",
|
||||
)
|
||||
async def api_info(app=Depends(lambda: get_app())):
|
||||
"""Эндпоинт информации об API"""
|
||||
"""Возвращает информацию о сервисе"""
|
||||
return JSONResponse(content=get_info(app))
|
||||
|
||||
|
||||
@@ -150,7 +162,7 @@ async def api_info(app=Depends(lambda: get_app())):
|
||||
description="Возвращает статистическую информацию о системе",
|
||||
)
|
||||
async def api_stats(session: Session = Depends(get_session)):
|
||||
"""Эндпоинт стстистики системы"""
|
||||
"""Возвращает статистику системы"""
|
||||
authors = select(func.count()).select_from(Author)
|
||||
books = select(func.count()).select_from(Book)
|
||||
genres = select(func.count()).select_from(Genre)
|
||||
|
||||
@@ -4,7 +4,7 @@ from typing import Dict, List
|
||||
from fastapi import APIRouter, Depends, HTTPException
|
||||
from sqlmodel import Session, select
|
||||
|
||||
from library_service.auth import RequireAuth
|
||||
from library_service.auth import RequireStaff
|
||||
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
|
||||
@@ -14,7 +14,7 @@ router = APIRouter(tags=["relations"])
|
||||
|
||||
|
||||
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")
|
||||
@@ -22,7 +22,7 @@ def check_entity_exists(session, model, entity_id, entity_name):
|
||||
|
||||
|
||||
def add_relationship(session, link_model, id1, field1, id2, field2, detail):
|
||||
"""Создание связи между сущностями в БД"""
|
||||
"""Создает связь между сущностями в базе данных"""
|
||||
existing_link = session.exec(
|
||||
select(link_model)
|
||||
.where(getattr(link_model, field1) == id1)
|
||||
@@ -40,7 +40,7 @@ def add_relationship(session, link_model, id1, field1, id2, field2, detail):
|
||||
|
||||
|
||||
def remove_relationship(session, link_model, id1, field1, id2, field2):
|
||||
"""Удаление связи между сущностями в БД"""
|
||||
"""Удаляет связь между сущностями в базе данных"""
|
||||
link = session.exec(
|
||||
select(link_model)
|
||||
.where(getattr(link_model, field1) == id1)
|
||||
@@ -66,7 +66,7 @@ def get_related(
|
||||
link_related_field,
|
||||
read_model
|
||||
):
|
||||
"""Получение связанных в БД сущностей"""
|
||||
"""Возвращает список связанных сущностей"""
|
||||
check_entity_exists(session, main_model, main_id, main_name)
|
||||
|
||||
related = session.exec(
|
||||
@@ -84,12 +84,12 @@ def get_related(
|
||||
description="Добавляет связь между автором и книгой в систему",
|
||||
)
|
||||
def add_author_to_book(
|
||||
current_user: RequireAuth,
|
||||
current_user: RequireStaff,
|
||||
author_id: int,
|
||||
book_id: int,
|
||||
session: Session = Depends(get_session),
|
||||
):
|
||||
"""Эндпоинт добавления автора к книге"""
|
||||
"""Добавляет связь между автором и книгой"""
|
||||
check_entity_exists(session, Author, author_id, "Author")
|
||||
check_entity_exists(session, Book, book_id, "Book")
|
||||
|
||||
@@ -104,12 +104,12 @@ def add_author_to_book(
|
||||
description="Удаляет связь между автором и книгой в системе",
|
||||
)
|
||||
def remove_author_from_book(
|
||||
current_user: RequireAuth,
|
||||
current_user: RequireStaff,
|
||||
author_id: int,
|
||||
book_id: int,
|
||||
session: Session = Depends(get_session),
|
||||
):
|
||||
"""Эндпоинт удаления автора из книги"""
|
||||
"""Удаляет связь между автором и книгой"""
|
||||
return remove_relationship(session, AuthorBookLink,
|
||||
author_id, "author_id", book_id, "book_id")
|
||||
|
||||
@@ -121,7 +121,7 @@ def remove_author_from_book(
|
||||
description="Возвращает все книги в системе, написанные автором",
|
||||
)
|
||||
def get_books_for_author(author_id: int, session: Session = Depends(get_session)):
|
||||
"""Эндпоинт получения книг, написанных автором"""
|
||||
"""Возвращает список книг автора"""
|
||||
return get_related(session,
|
||||
Author, author_id, "Author", Book,
|
||||
AuthorBookLink, "author_id", "book_id", BookRead)
|
||||
@@ -134,7 +134,7 @@ 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)):
|
||||
"""Эндпоинт получения авторов книги"""
|
||||
"""Возвращает список авторов книги"""
|
||||
return get_related(session,
|
||||
Book, book_id, "Book", Author,
|
||||
AuthorBookLink, "book_id", "author_id", AuthorRead)
|
||||
@@ -147,12 +147,12 @@ def get_authors_for_book(book_id: int, session: Session = Depends(get_session)):
|
||||
description="Добавляет связь между книгой и жанром в систему",
|
||||
)
|
||||
def add_genre_to_book(
|
||||
current_user: RequireAuth,
|
||||
current_user: RequireStaff,
|
||||
genre_id: int,
|
||||
book_id: int,
|
||||
session: Session = Depends(get_session),
|
||||
):
|
||||
"""Эндпоинт добавления жанра к книге"""
|
||||
"""Добавляет связь между жанром и книгой"""
|
||||
check_entity_exists(session, Genre, genre_id, "Genre")
|
||||
check_entity_exists(session, Book, book_id, "Book")
|
||||
|
||||
@@ -167,12 +167,12 @@ def add_genre_to_book(
|
||||
description="Удаляет связь между жанром и книгой в системе",
|
||||
)
|
||||
def remove_genre_from_book(
|
||||
current_user: RequireAuth,
|
||||
current_user: RequireStaff,
|
||||
genre_id: int,
|
||||
book_id: int,
|
||||
session: Session = Depends(get_session),
|
||||
):
|
||||
"""Эндпоинт удаления жанра из книги"""
|
||||
"""Удаляет связь между жанром и книгой"""
|
||||
return remove_relationship(session, GenreBookLink,
|
||||
genre_id, "genre_id", book_id, "book_id")
|
||||
|
||||
@@ -184,7 +184,7 @@ def remove_genre_from_book(
|
||||
description="Возвращает все книги в системе в этом жанре",
|
||||
)
|
||||
def get_books_for_genre(genre_id: int, session: Session = Depends(get_session)):
|
||||
"""Эндпоинт получения книг с жанром"""
|
||||
"""Возвращает список книг в жанре"""
|
||||
return get_related(session,
|
||||
Genre, genre_id, "Genre", Book,
|
||||
GenreBookLink, "genre_id", "book_id", BookRead)
|
||||
@@ -197,7 +197,7 @@ def get_books_for_genre(genre_id: int, session: Session = Depends(get_session)):
|
||||
description="Возвращает все жанры книги в системе",
|
||||
)
|
||||
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)
|
||||
|
||||
Reference in New Issue
Block a user