mirror of
https://github.com/th3r00t/pyShelf.git
synced 2026-04-28 01:59:35 -04:00
Added Books Book Collections Collection Endpoints
This commit is contained in:
9
src/backend/lib/models.py
vendored
9
src/backend/lib/models.py
vendored
@@ -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]
|
||||
|
||||
44
src/backend/lib/storage.py
vendored
44
src/backend/lib/storage.py
vendored
@@ -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
|
||||
|
||||
120
src/frontend/lib/FastAPIServer.py
vendored
120
src/frontend/lib/FastAPIServer.py
vendored
@@ -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."""
|
||||
|
||||
1
src/frontend/package.json
vendored
1
src/frontend/package.json
vendored
@@ -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": {
|
||||
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user