Added Books Book Collections Collection Endpoints

This commit is contained in:
th3r00t
2023-03-12 16:08:03 -04:00
parent af3fa3eec8
commit d0e71f4df2
7 changed files with 302 additions and 51 deletions

View File

@@ -1,6 +1,6 @@
from typing import Optional
from typing_extensions import Annotated
from sqlalchemy import func, DateTime, ForeignKey
from sqlalchemy import func, ForeignKey
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column
import datetime
@@ -11,10 +11,14 @@ timestamp = Annotated[
class Base(DeclarativeBase):
"""Base class for all models."""
pass
class Book(Base):
"""Book model."""
__tablename__ = "books"
book_id: Mapped[int] = mapped_column(primary_key=True, nullable=False)
@@ -33,7 +37,10 @@ class Book(Base):
publisher: Mapped[Optional[str]]
class Collection(Base):
"""Collection model."""
__tablename__ = "collections"
collection: Mapped[str]

View File

@@ -165,7 +165,7 @@ class Storage:
_collections.append(_p)
self.config.logger.info("Finished making collections.")
def get_books(self, collection=None):
def get_books(self, collection=None, skip=None, limit=None):
"""Get books from database.
Parameters
@@ -180,11 +180,43 @@ class Storage:
session = Session(self.engine)
if collection:
_result = session.execute(
select(Book).join(Collection).where(
Collection.collection == collection
)
).all()
select(Book).join(Collection)
.where(Collection.collection_id == collection)
.offset(skip).limit(limit)).all()
else:
_result = session.execute(select(Book)).all()
_result = session.execute(
select(Book).offset(skip).limit(limit)).all()
session.close()
return _result
def get_book(self, book_id):
"""Get book from database.
Parameters
----------
book_id : int
Book ID to filter by.
Returns
-------
_result : ScalarResult Object
"""
session = Session(self.engine)
_result = session.execute(select(Book).where(Book.book_id == book_id)).first()
session.close()
return _result
def get_collections(self):
"""Get collections from database.
Returns
-------
_result : ScalarResult Object
"""
session = Session(self.engine)
_result = session.execute(
select(Collection)
.join(Book)
).all()
session.close()
return _result

View File

@@ -2,9 +2,13 @@
import uvicorn
import os
import sass
import datetime
# import gzip
# import brotli
from json import dumps
from base64 import b64encode
from fastapi import FastAPI, Request
from fastapi.responses import HTMLResponse
from fastapi.responses import HTMLResponse, JSONResponse
from fastapi.routing import APIRoute
from fastapi.staticfiles import StaticFiles
from fastapi.templating import Jinja2Templates
@@ -18,15 +22,88 @@ templates = Jinja2Templates(directory="src/frontend/templates")
def base64decode(string) -> str:
"""Decode a base64 string."""
breakpoint()
try:
result = b64encode(string).decode("utf-8")
except Exception:
result = "static/images/placeholder.png"
result = "None"
return result
def summarize(string) -> str:
"""Summarize a string."""
try:
if len(string) > 50:
return string[:50] + "..."
return string
except TypeError:
return "None"
def convertDateTime(timestamp: datetime) -> str:
"""Convert a datetime object to a string."""
return timestamp.strftime("%d/%m/%Y %H:%M:%S")
def books_tojson(obj) -> dumps:
"""Convert an object to a dictionary."""
_books: list = []
for book in obj:
_books.append({
"book_id": book[0].book_id,
"title": book[0].title,
"author": book[0].author,
"categories": book[0].categories,
"cover": base64decode(book[0].cover),
"pages": book[0].pages,
"progress": book[0].progress,
"file_name": book[0].file_name,
"description": book[0].description,
"date": convertDateTime(book[0].date),
"rights": book[0].rights,
"tags": book[0].tags,
"identifier": book[0].identifier,
"publisher": book[0].publisher,
})
# compressed = gzip.compress(dumps(_books).encode("utf-8"))
# compressed = gzip.compress(dumps(_books).encode())
return dumps(_books)
def book_tojson(book) -> dumps:
"""Convert a book object to a json."""
return dumps({
"book_id": book[0].book_id,
"title": book[0].title,
"author": book[0].author,
"categories": book[0].categories,
"cover": base64decode(book[0].cover),
"pages": book[0].pages,
"progress": book[0].progress,
"file_name": book[0].file_name,
"description": book[0].description,
"date": convertDateTime(book[0].date),
"rights": book[0].rights,
"tags": book[0].tags,
"identifier": book[0].identifier,
"publisher": book[0].publisher,
})
def collections_tojson(collection) -> dumps:
"""Convert a collections object to json."""
_collections = []
for _collection in collection:
_collections.append({
"collection_id": _collection[0].collection_id,
"book_id": _collection[0].book_id,
"collection": _collection[0].collection,
})
return dumps(_collections)
templates.env.filters["b64decode"] = base64decode
templates.env.filters["summarize"] = summarize
templates.env.filters["books_tojson"] = books_tojson
class FastAPIServer():
@@ -63,28 +140,35 @@ class FastAPIServer():
route.operation_id = route.name
@app.get("/", response_class=HTMLResponse)
async def index(request: Request):
async def index(request: Request, skip: int = 0, limit: int = 10):
storage = Storage(Config(os.path.abspath(os.getcwd())))
books = storage.get_books()
books = storage.get_books(collection=None, skip=skip, limit=limit)
"""Home page responder."""
# _books = self.storage.get_books()
context = {"request": request, "books": books}
return templates.TemplateResponse("index.html", context)
@app.get("/users/me")
async def about_me(self):
"""About me page responder."""
return {"user_id": "CurrentUser"}
@app.get("/books", response_class=JSONResponse)
async def books(request: Request, skip: int = 0, limit: int = 10, collection=None):
storage = Storage(Config(os.path.abspath(os.getcwd())))
books = storage.get_books(collection, skip=skip, limit=limit)
headers = {"Accept-Encoding": "gzip"}
"""Home page responder."""
return JSONResponse(content=books_tojson(books))
@app.get("/users/{user_id}")
async def about_user(self, user_id: int):
"""About user page responder."""
return {"user_id": user_id}
@app.get("/book/{book_id}", response_class=JSONResponse)
async def book(request: Request, book_id: int):
storage = Storage(Config(os.path.abspath(os.getcwd())))
book = storage.get_book(book_id)
"""Home page responder."""
return JSONResponse(content=book_tojson(book))
@app.get("/collections", response_class=JSONResponse)
async def collections(request: Request):
storage = Storage(Config(os.path.abspath(os.getcwd())))
collections = storage.get_collections()
"""Home page responder."""
return JSONResponse(content=collections_tojson(collections))
@app.get("/dev/test/echo/{_test_item_}")
async def echo_test(self, _test_item_):
"""Test echo responder function."""
return {"Test Object": _test_item_}
async def run(self):
"""Front end server entrypoint."""

View File

@@ -30,6 +30,7 @@
"homepage": "https://github.com/th3r00t/pyShelf#readme",
"dependencies": {
"bulma": "^0.9.4",
"pako": "^2.1.0",
"typescript": "^4.9.5"
},
"devDependencies": {

View File

@@ -1,34 +1,32 @@
<!doctype html>
{% block javascript %}
<script type="text/javascript">
var books = {{ books|books_tojson }};
let inflatedJSON = {};
inflatedJSON = JSON.parse(pako.inflate(books, { to: 'string'}));
</script>
{% endblock %}
{% include 'header.html' %}
{% include 'navigation.html' %}
<section id="master">
<div class="container is-dark">
{% for book in books %}
<div class="card">
<div class="card-image">
<figure class="image is-4by3">
<img src="data:;base64,{{ book[0].cover|b64decode }}" alt="{{ book[0].title }}">
</figure>
</div>
<div class="card-content">
<div class="media">
<div class="media-left">
<figure class="image is-48x48">
<img src="data:;base64,{{ book[0].cover|b64decode }}" alt="Placeholder image">
</figure>
</div>
<div class="media-content">
<p class="title is-4">{{ book[0].title }}</p>
<p class="subtitle is-6">{{ book[0].author }}</p>
</div>
</div>
<div class="content">
{{ book[0].description }}
<br>
<time datetime="2016-1-1">{{ book[0].date }}</time>
</div>
</div>
</div>
{% set cover = book[0].cover|b64decode %}
{% if cover != 'None' %}
<div class="box is-dark book" id="{{book[0].id}}">
<div class="image book-thumbnail">
<figure class="image is-4by3">
<img src="data:;base64,{{ book[0].cover|b64decode }}" alt="{{ book[0].title }}">
</figure>
</div>
</div>
{% else %}
<div class="box is-dark book" id="{{book[0].id}}">
<h3 class="title is-3">{{ book[0].title }}</h3>
<h4 class="subtitle is-4">{{ book[0].author }}</h4>
<p class="content">{{ book[0].description|summarize }}</p>
</div>
{% endif %}
{% endfor %}
</div>
</section>