1
0
mirror of synced 2025-02-22 04:39:37 +01:00
beerpsi 58a5177a30 use SQL's limit/offset pagination for nextIndex/maxCount requests (#185)
Instead of retrieving the entire list of items/characters/scores/etc. at once (and even store them in memory), use SQL's `LIMIT ... OFFSET ...` pagination so we only take what we need.

Currently only CHUNITHM uses this, but this will also affect maimai DX and O.N.G.E.K.I. once the PR is ready.

Also snuck in a fix for CHUNITHM/maimai DX's `GetUserRivalMusicApi` to respect the `userRivalMusicLevelList` sent by the client.

### How this works

Say we have a `GetUserCharacterApi` request:

```json
{
    "userId": 10000,
    "maxCount": 700,
    "nextIndex": 0
}
```

Instead of getting the entire character list from the database (which can be very large if the user force unlocked everything), add limit/offset to the query:

```python
select(character)
.where(character.c.user == user_id)
.order_by(character.c.id.asc())
.limit(max_count + 1)
.offset(next_index)
```

The query takes `maxCount + 1` items from the database to determine if there is more items than can be returned:

```python
rows = ...

if len(rows) > max_count:
    # return only max_count rows
    next_index += max_count
else:
    # return everything left
    next_index = -1
```

This has the benefit of not needing to load everything into memory (and also having to store server state, as seen in the [`SCORE_BUFFER` list](2274b42358/titles/chuni/base.py (L13)).)

Reviewed-on: https://gitea.tendokyu.moe/Hay1tsme/artemis/pulls/185
Co-authored-by: beerpsi <beerpsi@duck.com>
Co-committed-by: beerpsi <beerpsi@duck.com>
2024-11-16 19:10:29 +00:00

811 lines
30 KiB
Python

