Updated UI, and backend to sort collections.

This push is going to be pre-refactor of the collections system. The
next step will be adding a 3rd table to house the books with collection
links. I will also be making collections a unique field to stop the
spamming of collections.
This commit is contained in:
2025-08-05 04:15:27 +00:00
parent 794abb7d28
commit a1225f3a24
5 changed files with 68 additions and 15 deletions

3
Makefile vendored
View File

@@ -22,3 +22,6 @@ lint: style typing
compile:
cd src/frontend && sh compile.sh && cd ../..
install:
cd src/frontend && npm install

View File

@@ -1,7 +1,7 @@
from typing import Optional
from typing_extensions import Annotated
from sqlalchemy import func, ForeignKey
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column, relationship
import datetime
timestamp = Annotated[
@@ -13,6 +13,7 @@ timestamp = Annotated[
class Base(DeclarativeBase):
"""Base class for all models."""
from sqlalchemy.orm import relationship
class Book(Base):
"""Book model."""
@@ -34,6 +35,9 @@ class Book(Base):
identifier: Mapped[Optional[str]]
publisher: Mapped[Optional[str]]
# One book → many collection entries
collections = relationship("Collection", back_populates="book", cascade="all, delete-orphan")
class Collection(Base):
"""Collection model."""
@@ -42,4 +46,41 @@ class Collection(Base):
id: Mapped[int] = mapped_column(primary_key=True)
collection: Mapped[str]
book_id: Mapped[int] = mapped_column(ForeignKey(Book.id))
book_id: Mapped[int] = mapped_column(ForeignKey("Book.id"))
# Each collection entry points to one book
book = relationship("Book", back_populates="collections")
# class Book(Base):
# """Book model."""
#
# __tablename__ = "Book"
#
# id: Mapped[int] = mapped_column(primary_key=True, nullable=False)
# title: Mapped[str]
# author: Mapped[Optional[str]]
# categories: Mapped[Optional[str]]
# cover: Mapped[Optional[bytes]]
# pages: Mapped[Optional[int]]
# progress: Mapped[Optional[float]]
# file_name: Mapped[str]
# description: Mapped[Optional[str]]
# date: Mapped[timestamp]
# rights: Mapped[Optional[str]]
# tags: Mapped[Optional[str]]
# identifier: Mapped[Optional[str]]
# publisher: Mapped[Optional[str]]
# collection = relationship("Collection", back_populates="book")
#
#
# class Collection(Base):
# """Collection model."""
#
# __tablename__ = "Collection"
#
# id: Mapped[int] = mapped_column(primary_key=True)
# collection: Mapped[str]
# book_id: Mapped[int] = mapped_column(ForeignKey(Book.id))
# book = relationship("Book", back_populates="collections")

View File

@@ -207,7 +207,8 @@ class Storage:
_result = session.execute(
select(Book)
.join(Collection)
.where(Collection.id == collection)
# .where(Collection.id == collection)
.where(Collection.collection == collection)
.offset(skip)
.limit(limit)
).all()
@@ -253,7 +254,7 @@ class Storage:
_result : ScalarResult Object
"""
session = Session(self.engine)
_result = session.execute(select(Collection).where(Collection.collection == name).join(Book)).all()
breakpoint()
_result = session.execute(select(Collection).where(Collection.name == name).join(Book)).all()
session.close()
return _result

View File

@@ -136,7 +136,7 @@ class FastAPIServer():
app.mount("/static",
StaticFiles(directory="src/frontend/static"),
name="static")
self.fe_config = uvicorn.Config(app, port=8080,
self.fe_config = uvicorn.Config(app, host="0.0.0.0", port=8080,
log_level="info", reload=True)
self.fe_server = uvicorn.Server(self.fe_config)
self.JSInterface: JSInterface = JSInterface(self.config)
@@ -207,12 +207,17 @@ class FastAPIServer():
"""Home page responder."""
return JSONResponse(content=collections_tojson(collections))
@app.get("/api/collection/{collection_id}", response_class=JSONResponse)
async def collection(request: Request, collection_name: str):
@app.get("/api/collection/{collection}", response_class=JSONResponse)
async def collection(request: Request, collection: str, skip=0, limit=30):
storage = Storage(Config(os.path.abspath(os.getcwd())))
collection = storage.get_collection(collection_name)
# collection = storage.get_collection(collection_name)
collection = storage.get_books(collection)
"""Collection file responder."""
return JSONResponse(content=collections_tojson(collection))
collections = storage.get_collections()
# books = JSONResponse(content=books_tojson(collection))
context = {"request": request, "books": collection, "collections": collections, "page": skip, "limit": limit}
return templates.TemplateResponse("index.html", context)
# return JSONResponse(content=collections_tojson(collection))
async def run(self):
"""Front end server entrypoint."""

View File

@@ -47,15 +47,18 @@
</div>
</div>
<div class="select is-small is-rounded is-link" id="collection_dropdown">
<select id="collection_select">
<!-- <select id="collection_select" onchange="window.location.href='/api/collection/' + this.value"> -->
<select id="collection_select">
{% for collection in collections %}
<option
value={{collection[0].id}}
class="collection_selection"
onclick="window.location.href='/api/collection/{{ collection[0].collection }}'"
>{{collection[0].collection}}</option>
<option value="{{collection[0].collection}}" class="collection_selection">{{collection[0].collection}}</option>
{% endfor %}
</select>
<script>
document.getElementById("collection_select").addEventListener("change", function() {
const value = encodeURIComponent(this.value);
window.location.href = `/api/collection/${value}`;
});
</script>
</div>
</div>
<div class="navbar-end">