idac: fixed frontend

This commit is contained in:
Dniel97 2024-03-12 17:48:12 +01:00
parent a0fba8c3a4
commit 88b3cfc750
Signed by untrusted user: Dniel97
GPG Key ID: 6180B3C768FB2E08
6 changed files with 87 additions and 79 deletions

View File

@ -1,11 +1,11 @@
import json import json
from typing import List
from starlette.routing import Route
from starlette.responses import Response, RedirectResponse
import yaml import yaml
import jinja2 import jinja2
from os import path from os import path
from typing import List, Any, Type
from starlette.routing import Route, Mount
from starlette.responses import Response, RedirectResponse, JSONResponse
from starlette.requests import Request from starlette.requests import Request
from core.frontend import FE_Base, UserSession from core.frontend import FE_Base, UserSession
@ -54,7 +54,7 @@ class RequestValidator:
required: bool = True, required: bool = True,
) -> None: ) -> None:
# Check if the parameter is missing # Check if the parameter is missing
if param_name.encode() not in request_args: if param_name not in request_args:
if required: if required:
self.success = False self.success = False
self.error += f"Missing parameter: '{param_name}'. " self.error += f"Missing parameter: '{param_name}'. "
@ -64,7 +64,7 @@ class RequestValidator:
return default return default
return None return None
param_value = request_args[param_name.encode()][0].decode() param_value = request_args[param_name]
# Check if the parameter type is not empty # Check if the parameter type is not empty
if param_type: if param_type:
@ -108,10 +108,6 @@ class RankingResponse:
return ret return ret
def to_json(self):
return json.dumps(self.make(), default=str, ensure_ascii=False).encode("utf-8")
class IDACFrontend(FE_Base): class IDACFrontend(FE_Base):
isLeaf = False isLeaf = False
children: Dict[str, Any] = {} children: Dict[str, Any] = {}
@ -127,36 +123,52 @@ class IDACFrontend(FE_Base):
self.game_cfg.update( self.game_cfg.update(
yaml.safe_load(open(f"{cfg_dir}/{IDACConstants.CONFIG_NAME}")) yaml.safe_load(open(f"{cfg_dir}/{IDACConstants.CONFIG_NAME}"))
) )
#self.nav_name = "頭文字D THE ARCADE" self.nav_name = "頭文字D THE ARCADE"
self.nav_name = "IDAC" # self.nav_name = "IDAC"
# TODO: Add version list # TODO: Add version list
self.version = IDACConstants.VER_IDAC_SEASON_2 self.version = IDACConstants.VER_IDAC_SEASON_2
self.putChild(b"profile", IDACProfileFrontend(cfg, self.environment)) self.profile = IDACProfileFrontend(cfg, self.environment)
self.putChild(b"ranking", IDACRankingFrontend(cfg, self.environment)) self.ranking = IDACRankingFrontend(cfg, self.environment)
def get_routes(self) -> List[Route]:
return [
Route("/", self.render_GET),
Mount("/profile", routes=[
Route("/", self.profile.render_GET),
# dirty hack
Route("/export.get", self.profile.render_GET),
]),
Mount("/ranking", routes=[
Route("/", self.ranking.render_GET),
# dirty hack
Route("/const.get", self.ranking.render_GET),
Route("/ranking.get", self.ranking.render_GET),
]),
]
async def render_GET(self, request: Request) -> bytes:
def render_GET(self, request: Request) -> bytes: uri: str = request.url.path
uri: str = request.uri.decode()
template = self.environment.get_template( template = self.environment.get_template(
"titles/idac/frontend/idac_index.jinja" "titles/idac/templates/idac_index.jinja"
) )
sesh: Session = request.getSession() usr_sesh = self.validate_session(request)
usr_sesh = IUserSession(sesh) if not usr_sesh:
usr_sesh = UserSession()
# redirect to the ranking page # redirect to the ranking page
if uri.startswith("/game/idac"): if uri.startswith("/game/idac"):
return redirectTo(b"/game/idac/ranking", request) return RedirectResponse("/game/idac/ranking", 303)
return template.render( return Response(template.render(
title=f"{self.core_config.server.name} | {self.nav_name}", title=f"{self.core_config.server.name} | {self.nav_name}",
game_list=self.environment.globals["game_list"], game_list=self.environment.globals["game_list"],
sesh=vars(usr_sesh), sesh=vars(usr_sesh),
active_page="idac", active_page="idac",
).encode("utf-16") ), media_type="text/html; charset=utf-8")
def render_POST(self, request: Request) -> bytes: async def render_POST(self, request: Request) -> bytes:
pass pass
@ -170,48 +182,42 @@ class IDACRankingFrontend(FE_Base):
# TODO: Add version list # TODO: Add version list
self.version = IDACConstants.VER_IDAC_SEASON_2 self.version = IDACConstants.VER_IDAC_SEASON_2
def render_GET(self, request: Request) -> bytes: async def render_GET(self, request: Request) -> bytes:
uri: str = request.uri.decode() uri: str = request.url.path
template = self.environment.get_template( template = self.environment.get_template(
"titles/idac/frontend/ranking/index.jinja" "titles/idac/templates/ranking/index.jinja"
) )
sesh: Session = request.getSession() usr_sesh = self.validate_session(request)
usr_sesh = IUserSession(sesh) if not usr_sesh:
user_id = usr_sesh.userId usr_sesh = UserSession()
# user_id = usr_sesh.user_id user_id = usr_sesh.user_id
# IDAC constants # IDAC constants
if uri.startswith("/game/idac/ranking/const.get"): 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 # get the constants
with open("titles/idac/frontend/const.json", "r", encoding="utf-8") as f: with open("titles/idac/templates/const.json", "r", encoding="utf-8") as f:
constants = json.load(f) constants = json.load(f)
return json.dumps(constants, ensure_ascii=False).encode("utf-8") return JSONResponse(constants)
# leaderboard ranking # leaderboard ranking
elif uri.startswith("/game/idac/ranking/ranking.get"): elif uri.startswith("/game/idac/ranking/ranking.get"):
# set the content type to json req = RankingRequest(request.query_params._dict)
request.responseHeaders.addRawHeader(b"content-type", b"application/json")
req = RankingRequest(request.args)
resp = RankingResponse() resp = RankingResponse()
if not req.success: if not req.success:
resp.error = req.error resp.error = req.error
return resp.to_json() return JSONResponse(resp.make())
# get the total number of records # get the total number of records
total_records = self.data.item.get_time_trial_ranking_by_course_total( total_records = await self.data.item.get_time_trial_ranking_by_course_total(
self.version, req.course_id self.version, req.course_id
) )
# return an error if there are no records # return an error if there are no records
if total_records is None or total_records == 0: if total_records is None or total_records == 0:
resp.error = "No records found." resp.error = "No records found."
return resp.to_json() return JSONResponse(resp.make())
# get the total number of records # get the total number of records
total = total_records["count"] total = total_records["count"]
@ -219,7 +225,7 @@ class IDACRankingFrontend(FE_Base):
limit = 50 limit = 50
offset = (req.page_number - 1) * limit offset = (req.page_number - 1) * limit
ranking = self.data.item.get_time_trial_ranking_by_course( ranking = await self.data.item.get_time_trial_ranking_by_course(
self.version, self.version,
req.course_id, req.course_id,
limit=limit, limit=limit,
@ -230,8 +236,8 @@ class IDACRankingFrontend(FE_Base):
user_id = rank["user"] user_id = rank["user"]
# get the username, country and store from the profile # get the username, country and store from the profile
profile = self.data.profile.get_profile(user_id, self.version) profile = await self.data.profile.get_profile(user_id, self.version)
arcade = self.data.arcade.get_arcade(profile["store"]) arcade = await self.data.arcade.get_arcade(profile["store"])
if arcade is None: if arcade is None:
arcade = {} arcade = {}
@ -258,15 +264,15 @@ class IDACRankingFrontend(FE_Base):
resp.success = True resp.success = True
resp.total_pages = (total // limit) + 1 resp.total_pages = (total // limit) + 1
resp.total_records = total resp.total_records = total
return resp.to_json() return JSONResponse(resp.make())
return template.render( return Response(template.render(
title=f"{self.core_config.server.name} | {self.nav_name}", title=f"{self.core_config.server.name} | {self.nav_name}",
game_list=self.environment.globals["game_list"], game_list=self.environment.globals["game_list"],
sesh=vars(usr_sesh), sesh=vars(usr_sesh),
active_page="idac", active_page="idac",
active_tab="ranking", active_tab="ranking",
).encode("utf-16") ), media_type="text/html; charset=utf-8")
class IDACProfileFrontend(FE_Base): class IDACProfileFrontend(FE_Base):
@ -285,11 +291,6 @@ class IDACProfileFrontend(FE_Base):
25: "full_tune_tickets", 25: "full_tune_tickets",
34: "full_tune_fragments", 34: "full_tune_fragments",
} }
def get_routes(self) -> List[Route]:
return [
Route("/", self.render_GET)
]
async def generate_all_tables_json(self, user_id: int): async def generate_all_tables_json(self, user_id: int):
json_export = {} json_export = {}
@ -344,25 +345,29 @@ class IDACProfileFrontend(FE_Base):
uri: str = request.url.path uri: str = request.url.path
template = self.environment.get_template( template = self.environment.get_template(
"titles/idac/templates/idac_index.jinja" "titles/idac/templates/profile/index.jinja"
) )
usr_sesh = self.validate_session(request) usr_sesh = self.validate_session(request)
if not usr_sesh: if not usr_sesh:
usr_sesh = UserSession() usr_sesh = UserSession()
user_id = usr_sesh.user_id user_id = usr_sesh.user_id
# user_id = usr_sesh.user_id
user = await self.data.user.get_user(user_id)
if user is None:
self.logger.debug(f"User {user_id} not found")
return RedirectResponse("/user/", 303)
# profile export # profile export
if uri.startswith("/game/idac/profile/export.get"): if uri.startswith("/game/idac/profile/export.get"):
if user_id == 0: if user_id == 0:
return RedirectResponse(b"/game/idac", request) return RedirectResponse("/game/idac", 303)
# set the file name, content type and size to download the json # set the file name, content type and size to download the json
content = await self.generate_all_tables_json(user_id).encode("utf-8") content = await self.generate_all_tables_json(user_id)
self.logger.info(f"User {user_id} exported their IDAC data") self.logger.info(f"User {user_id} exported their IDAC data")
return Response( return Response(
content, content.encode("utf-8"),
200, 200,
{'content-disposition': 'attachment; filename=idac_profile.json'}, {'content-disposition': 'attachment; filename=idac_profile.json'},
"application/octet-stream" "application/octet-stream"
@ -387,5 +392,7 @@ class IDACProfileFrontend(FE_Base):
tickets=tickets, tickets=tickets,
rank=rank, rank=rank,
sesh=vars(usr_sesh), sesh=vars(usr_sesh),
username=user["username"],
active_page="idac", active_page="idac",
active_tab="profile",
), media_type="text/html; charset=utf-8") ), media_type="text/html; charset=utf-8")

