Add deck POST/PUT

This commit is contained in:
Correl Roush 2023-06-14 21:42:10 -04:00
parent 3f6e84317a
commit a317dd31a8
3 changed files with 100 additions and 7 deletions

View file

@ -361,11 +361,23 @@ async def store_deck_card(
"""
INSERT INTO "deck_list" ("deck_id", "oracle_id", "quantity")
VALUES (%(deck_id)s, %(oracle_id)s, %(quantity)s)
ON CONFLICT ("deck_id", "oracle_id") DO UPDATE
SET "quantity" = "deck_list"."quantity" + EXCLUDED."quantity"
""",
{"deck_id": deck_id, "oracle_id": str(oracle_id), "quantity": quantity},
)
async def clear_deck(db: psycopg.AsyncCursor, deck_id: int) -> None:
await db.execute(
"""
DELETE FROM "deck_list"
WHERE "deck_id" = %(deck_id)s
""",
{"deck_id": deck_id},
)
async def get_decks(
db: psycopg.AsyncCursor, limit: int = 10, offset: int = 0
) -> typing.List[tutor.models.Deck]:
@ -479,8 +491,8 @@ async def get_deck(
'quantity', "deck_list"."quantity"
))) AS "cards"
FROM "decks"
LEFT JOIN "deck_list" USING ("deck_id")
LEFT JOIN "oracle_latest" USING ("oracle_id")
JOIN "deck_list" USING ("deck_id")
JOIN "oracle_latest" USING ("oracle_id")
WHERE "decks"."deck_id" = %(deck_id)s
GROUP BY "decks"."deck_id"
, "decks"."name"

View file

@ -119,6 +119,7 @@ class CardConstraint:
language: typing.Optional[str] = None
foil: typing.Optional[str] = None
@dataclasses.dataclass
class DeckCard:
card: Card

View file

@ -1,7 +1,11 @@
import asyncio
import decimal
import importlib.metadata
import importlib.resources
import io
import itertools
import json
import logging
import re
import typing
import urllib.parse
@ -13,12 +17,16 @@ import tornado.ioloop
import tornado.web
import tornado_openapi3.handler
import yaml
from tornado.ioloop import IOLoop
import tutor.database
import tutor.models
import tutor.search
logger = logging.getLogger(__name__)
def update_args(url: str, **qargs) -> str:
parts = urllib.parse.urlsplit(url)
return urllib.parse.urlunsplit(
@ -212,7 +220,33 @@ class CollectionHandler(RequestHandler):
)
class DeckImporter:
line_pattern = re.compile(r"^(?P<quantity>\d+)x? (?P<name>[^\]$]+)")
def __init__(self) -> None:
self.imported: int = 0
self.last_chunk: bytes = b""
def process(
self, chunk: bytes = b""
) -> typing.Iterator[tutor.models.CardConstraint]:
lines = (self.last_chunk + chunk).splitlines()
self.last_chunk = lines.pop()
for line in lines:
if match := self.line_pattern.match(line.decode("utf8").strip()):
for card in itertools.repeat(
tutor.models.CardConstraint(name=match.group("name")),
int(match.group("quantity")),
):
self.imported += 1
yield card
class DecksHandler(RequestHandler):
def initialize(self) -> None:
self.importer = DeckImporter()
self.deck_id = None
async def get(self) -> None:
page = max(1, int(self.get_argument("page", "1")))
limit = int(self.get_argument("limit", "10"))
@ -233,22 +267,68 @@ class DecksHandler(RequestHandler):
self.write(json.dumps(decks, cls=JSONEncoder))
async def post(self) -> None:
...
self.set_header("Content-Type", "application/json")
self.set_header("Access-Control-Allow-Origin", "*")
self.write(json.dumps({}))
@tornado.web.stream_request_body
class DeckHandler(RequestHandler):
async def prepare(self) -> None:
self.connection = await self.pool.getconn()
self.deck_id = self.path_args[0]
content_type = self.request.headers.get("Content-Type", "text/plain")
if content_type == "text/plain":
self.importer = DeckImporter()
if self.request.method == "PUT":
async with self.connection.cursor() as cursor:
await tutor.database.clear_deck(cursor, self.deck_id)
async def data_received(self, chunk) -> None:
for card in self.importer.process(chunk):
await self.add_card(card)
async def add_card(self, card: tutor.models.CardConstraint) -> None:
async with self.connection.cursor() as cursor:
oracle_id = await tutor.database.oracle_id_by_name(cursor, card.name)
if oracle_id:
logger.debug("Adding card %s to deck %s", card.name, self.deck_id)
await tutor.database.store_deck_card(cursor, self.deck_id, oracle_id, 1)
def on_finish(self) -> None:
IOLoop.current().add_callback(self.close_connection)
async def close_connection(self) -> None:
await self.pool.putconn(self.connection)
async def get(self, deck_id) -> None:
self.set_header("Content-Type", "application/json")
self.set_header("Access-Control-Allow-Origin", "*")
async with self.pool.connection() as conn:
async with conn.cursor() as cursor:
deck = await tutor.database.get_deck(cursor, deck_id)
async with self.connection.cursor() as cursor:
deck = await tutor.database.get_deck(cursor, deck_id)
if not deck:
raise tornado.web.HTTPError(404)
self.write(json.dumps(deck, cls=JSONEncoder))
async def post(self, deck_id) -> None:
for card in self.importer.process():
await self.add_card(card)
async with self.connection.cursor() as cursor:
deck = await tutor.database.get_deck(cursor, deck_id)
await self.connection.commit()
self.set_header("Content-Type", "application/json")
self.set_header("Access-Control-Allow-Origin", "*")
self.write(json.dumps(deck, cls=JSONEncoder))
async def put(self, deck_id) -> None:
...
for card in self.importer.process():
await self.add_card(card)
async with self.connection.cursor() as cursor:
deck = await tutor.database.get_deck(cursor, deck_id)
await self.connection.commit()
self.set_header("Content-Type", "application/json")
self.set_header("Access-Control-Allow-Origin", "*")
self.write(json.dumps(deck, cls=JSONEncoder))
class TemplateHandler(RequestHandler):