Add deck POST/PUT
This commit is contained in:
parent
3f6e84317a
commit
a317dd31a8
3 changed files with 100 additions and 7 deletions
|
@ -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"
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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):
|
||||||
|
|
Loading…
Reference in a new issue