View File

@ -1,15 +1,16 @@
import json import json
import traceback import traceback
from starlette.routing import Route
from starlette.requests import Request
from starlette.responses import JSONResponse
import yaml import yaml
import logging import logging
import coloredlogs import coloredlogs
import asyncio
from os import path from os import path
from typing import Dict, List, Tuple from typing import Dict, List, Tuple
from logging.handlers import TimedRotatingFileHandler from logging.handlers import TimedRotatingFileHandler
import asyncio from starlette.routing import Route
from starlette.requests import Request
from starlette.responses import JSONResponse
from core.config import CoreConfig from core.config import CoreConfig
from core.title import BaseServlet, JSONResponseNoASCII from core.title import BaseServlet, JSONResponseNoASCII

View File

@ -555,7 +555,7 @@ class IDACItemData(BaseData):
return None return None
return result.fetchall() return result.fetchall()
def get_time_trial_ranking_by_course_total( async def get_time_trial_ranking_by_course_total(
self, self,
version: int, version: int,
course_id: int, course_id: int,
@ -582,7 +582,7 @@ class IDACItemData(BaseData):
) )
) )
result = self.execute(sql) result = await self.execute(sql)
if result is None: if result is None:
return None return None
return result.fetchone() return result.fetchone()
@ -798,23 +798,23 @@ class IDACItemData(BaseData):
return None return None
return result.fetchall() return result.fetchall()
def get_vs_info_by_mode(self, aime_id: int, battle_mode: int) -> Optional[List[Row]]: async def get_vs_info_by_mode(self, aime_id: int, battle_mode: int) -> Optional[List[Row]]:
sql = select(vs_info).where( sql = select(vs_info).where(
and_(vs_info.c.user == aime_id, vs_info.c.battle_mode == battle_mode) and_(vs_info.c.user == aime_id, vs_info.c.battle_mode == battle_mode)
) )
result = self.execute(sql) result = await self.execute(sql)
if result is None: if result is None:
return None return None
return result.fetchone() return result.fetchone()
# This method returns a list of course_info # This method returns a list of course_info
def get_vs_course_infos_by_mode(self, aime_id: int, battle_mode: int) -> Optional[List[Row]]: async def get_vs_course_infos_by_mode(self, aime_id: int, battle_mode: int) -> Optional[List[Row]]:
sql = select(vs_course_info).where( sql = select(vs_course_info).where(
and_(vs_course_info.c.user == aime_id, vs_course_info.c.battle_mode == battle_mode) and_(vs_course_info.c.user == aime_id, vs_course_info.c.battle_mode == battle_mode)
) )
result = self.execute(sql) result = await self.execute(sql)
if result is None: if result is None:
return None return None
return result.fetchall() return result.fetchall()
@ -1015,7 +1015,7 @@ class IDACItemData(BaseData):
return None return None
return result.lastrowid return result.lastrowid
def put_vs_info(self, aime_id: int, battle_mode: int, vs_info_data: Dict) -> Optional[int]: async def put_vs_info(self, aime_id: int, battle_mode: int, vs_info_data: Dict) -> Optional[int]:
vs_info_data["user"] = aime_id vs_info_data["user"] = aime_id
vs_info_data["battle_mode"] = battle_mode vs_info_data["battle_mode"] = battle_mode
@ -1028,13 +1028,13 @@ class IDACItemData(BaseData):
return None return None
return result.lastrowid return result.lastrowid
def put_vs_course_info(self, aime_id: int, battle_mode: int, course_info_data: Dict) -> Optional[int]: async def put_vs_course_info(self, aime_id: int, battle_mode: int, course_info_data: Dict) -> Optional[int]:
course_info_data["user"] = aime_id course_info_data["user"] = aime_id
course_info_data["battle_mode"] = battle_mode course_info_data["battle_mode"] = battle_mode
sql = insert(vs_course_info).values(**course_info_data) sql = insert(vs_course_info).values(**course_info_data)
conflict = sql.on_duplicate_key_update(**course_info_data) conflict = sql.on_duplicate_key_update(**course_info_data)
result = self.execute(conflict) result = await self.execute(conflict)
if result is None: if result is None:
self.logger.warn(f"put_vs_course_info: Failed to update! aime_id: {aime_id}") self.logger.warn(f"put_vs_course_info: Failed to update! aime_id: {aime_id}")

