idac: added "simple" ranking to frontend

This commit is contained in:
Dniel97
2023-11-21 22:51:10 +01:00
parent d1a7b898a7
commit 6ea8cca1a2
10 changed files with 641 additions and 160 deletions

View File

@@ -1,7 +1,10 @@
import json
import yaml
import jinja2
from os import path
from typing import Any, Type
from twisted.web import resource
from twisted.web.util import redirectTo
from twisted.web.http import Request
from twisted.web.server import Session
@@ -15,12 +18,109 @@ from titles.idac.config import IDACConfig
from titles.idac.const import IDACConstants
class RankingData:
def __init__(
self,
rank: int,
name: str,
record: int,
store: str,
style_car_id: int,
update_date: str,
) -> None:
self.rank: int = rank
self.name: str = name
self.record: str = record
self.store: str = store
self.style_car_id: int = style_car_id
self.update_date: str = update_date
def make(self):
return vars(self)
class RequestValidator:
def __init__(self) -> None:
self.success: bool = True
self.error: str = ""
def validate_param(
self,
request_args: Dict[bytes, bytes],
param_name: str,
param_type: Type[None],
default=None,
required: bool = True,
) -> None:
# Check if the parameter is missing
if param_name.encode() not in request_args:
if required:
self.success = False
self.error += f"Missing parameter: '{param_name}'. "
else:
# If the parameter is not required,
# return the default value if it exists
return default
return None
param_value = request_args[param_name.encode()][0].decode()
# Check if the parameter type is not empty
if param_type:
try:
# Attempt to convert the parameter value to the specified type
param_value = param_type(param_value)
except ValueError:
# If the conversion fails, return an error
self.success = False
self.error += f"Invalid parameter type for '{param_name}'. "
return None
return param_value
class RankingRequest(RequestValidator):
def __init__(self, request_args: Dict[bytes, bytes]) -> None:
super().__init__()
self.course_id: int = self.validate_param(request_args, "courseId", int)
self.page_number: int = self.validate_param(
request_args, "pageNumber", int, default=1, required=False
)
class RankingResponse:
def __init__(self) -> None:
self.success: bool = False
self.error: str = ""
self.total_pages: int = 0
self.total_records: int = 0
self.updated_at: str = ""
self.ranking: list[RankingData] = []
def make(self):
ret = vars(self)
self.error = (
"Unknown error." if not self.success and self.error == "" else self.error
)
ret["ranking"] = [rank.make() for rank in self.ranking]
return ret
def to_json(self):
return json.dumps(self.make(), default=str, ensure_ascii=False).encode("utf-8")
class IDACFrontend(FE_Base):
isLeaf = False
children: Dict[str, Any] = {}
def __init__(
self, cfg: CoreConfig, environment: jinja2.Environment, cfg_dir: str
) -> None:
super().__init__(cfg, environment)
self.data = IDACData(cfg)
self.core_cfg = cfg
self.game_cfg = IDACConfig()
if path.exists(f"{cfg_dir}/{IDACConstants.CONFIG_NAME}"):
self.game_cfg.update(
@@ -30,13 +130,159 @@ class IDACFrontend(FE_Base):
# TODO: Add version list
self.version = IDACConstants.VER_IDAC_SEASON_2
self.putChild(b"profile", IDACProfileFrontend(cfg, self.environment))
self.putChild(b"ranking", IDACRankingFrontend(cfg, self.environment))
def render_GET(self, request: Request) -> bytes:
uri: str = request.uri.decode()
template = self.environment.get_template(
"titles/idac/frontend/idac_index.jinja"
)
sesh: Session = request.getSession()
usr_sesh = IUserSession(sesh)
# redirect to the ranking page
if uri.startswith("/game/idac"):
return redirectTo(b"/game/idac/ranking", request)
return template.render(
title=f"{self.core_config.server.name} | {self.nav_name}",
game_list=self.environment.globals["game_list"],
sesh=vars(usr_sesh),
active_page="idac",
).encode("utf-16")
def render_POST(self, request: Request) -> bytes:
pass
class IDACRankingFrontend(FE_Base):
def __init__(self, cfg: CoreConfig, environment: jinja2.Environment) -> None:
super().__init__(cfg, environment)
self.data = IDACData(cfg)
self.core_cfg = cfg
self.nav_name = "頭文字D THE ARCADE"
# TODO: Add version list
self.version = IDACConstants.VER_IDAC_SEASON_2
def render_GET(self, request: Request) -> bytes:
uri: str = request.uri.decode()
template = self.environment.get_template(
"titles/idac/frontend/ranking/index.jinja"
)
sesh: Session = request.getSession()
usr_sesh = IUserSession(sesh)
user_id = usr_sesh.userId
# user_id = usr_sesh.user_id
# IDAC constants
if uri.startswith("/game/idac/ranking/const.get"):
# set the content type to json
request.responseHeaders.addRawHeader(b"content-type", b"application/json")
# get the constants
with open("titles/idac/frontend/const.json", "r", encoding="utf-8") as f:
constants = json.load(f)
return json.dumps(constants, ensure_ascii=False).encode("utf-8")
# leaderboard ranking
elif uri.startswith("/game/idac/ranking/ranking.get"):
# set the content type to json
request.responseHeaders.addRawHeader(b"content-type", b"application/json")
req = RankingRequest(request.args)
resp = RankingResponse()
if not req.success:
resp.error = req.error
return resp.to_json()
# get the total number of records
total_records = self.data.item.get_time_trial_ranking_by_course_total(
self.version, req.course_id
)
# return an error if there are no records
if total_records is None or total_records == 0:
resp.error = "No records found."
return resp.to_json()
# get the total number of records
total = total_records["count"]
limit = 50
offset = (req.page_number - 1) * limit
ranking = self.data.item.get_time_trial_ranking_by_course(
self.version,
req.course_id,
limit=limit,
offset=offset,
)
for i, rank in enumerate(ranking):
user_id = rank["user"]
# get the username, country and store from the profile
profile = self.data.profile.get_profile(user_id, self.version)
arcade = self.data.arcade.get_arcade(profile["store"])
if arcade is None:
arcade = {}
arcade["name"] = self.core_config.server.name
# should never happen
if profile is None:
continue
resp.ranking.append(
RankingData(
rank=offset + i + 1,
name=profile["username"],
record=rank["goal_time"],
store=arcade["name"],
style_car_id=rank["style_car_id"],
update_date=str(rank["play_dt"]),
)
)
# now return the json data, with the total number of pages and records
# round up the total pages
resp.success = True
resp.total_pages = (total // limit) + 1
resp.total_records = total
return resp.to_json()
return template.render(
title=f"{self.core_config.server.name} | {self.nav_name}",
game_list=self.environment.globals["game_list"],
sesh=vars(usr_sesh),
active_page="idac",
active_tab="ranking",
).encode("utf-16")
class IDACProfileFrontend(FE_Base):
def __init__(self, cfg: CoreConfig, environment: jinja2.Environment) -> None:
super().__init__(cfg, environment)
self.data = IDACData(cfg)
self.core_cfg = cfg
self.nav_name = "頭文字D THE ARCADE"
# TODO: Add version list
self.version = IDACConstants.VER_IDAC_SEASON_2
self.ticket_names = {
3: "car_dressup_points",
5: "avatar_points",
25: "full_tune_tickets",
34: "full_tune_fragments",
}
def generate_all_tables_json(self, user_id: int):
json_export = {}
@@ -60,7 +306,7 @@ class IDACFrontend(FE_Base):
theory_running,
vs_info,
stamp,
timetrial_event
timetrial_event,
}
for table in idac_tables:
@@ -86,11 +332,12 @@ class IDACFrontend(FE_Base):
return json.dumps(json_export, indent=4, default=str, ensure_ascii=False)
def render_GET(self, request: Request) -> bytes:
uri: str = request.uri.decode()
template = self.environment.get_template(
"titles/idac/frontend/idac_index.jinja"
"titles/idac/frontend/profile/index.jinja"
)
sesh: Session = request.getSession()
usr_sesh = IUserSession(sesh)
@@ -98,7 +345,7 @@ class IDACFrontend(FE_Base):
# user_id = usr_sesh.user_id
# profile export
if uri.startswith("/game/idac/export"):
if uri.startswith("/game/idac/profile/export.get"):
if user_id == 0:
return redirectTo(b"/game/idac", request)
@@ -136,7 +383,5 @@ class IDACFrontend(FE_Base):
rank=rank,
sesh=vars(usr_sesh),
active_page="idac",
active_tab="profile",
).encode("utf-16")
def render_POST(self, request: Request) -> bytes:
pass