mirror of
https://github.com/wowlikon/LibraryAPI.git
synced 2025-12-11 21:30:46 +00:00
Форматирование кода, добавление лого, исправление тестов, улучшение эндпоинтов и документации
This commit is contained in:
@@ -1,6 +0,0 @@
|
||||
from fastapi import APIRouter
|
||||
import asyncpg
|
||||
|
||||
router = APIRouter(
|
||||
prefix='/devices'
|
||||
)
|
||||
1
library_service/favicon.svg
Normal file
1
library_service/favicon.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg width="100" height="100" xmlns="http://www.w3.org/2000/svg"><rect x="10" y="10" width="80" height="80" rx="4" ry="4" fill="#fff" stroke="#000" stroke-width="2"/><rect x="20" y="15" width="60" height="70" rx="10" ry="10"/><rect x="20" y="15" width="60" height="66" rx="10" ry="10" fill="#fff"/><rect x="20" y="15" width="60" height="62" rx="10" ry="10"/><rect x="20" y="15" width="60" height="60" rx="10" ry="10" fill="#fff"/><rect x="20" y="15" width="60" height="56" rx="10" ry="10"/><rect x="20" y="15" width="60" height="54" rx="10" ry="10" fill="#fff"/><rect x="20" y="15" width="60" height="50" rx="10" ry="10"/><rect x="20" y="15" width="60" height="48" rx="10" ry="10" fill="#fff"/><rect x="20" y="15" width="60" height="44" rx="10" ry="10"/><rect x="22" y="21" width="2" height="58" rx="10" ry="10" stroke="#000" stroke-width="4"/><rect x="22" y="55" width="4" height="26" rx="2" ry="15"/><text x="50" y="40" text-anchor="middle" dominant-baseline="middle" alignment-baseline="middle" stroke="#fff" stroke-width=".5" fill="none" font-size="20">『LiB』</text></svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
@@ -2,16 +2,24 @@ from .author import Author
|
||||
from .book import Book
|
||||
from .genre import Genre
|
||||
from .links import (
|
||||
AuthorBookLink, GenreBookLink,
|
||||
AuthorWithBooks, BookWithAuthors,
|
||||
GenreWithBooks, BookWithGenres,
|
||||
BookWithAuthorsAndGenres
|
||||
AuthorBookLink,
|
||||
GenreBookLink,
|
||||
AuthorWithBooks,
|
||||
BookWithAuthors,
|
||||
GenreWithBooks,
|
||||
BookWithGenres,
|
||||
BookWithAuthorsAndGenres,
|
||||
)
|
||||
|
||||
__all__ = [
|
||||
'Author', 'Book', 'Genre',
|
||||
'AuthorBookLink', 'AuthorWithBooks',
|
||||
'BookWithAuthors', 'GenreBookLink',
|
||||
'GenreWithBooks', 'BookWithGenres',
|
||||
'BookWithAuthorsAndGenres'
|
||||
"Author",
|
||||
"Book",
|
||||
"Genre",
|
||||
"AuthorBookLink",
|
||||
"AuthorWithBooks",
|
||||
"BookWithAuthors",
|
||||
"GenreBookLink",
|
||||
"GenreWithBooks",
|
||||
"BookWithGenres",
|
||||
"BookWithAuthorsAndGenres",
|
||||
]
|
||||
|
||||
@@ -6,9 +6,9 @@ from .links import AuthorBookLink
|
||||
if TYPE_CHECKING:
|
||||
from .book import Book
|
||||
|
||||
|
||||
class Author(AuthorBase, table=True):
|
||||
id: Optional[int] = Field(default=None, primary_key=True, index=True)
|
||||
books: List["Book"] = Relationship(
|
||||
back_populates="authors",
|
||||
link_model=AuthorBookLink
|
||||
back_populates="authors", link_model=AuthorBookLink
|
||||
)
|
||||
|
||||
@@ -7,13 +7,12 @@ if TYPE_CHECKING:
|
||||
from .author import Author
|
||||
from .genre import Genre
|
||||
|
||||
|
||||
class Book(BookBase, table=True):
|
||||
id: Optional[int] = Field(default=None, primary_key=True, index=True)
|
||||
authors: List["Author"] = Relationship(
|
||||
back_populates="books",
|
||||
link_model=AuthorBookLink
|
||||
back_populates="books", link_model=AuthorBookLink
|
||||
)
|
||||
genres: List["Genre"] = Relationship(
|
||||
back_populates="books",
|
||||
link_model=GenreBookLink
|
||||
back_populates="books", link_model=GenreBookLink
|
||||
)
|
||||
|
||||
@@ -6,9 +6,9 @@ from .links import GenreBookLink
|
||||
if TYPE_CHECKING:
|
||||
from .book import Book
|
||||
|
||||
|
||||
class Genre(GenreBase, table=True):
|
||||
id: Optional[int] = Field(default=None, primary_key=True, index=True)
|
||||
books: List["Book"] = Relationship(
|
||||
back_populates="genres",
|
||||
link_model=GenreBookLink
|
||||
back_populates="genres", link_model=GenreBookLink
|
||||
)
|
||||
|
||||
@@ -5,26 +5,35 @@ from library_service.models.dto.author import AuthorRead
|
||||
from library_service.models.dto.book import BookRead
|
||||
from library_service.models.dto.genre import GenreRead
|
||||
|
||||
|
||||
class AuthorBookLink(SQLModel, table=True):
|
||||
author_id: int | None = Field(default=None, foreign_key="author.id", primary_key=True)
|
||||
author_id: int | None = Field(
|
||||
default=None, foreign_key="author.id", primary_key=True
|
||||
)
|
||||
book_id: int | None = Field(default=None, foreign_key="book.id", primary_key=True)
|
||||
|
||||
|
||||
class GenreBookLink(SQLModel, table=True):
|
||||
genre_id: int | None = Field(default=None, foreign_key="genre.id", primary_key=True)
|
||||
book_id: int | None = Field(default=None, foreign_key="book.id", primary_key=True)
|
||||
|
||||
|
||||
class AuthorWithBooks(AuthorRead):
|
||||
books: List[BookRead] = Field(default_factory=list)
|
||||
|
||||
|
||||
class BookWithAuthors(BookRead):
|
||||
authors: List[AuthorRead] = Field(default_factory=list)
|
||||
|
||||
|
||||
class BookWithGenres(BookRead):
|
||||
genres: List[GenreRead] = Field(default_factory=list)
|
||||
|
||||
|
||||
class GenreWithBooks(GenreRead):
|
||||
books: List[BookRead] = Field(default_factory=list)
|
||||
|
||||
|
||||
class BookWithAuthorsAndGenres(BookRead):
|
||||
authors: List[AuthorRead] = Field(default_factory=list)
|
||||
genres: List[GenreRead] = Field(default_factory=list)
|
||||
|
||||
@@ -1,19 +1,22 @@
|
||||
from .author import (
|
||||
AuthorBase, AuthorCreate, AuthorUpdate,
|
||||
AuthorRead, AuthorList
|
||||
)
|
||||
from .book import (
|
||||
BookBase, BookCreate, BookUpdate,
|
||||
BookRead, BookList
|
||||
)
|
||||
from .author import AuthorBase, AuthorCreate, AuthorUpdate, AuthorRead, AuthorList
|
||||
from .book import BookBase, BookCreate, BookUpdate, BookRead, BookList
|
||||
|
||||
from .genre import (
|
||||
GenreBase, GenreCreate, GenreUpdate,
|
||||
GenreRead, GenreList
|
||||
)
|
||||
from .genre import GenreBase, GenreCreate, GenreUpdate, GenreRead, GenreList
|
||||
|
||||
__all__ = [
|
||||
'AuthorBase', 'AuthorCreate', 'AuthorUpdate', 'AuthorRead', 'AuthorList',
|
||||
'BookBase', 'BookCreate', 'BookUpdate', 'BookRead', 'BookList',
|
||||
'GenreBase', 'GenreCreate', 'GenreUpdate', 'GenreRead', 'GenreList',
|
||||
"AuthorBase",
|
||||
"AuthorCreate",
|
||||
"AuthorUpdate",
|
||||
"AuthorRead",
|
||||
"AuthorList",
|
||||
"BookBase",
|
||||
"BookCreate",
|
||||
"BookUpdate",
|
||||
"BookRead",
|
||||
"BookList",
|
||||
"GenreBase",
|
||||
"GenreCreate",
|
||||
"GenreUpdate",
|
||||
"GenreRead",
|
||||
"GenreList",
|
||||
]
|
||||
|
||||
@@ -2,24 +2,27 @@ from sqlmodel import SQLModel
|
||||
from pydantic import ConfigDict
|
||||
from typing import Optional, List
|
||||
|
||||
|
||||
class AuthorBase(SQLModel):
|
||||
name: str
|
||||
|
||||
model_config = ConfigDict( #pyright: ignore
|
||||
json_schema_extra={
|
||||
"example": {"name": "author_name"}
|
||||
}
|
||||
model_config = ConfigDict( # pyright: ignore
|
||||
json_schema_extra={"example": {"name": "author_name"}}
|
||||
)
|
||||
|
||||
|
||||
class AuthorCreate(AuthorBase):
|
||||
pass
|
||||
|
||||
|
||||
class AuthorUpdate(SQLModel):
|
||||
name: Optional[str] = None
|
||||
|
||||
|
||||
class AuthorRead(AuthorBase):
|
||||
id: int
|
||||
|
||||
|
||||
class AuthorList(SQLModel):
|
||||
authors: List[AuthorRead]
|
||||
total: int
|
||||
|
||||
@@ -2,29 +2,31 @@ from sqlmodel import SQLModel
|
||||
from pydantic import ConfigDict
|
||||
from typing import Optional, List
|
||||
|
||||
|
||||
class BookBase(SQLModel):
|
||||
title: str
|
||||
description: str
|
||||
|
||||
model_config = ConfigDict( #pyright: ignore
|
||||
model_config = ConfigDict( # pyright: ignore
|
||||
json_schema_extra={
|
||||
"example": {
|
||||
"title": "book_title",
|
||||
"description": "book_description"
|
||||
}
|
||||
"example": {"title": "book_title", "description": "book_description"}
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
class BookCreate(BookBase):
|
||||
pass
|
||||
|
||||
|
||||
class BookUpdate(SQLModel):
|
||||
title: Optional[str] = None
|
||||
description: Optional[str] = None
|
||||
|
||||
|
||||
class BookRead(BookBase):
|
||||
id: int
|
||||
|
||||
|
||||
class BookList(SQLModel):
|
||||
books: List[BookRead]
|
||||
total: int
|
||||
|
||||
@@ -2,24 +2,27 @@ from sqlmodel import SQLModel
|
||||
from pydantic import ConfigDict
|
||||
from typing import Optional, List
|
||||
|
||||
|
||||
class GenreBase(SQLModel):
|
||||
name: str
|
||||
|
||||
model_config = ConfigDict( #pyright: ignore
|
||||
json_schema_extra={
|
||||
"example": {"name": "genre_name"}
|
||||
}
|
||||
model_config = ConfigDict( # pyright: ignore
|
||||
json_schema_extra={"example": {"name": "genre_name"}}
|
||||
)
|
||||
|
||||
|
||||
class GenreCreate(GenreBase):
|
||||
pass
|
||||
|
||||
|
||||
class GenreUpdate(SQLModel):
|
||||
name: Optional[str] = None
|
||||
|
||||
|
||||
class GenreRead(GenreBase):
|
||||
id: int
|
||||
|
||||
|
||||
class GenreList(SQLModel):
|
||||
genres: List[GenreRead]
|
||||
total: int
|
||||
|
||||
@@ -1,17 +1,27 @@
|
||||
from fastapi import APIRouter, Depends, HTTPException
|
||||
from fastapi import APIRouter, Path, Depends, HTTPException
|
||||
from sqlmodel import Session, select
|
||||
|
||||
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
|
||||
AuthorCreate,
|
||||
AuthorUpdate,
|
||||
AuthorRead,
|
||||
AuthorList,
|
||||
BookRead,
|
||||
)
|
||||
|
||||
|
||||
router = APIRouter(prefix="/authors", tags=["authors"])
|
||||
|
||||
|
||||
# Create an author
|
||||
@router.post("/", response_model=AuthorRead)
|
||||
@router.post(
|
||||
"/",
|
||||
response_model=AuthorRead,
|
||||
summary="Создать автора",
|
||||
description="Добавляет автора в систему",
|
||||
)
|
||||
def create_author(author: AuthorCreate, session: Session = Depends(get_session)):
|
||||
db_author = Author(**author.model_dump())
|
||||
session.add(db_author)
|
||||
@@ -19,41 +29,60 @@ def create_author(author: AuthorCreate, session: Session = Depends(get_session))
|
||||
session.refresh(db_author)
|
||||
return AuthorRead(**db_author.model_dump())
|
||||
|
||||
|
||||
# Read authors
|
||||
@router.get("/", response_model=AuthorList)
|
||||
@router.get(
|
||||
"/",
|
||||
response_model=AuthorList,
|
||||
summary="Получить список авторов",
|
||||
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],
|
||||
total=len(authors)
|
||||
total=len(authors),
|
||||
)
|
||||
|
||||
|
||||
# Read an author with their books
|
||||
@router.get("/{author_id}", response_model=AuthorWithBooks)
|
||||
def get_author(author_id: int, session: Session = Depends(get_session)):
|
||||
@router.get(
|
||||
"/{author_id}",
|
||||
response_model=AuthorWithBooks,
|
||||
summary="Получить информацию об авторе",
|
||||
description="Возвращает информацию об авторе и его книгах",
|
||||
)
|
||||
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")
|
||||
|
||||
books = session.exec(
|
||||
select(Book)
|
||||
.join(AuthorBookLink)
|
||||
.where(AuthorBookLink.author_id == author_id)
|
||||
select(Book).join(AuthorBookLink).where(AuthorBookLink.author_id == author_id)
|
||||
).all()
|
||||
|
||||
book_reads = [BookRead(**book.model_dump()) for book in books]
|
||||
|
||||
author_data = author.model_dump()
|
||||
author_data['books'] = book_reads
|
||||
author_data["books"] = book_reads
|
||||
|
||||
return AuthorWithBooks(**author_data)
|
||||
|
||||
|
||||
# Update an author
|
||||
@router.put("/{author_id}", response_model=AuthorRead)
|
||||
@router.put(
|
||||
"/{author_id}",
|
||||
response_model=AuthorRead,
|
||||
summary="Обновить информацию об авторе",
|
||||
description="Обновляет информацию об авторе в системе",
|
||||
)
|
||||
def update_author(
|
||||
author_id: int,
|
||||
author: AuthorUpdate,
|
||||
session: Session = Depends(get_session)
|
||||
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:
|
||||
@@ -67,9 +96,18 @@ def update_author(
|
||||
session.refresh(db_author)
|
||||
return AuthorRead(**db_author.model_dump())
|
||||
|
||||
|
||||
# Delete an author
|
||||
@router.delete("/{author_id}", response_model=AuthorRead)
|
||||
def delete_author(author_id: int, session: Session = Depends(get_session)):
|
||||
@router.delete(
|
||||
"/{author_id}",
|
||||
response_model=AuthorRead,
|
||||
summary="Удалить автора",
|
||||
description="Удаляет автора из системы",
|
||||
)
|
||||
def delete_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")
|
||||
|
||||
@@ -1,17 +1,28 @@
|
||||
from fastapi import APIRouter, Depends, HTTPException
|
||||
from fastapi import APIRouter, Path, Depends, HTTPException
|
||||
from sqlmodel import Session, select
|
||||
|
||||
from library_service.models.db.links import BookWithAuthorsAndGenres
|
||||
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
|
||||
AuthorRead,
|
||||
BookList,
|
||||
BookRead,
|
||||
BookCreate,
|
||||
BookUpdate,
|
||||
)
|
||||
|
||||
|
||||
router = APIRouter(prefix="/books", tags=["books"])
|
||||
|
||||
|
||||
# Create a book
|
||||
@router.post("/", response_model=Book)
|
||||
@router.post(
|
||||
"/",
|
||||
response_model=Book,
|
||||
summary="Создать книгу",
|
||||
description="Добавляет книгу в систему",
|
||||
)
|
||||
def create_book(book: BookCreate, session: Session = Depends(get_session)):
|
||||
db_book = Book(**book.model_dump())
|
||||
session.add(db_book)
|
||||
@@ -19,38 +30,67 @@ def create_book(book: BookCreate, session: Session = Depends(get_session)):
|
||||
session.refresh(db_book)
|
||||
return BookRead(**db_book.model_dump())
|
||||
|
||||
|
||||
# Read books
|
||||
@router.get("/", response_model=BookList)
|
||||
@router.get(
|
||||
"/",
|
||||
response_model=BookList,
|
||||
summary="Получить список книг",
|
||||
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)
|
||||
books=[BookRead(**book.model_dump()) for book in books], total=len(books)
|
||||
)
|
||||
|
||||
# Read a book with their authors
|
||||
@router.get("/{book_id}", response_model=BookWithAuthors)
|
||||
def get_book(book_id: int, session: Session = Depends(get_session)):
|
||||
|
||||
# Read a book with their authors and genres
|
||||
@router.get(
|
||||
"/{book_id}",
|
||||
response_model=BookWithAuthorsAndGenres,
|
||||
summary="Получить информацию о книге",
|
||||
description="Возвращает информацию о книге, её авторах и жанрах",
|
||||
)
|
||||
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")
|
||||
|
||||
authors = session.exec(
|
||||
select(Author)
|
||||
.join(AuthorBookLink)
|
||||
.where(AuthorBookLink.book_id == book_id)
|
||||
select(Author).join(AuthorBookLink).where(AuthorBookLink.book_id == book_id)
|
||||
).all()
|
||||
|
||||
author_reads = [AuthorRead(**author.model_dump()) for author in authors]
|
||||
|
||||
genres = session.exec(
|
||||
select(Genre).join(GenreBookLink).where(GenreBookLink.book_id == book_id)
|
||||
).all()
|
||||
|
||||
genre_reads = [GenreRead(**genre.model_dump()) for genre in genres]
|
||||
|
||||
book_data = book.model_dump()
|
||||
book_data['authors'] = author_reads
|
||||
book_data["authors"] = author_reads
|
||||
book_data["genres"] = genre_reads
|
||||
|
||||
return BookWithAuthors(**book_data)
|
||||
|
||||
|
||||
# Update a book
|
||||
@router.put("/{book_id}", response_model=Book)
|
||||
def update_book(book_id: int, book: BookUpdate, session: Session = Depends(get_session)):
|
||||
@router.put(
|
||||
"/{book_id}",
|
||||
response_model=Book,
|
||||
summary="Обновить информацию о книге",
|
||||
description="Обновляет информацию о книге в системе",
|
||||
)
|
||||
def update_book(
|
||||
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")
|
||||
@@ -61,13 +101,24 @@ def update_book(book_id: int, book: BookUpdate, session: Session = Depends(get_s
|
||||
session.refresh(db_book)
|
||||
return db_book
|
||||
|
||||
|
||||
# Delete a book
|
||||
@router.delete("/{book_id}", response_model=BookRead)
|
||||
def delete_book(book_id: int, session: Session = Depends(get_session)):
|
||||
@router.delete(
|
||||
"/{book_id}",
|
||||
response_model=BookRead,
|
||||
summary="Удалить книгу",
|
||||
description="Удаляет книгу их системы",
|
||||
)
|
||||
def delete_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")
|
||||
book_read = BookRead(id=(book.id or 0), title=book.title, description=book.description)
|
||||
book_read = BookRead(
|
||||
id=(book.id or 0), title=book.title, description=book.description
|
||||
)
|
||||
session.delete(book)
|
||||
session.commit()
|
||||
return book_read
|
||||
|
||||
@@ -1,17 +1,27 @@
|
||||
from fastapi import APIRouter, Depends, HTTPException
|
||||
from fastapi import APIRouter, Path, Depends, HTTPException
|
||||
from sqlmodel import Session, select
|
||||
|
||||
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
|
||||
GenreCreate,
|
||||
GenreUpdate,
|
||||
GenreRead,
|
||||
GenreList,
|
||||
BookRead,
|
||||
)
|
||||
|
||||
|
||||
router = APIRouter(prefix="/genres", tags=["genres"])
|
||||
|
||||
|
||||
# Create a genre
|
||||
@router.post("/", response_model=GenreRead)
|
||||
@router.post(
|
||||
"/",
|
||||
response_model=GenreRead,
|
||||
summary="Создать жанр",
|
||||
description="Добавляет жанр книг в систему",
|
||||
)
|
||||
def create_genre(genre: GenreCreate, session: Session = Depends(get_session)):
|
||||
db_genre = Genre(**genre.model_dump())
|
||||
session.add(db_genre)
|
||||
@@ -19,41 +29,59 @@ def create_genre(genre: GenreCreate, session: Session = Depends(get_session)):
|
||||
session.refresh(db_genre)
|
||||
return GenreRead(**db_genre.model_dump())
|
||||
|
||||
|
||||
# Read genres
|
||||
@router.get("/", response_model=GenreList)
|
||||
@router.get(
|
||||
"/",
|
||||
response_model=GenreList,
|
||||
summary="Получить список жанров",
|
||||
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)
|
||||
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)
|
||||
def get_genre(genre_id: int, session: Session = Depends(get_session)):
|
||||
@router.get(
|
||||
"/{genre_id}",
|
||||
response_model=GenreWithBooks,
|
||||
summary="Получить информацию о жанре",
|
||||
description="Возвращает информацию о жанре и книгах с ним",
|
||||
)
|
||||
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")
|
||||
|
||||
books = session.exec(
|
||||
select(Book)
|
||||
.join(GenreBookLink)
|
||||
.where(GenreBookLink.genre_id == genre_id)
|
||||
select(Book).join(GenreBookLink).where(GenreBookLink.genre_id == genre_id)
|
||||
).all()
|
||||
|
||||
book_reads = [BookRead(**book.model_dump()) for book in books]
|
||||
|
||||
genre_data = genre.model_dump()
|
||||
genre_data['books'] = book_reads
|
||||
genre_data["books"] = book_reads
|
||||
|
||||
return GenreWithBooks(**genre_data)
|
||||
|
||||
|
||||
# Update a genre
|
||||
@router.put("/{genre_id}", response_model=GenreRead)
|
||||
@router.put(
|
||||
"/{genre_id}",
|
||||
response_model=GenreRead,
|
||||
summary="Обновляет информацию о жанре",
|
||||
description="Обновляет информацию о жанре в системе",
|
||||
)
|
||||
def update_genre(
|
||||
genre_id: int,
|
||||
genre: GenreUpdate,
|
||||
session: Session = Depends(get_session)
|
||||
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:
|
||||
@@ -67,9 +95,18 @@ def update_genre(
|
||||
session.refresh(db_genre)
|
||||
return GenreRead(**db_genre.model_dump())
|
||||
|
||||
|
||||
# Delete a genre
|
||||
@router.delete("/{genre_id}", response_model=GenreRead)
|
||||
def delete_genre(genre_id: int, session: Session = Depends(get_session)):
|
||||
@router.delete(
|
||||
"/{genre_id}",
|
||||
response_model=GenreRead,
|
||||
summary="Удалить жанр",
|
||||
description="Удаляет автора из системы",
|
||||
)
|
||||
def delete_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")
|
||||
|
||||
@@ -1,13 +1,11 @@
|
||||
from fastapi import APIRouter, Request, FastAPI
|
||||
from fastapi import APIRouter, Path, Request, FastAPI
|
||||
from fastapi.params import Depends
|
||||
from fastapi.responses import HTMLResponse, JSONResponse
|
||||
from fastapi.responses import FileResponse, HTMLResponse, JSONResponse, RedirectResponse
|
||||
from fastapi.templating import Jinja2Templates
|
||||
from pathlib import Path
|
||||
from datetime import datetime
|
||||
from typing import Dict
|
||||
|
||||
from httpx import get
|
||||
|
||||
from library_service.settings import get_app
|
||||
|
||||
# Загрузка шаблонов
|
||||
@@ -30,12 +28,28 @@ def get_info(app) -> Dict:
|
||||
|
||||
|
||||
# Эндпоинт главной страницы
|
||||
@router.get("/", response_class=HTMLResponse)
|
||||
@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("/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")
|
||||
|
||||
|
||||
# Эндпоинт информации об API
|
||||
@router.get("/api/info")
|
||||
@router.get(
|
||||
"/api/info",
|
||||
summary="Информация о сервисе",
|
||||
description="Возвращает информацию о системе",
|
||||
)
|
||||
async def api_info(app=Depends(get_app)):
|
||||
return JSONResponse(content=get_info(app))
|
||||
|
||||
@@ -8,9 +8,17 @@ from library_service.models.dto import AuthorRead, BookRead, GenreRead
|
||||
|
||||
router = APIRouter(tags=["relations"])
|
||||
|
||||
|
||||
# Add author to book
|
||||
@router.post("/relationships/author-book", response_model=AuthorBookLink)
|
||||
def add_author_to_book(author_id: int, book_id: int, session: Session = Depends(get_session)):
|
||||
@router.post(
|
||||
"/relationships/author-book",
|
||||
response_model=AuthorBookLink,
|
||||
summary="Связать автора и книгу",
|
||||
description="Добавляет связь между автором и книгой в систему",
|
||||
)
|
||||
def add_author_to_book(
|
||||
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")
|
||||
@@ -34,9 +42,17 @@ def add_author_to_book(author_id: int, book_id: int, session: Session = Depends(
|
||||
session.refresh(link)
|
||||
return link
|
||||
|
||||
|
||||
# Remove author from book
|
||||
@router.delete("/relationships/author-book", response_model=Dict[str, str])
|
||||
def remove_author_from_book(author_id: int, book_id: int, session: Session = Depends(get_session)):
|
||||
@router.delete(
|
||||
"/relationships/author-book",
|
||||
response_model=Dict[str, str],
|
||||
summary="Разделить автора и книгу",
|
||||
description="Удаляет связь между автором и книгой в системе",
|
||||
)
|
||||
def remove_author_from_book(
|
||||
author_id: int, book_id: int, session: Session = Depends(get_session)
|
||||
):
|
||||
link = session.exec(
|
||||
select(AuthorBookLink)
|
||||
.where(AuthorBookLink.author_id == author_id)
|
||||
@@ -50,15 +66,55 @@ def remove_author_from_book(author_id: int, book_id: int, session: Session = Dep
|
||||
session.commit()
|
||||
return {"message": "Relationship removed successfully"}
|
||||
|
||||
# Get relationships
|
||||
@router.get("/relationships/genre-book", response_model=List[GenreBookLink])
|
||||
def get_relationships(session: Session = Depends(get_session)):
|
||||
relationships = session.exec(select(GenreBookLink)).all()
|
||||
return relationships
|
||||
|
||||
# Add author to book
|
||||
@router.post("/relationships/genre-book", response_model=GenreBookLink)
|
||||
def add_genre_to_book(genre_id: int, book_id: int, session: Session = Depends(get_session)):
|
||||
# Get author's books
|
||||
@router.get(
|
||||
"/authors/{author_id}/books/",
|
||||
response_model=List[BookRead],
|
||||
summary="Получить книги, написанные автором",
|
||||
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]
|
||||
|
||||
|
||||
# Get book's authors
|
||||
@router.get(
|
||||
"/books/{book_id}/authors/",
|
||||
response_model=List[AuthorRead],
|
||||
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")
|
||||
|
||||
authors = session.exec(
|
||||
select(Author).join(AuthorBookLink).where(AuthorBookLink.book_id == book_id)
|
||||
).all()
|
||||
|
||||
return [AuthorRead(**author.model_dump()) for author in authors]
|
||||
|
||||
|
||||
# Add genre to book
|
||||
@router.post(
|
||||
"/relationships/genre-book",
|
||||
response_model=GenreBookLink,
|
||||
summary="Связать книгу и жанр",
|
||||
description="Добавляет связь между книгой и жанром в систему",
|
||||
)
|
||||
def add_genre_to_book(
|
||||
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")
|
||||
@@ -82,9 +138,17 @@ def add_genre_to_book(genre_id: int, book_id: int, session: Session = Depends(ge
|
||||
session.refresh(link)
|
||||
return link
|
||||
|
||||
|
||||
# Remove author from book
|
||||
@router.delete("/relationships/genre-book", response_model=Dict[str, str])
|
||||
def remove_genre_from_book(genre_id: int, book_id: int, session: Session = Depends(get_session)):
|
||||
@router.delete(
|
||||
"/relationships/genre-book",
|
||||
response_model=Dict[str, str],
|
||||
summary="Разделить жанр и книгу",
|
||||
description="Удаляет связь между жанром и книгой в системе",
|
||||
)
|
||||
def remove_genre_from_book(
|
||||
genre_id: int, book_id: int, session: Session = Depends(get_session)
|
||||
):
|
||||
link = session.exec(
|
||||
select(GenreBookLink)
|
||||
.where(GenreBookLink.genre_id == genre_id)
|
||||
@@ -98,38 +162,40 @@ def remove_genre_from_book(genre_id: int, book_id: int, session: Session = Depen
|
||||
session.commit()
|
||||
return {"message": "Relationship removed successfully"}
|
||||
|
||||
# Get relationships
|
||||
@router.get("/relationships/genre-book", response_model=List[GenreBookLink])
|
||||
def get__genre_relationships(session: Session = Depends(get_session)):
|
||||
relationships = session.exec(select(GenreBookLink)).all()
|
||||
return relationships
|
||||
|
||||
# Get author's books
|
||||
@router.get("/authors/{author_id}/books/", response_model=List[BookRead])
|
||||
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")
|
||||
# Get genre's books
|
||||
@router.get(
|
||||
"/genres/{author_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(AuthorBookLink)
|
||||
.where(AuthorBookLink.author_id == author_id)
|
||||
select(Book).join(GenreBookLink).where(GenreBookLink.author_id == genre_id)
|
||||
).all()
|
||||
|
||||
return [BookRead(**book.model_dump()) for book in books]
|
||||
|
||||
# Get book's authors
|
||||
@router.get("/books/{book_id}/authors/", response_model=List[AuthorRead])
|
||||
|
||||
# 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")
|
||||
|
||||
authors = session.exec(
|
||||
select(Author)
|
||||
.join(AuthorBookLink)
|
||||
.where(AuthorBookLink.book_id == book_id)
|
||||
genres = session.exec(
|
||||
select(Genre).join(GenreBookLink).where(GenreBookLink.book_id == book_id)
|
||||
).all()
|
||||
|
||||
return [AuthorRead(**author.model_dump()) for author in authors]
|
||||
return [GenreRead(**author.model_dump()) for genre in genres]
|
||||
|
||||
@@ -9,6 +9,7 @@ load_dotenv()
|
||||
with open("pyproject.toml") as f:
|
||||
config = load(f)
|
||||
|
||||
|
||||
# Dependency to get the FastAPI application instance
|
||||
def get_app() -> FastAPI:
|
||||
return FastAPI(
|
||||
@@ -35,10 +36,11 @@ def get_app() -> FastAPI:
|
||||
{
|
||||
"name": "misc",
|
||||
"description": "Miscellaneous operations.",
|
||||
}
|
||||
]
|
||||
},
|
||||
],
|
||||
)
|
||||
|
||||
|
||||
USER = os.getenv("POSTGRES_USER")
|
||||
PASSWORD = os.getenv("POSTGRES_PASSWORD")
|
||||
DATABASE = os.getenv("POSTGRES_DB")
|
||||
@@ -50,6 +52,7 @@ if not USER or not PASSWORD or not DATABASE or not HOST:
|
||||
POSTGRES_DATABASE_URL = f"postgresql://{USER}:{PASSWORD}@{HOST}:5432/{DATABASE}"
|
||||
engine = create_engine(POSTGRES_DATABASE_URL, echo=True, future=True)
|
||||
|
||||
|
||||
# Dependency to get a database session
|
||||
def get_session():
|
||||
with Session(engine) as session:
|
||||
|
||||
@@ -43,6 +43,7 @@
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<img src="/favicon.ico" />
|
||||
<h1>Welcome to {{ app_info.title }}!</h1>
|
||||
<p>Description: {{ app_info.description }}</p>
|
||||
<p>Version: {{ app_info.version }}</p>
|
||||
@@ -51,6 +52,9 @@
|
||||
<ul>
|
||||
<li><a href="/docs">Swagger UI</a></li>
|
||||
<li><a href="/redoc">ReDoc</a></li>
|
||||
<li>
|
||||
<a href="https://github.com/wowlikon/LibraryAPI">Source Code</a>
|
||||
</li>
|
||||
</ul>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
Reference in New Issue
Block a user