feat(chuni): Implement GameLoginApi/GameLogoutApi

This commit is contained in:
2024-06-23 12:30:30 +07:00
parent 79d27b0582
commit 63987f2095
3 changed files with 95 additions and 4 deletions

View File

@ -0,0 +1,36 @@
"""Implement GameLoginApi/GameLogoutApi
Revision ID: 263169f3a129
Revises: 48f4acc43a7e
Create Date: 2024-06-23 12:16:57.881129
"""
from alembic import op
import sqlalchemy as sa
from sqlalchemy.dialects import mysql
# revision identifiers, used by Alembic.
revision = '263169f3a129'
down_revision = '48f4acc43a7e'
branch_labels = None
depends_on = None
def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.create_table('aime_user_game_locks',
sa.Column('id', sa.Integer(), autoincrement=True, nullable=False),
sa.Column('user', sa.Integer(), nullable=False),
sa.Column('game', sa.String(length=4), nullable=False),
sa.Column('expires_at', sa.TIMESTAMP(), server_default=sa.text('date_add(now(), INTERVAL 15 MINUTE)'), nullable=True),
sa.Column('extra', sa.JSON(), nullable=True),
sa.ForeignKeyConstraint(['user'], ['aime_user.id'], onupdate='cascade', ondelete='cascade'),
sa.PrimaryKeyConstraint('id'),
sa.UniqueConstraint('user', 'game', name='aime_user_title_locks'),
mysql_charset='utf8mb4',
)
# ### end Alembic commands ###
def downgrade():
op.drop_table("aime_user_game_locks")

View File

@ -1,7 +1,9 @@
from typing import Optional, List from typing import Optional, List
from sqlalchemy import Table, Column from sqlalchemy import Table, Column, text, UniqueConstraint
from sqlalchemy.types import Integer, String, TIMESTAMP from sqlalchemy.dialects import mysql
from sqlalchemy.types import Integer, String, TIMESTAMP, JSON
from sqlalchemy.sql import func from sqlalchemy.sql import func
from sqlalchemy.sql.schema import ForeignKey
from sqlalchemy.dialects.mysql import insert from sqlalchemy.dialects.mysql import insert
from sqlalchemy.sql import func, select from sqlalchemy.sql import func, select
from sqlalchemy.engine import Row from sqlalchemy.engine import Row
@ -23,6 +25,18 @@ aime_user = Table(
mysql_charset="utf8mb4", mysql_charset="utf8mb4",
) )
game_locks = Table(
"aime_user_game_locks",
metadata,
Column("id", Integer, nullable=False, primary_key=True, autoincrement=True),
Column("user", ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"), nullable=False),
Column("game", String(4), nullable=False),
Column("expires_at", TIMESTAMP, server_default=func.date_add(func.now(), text("INTERVAL 15 MINUTE"))),
Column("extra", JSON),
UniqueConstraint("user", "game", name="aime_user_title_locks"),
mysql_charset="utf8mb4",
)
class UserData(BaseData): class UserData(BaseData):
async def create_user( async def create_user(
self, self,
@ -123,4 +137,33 @@ class UserData(BaseData):
async def get_user_by_username(self, username: str) -> Optional[Row]: async def get_user_by_username(self, username: str) -> Optional[Row]:
result = await self.execute(aime_user.select(aime_user.c.username == username)) result = await self.execute(aime_user.select(aime_user.c.username == username))
if result: return result.fetchone() if result:
return result.fetchone()
async def acquire_lock_for_game(self, user_id: int, game: str, extra: dict | None = None):
sql = game_locks.select(
(game_locks.c.user == user_id)
& (game_locks.c.game == game)
& func.timestampdiff(text("SECOND"), func.now(), game_locks.c.expires_at) > 0)
result = await self.execute(sql)
if result:
return result.fetchone()
sql = (
insert(game_locks)
.values(user=user_id, game=game, extra=extra)
.on_duplicate_key_update(
expires_at=func.date_add(func.now(), text("INTERVAL 15 MINUTE")),
extra=extra,
)
)
await self.execute(sql)
return None
async def release_lock_for_game(self, user_id: int, game: str):
sql = game_locks.delete(game_locks.c.user == user_id & game_locks.c.game == game)
await self.execute(sql)

View File

@ -31,12 +31,21 @@ class ChuniBase:
loginBonus 30 gets looped, only show the login banner every 24 hours, loginBonus 30 gets looped, only show the login banner every 24 hours,
adds the bonus to items (itemKind 6) adds the bonus to items (itemKind 6)
""" """
user_id = int(data["userId"])
lock_result = await self.data.user.acquire_lock_for_game(user_id, self.game, data)
if lock_result is not None:
self.logger.warn(
"User ID %d attempted to log in while having a profile lock expiring on %s.",
user_id, lock_result["expires_at"],
extra=lock_result["extra"]
)
return {"returnCode": 0}
# ignore the login bonus if disabled in config # ignore the login bonus if disabled in config
if not self.game_cfg.mods.use_login_bonus: if not self.game_cfg.mods.use_login_bonus:
return {"returnCode": 1} return {"returnCode": 1}
user_id = data["userId"]
login_bonus_presets = await self.data.static.get_login_bonus_presets(self.version) login_bonus_presets = await self.data.static.get_login_bonus_presets(self.version)
for preset in login_bonus_presets: for preset in login_bonus_presets:
@ -120,6 +129,9 @@ class ChuniBase:
async def handle_game_logout_api_request(self, data: Dict) -> Dict: async def handle_game_logout_api_request(self, data: Dict) -> Dict:
# self.data.base.log_event("chuni", "logout", logging.INFO, {"version": self.version, "user": data["userId"]}) # self.data.base.log_event("chuni", "logout", logging.INFO, {"version": self.version, "user": data["userId"]})
user_id = int(data["userId"])
await self.data.user.release_lock_for_game(user_id, self.game)
return {"returnCode": 1} return {"returnCode": 1}
async def handle_get_game_charge_api_request(self, data: Dict) -> Dict: async def handle_get_game_charge_api_request(self, data: Dict) -> Dict: