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") INSERT INTO "deck_list" ("deck_id", "oracle_id", "quantity")
VALUES (%(deck_id)s, %(oracle_id)s, %(quantity)s) 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}, {"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( async def get_decks(
db: psycopg.AsyncCursor, limit: int = 10, offset: int = 0 db: psycopg.AsyncCursor, limit: int = 10, offset: int = 0
) -> typing.List[tutor.models.Deck]: ) -> typing.List[tutor.models.Deck]:
@ -479,8 +491,8 @@ async def get_deck(
'quantity', "deck_list"."quantity" 'quantity', "deck_list"."quantity"
))) AS "cards" ))) AS "cards"
FROM "decks" FROM "decks"
LEFT JOIN "deck_list" USING ("deck_id") JOIN "deck_list" USING ("deck_id")
LEFT JOIN "oracle_latest" USING ("oracle_id") JOIN "oracle_latest" USING ("oracle_id")
WHERE "decks"."deck_id" = %(deck_id)s WHERE "decks"."deck_id" = %(deck_id)s
GROUP BY "decks"."deck_id" GROUP BY "decks"."deck_id"
, "decks"."name" , "decks"."name"

View file

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

View file

@ -1,7 +1,11 @@
import asyncio
import decimal import decimal
import importlib.metadata import importlib.metadata
import importlib.resources import importlib.resources
import io
import itertools
import json import json
import logging
import re import re
import typing import typing
import urllib.parse import urllib.parse
@ -13,12 +17,16 @@ import tornado.ioloop
import tornado.web import tornado.web
import tornado_openapi3.handler import tornado_openapi3.handler
import yaml import yaml
from tornado.ioloop import IOLoop
import tutor.database import tutor.database
import tutor.models import tutor.models
import tutor.search import tutor.search
logger = logging.getLogger(__name__)
def update_args(url: str, **qargs) -> str: def update_args(url: str, **qargs) -> str:
parts = urllib.parse.urlsplit(url) parts = urllib.parse.urlsplit(url)
return urllib.parse.urlunsplit( 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): class DecksHandler(RequestHandler):
def initialize(self) -> None:
self.importer = DeckImporter()
self.deck_id = None
async def get(self) -> None: async def get(self) -> None:
page = max(1, int(self.get_argument("page", "1"))) page = max(1, int(self.get_argument("page", "1")))
limit = int(self.get_argument("limit", "10")) limit = int(self.get_argument("limit", "10"))
@ -233,22 +267,68 @@ class DecksHandler(RequestHandler):
self.write(json.dumps(decks, cls=JSONEncoder)) self.write(json.dumps(decks, cls=JSONEncoder))
async def post(self) -> None: 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): 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: async def get(self, deck_id) -> None:
self.set_header("Content-Type", "application/json") self.set_header("Content-Type", "application/json")
self.set_header("Access-Control-Allow-Origin", "*") self.set_header("Access-Control-Allow-Origin", "*")
async with self.pool.connection() as conn: async with self.connection.cursor() as cursor:
async with conn.cursor() as cursor: deck = await tutor.database.get_deck(cursor, deck_id)
deck = await tutor.database.get_deck(cursor, deck_id)
if not deck: if not deck:
raise tornado.web.HTTPError(404) raise tornado.web.HTTPError(404)
self.write(json.dumps(deck, cls=JSONEncoder)) 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: 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): class TemplateHandler(RequestHandler):