From d0e71f4df2f55b1a4c26ffc27ad14c44f1aabf34 Mon Sep 17 00:00:00 2001 From: th3r00t Date: Sun, 12 Mar 2023 16:08:03 -0400 Subject: [PATCH] Added Books Book Collections Collection Endpoints --- Pipfile | 1 + Pipfile.lock | 130 +++++++++++++++++++++++++++++- src/backend/lib/models.py | 9 ++- src/backend/lib/storage.py | 44 ++++++++-- src/frontend/lib/FastAPIServer.py | 120 ++++++++++++++++++++++----- src/frontend/package.json | 1 + src/frontend/templates/index.html | 48 ++++++----- 7 files changed, 302 insertions(+), 51 deletions(-) diff --git a/Pipfile b/Pipfile index ba6f889..a29e91c 100644 --- a/Pipfile +++ b/Pipfile @@ -24,6 +24,7 @@ jinja2 = "*" libsass = "*" nodejs-bin = "*" npm = "*" +brotlipy = "*" [dev-packages] ptipython = "*" diff --git a/Pipfile.lock b/Pipfile.lock index 700b2ca..8775a94 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "c5c03b7e057380d9a18d1a52210153e3dbfe7eb1596a5488ff0d75efeffa419b" + "sha256": "be870815622ed679c589971cb713382f5d56d03e73b72f4e7e35af74f63adfc4" }, "pipfile-spec": 6, "requires": { @@ -40,6 +40,58 @@ "markers": "python_full_version >= '3.6.0'", "version": "==4.11.2" }, + "brotlipy": { + "hashes": [ + "sha256:07194f4768eb62a4f4ea76b6d0df6ade185e24ebd85877c351daa0a069f1111a", + "sha256:08a16ebe2ffc52f645c076f96b138f185e74e5d59b4a65e84af17d5997d82890", + "sha256:091b299bf36dd6ef7a06570dbc98c0f80a504a56c5b797f31934d2ad01ae7d17", + "sha256:09ec3e125d16749b31c74f021aba809541b3564e5359f8c265cbae442810b41a", + "sha256:0be698678a114addcf87a4b9496c552c68a2c99bf93cf8e08f5738b392e82057", + "sha256:0fa6088a9a87645d43d7e21e32b4a6bf8f7c3939015a50158c10972aa7f425b7", + "sha256:1379347337dc3d20b2d61456d44ccce13e0625db2611c368023b4194d5e2477f", + "sha256:1ea4e578241504b58f2456a6c69952c88866c794648bdc74baee74839da61d44", + "sha256:22a53ccebcce2425e19f99682c12be510bf27bd75c9b77a1720db63047a77554", + "sha256:2699945a0a992c04fc7dc7fa2f1d0575a2c8b4b769f2874a08e8eae46bef36ae", + "sha256:2a80319ae13ea8dd60ecdc4f5ccf6da3ae64787765923256b62c598c5bba4121", + "sha256:2e5c64522364a9ebcdf47c5744a5ddeb3f934742d31e61ebfbbc095460b47162", + "sha256:36def0b859beaf21910157b4c33eb3b06d8ce459c942102f16988cca6ea164df", + "sha256:382971a641125323e90486244d6266ffb0e1f4dd920fbdcf508d2a19acc7c3b3", + "sha256:3a3e56ced8b15fbbd363380344f70f3b438e0fd1fcf27b7526b6172ea950e867", + "sha256:3c1d5e2cf945a46975bdb11a19257fa057b67591eb232f393d260e7246d9e571", + "sha256:4864ac52c116ea3e3a844248a9c9fbebb8797891cbca55484ecb6eed3ebeba24", + "sha256:4bac11c1ffba9eaa2894ec958a44e7f17778b3303c2ee9f99c39fcc511c26668", + "sha256:4e4638b49835d567d447a2cfacec109f9a777f219f071312268b351b6839436d", + "sha256:50ca336374131cfad20612f26cc43c637ac0bfd2be3361495e99270883b52962", + "sha256:5664fe14f3a613431db622172bad923096a303d3adce55536f4409c8e2eafba4", + "sha256:5de6f7d010b7558f72f4b061a07395c5c3fd57f0285c5af7f126a677b976a868", + "sha256:637847560d671657f993313ecc6c6c6666a936b7a925779fd044065c7bc035b9", + "sha256:653faef61241bf8bf99d73ca7ec4baa63401ba7b2a2aa88958394869379d67c7", + "sha256:786afc8c9bd67de8d31f46e408a3386331e126829114e4db034f91eacb05396d", + "sha256:79aaf217072840f3e9a3b641cccc51f7fc23037496bd71e26211856b93f4b4cb", + "sha256:79ab3bca8dd12c17e092273484f2ac48b906de2b4828dcdf6a7d520f99646ab3", + "sha256:7b21341eab7c939214e457e24b265594067a6ad268305289148ebaf2dacef325", + "sha256:7e31f7adcc5851ca06134705fcf3478210da45d35ad75ec181e1ce9ce345bb38", + "sha256:7ff18e42f51ebc9d9d77a0db33f99ad95f01dd431e4491f0eca519b90e9415a9", + "sha256:82f61506d001e626ec3a1ac8a69df11eb3555a4878599befcb672c8178befac8", + "sha256:890b973039ba26c3ad2e86e8908ab527ed64f9b1357f81a676604da8088e4bf9", + "sha256:8b39abc3256c978f575df5cd7893153277216474f303e26f0e43ba3d3969ef96", + "sha256:8ef230ca9e168ce2b7dc173a48a0cc3d78bcdf0bd0ea7743472a317041a4768e", + "sha256:9448227b0df082e574c45c983fa5cd4bda7bfb11ea6b59def0940c1647be0c3c", + "sha256:96bc59ff9b5b5552843dc67999486a220e07a0522dddd3935da05dc194fa485c", + "sha256:a07647886e24e2fb2d68ca8bf3ada398eb56fd8eac46c733d4d95c64d17f743b", + "sha256:ac1d66c9774ee62e762750e399a0c95e93b180e96179b645f28b162b55ae8adc", + "sha256:af65d2699cb9f13b26ec3ba09e75e80d31ff422c03675fcb36ee4dabe588fdc2", + "sha256:b4c98b0d2c9c7020a524ca5bbff42027db1004c6571f8bc7b747f2b843128e7a", + "sha256:b7cf5bb69e767a59acc3da0d199d4b5d0c9fed7bef3ffa3efa80c6f39095686b", + "sha256:c6cc0036b1304dd0073eec416cb2f6b9e37ac8296afd9e481cac3b1f07f9db25", + "sha256:d2c1c724c4ac375feb2110f1af98ecdc0e5a8ea79d068efb5891f621a5b235cb", + "sha256:dc6c5ee0df9732a44d08edab32f8a616b769cc5a4155a12d2d010d248eb3fb07", + "sha256:e5c549ae5928dda952463196180445c24d6fad2d73cb13bd118293aced31b771", + "sha256:fd1d1c64214af5d90014d82cee5d8141b13d44c92ada7a0c0ec0679c6f15a471" + ], + "index": "pypi", + "version": "==0.7.0" + }, "bs4": { "hashes": [ "sha256:36ecea1fd7cc5c0c6e4a1ff075df26d50da647b75376626cc186e2212886dd3a" @@ -62,6 +114,75 @@ "markers": "python_version >= '3.6'", "version": "==2022.12.7" }, + "cffi": { + "hashes": [ + "sha256:00a9ed42e88df81ffae7a8ab6d9356b371399b91dbdf0c3cb1e84c03a13aceb5", + "sha256:03425bdae262c76aad70202debd780501fabeaca237cdfddc008987c0e0f59ef", + "sha256:04ed324bda3cda42b9b695d51bb7d54b680b9719cfab04227cdd1e04e5de3104", + "sha256:0e2642fe3142e4cc4af0799748233ad6da94c62a8bec3a6648bf8ee68b1c7426", + "sha256:173379135477dc8cac4bc58f45db08ab45d228b3363adb7af79436135d028405", + "sha256:198caafb44239b60e252492445da556afafc7d1e3ab7a1fb3f0584ef6d742375", + "sha256:1e74c6b51a9ed6589199c787bf5f9875612ca4a8a0785fb2d4a84429badaf22a", + "sha256:2012c72d854c2d03e45d06ae57f40d78e5770d252f195b93f581acf3ba44496e", + "sha256:21157295583fe8943475029ed5abdcf71eb3911894724e360acff1d61c1d54bc", + "sha256:2470043b93ff09bf8fb1d46d1cb756ce6132c54826661a32d4e4d132e1977adf", + "sha256:285d29981935eb726a4399badae8f0ffdff4f5050eaa6d0cfc3f64b857b77185", + "sha256:30d78fbc8ebf9c92c9b7823ee18eb92f2e6ef79b45ac84db507f52fbe3ec4497", + "sha256:320dab6e7cb2eacdf0e658569d2575c4dad258c0fcc794f46215e1e39f90f2c3", + "sha256:33ab79603146aace82c2427da5ca6e58f2b3f2fb5da893ceac0c42218a40be35", + "sha256:3548db281cd7d2561c9ad9984681c95f7b0e38881201e157833a2342c30d5e8c", + "sha256:3799aecf2e17cf585d977b780ce79ff0dc9b78d799fc694221ce814c2c19db83", + "sha256:39d39875251ca8f612b6f33e6b1195af86d1b3e60086068be9cc053aa4376e21", + "sha256:3b926aa83d1edb5aa5b427b4053dc420ec295a08e40911296b9eb1b6170f6cca", + "sha256:3bcde07039e586f91b45c88f8583ea7cf7a0770df3a1649627bf598332cb6984", + "sha256:3d08afd128ddaa624a48cf2b859afef385b720bb4b43df214f85616922e6a5ac", + "sha256:3eb6971dcff08619f8d91607cfc726518b6fa2a9eba42856be181c6d0d9515fd", + "sha256:40f4774f5a9d4f5e344f31a32b5096977b5d48560c5592e2f3d2c4374bd543ee", + "sha256:4289fc34b2f5316fbb762d75362931e351941fa95fa18789191b33fc4cf9504a", + "sha256:470c103ae716238bbe698d67ad020e1db9d9dba34fa5a899b5e21577e6d52ed2", + "sha256:4f2c9f67e9821cad2e5f480bc8d83b8742896f1242dba247911072d4fa94c192", + "sha256:50a74364d85fd319352182ef59c5c790484a336f6db772c1a9231f1c3ed0cbd7", + "sha256:54a2db7b78338edd780e7ef7f9f6c442500fb0d41a5a4ea24fff1c929d5af585", + "sha256:5635bd9cb9731e6d4a1132a498dd34f764034a8ce60cef4f5319c0541159392f", + "sha256:59c0b02d0a6c384d453fece7566d1c7e6b7bae4fc5874ef2ef46d56776d61c9e", + "sha256:5d598b938678ebf3c67377cdd45e09d431369c3b1a5b331058c338e201f12b27", + "sha256:5df2768244d19ab7f60546d0c7c63ce1581f7af8b5de3eb3004b9b6fc8a9f84b", + "sha256:5ef34d190326c3b1f822a5b7a45f6c4535e2f47ed06fec77d3d799c450b2651e", + "sha256:6975a3fac6bc83c4a65c9f9fcab9e47019a11d3d2cf7f3c0d03431bf145a941e", + "sha256:6c9a799e985904922a4d207a94eae35c78ebae90e128f0c4e521ce339396be9d", + "sha256:70df4e3b545a17496c9b3f41f5115e69a4f2e77e94e1d2a8e1070bc0c38c8a3c", + "sha256:7473e861101c9e72452f9bf8acb984947aa1661a7704553a9f6e4baa5ba64415", + "sha256:8102eaf27e1e448db915d08afa8b41d6c7ca7a04b7d73af6514df10a3e74bd82", + "sha256:87c450779d0914f2861b8526e035c5e6da0a3199d8f1add1a665e1cbc6fc6d02", + "sha256:8b7ee99e510d7b66cdb6c593f21c043c248537a32e0bedf02e01e9553a172314", + "sha256:91fc98adde3d7881af9b59ed0294046f3806221863722ba7d8d120c575314325", + "sha256:94411f22c3985acaec6f83c6df553f2dbe17b698cc7f8ae751ff2237d96b9e3c", + "sha256:98d85c6a2bef81588d9227dde12db8a7f47f639f4a17c9ae08e773aa9c697bf3", + "sha256:9ad5db27f9cabae298d151c85cf2bad1d359a1b9c686a275df03385758e2f914", + "sha256:a0b71b1b8fbf2b96e41c4d990244165e2c9be83d54962a9a1d118fd8657d2045", + "sha256:a0f100c8912c114ff53e1202d0078b425bee3649ae34d7b070e9697f93c5d52d", + "sha256:a591fe9e525846e4d154205572a029f653ada1a78b93697f3b5a8f1f2bc055b9", + "sha256:a5c84c68147988265e60416b57fc83425a78058853509c1b0629c180094904a5", + "sha256:a66d3508133af6e8548451b25058d5812812ec3798c886bf38ed24a98216fab2", + "sha256:a8c4917bd7ad33e8eb21e9a5bbba979b49d9a97acb3a803092cbc1133e20343c", + "sha256:b3bbeb01c2b273cca1e1e0c5df57f12dce9a4dd331b4fa1635b8bec26350bde3", + "sha256:cba9d6b9a7d64d4bd46167096fc9d2f835e25d7e4c121fb2ddfc6528fb0413b2", + "sha256:cc4d65aeeaa04136a12677d3dd0b1c0c94dc43abac5860ab33cceb42b801c1e8", + "sha256:ce4bcc037df4fc5e3d184794f27bdaab018943698f4ca31630bc7f84a7b69c6d", + "sha256:cec7d9412a9102bdc577382c3929b337320c4c4c4849f2c5cdd14d7368c5562d", + "sha256:d400bfb9a37b1351253cb402671cea7e89bdecc294e8016a707f6d1d8ac934f9", + "sha256:d61f4695e6c866a23a21acab0509af1cdfd2c013cf256bbf5b6b5e2695827162", + "sha256:db0fbb9c62743ce59a9ff687eb5f4afbe77e5e8403d6697f7446e5f609976f76", + "sha256:dd86c085fae2efd48ac91dd7ccffcfc0571387fe1193d33b6394db7ef31fe2a4", + "sha256:e00b098126fd45523dd056d2efba6c5a63b71ffe9f2bbe1a4fe1716e1d0c331e", + "sha256:e229a521186c75c8ad9490854fd8bbdd9a0c9aa3a524326b55be83b54d4e0ad9", + "sha256:e263d77ee3dd201c3a142934a086a4450861778baaeeb45db4591ef65550b0a6", + "sha256:ed9cb427ba5504c1dc15ede7d516b84757c3e3d7868ccc85121d9310d27eed0b", + "sha256:fa6693661a4c91757f4412306191b6dc88c1703f780c8234035eac011922bc01", + "sha256:fcd131dd944808b5bdb38e6f5b53013c5aa4f334c5cad0c72742f6eba4b73db0" + ], + "version": "==1.15.1" + }, "cfgv": { "hashes": [ "sha256:c6a0883f3917a037485059700b9e75da2464e6c27051014ad85ba6aaa5884426", @@ -794,6 +915,13 @@ "index": "pypi", "version": "==2022.1.3" }, + "pycparser": { + "hashes": [ + "sha256:8ee45429555515e1f6b185e78100aea234072576aa43ab53aefcae078162fca9", + "sha256:e644fdec12f7872f86c58ff790da456218b10f863970249516d60a5eaca77206" + ], + "version": "==2.21" + }, "pydantic": { "hashes": [ "sha256:012c99a9c0d18cfde7469aa1ebff922e24b0c706d03ead96940f5465f2c9cf62", diff --git a/src/backend/lib/models.py b/src/backend/lib/models.py index 2e5dd90..ba456d8 100644 --- a/src/backend/lib/models.py +++ b/src/backend/lib/models.py @@ -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] diff --git a/src/backend/lib/storage.py b/src/backend/lib/storage.py index 0f9efb2..969140c 100644 --- a/src/backend/lib/storage.py +++ b/src/backend/lib/storage.py @@ -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 diff --git a/src/frontend/lib/FastAPIServer.py b/src/frontend/lib/FastAPIServer.py index bf2ab90..59c72ea 100644 --- a/src/frontend/lib/FastAPIServer.py +++ b/src/frontend/lib/FastAPIServer.py @@ -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.""" diff --git a/src/frontend/package.json b/src/frontend/package.json index 239d584..9511452 100644 --- a/src/frontend/package.json +++ b/src/frontend/package.json @@ -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": { diff --git a/src/frontend/templates/index.html b/src/frontend/templates/index.html index dc15dd6..3ae63a2 100644 --- a/src/frontend/templates/index.html +++ b/src/frontend/templates/index.html @@ -1,34 +1,32 @@ +{% block javascript %} + +{% endblock %} {% include 'header.html' %} {% include 'navigation.html' %}
{% for book in books %} -
-
-
- {{ book[0].title }} -
-
-
-
-
-
- Placeholder image -
-
-
-

{{ book[0].title }}

-

{{ book[0].author }}

-
-
-
- {{ book[0].description }} -
- -
-
-
+ {% set cover = book[0].cover|b64decode %} + {% if cover != 'None' %} +
+
+
+ {{ book[0].title }} +
+
+
+ {% else %} +
+

{{ book[0].title }}

+

{{ book[0].author }}

+

{{ book[0].description|summarize }}

+
+ {% endif %} {% endfor %}