From b4a788826b168830a8ce3710c4d4c8fce0dafa97 Mon Sep 17 00:00:00 2001 From: Correl Date: Thu, 15 Jul 2021 20:51:33 -0400 Subject: [PATCH] Paginate search results --- pyproject.toml | 2 +- tutor/{__main__.py => cli.py} | 10 +- tutor/database.py | 3 +- tutor/server.py | 45 ++++++- www/elm.json | 2 + www/public/index.html | 1 + www/src/App.elm | 237 +++++++++++++++++++++++++++++----- 7 files changed, 257 insertions(+), 43 deletions(-) rename tutor/{__main__.py => cli.py} (96%) diff --git a/pyproject.toml b/pyproject.toml index 5164b0e..372f5ad 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -19,7 +19,7 @@ black = "^21.6b0" mypy = "^0.910" [tool.poetry.scripts] -tutor = 'tutor.__main__:cli' +tutor = 'tutor.cli:main' [build-system] requires = ["poetry>=0.12"] diff --git a/tutor/__main__.py b/tutor/cli.py similarity index 96% rename from tutor/__main__.py rename to tutor/cli.py index 9658051..9f404cd 100644 --- a/tutor/__main__.py +++ b/tutor/cli.py @@ -30,7 +30,7 @@ import tutor.server default="warn", ) @click.pass_context -def cli(ctx, database, log_level): +def main(ctx, database, log_level): logging.basicConfig( level={ "debug": logging.DEBUG, @@ -43,7 +43,7 @@ def cli(ctx, database, log_level): ctx.obj["database"] = database -@cli.command() +@main.command() @click.option("--port", type=int, envvar="TUTOR_PORT", default=8888) @click.option("--static", envvar="TUTOR_STATIC", type=click.Path(file_okay=False)) @click.option("--debug", is_flag=True) @@ -60,7 +60,7 @@ def server(ctx, port, static, debug): tornado.ioloop.IOLoop.current().start() -@cli.command("import") +@main.command("import") @click.argument("filename", type=click.Path(dir_okay=False)) @click.pass_context def import_cards(ctx, filename): @@ -69,7 +69,7 @@ def import_cards(ctx, filename): ) -@cli.command("update_scryfall") +@main.command("update_scryfall") @click.option("--filename", type=click.Path(dir_okay=False)) @click.pass_context def update_scryfall(ctx, filename): @@ -114,4 +114,4 @@ def update_scryfall(ctx, filename): if __name__ == "__main__": - cli() + main() diff --git a/tutor/database.py b/tutor/database.py index f2199d2..14b5423 100644 --- a/tutor/database.py +++ b/tutor/database.py @@ -93,6 +93,7 @@ async def advanced_search( db: aiosqlite.Connection, search: tutor.search.Search, limit: int = 10, + offset: int = 0, in_collection: typing.Optional[bool] = None, ) -> typing.List[tutor.models.Card]: db.row_factory = aiosqlite.Row @@ -146,7 +147,7 @@ async def advanced_search( " ".join(joins), "WHERE" if constraints else "", " AND ".join(constraints), - f"LIMIT {limit}", + f"LIMIT {offset},{limit}", ] ) cursor = await db.execute(query, params) diff --git a/tutor/server.py b/tutor/server.py index 197fbe9..b195386 100644 --- a/tutor/server.py +++ b/tutor/server.py @@ -1,4 +1,5 @@ import json +import urllib.parse import aiosqlite import tornado.web @@ -7,21 +8,57 @@ import tutor.database import tutor.models import tutor.search + +def update_args(url: str, **qargs) -> str: + parts = urllib.parse.urlsplit(url) + return urllib.parse.urlunsplit( + ( + parts.scheme, + parts.netloc, + parts.path, + urllib.parse.urlencode( + [ + (k, v) + for k, v in urllib.parse.parse_qsl(parts.query) + if k not in qargs.keys() + ] + + list(qargs.items()) + ), + parts.fragment, + ) + ) + + class SearchHandler(tornado.web.RequestHandler): + def set_links(self, **links) -> None: + self.set_header( + "Link", + ", ".join([f'<{url}>; rel="{rel}"' for rel, url in links.items()]), + ) + async def get(self) -> None: async with aiosqlite.connect(self.application.settings["database"]) as db: - name = self.get_argument("name", None) + query = self.get_argument("q", "") in_collection = self.get_argument("in_collection", None) + page = max(1, int(self.get_argument("page", 1))) limit = int(self.get_argument("limit", 10)) - search = tutor.search.search.parse(name) + search = tutor.search.search.parse(query) cards = await tutor.database.advanced_search( db, search, - limit=limit, + limit=limit + 1, + offset=limit * (page - 1), in_collection=in_collection, ) + has_more = cards and len(cards) > limit self.set_header("Content-Type", "application/json") self.set_header("Access-Control-Allow-Origin", "*") + links = {} + if page > 1: + links["prev"] = update_args(self.request.full_url(), page=page - 1) + if has_more: + links["next"] = update_args(self.request.full_url(), page=page + 1) + self.set_links(**links) self.write( json.dumps( [ @@ -35,7 +72,7 @@ class SearchHandler(tornado.web.RequestHandler): card.color_identity ), } - for card in cards + for card in cards[:limit] ] ) ) diff --git a/www/elm.json b/www/elm.json index c6885f9..210a7c5 100644 --- a/www/elm.json +++ b/www/elm.json @@ -12,7 +12,9 @@ "elm/html": "1.0.0", "elm/http": "2.0.0", "elm/json": "1.1.3", + "elm/regex": "1.0.0", "elm/url": "1.0.0", + "elm-community/maybe-extra": "5.2.0", "mdgriffith/elm-ui": "1.1.8" }, "indirect": { diff --git a/www/public/index.html b/www/public/index.html index 064d4ca..645d6cd 100644 --- a/www/public/index.html +++ b/www/public/index.html @@ -3,6 +3,7 @@ Bulk Tagging Dashboard +