View File

@ -5,10 +5,10 @@
<nav class="mb-3"> <nav class="mb-3">
<ul class="nav nav-tabs"> <ul class="nav nav-tabs">
<li class="nav-item"> <li class="nav-item">
<a class="nav-link {% if active_tab == 'ranking' %}active{% endif %}" aria-current="page" href="ranking">Ranking</a> <a class="nav-link {% if active_tab == 'ranking' %}active{% endif %}" aria-current="page" href="/game/idac/ranking">Ranking</a>
</li> </li>
<li class="nav-item"> <li class="nav-item">
<a class="nav-link {% if active_tab == 'profile' %}active{% endif %}" href="profile">Profile</a> <a class="nav-link {% if active_tab == 'profile' %}active{% endif %}" href="/game/idac/profile">Profile</a>
</li> </li>
</ul> </ul>
</nav> </nav>

View File

@ -1,12 +1,12 @@
{% extends "titles/idac/frontend/idac_index.jinja" %} {% extends "titles/idac/templates/idac_index.jinja" %}
{% block tab %} {% block tab %}
{% if sesh is defined and sesh["userId"] > 0 %} {% if sesh is defined and sesh["user_id"] > 0 %}
<div class="card mb-3"> <div class="card mb-3">
<div class="card-body"> <div class="card-body">
<div class="card-title"> <div class="card-title">
<div class="d-flex justify-content-between flex-wrap flex-md-nowrap align-items-center"> <div class="d-flex justify-content-between flex-wrap flex-md-nowrap align-items-center">
<h3>{{ sesh["username"] }}'s Profile</h3> <h3>{{ username }}'s Profile</h3>
<div class="btn-toolbar mb-2 mb-md-0"> <div class="btn-toolbar mb-2 mb-md-0">
<div class="btn-group me-2"> <div class="btn-group me-2">
<!--<button type="button" class="btn btn-sm btn-outline-secondary">Share</button>--> <!--<button type="button" class="btn btn-sm btn-outline-secondary">Share</button>-->
@ -123,7 +123,7 @@
</div> </div>
<script type="text/javascript"> <script type="text/javascript">
{% include "titles/idac/frontend/profile/js/scripts.js" %} {% include "titles/idac/templates/profile/js/scripts.js" %}
</script> </script>
{% endblock tab %} {% endblock tab %}

View File

@ -1,4 +1,4 @@
{% extends "titles/idac/frontend/idac_index.jinja" %} {% extends "titles/idac/templates/idac_index.jinja" %}
{% block tab %} {% block tab %}
<div class="tab-content" id="nav-tabContent"> <div class="tab-content" id="nav-tabContent">
@ -24,7 +24,7 @@
</div> </div>
<script type="text/javascript"> <script type="text/javascript">
{% include "titles/idac/frontend/ranking/js/scripts.js" %} {% include "titles/idac/templates/ranking/js/scripts.js" %}
</script> </script>
{% endblock tab %} {% endblock tab %}