from datetime import datetime
from typing import Dict, List, Optional
from sqlalchemy import Column, Table, UniqueConstraint, and_
from sqlalchemy.dialects.mysql import insert
from sqlalchemy.engine import Row
from sqlalchemy.schema import ForeignKey
from sqlalchemy.sql import func, select
from sqlalchemy.types import TIMESTAMP, Boolean, Integer, String
from core.data.schema import BaseData, metadata
card: Table = Table(
"ongeki_user_card",
metadata,
Column("id", Integer, primary_key=True, nullable=False),
Column("user", ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade")),
Column("cardId", Integer),
Column("digitalStock", Integer),
Column("analogStock", Integer),
Column("level", Integer),
Column("maxLevel", Integer),
Column("exp", Integer),
Column("printCount", Integer),
Column("useCount", Integer),
Column("isNew", Boolean),
Column("kaikaDate", String(25)),
Column("choKaikaDate", String(25)),
Column("skillId", Integer),
Column("isAcquired", Boolean),
Column("created", String(25)),
UniqueConstraint("user", "cardId", name="ongeki_user_card_uk"),
mysql_charset="utf8mb4",
)
deck = Table(
"ongeki_user_deck",
metadata,
Column("id", Integer, primary_key=True, nullable=False),
Column("user", ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade")),
Column("deckId", Integer),
Column("cardId1", Integer),
Column("cardId2", Integer),
Column("cardId3", Integer),
UniqueConstraint("user", "deckId", name="ongeki_user_deck_uk"),
mysql_charset="utf8mb4",
)
character: Table = Table(
"ongeki_user_character",
metadata,
Column("id", Integer, primary_key=True, nullable=False),
Column("user", ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade")),
Column("characterId", Integer),
Column("costumeId", Integer),
Column("attachmentId", Integer),
Column("playCount", Integer),
Column("intimateLevel", Integer),
Column("intimateCount", Integer),
Column("intimateCountRewarded", Integer),
Column("intimateCountDate", String(25)),
Column("isNew", Boolean),
UniqueConstraint("user", "characterId", name="ongeki_user_character_uk"),
mysql_charset="utf8mb4",
)
boss = Table(
"ongeki_user_boss",
metadata,
Column("id", Integer, primary_key=True, nullable=False),
Column("user", ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade")),
Column("musicId", Integer),
Column("damage", Integer),
Column("isClear", Boolean),
Column("eventId", Integer),
UniqueConstraint("user", "musicId", "eventId", name="ongeki_user_boss_uk"),
mysql_charset="utf8mb4",
)
story = Table(
"ongeki_user_story",
metadata,
Column("id", Integer, primary_key=True, nullable=False),
Column("user", ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade")),
Column("storyId", Integer),
Column("jewelCount", Integer),
Column("lastChapterId", Integer),
Column("lastPlayMusicId", Integer),
Column("lastPlayMusicCategory", Integer),
Column("lastPlayMusicLevel", Integer),
UniqueConstraint("user", "storyId", name="ongeki_user_story_uk"),
mysql_charset="utf8mb4",
)
chapter = Table(
"ongeki_user_chapter",
metadata,
Column("id", Integer, primary_key=True, nullable=False),
Column("user", ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade")),
Column("chapterId", Integer),
Column("jewelCount", Integer),
Column("isStoryWatched", Boolean),
Column("isClear", Boolean),
Column("lastPlayMusicId", Integer),
Column("lastPlayMusicCategory", Integer),
Column("lastPlayMusicLevel", Integer),
Column("skipTiming1", Integer),
Column("skipTiming2", Integer),
UniqueConstraint("user", "chapterId", name="ongeki_user_chapter_uk"),
mysql_charset="utf8mb4",
)
memorychapter = Table(
"ongeki_user_memorychapter",
metadata,
Column("id", Integer, primary_key=True, nullable=False),
Column("user", ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade")),
Column("chapterId", Integer),
Column("gaugeId", Integer),
Column("gaugeNum", Integer),
Column("jewelCount", Integer),
Column("isStoryWatched", Boolean),
Column("isBossWatched", Boolean),
Column("isDialogWatched", Boolean),
Column("isEndingWatched", Boolean),
Column("isClear", Boolean),
Column("lastPlayMusicId", Integer),
Column("lastPlayMusicLevel", Integer),
Column("lastPlayMusicCategory", Integer),
UniqueConstraint("user", "chapterId", name="ongeki_user_memorychapter_uk"),
mysql_charset="utf8mb4",
)
item: Table = Table(
"ongeki_user_item",
metadata,
Column("id", Integer, primary_key=True, nullable=False),
Column("user", ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade")),
Column("itemKind", Integer),
Column("itemId", Integer),
Column("stock", Integer),
Column("isValid", Boolean),
UniqueConstraint("user", "itemKind", "itemId", name="ongeki_user_item_uk"),
mysql_charset="utf8mb4",
)
music_item = Table(
"ongeki_user_music_item",
metadata,
Column("id", Integer, primary_key=True, nullable=False),
Column("user", ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade")),
Column("musicId", Integer),
Column("status", Integer),
UniqueConstraint("user", "musicId", name="ongeki_user_music_item_uk"),
mysql_charset="utf8mb4",
)
login_bonus = Table(
"ongeki_user_login_bonus",
metadata,
Column("id", Integer, primary_key=True, nullable=False),
Column("user", ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade")),
Column("bonusId", Integer),
Column("bonusCount", Integer),
Column("lastUpdateDate", String(25)),
UniqueConstraint("user", "bonusId", name="ongeki_user_login_bonus_uk"),
mysql_charset="utf8mb4",
)
event_point = Table(
"ongeki_user_event_point",
metadata,
Column("id", Integer, primary_key=True, nullable=False),
Column("user", ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade")),
Column("version", Integer, nullable=False),
Column("eventId", Integer, nullable=False),
Column("point", Integer, nullable=False),
Column("rank", Integer),
Column("type", Integer, nullable=False),
Column("date", String(25)),
Column("isRankingRewarded", Boolean),
UniqueConstraint("user", "eventId", name="ongeki_user_event_point_uk"),
mysql_charset="utf8mb4",
)
mission_point = Table(
"ongeki_user_mission_point",
metadata,
Column("id", Integer, primary_key=True, nullable=False),
Column("user", ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade")),
Column("version", Integer),
Column("eventId", Integer),
Column("point", Integer),
UniqueConstraint("user", "eventId", name="ongeki_user_mission_point_uk"),
mysql_charset="utf8mb4",
)
scenerio = Table(
"ongeki_user_scenerio",
metadata,
Column("id", Integer, primary_key=True, nullable=False),
Column("user", ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade")),
Column("scenarioId", Integer),
Column("playCount", Integer),
UniqueConstraint("user", "scenarioId", name="ongeki_user_scenerio_uk"),
mysql_charset="utf8mb4",
)
trade_item = Table(
"ongeki_user_trade_item",
metadata,
Column("id", Integer, primary_key=True, nullable=False),
Column("user", ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade")),
Column("chapterId", Integer),
Column("tradeItemId", Integer),
Column("tradeCount", Integer),
UniqueConstraint(
"user", "chapterId", "tradeItemId", name="ongeki_user_trade_item_uk"
),
mysql_charset="utf8mb4",
)
event_music = Table(
"ongeki_user_event_music",
metadata,
Column("id", Integer, primary_key=True, nullable=False),
Column("user", ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade")),
Column("eventId", Integer),
Column("type", Integer),
Column("musicId", Integer),
Column("level", Integer),
Column("techScoreMax", Integer),
Column("platinumScoreMax", Integer),
Column("techRecordDate", String(25)),
Column("isTechNewRecord", Boolean),
UniqueConstraint(
"user", "eventId", "type", "musicId", "level", name="ongeki_user_event_music"
),
mysql_charset="utf8mb4",
)
tech_event = Table(
"ongeki_user_tech_event",
metadata,
Column("id", Integer, primary_key=True, nullable=False),
Column("user", ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade")),
Column("version", Integer, nullable=False),
Column("eventId", Integer, nullable=False),
Column("totalTechScore", Integer, nullable=False),
Column("totalPlatinumScore", Integer, nullable=False),
Column("techRecordDate", String(25)),
Column("isRankingRewarded", Boolean),
Column("isTotalTechNewRecord", Boolean),
UniqueConstraint("user", "eventId", name="ongeki_user_tech_event_uk"),
mysql_charset="utf8mb4",
)
tech_ranking = Table(
"ongeki_tech_event_ranking",
metadata,
Column("id", Integer, primary_key=True, nullable=False),
Column("user", ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"), nullable=False),
Column("version", Integer, nullable=False),
Column("date", String(25)),
Column("eventId", Integer, nullable=False),
Column("rank", Integer),
Column("totalPlatinumScore", Integer, nullable=False),
Column("totalTechScore", Integer, nullable=False),
UniqueConstraint("user", "eventId", name="ongeki_tech_event_ranking_uk"),
mysql_charset="utf8mb4",
)
gacha = Table(
"ongeki_user_gacha",
metadata,
Column("id", Integer, primary_key=True, nullable=False),
Column(
"user",
ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"),
nullable=False,
),
Column("gachaId", Integer, nullable=False),
Column("totalGachaCnt", Integer, server_default="0"),
Column("ceilingGachaCnt", Integer, server_default="0"),
Column("selectPoint", Integer, server_default="0"),
Column("useSelectPoint", Integer, server_default="0"),
Column("dailyGachaCnt", Integer, server_default="0"),
Column("fiveGachaCnt", Integer, server_default="0"),
Column("elevenGachaCnt", Integer, server_default="0"),
Column("dailyGachaDate", TIMESTAMP, nullable=False, server_default=func.now()),
UniqueConstraint("user", "gachaId", name="ongeki_user_gacha_uk"),
mysql_charset="utf8mb4",
)
gacha_supply = Table(
"ongeki_user_gacha_supply",
metadata,
Column("id", Integer, primary_key=True, nullable=False),
Column(
"user",
ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"),
nullable=False,
),
Column("cardId", Integer, nullable=False),
UniqueConstraint("user", "cardId", name="ongeki_user_gacha_supply_uk"),
mysql_charset="utf8mb4",
)
print_detail = Table(
"ongeki_user_print_detail",
metadata,
Column("id", Integer, primary_key=True, nullable=False),
Column(
"user",
ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"),
nullable=False,
),
Column("cardId", Integer, nullable=False),
Column("cardType", Integer, server_default="0"),
Column("printDate", TIMESTAMP, nullable=False),
Column("serialId", String(20), nullable=False),
Column("placeId", Integer, nullable=False),
Column("clientId", String(11), nullable=False),
Column("printerSerialId", String(20), nullable=False),
Column("isHolograph", Boolean, server_default="0"),
Column("isAutographed", Boolean, server_default="0"),
Column("printOption1", Boolean, server_default="1"),
Column("printOption2", Boolean, server_default="1"),
Column("printOption3", Boolean, server_default="1"),
Column("printOption4", Boolean, server_default="1"),
Column("printOption5", Boolean, server_default="1"),
Column("printOption6", Boolean, server_default="1"),
Column("printOption7", Boolean, server_default="1"),
Column("printOption8", Boolean, server_default="1"),
Column("printOption9", Boolean, server_default="1"),
Column("printOption10", Boolean, server_default="0"),
UniqueConstraint("serialId", name="ongeki_user_print_detail_uk"),
mysql_charset="utf8mb4",
)
class OngekiItemData(BaseData):
async def put_card(self, aime_id: int, card_data: Dict) -> Optional[int]:
card_data["user"] = aime_id
sql = insert(card).values(**card_data)
conflict = sql.on_duplicate_key_update(**card_data)
result = await self.execute(conflict)
if result is None:
self.logger.warning(f"put_card: Failed to update! aime_id: {aime_id}")
return None
return result.lastrowid
async def get_cards(
self, aime_id: int, limit: Optional[int] = None, offset: Optional[int] = None
) -> Optional[List[Row]]:
sql = select(card).where(card.c.user == aime_id)
if limit is not None or offset is not None:
sql = sql.order_by(card.c.id)
if limit is not None:
sql = sql.limit(limit)
if offset is not None:
sql = sql.offset(offset)
result = await self.execute(sql)
if result is None:
return None
return result.fetchall()
async def put_character(self, aime_id: int, character_data: Dict) -> Optional[int]:
character_data["user"] = aime_id
sql = insert(character).values(**character_data)
conflict = sql.on_duplicate_key_update(**character_data)
result = await self.execute(conflict)
if result is None:
self.logger.warning(f"put_character: Failed to update! aime_id: {aime_id}")
return None
return result.lastrowid
async def get_characters(
self, aime_id: int, limit: Optional[int] = None, offset: Optional[int] = None
) -> Optional[List[Row]]:
sql = select(character).where(character.c.user == aime_id)
if limit is not None or offset is not None:
sql = sql.order_by(character.c.id)
if limit is not None:
sql = sql.limit(limit)
if offset is not None:
sql = sql.offset(offset)
result = await self.execute(sql)
if result is None:
return None
return result.fetchall()
async def put_deck(self, aime_id: int, deck_data: Dict) -> Optional[int]:
deck_data["user"] = aime_id
sql = insert(deck).values(**deck_data)
conflict = sql.on_duplicate_key_update(**deck_data)
result = await self.execute(conflict)
if result is None:
self.logger.warning(f"put_deck: Failed to update! aime_id: {aime_id}")
return None
return result.lastrowid
async def get_deck(self, aime_id: int, deck_id: int) -> Optional[Dict]:
sql = select(deck).where(and_(deck.c.user == aime_id, deck.c.deckId == deck_id))
result = await self.execute(sql)
if result is None:
return None
return result.fetchone()
async def get_decks(self, aime_id: int) -> Optional[List[Dict]]:
sql = select(deck).where(deck.c.user == aime_id)
result = await self.execute(sql)
if result is None:
return None
return result.fetchall()
async def put_boss(self, aime_id: int, boss_data: Dict) -> Optional[int]:
boss_data["user"] = aime_id
sql = insert(boss).values(**boss_data)
conflict = sql.on_duplicate_key_update(**boss_data)
result = await self.execute(conflict)
if result is None:
self.logger.warning(f"put_boss: Failed to update! aime_id: {aime_id}")
return None
return result.lastrowid
async def put_story(self, aime_id: int, story_data: Dict) -> Optional[int]:
story_data["user"] = aime_id
sql = insert(story).values(**story_data)
conflict = sql.on_duplicate_key_update(**story_data)
result = await self.execute(conflict)
if result is None:
self.logger.warning(f"put_story: Failed to update! aime_id: {aime_id}")
return None
return result.lastrowid
async def get_stories(self, aime_id: int) -> Optional[List[Dict]]:
sql = select(story).where(story.c.user == aime_id)
result = await self.execute(sql)
if result is None:
return None
return result.fetchall()
async def put_chapter(self, aime_id: int, chapter_data: Dict) -> Optional[int]:
chapter_data["user"] = aime_id
sql = insert(chapter).values(**chapter_data)
conflict = sql.on_duplicate_key_update(**chapter_data)
result = await self.execute(conflict)
if result is None:
self.logger.warning(f"put_chapter: Failed to update! aime_id: {aime_id}")
return None
return result.lastrowid
async def get_chapters(self, aime_id: int) -> Optional[List[Dict]]:
sql = select(chapter).where(chapter.c.user == aime_id)
result = await self.execute(sql)
if result is None:
return None
return result.fetchall()
async def put_item(self, aime_id: int, item_data: Dict) -> Optional[int]:
item_data["user"] = aime_id
sql = insert(item).values(**item_data)
conflict = sql.on_duplicate_key_update(**item_data)
result = await self.execute(conflict)
if result is None:
self.logger.warning(f"put_item: Failed to update! aime_id: {aime_id}")
return None
return result.lastrowid
async def get_item(self, aime_id: int, item_id: int, item_kind: int) -> Optional[Dict]:
sql = select(item).where(and_(item.c.user == aime_id, item.c.itemId == item_id))
result = await self.execute(sql)
if result is None:
return None
return result.fetchone()
async def get_items(
self,
aime_id: int,
item_kind: Optional[int] = None,
limit: Optional[int] = None,
offset: Optional[int] = None,
) -> Optional[List[Row]]:
cond = item.c.user == aime_id
if item_kind is not None:
cond &= item.c.itemKind == item_kind
sql = select(item).where(cond)
if limit is not None or offset is not None:
sql = sql.order_by(item.c.id)
if limit is not None:
sql = sql.limit(limit)
if offset is not None:
sql = sql.offset(offset)
result = await self.execute(sql)
if result is None:
return None
return result.fetchall()
async def put_music_item(self, aime_id: int, music_item_data: Dict) -> Optional[int]:
music_item_data["user"] = aime_id
sql = insert(music_item).values(**music_item_data)
conflict = sql.on_duplicate_key_update(**music_item_data)
result = await self.execute(conflict)
if result is None:
self.logger.warning(f"put_music_item: Failed to update! aime_id: {aime_id}")
return None
return result.lastrowid
async def get_music_items(self, aime_id: int) -> Optional[List[Dict]]:
sql = select(music_item).where(music_item.c.user == aime_id)
result = await self.execute(sql)
if result is None:
return None
return result.fetchall()
async def put_login_bonus(self, aime_id: int, login_bonus_data: Dict) -> Optional[int]:
login_bonus_data["user"] = aime_id
sql = insert(login_bonus).values(**login_bonus_data)
conflict = sql.on_duplicate_key_update(**login_bonus_data)
result = await self.execute(conflict)
if result is None:
self.logger.warning(f"put_login_bonus: Failed to update! aime_id: {aime_id}")
return None
return result.lastrowid
async def get_login_bonuses(self, aime_id: int) -> Optional[List[Dict]]:
sql = select(login_bonus).where(login_bonus.c.user == aime_id)
result = await self.execute(sql)
if result is None:
return None
return result.fetchall()
async def put_mission_point(self, aime_id: int, version: int, mission_point_data: Dict) -> Optional[int]:
mission_point_data["version"] = version
mission_point_data["user"] = aime_id
sql = insert(mission_point).values(**mission_point_data)
conflict = sql.on_duplicate_key_update(**mission_point_data)
result = await self.execute(conflict)
if result is None:
self.logger.warning(f"put_mission_point: Failed to update! aime_id: {aime_id}")
return None
return result.lastrowid
async def get_mission_points(self, version: int, aime_id: int) -> Optional[List[Dict]]:
sql = select(mission_point).where(and_(mission_point.c.user == aime_id, mission_point.c.version == version))
result = await self.execute(sql)
if result is None:
return None
return result.fetchall()
async def put_event_point(self, aime_id: int, version: int, event_point_data: Dict) -> Optional[int]:
# We update only the newest (type: 1) entry, in official spec game watches for both latest(type:1) and previous (type:2) entries to give an additional info how many ranks has player moved up or down
# This fully featured is on TODO list, at the moment we just update the tables as data comes and give out rank as request comes
event_point_data["user"] = aime_id
event_point_data["version"] = version
event_point_data["type"] = 1
event_point_time = datetime.now()
event_point_data["date"] = datetime.strftime(event_point_time, "%Y-%m-%d %H:%M")
sql = insert(event_point).values(**event_point_data)
conflict = sql.on_duplicate_key_update(**event_point_data)
result = await self.execute(conflict)
if result is None:
self.logger.warning(f"put_event_point: Failed to update! aime_id: {aime_id}")
return None
return result.lastrowid
async def get_event_points(self, aime_id: int) -> Optional[List[Dict]]:
sql = select(event_point).where(event_point.c.user == aime_id)
result = await self.execute(sql)
if result is None:
return None
return result.fetchall()
async def put_scenerio(self, aime_id: int, scenerio_data: Dict) -> Optional[int]:
scenerio_data["user"] = aime_id
sql = insert(scenerio).values(**scenerio_data)
conflict = sql.on_duplicate_key_update(**scenerio_data)
result = await self.execute(conflict)
if result is None:
self.logger.warning(f"put_scenerio: Failed to update! aime_id: {aime_id}")
return None
return result.lastrowid
async def get_scenerios(self, aime_id: int) -> Optional[List[Dict]]:
sql = select(scenerio).where(scenerio.c.user == aime_id)
result = await self.execute(sql)
if result is None:
return None
return result.fetchall()
async def put_trade_item(self, aime_id: int, trade_item_data: Dict) -> Optional[int]:
trade_item_data["user"] = aime_id
sql = insert(trade_item).values(**trade_item_data)
conflict = sql.on_duplicate_key_update(**trade_item_data)
result = await self.execute(conflict)
if result is None:
self.logger.warning(f"put_trade_item: Failed to update! aime_id: {aime_id}")
return None
return result.lastrowid
async def get_trade_items(self, aime_id: int) -> Optional[List[Dict]]:
sql = select(trade_item).where(trade_item.c.user == aime_id)
result = await self.execute(sql)
if result is None:
return None
return result.fetchall()
async def put_event_music(self, aime_id: int, event_music_data: Dict) -> Optional[int]:
event_music_data["user"] = aime_id
sql = insert(event_music).values(**event_music_data)
conflict = sql.on_duplicate_key_update(**event_music_data)
result = await self.execute(conflict)
if result is None:
self.logger.warning(f"put_event_music: Failed to update! aime_id: {aime_id}")
return None
return result.lastrowid
async def get_event_music(self, aime_id: int) -> Optional[List[Dict]]:
sql = select(event_music).where(event_music.c.user == aime_id)
result = await self.execute(sql)
if result is None:
return None
return result.fetchall()
async def put_tech_event(self, aime_id: int, version: int, tech_event_data: Dict) -> Optional[int]:
tech_event_data["user"] = aime_id
tech_event_data["version"] = version
sql = insert(tech_event).values(**tech_event_data)
conflict = sql.on_duplicate_key_update(**tech_event_data)
result = await self.execute(conflict)
if result is None:
self.logger.warning(f"put_tech_event: Failed to update! aime_id: {aime_id}")
return None
return result.lastrowid
async def put_tech_event_ranking(self, aime_id: int, version: int, tech_event_data: Dict) -> Optional[int]:
tech_event_data["user"] = aime_id
tech_event_data["version"] = version
tech_event_data.pop("isRankingRewarded")
tech_event_data.pop("isTotalTechNewRecord")
tech_event_data["date"] = tech_event_data.pop("techRecordDate")
tech_event_data["rank"] = 0
sql = insert(tech_ranking).values(**tech_event_data)
conflict = sql.on_duplicate_key_update(**tech_event_data)
result = await self.execute(conflict)
if result is None:
self.logger.warning(f"put_tech_event_ranking: Failed to update ranking! aime_id {aime_id}")
return None
return result.lastrowid
async def get_tech_event(self, version: int, aime_id: int) -> Optional[List[Dict]]:
sql = select(tech_event).where(and_(tech_event.c.user == aime_id, tech_event.c.version == version))
result = await self.execute(sql)
if result is None:
return None
return result.fetchall()
async def get_bosses(self, aime_id: int) -> Optional[List[Dict]]:
sql = select(boss).where(boss.c.user == aime_id)
result = await self.execute(sql)
if result is None:
return None
return result.fetchall()
async def put_memorychapter(
self, aime_id: int, memorychapter_data: Dict
) -> Optional[int]:
memorychapter_data["user"] = aime_id
sql = insert(memorychapter).values(**memorychapter_data)
conflict = sql.on_duplicate_key_update(**memorychapter_data)
result = await self.execute(conflict)
if result is None:
self.logger.warning(f"put_memorychapter: Failed to update! aime_id: {aime_id}")
return None
return result.lastrowid
async def get_memorychapters(self, aime_id: int) -> Optional[List[Dict]]:
sql = select(memorychapter).where(memorychapter.c.user == aime_id)
result = await self.execute(sql)
if result is None:
return None
return result.fetchall()
async def get_user_gacha(self, aime_id: int, gacha_id: int) -> Optional[Row]:
sql = gacha.select(and_(gacha.c.user == aime_id, gacha.c.gachaId == gacha_id))
result = await self.execute(sql)
if result is None:
return None
return result.fetchone()
async def get_user_gachas(self, aime_id: int) -> Optional[List[Row]]:
sql = gacha.select(gacha.c.user == aime_id)
result = await self.execute(sql)
if result is None:
return None
return result.fetchall()
async def get_user_gacha_supplies(self, aime_id: int) -> Optional[List[Row]]:
sql = gacha_supply.select(gacha_supply.c.user == aime_id)
result = await self.execute(sql)
if result is None:
return None
return result.fetchall()
async def put_user_gacha(self, aime_id: int, gacha_id: int, **data) -> Optional[int]:
sql = insert(gacha).values(user=aime_id, gachaId=gacha_id, **data)
conflict = sql.on_duplicate_key_update(user=aime_id, gachaId=gacha_id, **data)
result = await self.execute(conflict)
if result is None:
self.logger.warning(f"put_user_gacha: Failed to insert! aime_id: {aime_id}")
return None
return result.lastrowid
async def put_user_print_detail(
self, aime_id: int, serial_id: str, user_print_data: Dict
) -> Optional[int]:
sql = insert(print_detail).values(
user=aime_id, serialId=serial_id, **user_print_data
)
conflict = sql.on_duplicate_key_update(user=aime_id, **user_print_data)
result = await self.execute(conflict)
if result is None:
self.logger.warning(
f"put_user_print_detail: Failed to insert! aime_id: {aime_id}"
)
return None
return result.lastrowid
async def get_ranking_event_ranks(self, version: int, aime_id: int) -> Optional[List[Dict]]:
# Calculates player rank on GameRequest from server, and sends it back, official spec would rank players in maintenance period, on TODO list
sql = select(event_point.c.id, event_point.c.user, event_point.c.eventId, event_point.c.type, func.row_number().over(partition_by=event_point.c.eventId, order_by=event_point.c.point.desc()).label('rank'), event_point.c.date, event_point.c.point).where(event_point.c.version == version)
result = await self.execute(sql)
if result is None:
self.logger.error(f"failed to rank aime_id: {aime_id} ranking event positions")
return None
return result.fetchall()
async def get_tech_event_ranking(self, version: int, aime_id: int) -> Optional[List[Dict]]:
sql = select(tech_ranking.c.id, tech_ranking.c.user, tech_ranking.c.date, tech_ranking.c.eventId, func.row_number().over(partition_by=tech_ranking.c.eventId, order_by=[tech_ranking.c.totalTechScore.desc(),tech_ranking.c.totalPlatinumScore.desc()]).label('rank'), tech_ranking.c.totalTechScore, tech_ranking.c.totalPlatinumScore).where(tech_ranking.c.version == version)
result = await self.execute(sql)
if result is None:
self.logger.warning(f"aime_id: {aime_id} has no tech ranking ranks")
return None
return result.fetchall()