basic campaign support

This commit is contained in:
Hay1tsme
2026-07-05 06:13:50 -04:00
parent 0b1bd58033
commit c1d3792d90
5 changed files with 353 additions and 10 deletions
+152 -2
View File
@@ -3,15 +3,17 @@ import json
import logging
from random import randrange
from typing import Any, Dict, List, Optional
from datetime import datetime
from sqlalchemy import Column, MetaData, Table
from sqlalchemy import Column, MetaData, Table, UniqueConstraint
from sqlalchemy.engine import Row
from sqlalchemy.engine.cursor import CursorResult
from sqlalchemy.exc import SQLAlchemyError
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy.orm import sessionmaker
from sqlalchemy.schema import ForeignKey
from sqlalchemy.sql import func, text
from sqlalchemy.sql import func, text, and_, or_
from sqlalchemy.dialects.mysql import insert
from sqlalchemy.types import INTEGER, JSON, TEXT, TIMESTAMP, Integer, String
from core.config import CoreConfig
@@ -37,6 +39,33 @@ event_log: Table = Table(
mysql_charset="utf8mb4",
)
campaign: Table = Table(
"campaign",
metadata,
Column("id", INTEGER, primary_key=True, nullable=False),
Column("name", TEXT(127), nullable=False),
Column("announce_date", TIMESTAMP), # None = start announcing now
Column("start_date", TIMESTAMP), # None = start now
Column("end_date", TIMESTAMP), # None = never end
Column("distrib_start_date", TIMESTAMP), # None = start distributing now
Column("distrib_end_date", TIMESTAMP), # None = never stop distributing
# These are for legacy campaign requests prior to ADB v3.03.0.
Column("info0", INTEGER),
Column("info1", INTEGER),
Column("info2", INTEGER),
Column("info3", INTEGER),
mysql_charset="utf8mb4",
)
# Unfortunatly without a machine table change there's no real way to gate by game version...
campaign_game: Table = Table(
"campaign_game",
metadata,
Column("campaign_id", INTEGER, ForeignKey("campaign.id", ondelete="cascade", onupdate="cascade"), nullable=False),
Column("game_id", TEXT(5), nullable=False),
UniqueConstraint("campaign_id", "game_id", name="campaign_game_uk"),
mysql_charset="utf8mb4",
)
class BaseData:
def __init__(self, cfg: CoreConfig, conn: "sessionmaker[AsyncSession]") -> None:
@@ -119,6 +148,127 @@ class BaseData:
return None
return result.fetchall()
async def create_campaign(self, campaign_id: int, name: str, announce_date: Optional[datetime] = None,
start_date: Optional[datetime] = None, end_date: Optional[datetime] = None,
distribute_start_date: Optional[datetime] = None, distribute_end_date: Optional[datetime] = None) -> Optional[int]:
sql = insert(campaign).values(
id = campaign_id,
name = name,
announce_date = announce_date,
start_date = start_date,
end_date = end_date,
distribute_start_date = distribute_start_date,
distribute_end_date = distribute_end_date,
)
conflict = sql.on_duplicate_key_update(
announce_date = announce_date,
start_date = start_date,
end_date = end_date,
distribute_start_date = distribute_start_date,
distribute_end_date = distribute_end_date,
)
result = await self.execute(conflict)
if result is None:
self.logger.error(f"Failed to create campaign {name} ID {campaign_id}!")
return None
return result.lastrowid
async def create_campaign_old(self, campaign_id: int, info0: int = 0, info1: int = 0, info2: int = 0, info3: int = 0) -> Optional[int]:
sql = insert(campaign).values(
id = campaign_id,
info0 = info0,
info1 = info1,
info2 = info2,
info3 = info3
)
conflict = sql.on_duplicate_key_update(
info0 = info0,
info1 = info1,
info2 = info2,
info3 = info3
)
result = await self.execute(conflict)
if result is None:
self.logger.error(f"Failed to create legacy campaign with ID {campaign_id}!")
return None
return result.lastrowid
async def add_game_to_campaign(self, campaign_id: int, game_id: str) -> bool:
sql = insert(campaign_game).values(
id = campaign_id,
game_id = game_id
)
conflict = sql.on_duplicate_key_do_nothing()
result = await self.execute(conflict)
if result is None:
self.logger.error(f"Failed to add game {game_id} to campaign {campaign_id}!")
return False
return True
async def get_campaigns_by_game(self, game_id: str) -> Optional[List[Row]]:
result = await self.execute(campaign
.join(campaign_game, campaign.c.id == campaign_game.c.campaign_id)
.select(campaign_game.c.game_id == game_id))
if result is not None:
return result.fetchall()
async def get_active_campaigns_by_game(self, game_id: str) -> Optional[List[Row]]:
result = await self.execute(campaign
.join(campaign_game, campaign.c.id == campaign_game.c.campaign_id)
.select(and_(campaign_game.c.game_id == game_id, and_(
# TODO: Maybe add a RANK statement to prioritize campaigns that have set end dates?
or_(campaign.c.announce_date >= datetime.now(), campaign.c.announce_date == None),
or_(campaign.c.distrib_end_date < datetime.now(), campaign.c.distrib_end_date == None),
))))
if result is not None:
return result.fetchall()
async def get_campaigns(self) -> Optional[List[Row]]:
result = await self.execute(campaign.select())
if result is not None:
return result.fetchall()
async def get_active_campaigns(self) -> Optional[List[Row]]:
result = await self.execute(campaign
.select(and_(
or_(campaign.c.announce_date >= datetime.now(), campaign.c.announce_date == None),
or_(campaign.c.distrib_end_date < datetime.now(), campaign.c.distrib_end_date == None),
)))
if result is not None:
return result.fetchall()
async def get_campaign_by_id(self, campaign_id: int) -> Optional[Row]:
result = await self.execute(campaign.select(campaign.c.id == campaign_id))
if result is not None:
return result.fetchone()
async def get_games_in_campaign(self, campaign_id: int) -> Optional[List[Row]]:
result = await self.execute(campaign_game.select(campaign_game.c.campaign_id == campaign_id))
if result is not None:
return result.fetchall()
async def is_game_in_campaign(self, game_id: str, campaign_id: int) -> bool:
result = await self.execute(campaign_game.select(and_(
campaign_game.c.campaign_id == campaign_id,
campaign_game.c.game_id == game_id
)))
if result is not None:
return result.fetchone() is not None
return False
def fix_bools(self, data: Dict) -> Dict:
for k, v in data.items():
if k == "userName" or k == "teamName":