From c1bd04fe3a80d702476d3c3d96cac4ad932d501e Mon Sep 17 00:00:00 2001 From: Ben Date: Fri, 14 Feb 2020 22:00:31 +0000 Subject: [PATCH] Redemption join handlers --- houdini.sql | 41 +++++--- houdini/data/redemption.py | 124 +++++++++++++++++++++--- houdini/handlers/redemption/__init__.py | 68 +++++++++++++ 3 files changed, 205 insertions(+), 28 deletions(-) diff --git a/houdini.sql b/houdini.sql index f19eaf5..5754ead 100644 --- a/houdini.sql +++ b/houdini.sql @@ -1168,6 +1168,7 @@ CREATE TABLE redemption_code ( type VARCHAR(8) NOT NULL DEFAULT 'BLANKET', coins INT NOT NULL DEFAULT 0, expires TIMESTAMP DEFAULT NULL, + uses INT DEFAULT NULL, PRIMARY KEY (id) ); @@ -1180,6 +1181,7 @@ COMMENT ON COLUMN redemption_code.code IS 'Redemption code'; COMMENT ON COLUMN redemption_code.type IS 'Code type'; COMMENT ON COLUMN redemption_code.coins IS 'Code coins amount'; COMMENT ON COLUMN redemption_code.expires IS 'Expiry date'; +COMMENT ON COLUMN redemption_code.uses IS 'Number of uses'; DROP TABLE IF EXISTS redemption_book; CREATE TABLE redemption_book ( @@ -1212,24 +1214,39 @@ COMMENT ON COLUMN redemption_book_word.line IS 'Line number of page'; COMMENT ON COLUMN redemption_book_word.word_number IS 'The nth word on the line'; COMMENT ON COLUMN redemption_book_word.answer IS 'The correct word'; -DROP TABLE IF EXISTS penguin_redemption; -CREATE TABLE penguin_redemption ( +DROP TABLE IF EXISTS penguin_redemption_code; +CREATE TABLE penguin_redemption_code ( penguin_id INT NOT NULL, - code_id INT DEFAULT NULL, - book_id INT DEFAULT NULL, + code_id INT NOT NULL, PRIMARY KEY (penguin_id, code_id), - CONSTRAINT penguin_redemption_ibfk_1 FOREIGN KEY (penguin_id) REFERENCES penguin (id) ON DELETE RESTRICT ON UPDATE CASCADE, - CONSTRAINT penguin_redemption_ibfk_2 FOREIGN KEY (code_id) REFERENCES redemption_code (id) ON DELETE RESTRICT ON UPDATE CASCADE, - CONSTRAINT penguin_redemption_ibfk_3 FOREIGN KEY (book_id) REFERENCES redemption_book (id) ON DELETE RESTRICT ON UPDATE CASCADE + CONSTRAINT penguin_redemption_code_ibfk_1 FOREIGN KEY (penguin_id) REFERENCES penguin (id) ON DELETE RESTRICT ON UPDATE CASCADE, + CONSTRAINT penguin_redemption_code_ibfk_2 FOREIGN KEY (code_id) REFERENCES redemption_code (id) ON DELETE RESTRICT ON UPDATE CASCADE ); -CREATE INDEX penguin_redemption_code_id ON penguin_redemption (code_id); -CREATE INDEX penguin_redemption_book_id ON penguin_redemption (book_id); +CREATE INDEX penguin_redemption_code_code_id ON penguin_redemption_code (code_id); -COMMENT ON TABLE penguin_redemption IS 'Redeemed codes'; +COMMENT ON TABLE penguin_redemption_code IS 'Redeemed codes'; + +COMMENT ON COLUMN penguin_redemption_code.penguin_id IS 'Unique penguin ID'; +COMMENT ON COLUMN penguin_redemption_code.code_id IS 'Unique code ID'; + + +DROP TABLE IF EXISTS penguin_redemption_book; +CREATE TABLE penguin_redemption_book ( + penguin_id INT NOT NULL, + book_id INT NOT NULL, + PRIMARY KEY (penguin_id, book_id), + CONSTRAINT penguin_redemption_book_ibfk_1 FOREIGN KEY (penguin_id) REFERENCES penguin (id) ON DELETE RESTRICT ON UPDATE CASCADE, + CONSTRAINT penguin_redemption_book_ibfk_2 FOREIGN KEY (book_id) REFERENCES redemption_book (id) ON DELETE RESTRICT ON UPDATE CASCADE +); + +CREATE INDEX penguin_redemption_book_book_id ON penguin_redemption_book (book_id); + +COMMENT ON TABLE penguin_redemption_book IS 'Redeemed book codes'; + +COMMENT ON COLUMN penguin_redemption_book.penguin_id IS 'Unique penguin ID'; +COMMENT ON COLUMN penguin_redemption_book.book_id IS 'Unique book ID'; -COMMENT ON COLUMN penguin_redemption.penguin_id IS 'Unique penguin ID'; -COMMENT ON COLUMN penguin_redemption.code_id IS 'Unique code ID'; DROP TABLE IF EXISTS redemption_award_card; CREATE TABLE redemption_award_card ( diff --git a/houdini/data/redemption.py b/houdini/data/redemption.py index 5015db7..24031e7 100644 --- a/houdini/data/redemption.py +++ b/houdini/data/redemption.py @@ -1,6 +1,101 @@ from houdini.data import db +class RedemptionCode(db.Model): + __tablename__ = 'redemption_code' + + id = db.Column(db.Integer, primary_key=True, + server_default=db.text("nextval('\"redemption_code_id_seq\"'::regclass)")) + code = db.Column(db.String(16), nullable=False, unique=True) + type = db.Column(db.String(8), nullable=False, server_default=db.text("'BLANKET'::character varying")) + coins = db.Column(db.Integer, nullable=False, server_default=db.text("0")) + expires = db.Column(db.DateTime) + uses = db.Column(db.Integer) + + def __init__(self, **kwargs): + super().__init__(**kwargs) + self._cards = set() + self._flooring = set() + self._furniture = set() + self._igloos = set() + self._items = set() + self._locations = set() + self._puffles = set() + self._puffle_items = set() + + @property + def cards(self): + return self._cards + + @cards.setter + def cards(self, child): + if isinstance(child, RedemptionAwardCard): + self._cards.add(child) + + @property + def flooring(self): + return self._flooring + + @flooring.setter + def flooring(self, child): + if isinstance(child, RedemptionAwardFlooring): + self._flooring.add(child) + + @property + def furniture(self): + return self._furniture + + @furniture.setter + def furniture(self, child): + if isinstance(child, RedemptionAwardFurniture): + self._furniture.add(child) + + @property + def igloos(self): + return self._igloos + + @igloos.setter + def igloos(self, child): + if isinstance(child, RedemptionAwardIgloo): + self._igloos.add(child) + + @property + def items(self): + return self._items + + @items.setter + def items(self, child): + if isinstance(child, RedemptionAwardItem): + self._items.add(child) + + @property + def locations(self): + return self._locations + + @locations.setter + def locations(self, child): + if isinstance(child, RedemptionAwardLocation): + self._locations.add(child) + + @property + def puffles(self): + return self._puffles + + @puffles.setter + def puffles(self, child): + if isinstance(child, RedemptionAwardPuffle): + self._puffles.add(child) + + @property + def puffle_items(self): + return self._puffle_items + + @puffle_items.setter + def puffle_items(self, child): + if isinstance(child, RedemptionAwardPuffleItem): + self._puffle_items.add(child) + + class RedemptionBook(db.Model): __tablename__ = 'redemption_book' @@ -19,17 +114,6 @@ class RedemptionBookWord(db.Model): answer = db.Column(db.String(20), nullable=False) -class RedemptionCode(db.Model): - __tablename__ = 'redemption_code' - - id = db.Column(db.Integer, primary_key=True, - server_default=db.text("nextval('\"redemption_code_id_seq\"'::regclass)")) - code = db.Column(db.String(16), nullable=False, unique=True) - type = db.Column(db.String(8), nullable=False, server_default=db.text("'BLANKET'::character varying")) - coins = db.Column(db.Integer, nullable=False, server_default=db.text("0")) - expires = db.Column(db.DateTime) - - class RedemptionAwardCard(db.Model): __tablename__ = 'redemption_award_card' code_id = db.Column(db.ForeignKey('redemption_code.id', ondelete='CASCADE', onupdate='CASCADE'), primary_key=True, @@ -56,8 +140,8 @@ class RedemptionAwardFurniture(db.Model): class RedemptionAwardIgloo(db.Model): __tablename__ = 'redemption_award_igloo' - code_id = db.Column('CodeID', db.ForeignKey('redemption_code.id', ondelete='CASCADE', onupdate='CASCADE'), - primary_key=True,nullable=False) + code_id = db.Column(db.ForeignKey('redemption_code.id', ondelete='CASCADE', onupdate='CASCADE'), + primary_key=True, nullable=False) igloo_id = db.Column(db.ForeignKey('igloo.id', ondelete='CASCADE', onupdate='CASCADE'), primary_key=True, nullable=False) @@ -94,11 +178,19 @@ class RedemptionAwardPuffleItem(db.Model): primary_key=True, nullable=False) -class PenguinRedemption(db.Model): - __tablename__ = 'penguin_redemption' +class PenguinRedemptionCode(db.Model): + __tablename__ = 'penguin_redemption_code' penguin_id = db.Column(db.ForeignKey('penguin.id', ondelete='CASCADE', onupdate='CASCADE'), primary_key=True, nullable=False) code_id = db.Column(db.ForeignKey('redemption_code.id', ondelete='CASCADE', onupdate='CASCADE'), primary_key=True, nullable=False, index=True) - book_id = db.Column(db.ForeignKey('redemption_book.id', ondelete='CASCADE', onupdate='CASCADE'), index=True) + + +class PenguinRedemptionBook(db.Model): + __tablename__ = 'penguin_redemption_book' + + penguin_id = db.Column(db.ForeignKey('penguin.id', ondelete='CASCADE', onupdate='CASCADE'), primary_key=True, + nullable=False) + book_id = db.Column(db.ForeignKey('redemption_book.id', ondelete='CASCADE', onupdate='CASCADE'), primary_key=True, + nullable=False, index=True) diff --git a/houdini/handlers/redemption/__init__.py b/houdini/handlers/redemption/__init__.py index e69de29..b791e72 100644 --- a/houdini/handlers/redemption/__init__.py +++ b/houdini/handlers/redemption/__init__.py @@ -0,0 +1,68 @@ +from houdini import handlers +from houdini.handlers import XTPacket +from houdini.constants import ClientType + +from houdini.data import db +from houdini.data.redemption import RedemptionCode, RedemptionAwardCard, RedemptionAwardFlooring, \ + RedemptionAwardFurniture, RedemptionAwardIgloo, RedemptionAwardItem, RedemptionAwardLocation,\ + RedemptionAwardPuffle, RedemptionAwardPuffleItem, PenguinRedemptionBook, PenguinRedemptionCode + +from datetime import datetime + + +@handlers.handler(XTPacket('rjs', ext='red'), pre_login=True, client=ClientType.Vanilla) +@handlers.allow_once +async def handle_join_redemption_server_vanilla(p, credentials: str, confirmation_hash: str, lang: str): + pid, _, username, login_key, rdnk, approved, rejected = credentials.split('|') + + if login_key != p.login_key: + return await p.close() + + tr = p.server.redis.multi_exec() + tr.setex(f'{username}.lkey', p.server.config.auth_ttl, login_key) + tr.setex(f'{username}.ckey', p.server.config.auth_ttl, confirmation_hash) + await tr.execute() + + redeemed_books = await PenguinRedemptionBook.query.where(PenguinRedemptionBook.penguin_id == p.id).gino.all() + await p.send_xt('rjs', ','.join(str(redeemed_book.book_id) for redeemed_book in redeemed_books), 'houdini', + int(p.is_member)) + + +@handlers.handler(XTPacket('rsc', ext='red'), pre_login=True) +@handlers.depends_on_packet(XTPacket('rjs', ext='red')) +async def handle_code(p, redemption_code: str): + query = RedemptionCode.load(cards=RedemptionAwardCard, + flooring=RedemptionAwardFlooring, + furniture=RedemptionAwardFurniture, + igloos=RedemptionAwardIgloo, + items=RedemptionAwardItem, + locations=RedemptionAwardLocation, + puffles=RedemptionAwardPuffle, + puffle_items=RedemptionAwardPuffleItem)\ + .query.where(RedemptionCode.code == redemption_code) + code = await query.gino.first() + + if code is None: + return await p.send_error(720) + + if code.uses is not None: + redeemed_count = await db.select([db.func.count(PenguinRedemptionCode.code_id)]).where( + PenguinRedemptionCode.code_id == code.id).gino.scalar() + + if redeemed_count >= code.uses: + return await p.send_error(721) + + penguin_redeemed = await PenguinRedemptionCode.query.where((PenguinRedemptionCode.code_id == code.id) & + (PenguinRedemptionCode.penguin_id == p.id)).gino.scalar() + if penguin_redeemed: + return await p.send_error(721) + + if code.expires is not None and code.expires < datetime.now(): + return await p.send_error(726) + + if code.type == 'CATALOG': + owned_ids = ','.join((item.item_id for item in code.items if item.item_id in p.inventory)) + return await p.send_xt('rsc', 'treasurebook', 3, owned_ids, 0) + + await p.send_xt('rsc', code.type, '', code.coins) +