from core.data.schema.base import BaseData, metadata from typing import Optional, Dict, List from sqlalchemy import Table, Column, UniqueConstraint, PrimaryKeyConstraint, and_ from sqlalchemy.types import Integer, String, TIMESTAMP, Boolean, BIGINT, Float, INTEGER, BOOLEAN, VARCHAR from sqlalchemy.schema import ForeignKey from sqlalchemy.sql import func, select from sqlalchemy.engine import Row from sqlalchemy.dialects.mysql import insert from sqlalchemy.sql.functions import coalesce from datetime import datetime opts = Table( "mai2_static_opt", metadata, Column("id", BIGINT, primary_key=True, nullable=False), Column("version", INTEGER, nullable=False), Column("name", VARCHAR(4), nullable=False), # Axxx Column("sequence", INTEGER, nullable=False), # release in DataConfig.xml Column("cmReleaseVer", INTEGER, nullable=False), Column("whenRead", TIMESTAMP, nullable=False, server_default=func.now()), Column("isEnable", BOOLEAN, nullable=False, server_default="1"), UniqueConstraint("version", "name", name="mai2_static_opt_uk"), mysql_charset="utf8mb4", ) event = Table( "mai2_static_event", metadata, Column("id", Integer, primary_key=True, nullable=False), Column("version", Integer, nullable=False), Column("eventId", Integer), Column("type", Integer), Column("name", String(255)), Column("startDate", TIMESTAMP, server_default=func.now()), Column("enabled", Boolean, server_default="1"), Column("opt", ForeignKey("mai2_static_opt.id", ondelete="SET NULL", onupdate="cascade")), UniqueConstraint("version", "eventId", "type", name="mai2_static_event_uk"), mysql_charset="utf8mb4", ) music = Table( "mai2_static_music", metadata, Column("id", Integer, primary_key=True, nullable=False), Column("version", Integer, nullable=False), Column("songId", Integer), Column("chartId", Integer), Column("title", String(255)), Column("artist", String(255)), Column("genre", String(255)), Column("bpm", Integer), Column("addedVersion", String(255)), Column("difficulty", Float), Column("noteDesigner", String(255)), Column("opt", ForeignKey("mai2_static_opt.id", ondelete="SET NULL", onupdate="cascade")), UniqueConstraint("songId", "chartId", "version", name="mai2_static_music_uk"), mysql_charset="utf8mb4", ) ticket = Table( "mai2_static_ticket", metadata, Column("id", Integer, primary_key=True, nullable=False), Column("version", Integer, nullable=False), Column("ticketId", Integer), Column("kind", Integer), Column("name", String(255)), Column("price", Integer, server_default="1"), Column("enabled", Boolean, server_default="1"), Column("opt", ForeignKey("mai2_static_opt.id", ondelete="SET NULL", onupdate="cascade")), UniqueConstraint("version", "ticketId", name="mai2_static_ticket_uk"), mysql_charset="utf8mb4", ) cards = Table( "mai2_static_cards", metadata, Column("id", Integer, primary_key=True, nullable=False), Column("version", Integer, nullable=False), Column("cardId", Integer, nullable=False), Column("cardName", String(255), nullable=False), Column("startDate", TIMESTAMP, server_default="2018-01-01 00:00:00.0"), Column("endDate", TIMESTAMP, server_default="2038-01-01 00:00:00.0"), Column("noticeStartDate", TIMESTAMP, server_default="2018-01-01 00:00:00.0"), Column("noticeEndDate", TIMESTAMP, server_default="2038-01-01 00:00:00.0"), Column("enabled", Boolean, server_default="1"), Column("opt", ForeignKey("cm_static_opts.id", ondelete="SET NULL", onupdate="cascade")), UniqueConstraint("version", "cardId", "cardName", name="mai2_static_cards_uk"), mysql_charset="utf8mb4", ) class Mai2StaticData(BaseData): async def put_game_event( self, version: int, type: int, event_id: int, name: str, opt_id: int = None ) -> Optional[int]: sql = insert(event).values( version=version, type=type, eventId=event_id, name=name, opt=coalesce(event.c.opt, opt_id) ) conflict = sql.on_duplicate_key_update(eventId=event_id, opt=coalesce(event.c.opt, opt_id)) result = await self.execute(conflict) if result is None: self.logger.warning( f"put_game_event: Failed to insert event! event_id {event_id} type {type} name {name}" ) return result.lastrowid async def get_game_events(self, version: int) -> Optional[List[Row]]: sql = event.select(event.c.version == version) result = await self.execute(sql) if result is None: return None return result.fetchall() async def get_enabled_events(self, version: int) -> Optional[List[Row]]: sql = select(event).where( and_(event.c.version == version, event.c.enabled == True) ) result = await self.execute(sql) if result is None: return None return result.fetchall() async def toggle_game_event( self, version: int, event_id: int, toggle: bool ) -> Optional[List]: sql = event.update( and_(event.c.version == version, event.c.eventId == event_id) ).values(enabled=int(toggle)) result = await self.execute(sql) if result is None: self.logger.warning( f"toggle_game_event: Failed to update event! event_id {event_id} toggle {toggle}" ) return result.last_updated_params() async def put_game_music( self, version: int, song_id: int, chart_id: int, title: str, artist: str, genre: str, bpm: str, added_version: str, difficulty: float, note_designer: str, opt_id: int = None ) -> None: sql = insert(music).values( version=version, songId=song_id, chartId=chart_id, title=title, artist=artist, genre=genre, bpm=bpm, addedVersion=added_version, difficulty=difficulty, noteDesigner=note_designer, opt=coalesce(music.c.opt, opt_id) ) conflict = sql.on_duplicate_key_update( title=title, artist=artist, genre=genre, bpm=bpm, addedVersion=added_version, difficulty=difficulty, noteDesigner=note_designer, opt=coalesce(music.c.opt, opt_id) ) result = await self.execute(conflict) if result is None: self.logger.warning(f"Failed to insert song {song_id} chart {chart_id}") return None return result.lastrowid async def put_game_ticket( self, version: int, ticket_id: int, ticket_type: int, ticket_price: int, name: str, opt_id: int = None ) -> Optional[int]: sql = insert(ticket).values( version=version, ticketId=ticket_id, kind=ticket_type, price=ticket_price, name=name, opt=coalesce(ticket.c.opt, opt_id) ) conflict = sql.on_duplicate_key_update(price=ticket_price, opt=coalesce(ticket.c.opt, opt_id)) result = await self.execute(conflict) if result is None: self.logger.warning(f"Failed to insert charge {ticket_id} type {ticket_type}") return None return result.lastrowid async def get_enabled_tickets( self, version: int, kind: int = None ) -> Optional[List[Row]]: if kind is not None: sql = select(ticket).where( and_( ticket.c.version == version, ticket.c.enabled == True, ticket.c.kind == kind, ) ) else: sql = select(ticket).where( and_(ticket.c.version == version, ticket.c.enabled == True) ) result = await self.execute(sql) if result is None: return None return result.fetchall() async def get_music_chart( self, version: int, song_id: int, chart_id: int ) -> Optional[List[Row]]: sql = select(music).where( and_( music.c.version == version, music.c.songId == song_id, music.c.chartId == chart_id, ) ) result = await self.execute(sql) if result is None: return None return result.fetchone() async def put_card(self, version: int, card_id: int, card_name: str, opt_id: int = None, **card_data) -> int: sql = insert(cards).values( version=version, cardId=card_id, cardName=card_name, opt=coalesce(cards.c.opt, opt_id), **card_data ) conflict = sql.on_duplicate_key_update(opt=coalesce(cards.c.opt, opt_id), **card_data) result = await self.execute(conflict) if result is None: self.logger.warning(f"Failed to insert card {card_id}") return None return result.lastrowid async def get_enabled_cards(self, version: int) -> Optional[List[Row]]: sql = cards.select(and_(cards.c.version == version, cards.c.enabled == True)) result = await self.execute(sql) if result is None: return None return result.fetchall() async def get_event_by_id(self, table_id: int) -> Optional[Row]: result = await self.execute(event.select(event.c.id == table_id)) if result: return result.fetchone() async def get_events_by_event_id(self, event_id: int) -> Optional[List[Row]]: result = await self.execute(event.select(event.c.eventId == event_id)) if result: return result.fetchall() async def update_event_by_id(self, table_id: int, is_enable: bool, start_date: datetime) -> None: result = await self.execute(event.update(event.c.id == table_id).values(enabled=is_enable, startDate = start_date)) if not result: self.logger.error(f"Failed to update event {table_id} - {is_enable} {start_date}") async def put_opt(self, version: int, folder: str, sequence: int, cm_seq: int = None) -> Optional[int]: sql = insert(opts).values(version=version, name=folder, sequence=sequence, cmReleaseVer=cm_seq) conflict = sql.on_duplicate_key_update(sequence=sequence, whenRead=datetime.now()) result = await self.execute(conflict) if result is None: self.logger.warning(f"Failed to insert opt! version {version} folder {folder} sequence {sequence}") return None return result.lastrowid async def get_opt_by_version_folder(self, version: int, folder: str) -> Optional[Row]: result = await self.execute(opts.select(and_( opts.c.version == version, opts.c.name == folder, ))) if result is None: return None return result.fetchone() async def get_opt_by_version_sequence(self, version: int, sequence: str) -> Optional[Row]: result = await self.execute(opts.select(and_( opts.c.version == version, opts.c.sequence == sequence, ))) if result is None: return None return result.fetchone() async def get_opts_by_version(self, version: int) -> Optional[List[Row]]: result = await self.execute(opts.select(opts.c.version == version)) if result is None: return None return result.fetchall() async def get_opts_enabled_by_version(self, version: int) -> Optional[List[Row]]: result = await self.execute(opts.select(and_( opts.c.version == version, opts.c.isEnable == True, ))) if result is None: return None return result.fetchall() async def get_latest_enabled_opt_by_version(self, version: int) -> Optional[Row]: result = await self.execute( opts.select(and_( opts.c.version == version, opts.c.isEnable == True, )).order_by(opts.c.sequence.desc()) ) if result is None: return None return result.fetchone() async def get_opts(self) -> Optional[List[Row]]: result = await self.execute(opts.select()) if result is None: return None return result.fetchall() async def get_opts(self) -> Optional[List[Row]]: result = await self.execute(opts.select()) if result is None: return None return result.fetchall() async def set_opt_enabled(self, opt_id: int, enabled: bool) -> bool: result = await self.execute(opts.update(opts.c.id == opt_id).values(isEnable=enabled)) if result is None: self.logger.error(f"Failed to set opt enabled status to {enabled} for opt {opt_id}") return False return True