mirror of
https://github.com/th3r00t/pyShelf.git
synced 2026-04-28 01:59:35 -04:00
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:
3
Makefile
vendored
3
Makefile
vendored
@@ -22,3 +22,6 @@ lint: style typing
|
|||||||
|
|
||||||
compile:
|
compile:
|
||||||
cd src/frontend && sh compile.sh && cd ../..
|
cd src/frontend && sh compile.sh && cd ../..
|
||||||
|
|
||||||
|
install:
|
||||||
|
cd src/frontend && npm install
|
||||||
|
|||||||
45
src/backend/lib/models.py
vendored
45
src/backend/lib/models.py
vendored
@@ -1,7 +1,7 @@
|
|||||||
from typing import Optional
|
from typing import Optional
|
||||||
from typing_extensions import Annotated
|
from typing_extensions import Annotated
|
||||||
from sqlalchemy import func, ForeignKey
|
from sqlalchemy import func, ForeignKey
|
||||||
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column
|
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column, relationship
|
||||||
import datetime
|
import datetime
|
||||||
|
|
||||||
timestamp = Annotated[
|
timestamp = Annotated[
|
||||||
@@ -13,6 +13,7 @@ timestamp = Annotated[
|
|||||||
class Base(DeclarativeBase):
|
class Base(DeclarativeBase):
|
||||||
"""Base class for all models."""
|
"""Base class for all models."""
|
||||||
|
|
||||||
|
from sqlalchemy.orm import relationship
|
||||||
|
|
||||||
class Book(Base):
|
class Book(Base):
|
||||||
"""Book model."""
|
"""Book model."""
|
||||||
@@ -34,6 +35,9 @@ class Book(Base):
|
|||||||
identifier: Mapped[Optional[str]]
|
identifier: Mapped[Optional[str]]
|
||||||
publisher: 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):
|
class Collection(Base):
|
||||||
"""Collection model."""
|
"""Collection model."""
|
||||||
@@ -42,4 +46,41 @@ class Collection(Base):
|
|||||||
|
|
||||||
id: Mapped[int] = mapped_column(primary_key=True)
|
id: Mapped[int] = mapped_column(primary_key=True)
|
||||||
collection: Mapped[str]
|
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")
|
||||||
|
|||||||
5
src/backend/lib/storage.py
vendored
5
src/backend/lib/storage.py
vendored
@@ -207,7 +207,8 @@ class Storage:
|
|||||||
_result = session.execute(
|
_result = session.execute(
|
||||||
select(Book)
|
select(Book)
|
||||||
.join(Collection)
|
.join(Collection)
|
||||||
.where(Collection.id == collection)
|
# .where(Collection.id == collection)
|
||||||
|
.where(Collection.collection == collection)
|
||||||
.offset(skip)
|
.offset(skip)
|
||||||
.limit(limit)
|
.limit(limit)
|
||||||
).all()
|
).all()
|
||||||
@@ -253,7 +254,7 @@ class Storage:
|
|||||||
_result : ScalarResult Object
|
_result : ScalarResult Object
|
||||||
"""
|
"""
|
||||||
session = Session(self.engine)
|
session = Session(self.engine)
|
||||||
|
_result = session.execute(select(Collection).where(Collection.collection == name).join(Book)).all()
|
||||||
breakpoint()
|
breakpoint()
|
||||||
_result = session.execute(select(Collection).where(Collection.name == name).join(Book)).all()
|
|
||||||
session.close()
|
session.close()
|
||||||
return _result
|
return _result
|
||||||
|
|||||||
15
src/frontend/lib/FastAPIServer.py
vendored
15
src/frontend/lib/FastAPIServer.py
vendored
@@ -136,7 +136,7 @@ class FastAPIServer():
|
|||||||
app.mount("/static",
|
app.mount("/static",
|
||||||
StaticFiles(directory="src/frontend/static"),
|
StaticFiles(directory="src/frontend/static"),
|
||||||
name="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)
|
log_level="info", reload=True)
|
||||||
self.fe_server = uvicorn.Server(self.fe_config)
|
self.fe_server = uvicorn.Server(self.fe_config)
|
||||||
self.JSInterface: JSInterface = JSInterface(self.config)
|
self.JSInterface: JSInterface = JSInterface(self.config)
|
||||||
@@ -207,12 +207,17 @@ class FastAPIServer():
|
|||||||
"""Home page responder."""
|
"""Home page responder."""
|
||||||
return JSONResponse(content=collections_tojson(collections))
|
return JSONResponse(content=collections_tojson(collections))
|
||||||
|
|
||||||
@app.get("/api/collection/{collection_id}", response_class=JSONResponse)
|
@app.get("/api/collection/{collection}", response_class=JSONResponse)
|
||||||
async def collection(request: Request, collection_name: str):
|
async def collection(request: Request, collection: str, skip=0, limit=30):
|
||||||
storage = Storage(Config(os.path.abspath(os.getcwd())))
|
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."""
|
"""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):
|
async def run(self):
|
||||||
"""Front end server entrypoint."""
|
"""Front end server entrypoint."""
|
||||||
|
|||||||
@@ -47,15 +47,18 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="select is-small is-rounded is-link" id="collection_dropdown">
|
<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 %}
|
{% for collection in collections %}
|
||||||
<option
|
<option value="{{collection[0].collection}}" class="collection_selection">{{collection[0].collection}}</option>
|
||||||
value={{collection[0].id}}
|
|
||||||
class="collection_selection"
|
|
||||||
onclick="window.location.href='/api/collection/{{ collection[0].collection }}'"
|
|
||||||
>{{collection[0].collection}}</option>
|
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</select>
|
</select>
|
||||||
|
<script>
|
||||||
|
document.getElementById("collection_select").addEventListener("change", function() {
|
||||||
|
const value = encodeURIComponent(this.value);
|
||||||
|
window.location.href = `/api/collection/${value}`;
|
||||||
|
});
|
||||||
|
</script>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="navbar-end">
|
<div class="navbar-end">
|
||||||
|
|||||||
Reference in New Issue
Block a user