3
2
forked from Dniel97/artemis

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
from typing import List
from starlette.routing import Route
from starlette.responses import Response, RedirectResponse
import yaml
import jinja2
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 core.frontend import FE_Base, UserSession
@ -54,7 +54,7 @@ class RequestValidator:
required: bool = True,
) -> None:
# Check if the parameter is missing
if param_name.encode() not in request_args:
if param_name not in request_args:
if required:
self.success = False
self.error += f"Missing parameter: '{param_name}'. "
@ -64,7 +64,7 @@ class RequestValidator:
return default
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
if param_type:
@ -108,10 +108,6 @@ class RankingResponse:
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] = {}
@ -127,36 +123,52 @@ class IDACFrontend(FE_Base):
self.game_cfg.update(
yaml.safe_load(open(f"{cfg_dir}/{IDACConstants.CONFIG_NAME}"))
)
#self.nav_name = "頭文字D THE ARCADE"
self.nav_name = "IDAC"
self.nav_name = "頭文字D THE ARCADE"
# self.nav_name = "IDAC"
# 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))
self.profile = IDACProfileFrontend(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),
]),
]
def render_GET(self, request: Request) -> bytes:
uri: str = request.uri.decode()
async def render_GET(self, request: Request) -> bytes:
uri: str = request.url.path
template = self.environment.get_template(
"titles/idac/frontend/idac_index.jinja"
"titles/idac/templates/idac_index.jinja"
)
sesh: Session = request.getSession()
usr_sesh = IUserSession(sesh)
usr_sesh = self.validate_session(request)
if not usr_sesh:
usr_sesh = UserSession()
# redirect to the ranking page
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}",
game_list=self.environment.globals["game_list"],
sesh=vars(usr_sesh),
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
@ -170,48 +182,42 @@ class IDACRankingFrontend(FE_Base):
# TODO: Add version list
self.version = IDACConstants.VER_IDAC_SEASON_2
def render_GET(self, request: Request) -> bytes:
uri: str = request.uri.decode()
async def render_GET(self, request: Request) -> bytes:
uri: str = request.url.path
template = self.environment.get_template(
"titles/idac/frontend/ranking/index.jinja"
"titles/idac/templates/ranking/index.jinja"
)
sesh: Session = request.getSession()
usr_sesh = IUserSession(sesh)
user_id = usr_sesh.userId
# user_id = usr_sesh.user_id
usr_sesh = self.validate_session(request)
if not usr_sesh:
usr_sesh = UserSession()
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:
with open("titles/idac/templates/const.json", "r", encoding="utf-8") as f:
constants = json.load(f)
return json.dumps(constants, ensure_ascii=False).encode("utf-8")
return JSONResponse(constants)
# 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)
req = RankingRequest(request.query_params._dict)
resp = RankingResponse()
if not req.success:
resp.error = req.error
return resp.to_json()
return JSONResponse(resp.make())
# 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
)
# 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()
return JSONResponse(resp.make())
# get the total number of records
total = total_records["count"]
@ -219,7 +225,7 @@ class IDACRankingFrontend(FE_Base):
limit = 50
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,
req.course_id,
limit=limit,
@ -230,8 +236,8 @@ class IDACRankingFrontend(FE_Base):
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"])
profile = await self.data.profile.get_profile(user_id, self.version)
arcade = await self.data.arcade.get_arcade(profile["store"])
if arcade is None:
arcade = {}
@ -258,15 +264,15 @@ class IDACRankingFrontend(FE_Base):
resp.success = True
resp.total_pages = (total // limit) + 1
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}",
game_list=self.environment.globals["game_list"],
sesh=vars(usr_sesh),
active_page="idac",
active_tab="ranking",
).encode("utf-16")
), media_type="text/html; charset=utf-8")
class IDACProfileFrontend(FE_Base):
@ -286,11 +292,6 @@ class IDACProfileFrontend(FE_Base):
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):
json_export = {}
@ -344,25 +345,29 @@ class IDACProfileFrontend(FE_Base):
uri: str = request.url.path
template = self.environment.get_template(
"titles/idac/templates/idac_index.jinja"
"titles/idac/templates/profile/index.jinja"
)
usr_sesh = self.validate_session(request)
if not usr_sesh:
usr_sesh = UserSession()
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
if uri.startswith("/game/idac/profile/export.get"):
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
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")
return Response(
content,
content.encode("utf-8"),
200,
{'content-disposition': 'attachment; filename=idac_profile.json'},
"application/octet-stream"
@ -387,5 +392,7 @@ class IDACProfileFrontend(FE_Base):
tickets=tickets,
rank=rank,
sesh=vars(usr_sesh),
username=user["username"],
active_page="idac",
active_tab="profile",
), media_type="text/html; charset=utf-8")

View File

@ -1,15 +1,16 @@
import json
import traceback
from starlette.routing import Route
from starlette.requests import Request
from starlette.responses import JSONResponse
import yaml
import logging
import coloredlogs
import asyncio
from os import path
from typing import Dict, List, Tuple
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.title import BaseServlet, JSONResponseNoASCII

View File

@ -555,7 +555,7 @@ class IDACItemData(BaseData):
return None
return result.fetchall()
def get_time_trial_ranking_by_course_total(
async def get_time_trial_ranking_by_course_total(
self,
version: int,
course_id: int,
@ -582,7 +582,7 @@ class IDACItemData(BaseData):
)
)
result = self.execute(sql)
result = await self.execute(sql)
if result is None:
return None
return result.fetchone()
@ -798,23 +798,23 @@ class IDACItemData(BaseData):
return None
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(
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:
return None
return result.fetchone()
# 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(
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:
return None
return result.fetchall()
@ -1015,7 +1015,7 @@ class IDACItemData(BaseData):
return None
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["battle_mode"] = battle_mode
@ -1028,13 +1028,13 @@ class IDACItemData(BaseData):
return None
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["battle_mode"] = battle_mode
sql = insert(vs_course_info).values(**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:
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">
<ul class="nav nav-tabs">
<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 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>
</ul>
</nav>

View File

@ -1,12 +1,12 @@
{% extends "titles/idac/frontend/idac_index.jinja" %}
{% extends "titles/idac/templates/idac_index.jinja" %}
{% 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-body">
<div class="card-title">
<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-group me-2">
<!--<button type="button" class="btn btn-sm btn-outline-secondary">Share</button>-->
@ -123,7 +123,7 @@
</div>
<script type="text/javascript">
{% include "titles/idac/frontend/profile/js/scripts.js" %}
{% include "titles/idac/templates/profile/js/scripts.js" %}
</script>
{% endblock tab %}

View File

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