port frontend changes from different project

This commit is contained in:
Hay1tsme 2024-01-09 15:54:34 -05:00
parent e27ac4b81f
commit 9dab26b122
31 changed files with 1062 additions and 574 deletions

View File

@ -76,10 +76,12 @@ if not cfg.billing.standalone:
Route("/request/", billing.handle_billing_request, methods=["POST"]), Route("/request/", billing.handle_billing_request, methods=["POST"]),
] ]
if not cfg.frontend.standalone: if not cfg.frontend.standalone and cfg.frontend.secret:
frontend = FrontendServlet(cfg, cfg_dir) frontend = FrontendServlet(cfg, cfg_dir)
route_lst += frontend.get_routes() route_lst += frontend.get_routes()
else: else:
if not cfg.frontend.secret:
logger.error("Frontend secret not specified, cannot start frontend!")
route_lst.append(Route("/", dummy_rt)) route_lst.append(Route("/", dummy_rt))
route_lst.append(Route("/robots.txt", FrontendServlet.robots)) route_lst.append(Route("/robots.txt", FrontendServlet.robots))

View File

@ -212,7 +212,7 @@ class FrontendConfig:
@property @property
def secret(self) -> str: def secret(self) -> str:
CoreConfig.get_config_field( return CoreConfig.get_config_field(
self.__config, "core", "frontend", "secret", default="" self.__config, "core", "frontend", "secret", default=""
) )

View File

@ -58,7 +58,7 @@ class CardData(BaseData):
""" """
Given a 20 digit access code as a string, get the user id associated with that card Given a 20 digit access code as a string, get the user id associated with that card
""" """
card = self.get_card_by_access_code(access_code) card = await self.get_card_by_access_code(access_code)
if card is None: if card is None:
return None return None
@ -68,7 +68,7 @@ class CardData(BaseData):
""" """
Given a 20 digit access code as a string, check if the card is banned Given a 20 digit access code as a string, check if the card is banned
""" """
card = self.get_card_by_access_code(access_code) card = await self.get_card_by_access_code(access_code)
if card is None: if card is None:
return None return None
if card["is_banned"]: if card["is_banned"]:
@ -78,7 +78,7 @@ class CardData(BaseData):
""" """
Given a 20 digit access code as a string, check if the card is locked Given a 20 digit access code as a string, check if the card is locked
""" """
card = self.get_card_by_access_code(access_code) card = await self.get_card_by_access_code(access_code)
if card is None: if card is None:
return None return None
if card["is_locked"]: if card["is_locked"]:

View File

@ -64,8 +64,8 @@ class UserData(BaseData):
return False return False
return result.fetchone() return result.fetchone()
def check_password(self, user_id: int, passwd: bytes = None) -> bool: async def check_password(self, user_id: int, passwd: bytes = None) -> bool:
usr = self.get_user(user_id) usr = await self.get_user(user_id)
if usr is None: if usr is None:
return False return False

View File

@ -1,30 +1,21 @@
import logging, coloredlogs import logging, coloredlogs
from typing import Any, Dict, List from typing import Any, Dict, List, Union, Optional
from twisted.web import resource
from twisted.web.util import redirectTo
from starlette.requests import Request from starlette.requests import Request
from starlette.routing import Route from starlette.routing import Route, Mount
from starlette.responses import Response, PlainTextResponse from starlette.responses import Response, PlainTextResponse, RedirectResponse
from logging.handlers import TimedRotatingFileHandler from logging.handlers import TimedRotatingFileHandler
from twisted.web.server import Session
from zope.interface import Interface, Attribute, implementer
from twisted.python.components import registerAdapter
import jinja2 import jinja2
import bcrypt import bcrypt
import re import re
import jwt
from base64 import b64decode
from enum import Enum from enum import Enum
from urllib import parse from urllib import parse
from datetime import datetime, timezone
from core import CoreConfig, Utils from core import CoreConfig, Utils
from core.data import Data from core.data import Data
class IUserSession(Interface):
userId = Attribute("User's ID")
current_ip = Attribute("User's current ip address")
permissions = Attribute("User's permission level")
ongeki_version = Attribute("User's selected Ongeki Version")
class PermissionOffset(Enum): class PermissionOffset(Enum):
USER = 0 # Regular user USER = 0 # Regular user
USERMOD = 1 # Can moderate other users USERMOD = 1 # Can moderate other users
@ -33,31 +24,38 @@ class PermissionOffset(Enum):
# 4 - 6 reserved for future use # 4 - 6 reserved for future use
OWNER = 7 # Can do anything OWNER = 7 # Can do anything
@implementer(IUserSession) class ShopPermissionOffset(Enum):
class UserSession(object): VIEW = 0 # View info and cabs
def __init__(self, session): BOOKKEEP = 1 # View bookeeping info
self.userId = 0 EDITOR = 2 # Can edit name, settings
self.current_ip = "0.0.0.0" REGISTRAR = 3 # Can add cabs
self.permissions = 0 # 4 - 6 reserved for future use
self.ongeki_version = 7 OWNER = 7 # Can do anything
class ShopOwner():
def __init__(self, usr_id: int = 0, usr_name: str = "", perms: int = 0) -> None:
self.user_id = usr_id
self.username = usr_name
self.permissions = perms
class FrontendServlet(resource.Resource): class UserSession():
def getChild(self, name: bytes, request: Request): def __init__(self, usr_id: int = 0, ip: str = "", perms: int = 0, ongeki_ver: int = 7):
self.logger.debug(f"{Utils.get_ip_addr(request)} -> {name.decode()}") self.user_id = usr_id
if name == b"": self.current_ip = ip
return self self.permissions = perms
return resource.Resource.getChild(self, name, request) self.ongeki_version = ongeki_ver
class FrontendServlet():
def __init__(self, cfg: CoreConfig, config_dir: str) -> None: def __init__(self, cfg: CoreConfig, config_dir: str) -> None:
self.config = cfg self.config = cfg
log_fmt_str = "[%(asctime)s] Frontend | %(levelname)s | %(message)s" log_fmt_str = "[%(asctime)s] Frontend | %(levelname)s | %(message)s"
log_fmt = logging.Formatter(log_fmt_str) log_fmt = logging.Formatter(log_fmt_str)
self.logger = logging.getLogger("frontend")
self.environment = jinja2.Environment(loader=jinja2.FileSystemLoader(".")) self.environment = jinja2.Environment(loader=jinja2.FileSystemLoader("."))
self.game_list: List[Dict[str, str]] = [] self.game_list: Dict[str, Dict[str, Any]] = {}
self.children: Dict[str, Any] = {} self.sn_cvt: Dict[str, str] = {}
self.logger = logging.getLogger("frontend")
if not hasattr(self.logger, "inited"):
fileHandler = TimedRotatingFileHandler( fileHandler = TimedRotatingFileHandler(
"{0}/{1}.log".format(self.config.server.log_dir, "frontend"), "{0}/{1}.log".format(self.config.server.log_dir, "frontend"),
when="d", when="d",
@ -75,144 +73,276 @@ class FrontendServlet(resource.Resource):
coloredlogs.install( coloredlogs.install(
level=cfg.frontend.loglevel, logger=self.logger, fmt=log_fmt_str level=cfg.frontend.loglevel, logger=self.logger, fmt=log_fmt_str
) )
registerAdapter(UserSession, Session, IUserSession)
fe_game = FE_Game(cfg, self.environment) self.logger.inited = True
games = Utils.get_all_titles() games = Utils.get_all_titles()
for game_dir, game_mod in games.items(): for game_dir, game_mod in games.items():
if hasattr(game_mod, "frontend"): if hasattr(game_mod, "frontend") and hasattr(game_mod, "index") and hasattr(game_mod, "game_codes"):
try: try:
if game_mod.index.is_game_enabled(game_mod.game_codes[0], self.config, config_dir):
game_fe = game_mod.frontend(cfg, self.environment, config_dir) game_fe = game_mod.frontend(cfg, self.environment, config_dir)
self.game_list.append({"url": game_dir, "name": game_fe.nav_name}) self.game_list[game_fe.nav_name] = {"url": f"/{game_dir}", "class": game_fe }
fe_game.putChild(game_dir.encode(), game_fe)
if hasattr(game_fe, "SN_PREFIX") and hasattr(game_fe, "NETID_PREFIX"):
if len(game_fe.SN_PREFIX) == len(game_fe.NETID_PREFIX):
for x in range(len(game_fe.SN_PREFIX)):
self.sn_cvt[game_fe.SN_PREFIX[x]] = game_fe.NETID_PREFIX[x]
except Exception as e: except Exception as e:
self.logger.error( self.logger.error(
f"Failed to import frontend from {game_dir} because {e}" f"Failed to import frontend from {game_dir} because {e}"
) )
self.environment.globals["game_list"] = self.game_list
self.putChild(b"gate", FE_Gate(cfg, self.environment))
self.putChild(b"user", FE_User(cfg, self.environment))
self.putChild(b"sys", FE_System(cfg, self.environment))
self.putChild(b"arcade", FE_Arcade(cfg, self.environment))
self.putChild(b"cab", FE_Machine(cfg, self.environment))
self.putChild(b"game", fe_game)
self.logger.info( self.environment.globals["game_list"] = self.game_list
f"Ready on port {self.config.server.port} serving {len(fe_game.children)} games" self.environment.globals["sn_cvt"] = self.sn_cvt
) self.base = FE_Base(cfg, self.environment)
self.gate = FE_Gate(cfg, self.environment)
self.user = FE_User(cfg, self.environment)
self.system = FE_System(cfg, self.environment)
self.arcade = FE_Arcade(cfg, self.environment)
self.machine = FE_Machine(cfg, self.environment)
def get_routes(self) -> List[Route]: def get_routes(self) -> List[Route]:
return [] g_routes = []
for nav_name, g_data in self.environment.globals["game_list"].items():
g_routes.append(Mount(g_data['url'], routes=g_data['class'].get_routes()))
return [
Route("/", self.base.render_GET, methods=['GET']),
Mount("/user", routes=[
Route("/", self.user.render_GET, methods=['GET']),
Route("/{user_id:int}", self.user.render_GET, methods=['GET']),
Route("/update.pw", self.user.render_POST, methods=['POST']),
Route("/update.name", self.user.update_username, methods=['POST']),
Route("/edit.card", self.user.edit_card, methods=['POST']),
Route("/add.card", self.user.add_card, methods=['POST']),
Route("/logout", self.user.render_logout, methods=['GET']),
]),
Mount("/gate", routes=[
Route("/", self.gate.render_GET, methods=['GET', 'POST']),
Route("/gate.login", self.gate.render_login, methods=['POST']),
Route("/gate.create", self.gate.render_create, methods=['POST']),
Route("/create", self.gate.render_create_get, methods=['GET']),
]),
Mount("/sys", routes=[
Route("/", self.system.render_GET, methods=['GET']),
Route("/lookup.user", self.system.lookup_user, methods=['GET']),
Route("/lookup.shop", self.system.lookup_shop, methods=['GET']),
]),
Mount("/shop", routes=[
Route("/", self.arcade.render_GET, methods=['GET']),
Route("/{shop_id:int}", self.arcade.render_GET, methods=['GET']),
]),
Mount("/cab", routes=[
Route("/", self.machine.render_GET, methods=['GET']),
Route("/{machine_id:int}", self.machine.render_GET, methods=['GET']),
]),
Mount("/game", routes=g_routes),
Route("/robots.txt", self.robots)
]
def startup(self) -> None:
self.config.update({
"frontend": {
"standalone": True,
"loglevel": CoreConfig.loglevel_to_str(self.config.frontend.loglevel),
"secret": self.config.frontend.secret
}
})
self.logger.info(f"Serving {len(self.game_list)} games")
@classmethod @classmethod
async def robots(cls, request: Request) -> PlainTextResponse: async def robots(cls, request: Request) -> PlainTextResponse:
return PlainTextResponse("User-agent: *\nDisallow: /\n\nUser-agent: AdsBot-Google\nDisallow: /") return PlainTextResponse("User-agent: *\nDisallow: /\n\nUser-agent: AdsBot-Google\nDisallow: /")
async def render_GET(self, request): class FE_Base():
self.logger.debug(f"{Utils.get_ip_addr(request)} -> {request.uri.decode()}")
template = self.environment.get_template("core/frontend/index.jinja")
return template.render(
server_name=self.config.server.name,
title=self.config.server.name,
game_list=self.game_list,
sesh=vars(IUserSession(request.getSession())),
).encode("utf-16")
class FE_Base(resource.Resource):
""" """
A Generic skeleton class that all frontend handlers should inherit from A Generic skeleton class that all frontend handlers should inherit from
Initializes the environment, data, logger, config, and sets isLeaf to true Initializes the environment, data, logger, config, and sets isLeaf to true
It is expected that game implementations of this class overwrite many of these It is expected that game implementations of this class overwrite many of these
""" """
isLeaf = True
def __init__(self, cfg: CoreConfig, environment: jinja2.Environment) -> None: def __init__(self, cfg: CoreConfig, environment: jinja2.Environment) -> None:
self.core_config = cfg self.core_config = cfg
self.data = Data(cfg) self.data = Data(cfg)
self.logger = logging.getLogger("frontend") self.logger = logging.getLogger("frontend")
self.environment = environment self.environment = environment
self.nav_name = "nav_name" self.nav_name = "index"
async def render_GET(self, request: Request):
self.logger.debug(f"{Utils.get_ip_addr(request)} -> {request.url}")
template = self.environment.get_template("core/templates/index.jinja")
sesh = self.validate_session(request)
resp = Response(template.render(
server_name=self.core_config.server.name,
title=self.core_config.server.name,
game_list=self.environment.globals["game_list"],
sesh=vars(sesh) if sesh is not None else vars(UserSession()),
))
if sesh is None:
resp.delete_cookie("DIANA_SESH")
return resp
def get_routes(self) -> List[Route]:
return []
@classmethod
def test_perm(cls, permission: int, offset: Union[PermissionOffset, ShopPermissionOffset]) -> bool:
logging.getLogger('frontend').debug(f"{permission} vs {1 << offset.value}")
return permission & 1 << offset.value == 1 << offset.value
@classmethod
def test_perm_minimum(cls, permission: int, offset: Union[PermissionOffset, ShopPermissionOffset]) -> bool:
return permission >= 1 << offset.value
def decode_session(self, token: str) -> UserSession:
sesh = UserSession()
if not token: return sesh
try:
tk = jwt.decode(token, b64decode(self.core_config.frontend.secret), options={"verify_signature": True}, algorithms=["HS256"])
sesh.user_id = tk['user_id']
sesh.current_ip = tk['current_ip']
sesh.permissions = tk['permissions']
if sesh.user_id <= 0:
self.logger.error("User session failed to validate due to an invalid ID!")
return UserSession()
return sesh
except jwt.ExpiredSignatureError:
self.logger.error("User session failed to validate due to an expired signature!")
return sesh
except jwt.InvalidSignatureError:
self.logger.error("User session failed to validate due to an invalid signature!")
return sesh
except jwt.DecodeError as e:
self.logger.error(f"User session failed to decode! {e}")
return sesh
except jwt.InvalidTokenError as e:
self.logger.error(f"User session is invalid! {e}")
return sesh
except KeyError as e:
self.logger.error(f"{e} missing from User session!")
return UserSession()
except Exception as e:
self.logger.error(f"Unknown exception occoured when decoding User session! {e}")
return UserSession()
def validate_session(self, request: Request) -> Optional[UserSession]:
sesh = request.cookies.get('DIANA_SESH', "")
if not sesh:
return None
usr_sesh = self.decode_session(sesh)
req_ip = Utils.get_ip_addr(request)
if usr_sesh.current_ip != req_ip:
self.logger.error(f"User session failed to validate due to mismatched IPs! {usr_sesh.current_ip} -> {req_ip}")
return None
if usr_sesh.permissions <= 0 or usr_sesh.permissions > 255:
self.logger.error(f"User session failed to validate due to an invalid permission value! {usr_sesh.permissions}")
return None
return usr_sesh
def encode_session(self, sesh: UserSession, exp_seconds: int = 86400) -> str:
try:
return jwt.encode({ "user_id": sesh.user_id, "current_ip": sesh.current_ip, "permissions": sesh.permissions, "ongeki_version": sesh.ongeki_version, "exp": int(datetime.now(tz=timezone.utc).timestamp()) + exp_seconds }, b64decode(self.core_config.frontend.secret), algorithm="HS256")
except jwt.InvalidKeyError:
self.logger.error("Failed to encode User session because the secret is invalid!")
return ""
except Exception as e:
self.logger.error(f"Unknown exception occoured when encoding User session! {e}")
return ""
class FE_Gate(FE_Base): class FE_Gate(FE_Base):
def render_GET(self, request: Request): async def render_GET(self, request: Request):
self.logger.debug(f"{Utils.get_ip_addr(request)} -> {request.uri.decode()}") self.logger.debug(f"{Utils.get_ip_addr(request)} -> {request.url.path}")
uri: str = request.uri.decode()
sesh = request.getSession() usr_sesh = self.validate_session(request)
usr_sesh = IUserSession(sesh) if usr_sesh and usr_sesh.user_id > 0:
if usr_sesh.userId > 0: return RedirectResponse("/user/", 303)
return redirectTo(b"/user", request)
if uri.startswith("/gate/create"):
return self.create_user(request)
if b"e" in request.args: if "e" in request.query_params:
try: try:
err = int(request.args[b"e"][0].decode()) err = int(request.query_params.get("e", ["0"])[0])
except Exception: except Exception:
err = 0 err = 0
else: else:
err = 0 err = 0
template = self.environment.get_template("core/frontend/gate/gate.jinja") template = self.environment.get_template("core/templates/gate/gate.jinja")
return template.render( resp = Response(template.render(
title=f"{self.core_config.server.name} | Login Gate", title=f"{self.core_config.server.name} | Login Gate",
error=err, error=err,
sesh=vars(usr_sesh), sesh=vars(UserSession()),
).encode("utf-16") ))
resp.delete_cookie("DIANA_SESH")
return resp
async def render_POST(self, request: Request): async def render_login(self, request: Request):
uri = request.uri.decode()
ip = Utils.get_ip_addr(request) ip = Utils.get_ip_addr(request)
frm = await request.form()
access_code: str = frm.get("access_code", None)
if not access_code:
return RedirectResponse("/gate/?e=1", 303)
if uri == "/gate/gate.login": passwd: bytes = frm.get("passwd", "").encode()
access_code: str = request.args[b"access_code"][0].decode()
passwd: bytes = request.args[b"passwd"][0]
if passwd == b"": if passwd == b"":
passwd = None passwd = None
uid = await self.data.card.get_user_id_from_card(access_code) uid = await self.data.card.get_user_id_from_card(access_code)
user = await self.data.user.get_user(uid)
if uid is None: if uid is None:
return redirectTo(b"/gate?e=1", request) self.logger.debug(f"Failed to find user for card {access_code}")
return RedirectResponse("/gate/?e=1", 303)
user = await self.data.user.get_user(uid)
if user is None:
self.logger.error(f"Failed to load user {uid}")
return RedirectResponse("/gate/?e=1", 303)
if passwd is None: if passwd is None:
sesh = await self.data.user.check_password(uid) sesh = await self.data.user.check_password(uid)
if sesh is not None: if sesh is not None:
return redirectTo( return RedirectResponse(f"/gate/create?ac={access_code}")
f"/gate/create?ac={access_code}".encode(), request
)
return redirectTo(b"/gate?e=1", request)
if not self.data.user.check_password(uid, passwd): return RedirectResponse("/gate/?e=1", 303)
return redirectTo(b"/gate?e=1", request)
if not await self.data.user.check_password(uid, passwd):
self.logger.debug(f"Failed password for access code {access_code}")
return RedirectResponse("/gate/?e=1", 303)
self.logger.info(f"Successful login of user {uid} at {ip}") self.logger.info(f"Successful login of user {uid} at {ip}")
sesh = request.getSession() sesh = UserSession()
usr_sesh = IUserSession(sesh) sesh.user_id = uid
usr_sesh.userId = uid sesh.current_ip = ip
usr_sesh.current_ip = ip sesh.permissions = user['permissions']
usr_sesh.permissions = user['permissions']
return redirectTo(b"/user", request) usr_sesh = self.encode_session(sesh)
self.logger.debug(f"Created session with JWT {usr_sesh}")
resp = RedirectResponse("/user/", 303)
resp.set_cookie("DIANA_SESH", usr_sesh)
elif uri == "/gate/gate.create": return resp
access_code: str = request.args[b"access_code"][0].decode()
username: str = request.args[b"username"][0] async def render_create(self, request: Request):
email: str = request.args[b"email"][0].decode() ip = Utils.get_ip_addr(request)
passwd: bytes = request.args[b"passwd"][0] access_code: str = request.query_params.get("access_code", "")
username: str = request.query_params.get("username", "")
email: str = request.query_params.get("email", "")
passwd: bytes = request.query_params.get("passwd", "").encode()
if not access_code or not username or not email or not passwd:
return RedirectResponse("/gate/?e=1", 303)
uid = await self.data.card.get_user_id_from_card(access_code) uid = await self.data.card.get_user_id_from_card(access_code)
if uid is None: if uid is None:
return redirectTo(b"/gate?e=1", request) return RedirectResponse("/gate/?e=1", 303)
salt = bcrypt.gensalt() salt = bcrypt.gensalt()
hashed = bcrypt.hashpw(passwd, salt) hashed = bcrypt.hashpw(passwd, salt)
@ -221,67 +351,72 @@ class FE_Gate(FE_Base):
uid, username, email.lower(), hashed.decode(), 1 uid, username, email.lower(), hashed.decode(), 1
) )
if result is None: if result is None:
return redirectTo(b"/gate?e=3", request) return RedirectResponse("/gate/?e=3", 303)
if not self.data.user.check_password(uid, passwd): if not await self.data.user.check_password(uid, passwd):
return redirectTo(b"/gate", request) return RedirectResponse("/gate/", 303)
return redirectTo(b"/user", request) sesh = UserSession()
sesh.user_id = uid
sesh.current_ip = ip
sesh.permissions = 1
else: usr_sesh = self.encode_session(sesh)
return b"" self.logger.debug(f"Created session with JWT {usr_sesh}")
resp = RedirectResponse("/user", 303)
resp.set_cookie("DIANA_SESH", usr_sesh)
async def create_user(self, request: Request): return resp
if b"ac" not in request.args or len(request.args[b"ac"][0].decode()) != 20:
return redirectTo(b"/gate?e=2", request) async def render_create_get(self, request: Request):
ac = request.query_params.get(b"ac", [b""])[0].decode()
if len(ac) != 20:
return RedirectResponse("/gate/?e=2", 303)
ac = request.args[b"ac"][0].decode()
card = await self.data.card.get_card_by_access_code(ac) card = await self.data.card.get_card_by_access_code(ac)
if card is None: if card is None:
return redirectTo(b"/gate?e=1", request) return RedirectResponse("/gate/?e=1", 303)
user = await self.data.user.get_user(card['user']) user = await self.data.user.get_user(card['user'])
if user is None: if user is None:
self.logger.warning(f"Card {ac} exists with no/invalid associated user ID {card['user']}") self.logger.warning(f"Card {ac} exists with no/invalid associated user ID {card['user']}")
return redirectTo(b"/gate?e=0", request) return RedirectResponse("/gate/?e=0", 303)
if user['password'] is not None: if user['password'] is not None:
return redirectTo(b"/gate?e=1", request) return RedirectResponse("/gate/?e=1", 303)
template = self.environment.get_template("core/frontend/gate/create.jinja") template = self.environment.get_template("core/templates/gate/create.jinja")
return template.render( return Response(template.render(
title=f"{self.core_config.server.name} | Create User", title=f"{self.core_config.server.name} | Create User",
code=ac, code=ac,
sesh={"userId": 0, "permissions": 0}, sesh={"user_id": 0, "permissions": 0},
).encode("utf-16") ))
class FE_User(FE_Base): class FE_User(FE_Base):
async def render_GET(self, request: Request): async def render_GET(self, request: Request):
uri = request.uri.decode() uri = request.url.path
template = self.environment.get_template("core/frontend/user/index.jinja") user_id = request.path_params.get('user_id', None)
self.logger.debug(f"{Utils.get_ip_addr(request)} -> {uri}")
template = self.environment.get_template("core/templates/user/index.jinja")
sesh: Session = request.getSession() usr_sesh = self.validate_session(request)
usr_sesh = IUserSession(sesh) if not usr_sesh:
if usr_sesh.userId == 0: return RedirectResponse("/gate/", 303)
return redirectTo(b"/gate", request)
m = re.match("\/user\/(\d*)", uri) if user_id:
if not self.test_perm(usr_sesh.permissions, PermissionOffset.USERMOD) and user_id != usr_sesh.user_id:
if m is not None: self.logger.warn(f"User {usr_sesh.user_id} does not have permission to view user {user_id}")
usrid = m.group(1) return RedirectResponse("/user/", 303)
if usr_sesh.permissions < 1 << PermissionOffset.USERMOD.value or not usrid == usr_sesh.userId:
return redirectTo(b"/user", request)
else: else:
usrid = usr_sesh.userId user_id = usr_sesh.user_id
user = await self.data.user.get_user(usrid) user = await self.data.user.get_user(user_id)
if user is None: if user is None:
return redirectTo(b"/user", request) self.logger.debug(f"User {user_id} not found")
return RedirectResponse("/user/", 303)
cards = await self.data.card.get_user_cards(usrid) cards = await self.data.card.get_user_cards(user_id)
arcades = await self.data.arcade.get_arcades_managed_by_user(usrid)
card_data = [] card_data = []
arcade_data = [] arcade_data = []
@ -294,176 +429,285 @@ class FE_User(FE_Base):
else: else:
status = 'Active' status = 'Active'
card_data.append({'access_code': c['access_code'], 'status': status}) idm = c['idm']
ac = c['access_code']
for a in arcades: if ac.startswith("5") or idm is not None:
arcade_data.append({'id': a['id'], 'name': a['name']}) c_type = "AmusementIC"
elif ac.startswith("3"):
c_type = "Banapass"
elif ac.startswith("010"):
c_type = "Unknown Aime"
desc, _ = self.data.card.get_aime_ac_key_desc(ac)
if desc is not None:
c_type = desc
else:
c_type = "Unknown"
return template.render( card_data.append({'access_code': ac, 'status': status, 'chip_id': None if c['chip_id'] is None else f"{c['chip_id']:X}", 'idm': idm, 'type': c_type, "memo": ""})
if "e" in request.query_params:
try:
err = int(request.query_params.get("e", 0))
except Exception:
err = 0
else:
err = 0
if "s" in request.query_params:
try:
succ = int(request.query_params.get("s", 0))
except Exception:
succ = 0
else:
succ = 0
return Response(template.render(
title=f"{self.core_config.server.name} | Account", title=f"{self.core_config.server.name} | Account",
sesh=vars(usr_sesh), sesh=vars(usr_sesh),
cards=card_data, cards=card_data,
error=err,
success=succ,
username=user['username'], username=user['username'],
arcades=arcade_data arcades=arcade_data
).encode("utf-16") ))
async def render_logout(self, request: Request):
resp = RedirectResponse("/gate/", 303)
resp.delete_cookie("DIANA_SESH")
return resp
async def edit_card(self, request: Request) -> RedirectResponse:
return RedirectResponse("/user/", 303)
async def add_card(self, request: Request) -> RedirectResponse:
return RedirectResponse("/user/", 303)
async def render_POST(self, request: Request): async def render_POST(self, request: Request):
pass frm = await request.form()
usr_sesh = self.validate_session(request)
if not usr_sesh or not self.test_perm(usr_sesh.permissions, PermissionOffset.USERMOD):
return RedirectResponse("/gate/", 303)
old_pw: str = frm.get('current_pw', None)
pw1: str = frm.get('password1', None)
pw2: str = frm.get('password2', None)
if old_pw is None or pw1 is None or pw2 is None:
return RedirectResponse("/user/?e=4", 303)
if pw1 != pw2:
return RedirectResponse("/user/?e=6", 303)
if not await self.data.user.check_password(usr_sesh.user_id, old_pw.encode()):
return RedirectResponse("/user/?e=5", 303)
if len(pw1) < 10 or not any(ele.isupper() for ele in pw1) or not any(ele.islower() for ele in pw1) \
or not any(ele.isdigit() for ele in pw1) or not any(not ele.isalnum() for ele in pw1):
return RedirectResponse("/user/?e=7", 303)
salt = bcrypt.gensalt()
hashed = bcrypt.hashpw(pw1.encode(), salt)
if not await self.data.user.change_password(usr_sesh.user_id, hashed.decode()):
return RedirectResponse("/gate/?e=1", 303)
return RedirectResponse("/user/?s=1", 303)
async def update_username(self, request: Request):
frm = await request.form()
new_name: bytes = frm.get('new_name', "")
usr_sesh = self.validate_session(request)
if not usr_sesh or not self.test_perm(usr_sesh.permissions, PermissionOffset.USERMOD):
return RedirectResponse("/gate/", 303)
if new_name is None or not new_name:
return RedirectResponse("/user/?e=4", 303)
if len(new_name) > 10:
return RedirectResponse("/user/?e=8", 303)
if not await self.data.user.change_username(usr_sesh.user_id, new_name):
return RedirectResponse("/user/?e=8", 303)
return RedirectResponse("/user/?s=2", 303)
class FE_System(FE_Base): class FE_System(FE_Base):
async def render_GET(self, request: Request): async def render_GET(self, request: Request):
uri = request.uri.decode() template = self.environment.get_template("core/templates/sys/index.jinja")
template = self.environment.get_template("core/frontend/sys/index.jinja") self.logger.debug(f"{Utils.get_ip_addr(request)} -> {request.url.path}")
usr_sesh = self.validate_session(request)
if not usr_sesh or not self.test_perm_minimum(usr_sesh.permissions, PermissionOffset.USERMOD):
return RedirectResponse("/gate/", 303)
return Response(template.render(
title=f"{self.core_config.server.name} | System",
sesh=vars(usr_sesh),
usrlist=[],
))
async def lookup_user(self, request: Request):
template = self.environment.get_template("core/templates/sys/index.jinja")
usrlist: List[Dict] = [] usrlist: List[Dict] = []
aclist: List[Dict] = [] usr_sesh = self.validate_session(request)
cablist: List[Dict] = [] if not usr_sesh or not self.test_perm(usr_sesh.permissions, PermissionOffset.USERMOD):
return RedirectResponse("/gate/", 303)
sesh: Session = request.getSession() uid_search = request.query_params.get("usrId", None)
usr_sesh = IUserSession(sesh) email_search = request.query_params.get("usrEmail", None)
if usr_sesh.userId == 0 or usr_sesh.permissions < 1 << PermissionOffset.USERMOD.value: uname_search = request.query_params.get("usrName", None)
return redirectTo(b"/gate", request)
if uri.startswith("/sys/lookup.user?"): if uid_search:
uri_parse = parse.parse_qs(uri.replace("/sys/lookup.user?", "")) # lop off the first bit u = await self.data.user.get_user(uid_search)
uid_search = uri_parse.get("usrId")
email_search = uri_parse.get("usrEmail")
uname_search = uri_parse.get("usrName")
if uid_search is not None:
u = await self.data.user.get_user(uid_search[0])
if u is not None: if u is not None:
usrlist.append(u._asdict()) usrlist.append(u._asdict())
elif email_search is not None: elif email_search:
u = await self.data.user.find_user_by_email(email_search[0]) u = await self.data.user.find_user_by_email(email_search)
if u is not None: if u is not None:
usrlist.append(u._asdict()) usrlist.append(u._asdict())
elif uname_search is not None: elif uname_search:
ul = await self.data.user.find_user_by_username(uname_search[0]) ul = await self.data.user.find_user_by_username(uname_search)
for u in ul: for u in ul:
usrlist.append(u._asdict()) usrlist.append(u._asdict())
elif uri.startswith("/sys/lookup.arcade?"): return Response(template.render(
uri_parse = parse.parse_qs(uri.replace("/sys/lookup.arcade?", "")) # lop off the first bit
ac_id_search = uri_parse.get("arcadeId")
ac_name_search = uri_parse.get("arcadeName")
ac_user_search = uri_parse.get("arcadeUser")
ac_ip_search = uri_parse.get("arcadeIp")
if ac_id_search is not None:
u = await self.data.arcade.get_arcade(ac_id_search[0])
if u is not None:
aclist.append(u._asdict())
elif ac_name_search is not None:
ul = await self.data.arcade.get_arcade_by_name(ac_name_search[0])
if ul is not None:
for u in ul:
aclist.append(u._asdict())
elif ac_user_search is not None:
ul = await self.data.arcade.get_arcades_managed_by_user(ac_user_search[0])
if ul is not None:
for u in ul:
aclist.append(u._asdict())
elif ac_ip_search is not None:
ul = await self.data.arcade.get_arcades_by_ip(ac_ip_search[0])
if ul is not None:
for u in ul:
aclist.append(u._asdict())
elif uri.startswith("/sys/lookup.cab?"):
uri_parse = parse.parse_qs(uri.replace("/sys/lookup.cab?", "")) # lop off the first bit
cab_id_search = uri_parse.get("cabId")
cab_serial_search = uri_parse.get("cabSerial")
cab_acid_search = uri_parse.get("cabAcId")
if cab_id_search is not None:
u = await self.data.arcade.get_machine(id=cab_id_search[0])
if u is not None:
cablist.append(u._asdict())
elif cab_serial_search is not None:
u = await self.data.arcade.get_machine(serial=cab_serial_search[0])
if u is not None:
cablist.append(u._asdict())
elif cab_acid_search is not None:
ul = await self.data.arcade.get_arcade_machines(cab_acid_search[0])
for u in ul:
cablist.append(u._asdict())
return template.render(
title=f"{self.core_config.server.name} | System", title=f"{self.core_config.server.name} | System",
sesh=vars(usr_sesh), sesh=vars(usr_sesh),
usrlist=usrlist, usrlist=usrlist,
aclist=aclist, shoplist=[],
cablist=cablist, ))
).encode("utf-16")
async def lookup_shop(self, request: Request):
shoplist = []
template = self.environment.get_template("core/templates/sys/index.jinja")
usr_sesh = self.validate_session(request)
if not usr_sesh or not self.test_perm(usr_sesh.permissions, PermissionOffset.ACMOD):
return RedirectResponse("/gate/", 303)
shopid_search = request.query_params.get("shopId", None)
sn_search = request.query_params.get("serialNum", None)
if shopid_search:
if shopid_search.isdigit():
shopid_search = int(shopid_search)
try:
sinfo = await self.data.arcade.get_arcade(shopid_search)
except Exception as e:
self.logger.error(f"Failed to fetch shop info for shop {shopid_search} in lookup_shop - {e}")
sinfo = None
if sinfo:
shoplist.append({
"name": sinfo.name,
"id": sinfo.allnet_id
})
else:
return Response(template.render(
title=f"{self.core_config.server.name} | System",
sesh=vars(usr_sesh),
usrlist=[],
shoplist=shoplist,
error=4
))
if sn_search:
sn_search = sn_search.upper().replace("-", "").strip()
if sn_search.isdigit() and len(sn_search) == 12:
prefix = sn_search[:4]
suffix = sn_search[5:]
netid_prefix = self.environment.globals["sn_cvt"].get(prefix, "")
sn_search = netid_prefix + suffix
if re.match("^AB[D|G|L]N\d{7}$", sn_search):
cabinfo = await self.data.arcade.get_machine(sn_search)
if cabinfo is None: sinfo = None
else:
sinfo = await self.data.arcade.get_arcade(cabinfo['arcade'])
if sinfo:
shoplist.append({
"name": sinfo['name'],
"id": sinfo['id']
})
else:
return Response(template.render(
title=f"{self.core_config.server.name} | System",
sesh=vars(usr_sesh),
usrlist=[],
shoplist=shoplist,
error=10
))
class FE_Game(FE_Base): return Response(template.render(
isLeaf = False title=f"{self.core_config.server.name} | System",
children: Dict[str, Any] = {} sesh=vars(usr_sesh),
usrlist=[],
def getChild(self, name: bytes, request: Request): shoplist=shoplist,
if name == b"": ))
return self
return resource.Resource.getChild(self, name, request)
async def render_GET(self, request: Request) -> bytes:
return redirectTo(b"/user", request)
class FE_Arcade(FE_Base): class FE_Arcade(FE_Base):
async def render_GET(self, request: Request): async def render_GET(self, request: Request):
uri = request.uri.decode() template = self.environment.get_template("core/templates/arcade/index.jinja")
template = self.environment.get_template("core/frontend/arcade/index.jinja") shop_id = request.path_params.get('shop_id', None)
managed = []
sesh: Session = request.getSession() usr_sesh = self.validate_session(request)
usr_sesh = IUserSession(sesh) if not usr_sesh or not self.test_perm(usr_sesh.permissions, PermissionOffset.ACMOD):
if usr_sesh.userId == 0: self.logger.warn(f"User {usr_sesh.user_id} does not have permission to view shops!")
return redirectTo(b"/gate", request) return RedirectResponse("/gate/", 303)
m = re.match("\/arcade\/(\d*)", uri) if not shop_id:
return Response(template.render(
if m is not None:
arcadeid = m.group(1)
perms = await self.data.arcade.get_manager_permissions(usr_sesh.userId, arcadeid)
arcade = await self.data.arcade.get_arcade(arcadeid)
if perms is None:
perms = 0
else:
return redirectTo(b"/user", request)
return template.render(
title=f"{self.core_config.server.name} | Arcade", title=f"{self.core_config.server.name} | Arcade",
sesh=vars(usr_sesh), sesh=vars(usr_sesh),
error=0, ))
perms=perms,
arcade=arcade._asdict()
).encode("utf-16")
try:
sinfo = await self.data.arcade.get_arcade(shop_id)
except Exception as e:
self.logger.error(f"Failed to fetch shop info for shop {shop_id} in render_GET - {e}")
sinfo = None
if not sinfo:
return Response(template.render(
title=f"{self.core_config.server.name} | Arcade",
sesh=vars(usr_sesh),
))
return Response(template.render(
title=f"{self.core_config.server.name} | Arcade",
sesh=vars(usr_sesh),
arcade={
"name": sinfo['name'],
"id": sinfo['id'],
"cabs": []
}
))
class FE_Machine(FE_Base): class FE_Machine(FE_Base):
async def render_GET(self, request: Request): async def render_GET(self, request: Request):
uri = request.uri.decode() template = self.environment.get_template("core/templates/machine/index.jinja")
template = self.environment.get_template("core/frontend/machine/index.jinja") cab_id = request.path_params.get('cab_id', None)
sesh: Session = request.getSession() usr_sesh = self.validate_session(request)
usr_sesh = IUserSession(sesh) if not usr_sesh or not self.test_perm(usr_sesh.permissions, PermissionOffset.ACMOD):
if usr_sesh.userId == 0: self.logger.warn(f"User {usr_sesh.user_id} does not have permission to view shops!")
return redirectTo(b"/gate", request) return RedirectResponse("/gate/", 303)
return template.render( if not cab_id:
return Response(template.render(
title=f"{self.core_config.server.name} | Machine", title=f"{self.core_config.server.name} | Machine",
sesh=vars(usr_sesh), sesh=vars(usr_sesh),
arcade={}, ))
error=0,
).encode("utf-16") return Response(template.render(
title=f"{self.core_config.server.name} | Machine",
sesh=vars(usr_sesh),
arcade={}
))

View File

@ -1,4 +0,0 @@
{% extends "core/frontend/index.jinja" %}
{% block content %}
<h1>{{ arcade.name }}</h1>
{% endblock content %}

View File

@ -1,5 +0,0 @@
{% extends "core/frontend/index.jinja" %}
{% block content %}
{% include "core/frontend/widgets/err_banner.jinja" %}
<h1>Machine Management</h1>
{% endblock content %}

View File

@ -1,103 +0,0 @@
{% extends "core/frontend/index.jinja" %}
{% block content %}
<h1>System Management</h1>
<div class="row" id="rowForm">
{% if sesh.permissions >= 2 %}
<div class="col-sm-6" style="max-width: 25%;">
<form id="usrLookup" name="usrLookup" action="/sys/lookup.user" class="form-inline">
<h3>User Search</h3>
<div class="form-group">
<label for="usrId">User ID</label>
<input type="number" class="form-control" id="usrId" name="usrId">
</div>
OR
<div class="form-group">
<label for="usrName">Username</label>
<input type="text" class="form-control" id="usrName" name="usrName">
</div>
OR
<div class="form-group">
<label for="usrEmail">Email address</label>
<input type="email" class="form-control" id="usrEmail" name="usrEmail" aria-describedby="emailHelp">
</div>
<br />
<button type="submit" class="btn btn-primary">Search</button>
</form>
</div>
{% endif %}
{% if sesh.permissions >= 4 %}
<div class="col-sm-6" style="max-width: 25%;">
<form id="arcadeLookup" name="arcadeLookup" action="/sys/lookup.arcade" class="form-inline" >
<h3>Arcade Search</h3>
<div class="form-group">
<label for="arcadeId">Arcade ID</label>
<input type="number" class="form-control" id="arcadeId" name="arcadeId">
</div>
OR
<div class="form-group">
<label for="arcadeName">Arcade Name</label>
<input type="text" class="form-control" id="arcadeName" name="arcadeName">
</div>
OR
<div class="form-group">
<label for="arcadeUser">Owner User ID</label>
<input type="number" class="form-control" id="arcadeUser" name="arcadeUser">
</div>
OR
<div class="form-group">
<label for="arcadeIp">Assigned IP Address</label>
<input type="text" class="form-control" id="arcadeIp" name="arcadeIp">
</div>
<br />
<button type="submit" class="btn btn-primary">Search</button>
</form>
</div>
<div class="col-sm-6" style="max-width: 25%;">
<form id="cabLookup" name="cabLookup" action="/sys/lookup.cab" class="form-inline" >
<h3>Machine Search</h3>
<div class="form-group">
<label for="cabId">Machine ID</label>
<input type="number" class="form-control" id="cabId" name="cabId">
</div>
OR
<div class="form-group">
<label for="cabSerial">Machine Serial</label>
<input type="text" class="form-control" id="cabSerial" name="cabSerial">
</div>
OR
<div class="form-group">
<label for="cabAcId">Arcade ID</label>
<input type="number" class="form-control" id="cabAcId" name="cabAcId">
</div>
<br />
<button type="submit" class="btn btn-primary">Search</button>
</form>
</div>
{% endif %}
</div>
<div class="row" id="rowResult" style="margin: 10px;">
{% if sesh.permissions >= 2 %}
<div id="userSearchResult" class="col-sm-6" style="max-width: 25%;">
{% for usr in usrlist %}
<a href=/user/{{ usr.id }}><pre>{{ usr.id }} | {{ usr.username if usr.username != None else "<i>No Name Set</i>"}}</pre></a>
{% endfor %}
</div>
{% endif %}
{% if sesh.permissions >= 4 %}
<div id="arcadeSearchResult" class="col-sm-6" style="max-width: 25%;">
{% for ac in aclist %}
<pre><a href=/arcade/{{ ac.id }}>{{ ac.id }} | {{ ac.name if ac.name != None else "<i>No Name Set</i>" }} | {{ ac.ip if ac.ip != None else "<i>No IP Assigned</i>"}}</pre></a>
{% endfor %}
</div
><div id="cabSearchResult" class="col-sm-6" style="max-width: 25%;">
{% for cab in cablist %}
<a href=/cab/{{ cab.id }}><pre>{{ cab.id }} | {{ cab.game if cab.game != None else "<i>ANY </i>" }} | {{ cab.serial }}</pre></a>
{% endfor %}
</div>
{% endif %}
</div>
<div class="row" id="rowAdd">
</div>
{% endblock content %}

View File

@ -1,41 +0,0 @@
{% extends "core/frontend/index.jinja" %}
{% block content %}
<h1>Management for {{ username }}</h1>
<h2>Cards <button class="btn btn-success" data-bs-toggle="modal" data-bs-target="#card_add">Add</button></h2>
<ul style="font-size: 20px;">
{% for c in cards %}
<li>{{ c.access_code }}: {{ c.status }}&nbsp;{% if c.status == 'Active'%}<button class="btn-warning btn">Lock</button>{% elif c.status == 'Locked' %}<button class="btn-warning btn">Unlock</button>{% endif %}&nbsp;<button class="btn-danger btn">Delete</button></li>
{% endfor %}
</ul>
{% if arcades is defined %}
<h2>Arcades</h2>
<ul style="font-size: 20px;">
{% for a in arcades %}
<li><a href=/arcade/{{ a.id }}>{{ a.name }}</a></li>
{% endfor %}
</ul>
{% endif %}
<div class="modal fade" id="card_add" tabindex="-1" aria-labelledby="card_add_label" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h1 class="modal-title fs-5" id="card_add_label">Add Card</h1>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
HOW TO:<br>
Scan your card on any networked game and press the "View Access Code" button (varies by game) and enter the 20 digit code below.<br>
!!FOR AMUSEIC CARDS: DO NOT ENTER THE CODE SHOWN ON THE BACK OF THE CARD ITSELF OR IT WILL NOT WORK!!
<p /><label for="card_add_frm_access_code">Access Code:&nbsp;</label><input id="card_add_frm_access_code" maxlength="20" type="text" required>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-primary">Add</button>
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
</div>
</div>
</div>
</div>
{% endblock content %}

View File

@ -1,18 +0,0 @@
{% if error > 0 %}
<div class="err-banner">
<h3>Error</h3>
{% if error == 1 %}
Card not registered, or wrong password
{% elif error == 2 %}
Missing or malformed access code
{% elif error == 3 %}
Failed to create user
{% elif error == 4 %}
Arcade not found
{% elif error == 5 %}
Machine not found
{% else %}
An unknown error occoured
{% endif %}
</div>
{% endif %}

View File

@ -0,0 +1,4 @@
{% extends "core/templates/index.jinja" %}
{% block content %}
<h1>{{ arcade.name }}</h1>
{% endblock content %}

View File

@ -1,4 +1,4 @@
{% extends "core/frontend/index.jinja" %} {% extends "core/templates/index.jinja" %}
{% block content %} {% block content %}
<h1>Create User</h1> <h1>Create User</h1>
<form id="create" style="max-width: 240px; min-width: 10%;" action="/gate/gate.create" method="post"> <form id="create" style="max-width: 240px; min-width: 10%;" action="/gate/gate.create" method="post">

View File

@ -1,7 +1,7 @@
{% extends "core/frontend/index.jinja" %} {% extends "core/templates/index.jinja" %}
{% block content %} {% block content %}
<h1>Gate</h1> <h1>Gate</h1>
{% include "core/frontend/widgets/err_banner.jinja" %} {% include "core/templates/widgets/err_banner.jinja" %}
<style> <style>
/* Chrome, Safari, Edge, Opera */ /* Chrome, Safari, Edge, Opera */
input::-webkit-outer-spin-button, input::-webkit-outer-spin-button,

View File

@ -84,7 +84,7 @@
</style> </style>
</head> </head>
<body> <body>
{% include "core/frontend/widgets/topbar.jinja" %} {% include "core/templates/widgets/topbar.jinja" %}
{% block content %} {% block content %}
<h1>{{ server_name }}</h1> <h1>{{ server_name }}</h1>
{% endblock content %} {% endblock content %}

View File

@ -0,0 +1,5 @@
{% extends "core/templates/index.jinja" %}
{% block content %}
{% include "core/templates/widgets/err_banner.jinja" %}
<h1>Machine Management</h1>
{% endblock content %}

View File

@ -0,0 +1,69 @@
{% extends "core/templates/index.jinja" %}
{% block content %}
<h1>System Management</h1>
{% if error is defined %}
{% include "core/templates/widgets/err_banner.jinja" %}
{% endif %}
<div class="row" id="rowForm">
{% if "{:08b}".format(sesh.permissions)[6] == "1" %}
<div class="col-sm-6" style="max-width: 25%;">
<form id="usrLookup" name="usrLookup" action="/sys/lookup.user" class="form-inline">
<h3>User Search</h3>
<div class="form-group">
<label for="usrId">User ID</label>
<input type="number" class="form-control" id="usrId" name="usrId">
</div>
OR
<div class="form-group">
<label for="usrName">Username</label>
<input type="text" class="form-control" id="usrName" name="usrName">
</div>
OR
<div class="form-group">
<label for="usrEmail">Email address</label>
<input type="email" class="form-control" id="usrEmail" name="usrEmail" aria-describedby="emailHelp">
</div>
<br />
<button type="submit" class="btn btn-primary">Search</button>
</form>
</div>
{% endif %}
{% if "{:08b}".format(sesh.permissions)[5] == "1" %}
<div class="col-sm-6" style="max-width: 25%;">
<form id="shopLookup" name="shopLookup" action="/sys/lookup.shop" class="form-inline">
<h3>Shop search</h3>
<div class="form-group">
<label for="shopId">Shop ID</label>
<input type="number" class="form-control" id="shopId" name="shopId">
</div>
OR
<div class="form-group">
<label for="serialNum">Serial Number</label>
<input type="text" class="form-control" id="serialNum" name="serialNum" maxlength="12">
</div>
<br />
<button type="submit" class="btn btn-primary">Search</button>
</form>
</div>
{% endif %}
</div>
<div class="row" id="rowResult" style="margin: 10px;">
{% if "{:08b}".format(sesh.permissions)[6] == "1" %}
<div id="userSearchResult" class="col-sm-6" style="max-width: 25%;">
{% for usr in usrlist %}
<a href=/user/{{ usr.id }}><pre>{{ usr.username if usr.username is not none else "<i>No Name Set</i>"}}</pre></a>
{% endfor %}
</div>
{% endif %}
{% if "{:08b}".format(sesh.permissions)[5] == "1" %}
<div id="shopSearchResult" class="col-sm-6" style="max-width: 25%;">
{% for shop in shoplist %}
<a href="/shop/{{ shop.id }}"><pre>{{ shop.name if shop.name else "<i>No Name Set</i>"}}</pre></a>
{% endfor %}
</div>
{% endif %}
</div>
<div class="row" id="rowAdd">
</div>
{% endblock content %}

View File

@ -0,0 +1,175 @@
{% extends "core/templates/index.jinja" %}
{% block content %}
<script type="text/javascript">
function toggle_new_name_form() {
let frm = document.getElementById("new_name_form");
let btn = document.getElementById("btn_toggle_form");
if (frm.style['display'] != "") {
frm.style['display'] = "";
frm.style['max-height'] = "";
btn.innerText = "Cancel";
} else {
frm.style['display'] = "none";
frm.style['max-height'] = "0px";
btn.innerText = "Edit";
}
}
function toggle_add_card_form() {
let btn = document.getElementById("btn_add_card");
let dv = document.getElementById("add_card_container")
if (dv.style['display'] != "") {
btn.innerText = "Cancel";
dv.style['display'] = "";
} else {
btn.innerText = "Add";
dv.style['display'] = "none";
}
}
function prep_edit_form(access_code, chip_id, idm, card_type, u_memo) {
ac = document.getElementById("card_edit_frm_access_code");
cid = document.getElementById("card_edit_frm_chip_id");
fidm = document.getElementById("card_edit_frm_idm");
memo = document.getElementById("card_edit_frm_memo");
if (chip_id == "None" || chip_id == undefined) {
chip_id = ""
}
if (idm == "None" || idm == undefined) {
idm = ""
}
if (u_memo == "None" || u_memo == undefined) {
u_memo = ""
}
ac.value = access_code;
cid.value = chip_id;
fidm.value = idm;
memo.value = u_memo;
if (card_type == "AmusementIC") {
cid.disabled = true;
fidm.disabled = false;
} else {
cid.disabled = false;
fidm.disabled = true;
}
}
</script>
<h1>Management for {{ username }}&nbsp;<button onclick="toggle_new_name_form()" class="btn btn-secondary" id="btn_toggle_form">Edit</button></h1>
{% if error is defined %}
{% include "core/templates/widgets/err_banner.jinja" %}
{% endif %}
{% if success is defined and success == 2 %}
<div style="background-color: #00AA00; padding: 20px; margin-bottom: 10px; width: 15%;">
Update successful
</div>
{% endif %}
<form style="max-width: 33%; display: none; max-height: 0px;" action="/user/update.name" method="post" id="new_name_form">
<div class="mb-3">
<label for="new_name" class="form-label">New Nickname</label>
<input type="text" class="form-control" id="new_name" name="new_name" aria-describedby="new_name_help">
<div id="new_name_help" class="form-text">Must be 10 characters or less</div>
</div>
<button type="submit" class="btn btn-primary">Submit</button>
</form>
<p></p>
<h2>Cards <button class="btn btn-success" id="btn_add_card" onclick="toggle_add_card_form()">Add</button></h2>
{% if success is defined and success == 3 %}
<div style="background-color: #00AA00; padding: 20px; margin-bottom: 10px; width: 15%;">
Card added successfully
</div>
{% endif %}
<div id="add_card_container" style="display: none; max-width: 33%;">
<form action="/user/add.card" method="post", id="frm_add_card">
<label class="form-label" for="card_add_frm_access_code">Access Code:</label>
<input class="form-control" name="add_access_code" id="card_add_frm_access_code" maxlength="20" type="text" required aria-describedby="ac_help">
<div id="ac_help" class="form-text">20 digit code on the back of the card.</div>
<button type="submit" class="btn btn-primary">Add</button>
</form>
<br>
</div>
<ul style="font-size: 20px;">
{% for c in cards %}
<li>{{ c.access_code }} ({{ c.type}}): {{ c.status }}&nbsp;<button onclick="prep_edit_form('{{ c.access_code }}', '{{ c.chip_id}}', '{{ c.idm }}', '{{ c.type }}', '{{ c.memo }}')" data-bs-toggle="modal" data-bs-target="#card_edit" class="btn btn-secondary" id="btn_edit_card_{{ c.access_code }}">Edit</button>&nbsp;{% if c.status == 'Active'%}<button class="btn-warning btn">Lock</button>{% elif c.status == 'Locked' %}<button class="btn-warning btn">Unlock</button>{% endif %}&nbsp;<button class="btn-danger btn">Delete</button></li>
{% endfor %}
</ul>
<h2>Reset Password</h2>
{% if success is defined and success == 1 %}
<div style="background-color: #00AA00; padding: 20px; margin-bottom: 10px; width: 15%;">
Update successful
</div>
{% endif %}
<form style="max-width: 33%;" action="/user/update.pw" method="post">
<div class="mb-3">
<label for="current_pw" class="form-label">Current Password</label>
<input type="password" class="form-control" id="current_pw" name="current_pw">
</div>
<div class="mb-3">
<label for="password1" class="form-label">New Password</label>
<input type="password" class="form-control" id="password1" name="password1" aria-describedby="password_help">
<div id="password_help" class="form-text">Password must be at least 10 characters long, contain an upper and lowercase character, number, and special character</div>
</div>
<div class="mb-3">
<label for="password2" class="form-label">Retype New Password</label>
<input type="password" class="form-control" id="password2" name="password2">
</div>
<button type="submit" class="btn btn-primary">Submit</button>
</form>
{% if arcades is defined and arcades|length > 0 %}
<h2>Arcades</h2>
<ul>
{% for a in arcades %}
<li><h3>{{ a.name }}</h3>
{% if a.machines|length > 0 %}
<table>
<tr><th>Serial</th><th>Game</th><th>Last Seen</th></tr>
{% for m in a.machines %}
<tr><td>{{ m.serial }}</td><td>{{ m.game }}</td><td>{{ m.last_seen }}</td></tr>
{% endfor %}
</table>
{% endif %}
</li>
{% endfor %}
</ul>
{% endif %}
<div class="modal fade" id="card_edit" tabindex="-1" aria-labelledby="card_edit_label" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h1 class="modal-title fs-5" id="card_edit_label">Edit Card</h1>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<form action="/user/edit.card" method="post", id="frm_edit_card">
<label class="form-label" for="card_edit_frm_access_code">Access Code:</label>
<input class="form-control" readonly name="add_access_code" id="card_edit_frm_access_code" maxlength="20" type="text" required aria-describedby="ac_help">
<div id="ac_help" class="form-text">20 digit code on the back of the card. If this is incorrect, contact a sysadmin.</div>
<label class="form-label" for="card_edit_frm_memo" id="card_edit_frm_memo_lbl">Memo:</label>
<input class="form-control" aria-describedby="memo_help" name="add_memo" id="card_edit_frm_memo" maxlength="16" type="text">
<div id="memo_help" class="form-text">Must be 16 characters or less.</div>
<label class="form-label" for="card_edit_frm_idm" id="card_edit_frm_idm_lbl">FeliCa IDm:</label>
<input class="form-control" aria-describedby="idm_help" name="add_felica_idm" id="card_edit_frm_idm" maxlength="16" type="text">
<div id="idm_help" class="form-text">8 bytes that uniquly idenfites a FeliCa card. Obtained by reading the card with an NFC reader.</div>
<label class="form-label" for="card_edit_frm_chip_id" id="card_edit_frm_chip_id_lbl">Mifare UID:</label>
<input class="form-control" aria-describedby="chip_id_help" name="add_mifare_chip_id" id="card_edit_frm_chip_id" maxlength="8" type="text">
<div id="chip_id_help" class="form-text">4 byte integer that uniquly identifies a Mifare card. Obtained by reading the card with an NFC reader.</div>
</form>
</div>
<div class="modal-footer">
<button type="submit" class="btn btn-primary" form="frm_edit_card">Edit</button>
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
</div>
</div>
</div>
</div>
{% endblock content %}

View File

@ -0,0 +1,29 @@
{% if error > 0 %}
<div class="err-banner">
<h3>Error</h3>
{% if error == 1 %}
Card not registered, or wrong password
{% elif error == 2 %}
Missing or malformed access code
{% elif error == 3 %}
Failed to create user
{% elif error == 4 %}
Required field not filled or invalid
{% elif error == 5 %}
Incorrect old password
{% elif error == 6 %}
Passwords don't match
{% elif error == 7 %}
New password not acceptable
{% elif error == 8 %}
New Nickname too long
{% elif error == 9 %}
You must be logged in to preform this action
New Nickname too long
{% elif error == 10 %}
Invalid serial number
{% else %}
An unknown error occoured
{% endif %}
</div>
{% endif %}

View File

@ -3,19 +3,20 @@
</div> </div>
<div style="background: #333; color: #f9f9f9; width: 80%; height: 50px; line-height: 50px; padding-left: 10px; float: left;"> <div style="background: #333; color: #f9f9f9; width: 80%; height: 50px; line-height: 50px; padding-left: 10px; float: left;">
<a href=/><button class="btn btn-primary">Home</button></a>&nbsp; <a href=/><button class="btn btn-primary">Home</button></a>&nbsp;
{% for game in game_list %} {% for game, data in game_list|items %}
<a href=/game/{{ game.url }}><button class="btn btn-success">{{ game.name }}</button></a>&nbsp; <a href=/game{{ data.url }}/><button class="btn btn-success">{{ game }}</button></a>&nbsp;
{% endfor %} {% endfor %}
</div> </div>
</div> </div>
<div style="background: #333; color: #f9f9f9; width: 10%; height: 50px; line-height: 50px; text-align: center; float: left;"> <div style="background: #333; color: #f9f9f9; width: 10%; height: 50px; line-height: 50px; text-align: center; float: left;">
{% if sesh is defined and sesh["permissions"] >= 2 %} {% if sesh is defined and sesh["permissions"] >= 2 %}
<a href="/sys"><button class="btn btn-primary">System</button></a> <a href="/sys/"><button class="btn btn-primary">System</button></a>
{% endif %} {% endif %}
{% if sesh is defined and sesh["userId"] > 0 %} {% if sesh is defined and sesh["user_id"] > 0 %}
<a href="/user"><button class="btn btn-primary">Account</button></a> <a href="/user/"><button class="btn btn-primary">Account</button></a>
<a href="/user/logout"><button class="btn btn-danger">Logout</button></a>
{% else %} {% else %}
<a href="/gate"><button class="btn btn-primary">Gate</button></a> <a href="/gate/"><button class="btn btn-primary">Gate</button></a>
{% endif %} {% endif %}
</div> </div>

View File

@ -1,12 +1,13 @@
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 twisted.web.util import redirectTo
from starlette.requests import Request from starlette.requests import Request
from twisted.web.server import Session
from core.frontend import FE_Base, IUserSession from core.frontend import FE_Base, UserSession
from core.config import CoreConfig from core.config import CoreConfig
from titles.idac.database import IDACData from titles.idac.database import IDACData
from titles.idac.schema.profile import * from titles.idac.schema.profile import *
@ -26,7 +27,8 @@ 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"
# TODO: Add version list # TODO: Add version list
self.version = IDACConstants.VER_IDAC_SEASON_2 self.version = IDACConstants.VER_IDAC_SEASON_2
@ -37,6 +39,11 @@ class IDACFrontend(FE_Base):
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 = {}
@ -87,35 +94,32 @@ class IDACFrontend(FE_Base):
return json.dumps(json_export, indent=4, default=str, ensure_ascii=False) return json.dumps(json_export, indent=4, default=str, ensure_ascii=False)
async 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/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:
user_id = usr_sesh.userId usr_sesh = UserSession()
user_id = usr_sesh.user_id
# user_id = usr_sesh.user_id # user_id = usr_sesh.user_id
# profile export # profile export
if uri.startswith("/game/idac/export"): if uri.startswith("/game/idac/export"):
if user_id == 0: if user_id == 0:
return redirectTo(b"/game/idac", request) return RedirectResponse(b"/game/idac", request)
# 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).encode("utf-8")
request.responseHeaders.addRawHeader(
b"content-type", b"application/octet-stream"
)
request.responseHeaders.addRawHeader(
b"content-disposition", b"attachment; filename=idac_profile.json"
)
request.responseHeaders.addRawHeader(
b"content-length", str(len(content)).encode("utf-8")
)
self.logger.info(f"User {user_id} exported their IDAC data") self.logger.info(f"User {user_id} exported their IDAC data")
return content return Response(
content,
200,
{'content-disposition': 'attachment; filename=idac_profile.json'},
"application/octet-stream"
)
profile_data, tickets, rank = None, None, None profile_data, tickets, rank = None, None, None
if user_id > 0: if user_id > 0:
@ -128,7 +132,7 @@ class IDACFrontend(FE_Base):
for ticket in ticket_data for ticket in ticket_data
} }
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"],
profile=profile_data, profile=profile_data,
@ -136,4 +140,4 @@ class IDACFrontend(FE_Base):
rank=rank, rank=rank,
sesh=vars(usr_sesh), sesh=vars(usr_sesh),
active_page="idac", active_page="idac",
).encode("utf-16") ))

View File

@ -1,8 +1,8 @@
{% extends "core/frontend/index.jinja" %} {% extends "core/templates/index.jinja" %}
{% block content %} {% block content %}
<h1 class="mb-3">頭文字D THE ARCADE</h1> <h1 class="mb-3">頭文字D THE ARCADE</h1>
{% 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">
@ -128,7 +128,7 @@
</div> </div>
<script type="text/javascript"> <script type="text/javascript">
{% include "titles/idac/frontend/js/idac_scripts.js" %} {% include "titles/idac/templates/js/idac_scripts.js" %}
</script> </script>
{% endblock content %} {% endblock content %}

View File

@ -1,11 +1,14 @@
from typing import List
from starlette.routing import Route
import yaml import yaml
import jinja2 import jinja2
from starlette.requests import Request from starlette.requests import Request
from starlette.responses import Response, RedirectResponse
from os import path from os import path
from twisted.web.util import redirectTo from twisted.web.util import redirectTo
from twisted.web.server import Session from twisted.web.server import Session
from core.frontend import FE_Base, IUserSession from core.frontend import FE_Base, UserSession
from core.config import CoreConfig from core.config import CoreConfig
from titles.ongeki.config import OngekiConfig from titles.ongeki.config import OngekiConfig
@ -28,23 +31,30 @@ class OngekiFrontend(FE_Base):
self.nav_name = "O.N.G.E.K.I." self.nav_name = "O.N.G.E.K.I."
self.version_list = OngekiConstants.VERSION_NAMES self.version_list = OngekiConstants.VERSION_NAMES
def get_routes(self) -> List[Route]:
return [
Route("/", self.render_GET)
]
async def render_GET(self, request: Request) -> bytes: async def render_GET(self, request: Request) -> bytes:
template = self.environment.get_template( template = self.environment.get_template(
"titles/ongeki/frontend/ongeki_index.jinja" "titles/ongeki/templates/ongeki_index.jinja"
) )
sesh: Session = request.getSession() usr_sesh = self.validate_session(request)
usr_sesh = IUserSession(sesh) if not usr_sesh:
usr_sesh = UserSession()
self.version = usr_sesh.ongeki_version self.version = usr_sesh.ongeki_version
if getattr(usr_sesh, "userId", 0) != 0: if getattr(usr_sesh, "userId", 0) != 0:
profile_data =self.data.profile.get_profile_data(usr_sesh.userId, self.version) profile_data =self.data.profile.get_profile_data(usr_sesh.user_id, self.version)
rival_list = await self.data.profile.get_rivals(usr_sesh.userId) rival_list = await self.data.profile.get_rivals(usr_sesh.user_id)
rival_data = { rival_data = {
"userRivalList": rival_list, "userRivalList": rival_list,
"userId": usr_sesh.userId "userId": usr_sesh.user_id
} }
rival_info = OngekiBase.handle_get_user_rival_data_api_request(self, rival_data) rival_info = OngekiBase.handle_get_user_rival_data_api_request(self, rival_data)
return template.render( return Response(template.render(
data=self.data.profile, data=self.data.profile,
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"],
@ -54,34 +64,36 @@ class OngekiFrontend(FE_Base):
version_list=self.version_list, version_list=self.version_list,
version=self.version, version=self.version,
sesh=vars(usr_sesh) sesh=vars(usr_sesh)
).encode("utf-16") ))
else: else:
return redirectTo(b"/gate/", request) return RedirectResponse("/gate/", 303)
async def render_POST(self, request: Request): async def render_POST(self, request: Request):
uri = request.uri.decode() uri = request.uri.decode()
sesh: Session = request.getSession() usr_sesh = self.validate_session(request)
usr_sesh = IUserSession(sesh) if not usr_sesh:
if hasattr(usr_sesh, "userId"): usr_sesh = UserSession()
if usr_sesh.user_id > 0:
if uri == "/game/ongeki/rival.add": if uri == "/game/ongeki/rival.add":
rival_id = request.args[b"rivalUserId"][0].decode() rival_id = request.args[b"rivalUserId"][0].decode()
await self.data.profile.put_rival(usr_sesh.userId, rival_id) await self.data.profile.put_rival(usr_sesh.user_id, rival_id)
# self.logger.info(f"{usr_sesh.userId} added a rival") # self.logger.info(f"{usr_sesh.user_id} added a rival")
return redirectTo(b"/game/ongeki/", request) return RedirectResponse(b"/game/ongeki/", 303)
elif uri == "/game/ongeki/rival.delete": elif uri == "/game/ongeki/rival.delete":
rival_id = request.args[b"rivalUserId"][0].decode() rival_id = request.args[b"rivalUserId"][0].decode()
await self.data.profile.delete_rival(usr_sesh.userId, rival_id) await self.data.profile.delete_rival(usr_sesh.user_id, rival_id)
# self.logger.info(f"{response}") # self.logger.info(f"{response}")
return redirectTo(b"/game/ongeki/", request) return RedirectResponse(b"/game/ongeki/", 303)
elif uri == "/game/ongeki/version.change": elif uri == "/game/ongeki/version.change":
ongeki_version=request.args[b"version"][0].decode() ongeki_version=request.args[b"version"][0].decode()
if(ongeki_version.isdigit()): if(ongeki_version.isdigit()):
usr_sesh.ongeki_version=int(ongeki_version) usr_sesh.ongeki_version=int(ongeki_version)
return redirectTo(b"/game/ongeki/", request) return RedirectResponse("/game/ongeki/", 303)
else: else:
return b"Something went wrong" Response("Something went wrong", status_code=500)
else: else:
return b"User is not logged in" return RedirectResponse("/gate/", 303)

View File

@ -1,7 +1,7 @@
{% extends "core/frontend/index.jinja" %} {% extends "core/templates/index.jinja" %}
{% block content %} {% block content %}
{% if sesh is defined and sesh["userId"] > 0 %} {% if sesh is defined and sesh["user_id"] > 0 %}
<br> <br>
<br> <br>
<br> <br>
@ -75,7 +75,7 @@
</div> </div>
<script> <script>
{% include 'titles/ongeki/frontend/js/ongeki_scripts.js' %} {% include 'titles/ongeki/templates/js/ongeki_scripts.js' %}
</script> </script>
{% else %} {% else %}
<h2>Not Currently Logged In</h2> <h2>Not Currently Logged In</h2>

View File

@ -1,10 +1,12 @@
import yaml import yaml
import jinja2 import jinja2
from typing import List
from starlette.requests import Request from starlette.requests import Request
from starlette.responses import Response, RedirectResponse
from starlette.routing import Route
from os import path from os import path
from twisted.web.server import Session
from core.frontend import FE_Base, IUserSession from core.frontend import FE_Base, UserSession
from core.config import CoreConfig from core.config import CoreConfig
from .database import PokkenData from .database import PokkenData
from .config import PokkenConfig from .config import PokkenConfig
@ -12,6 +14,8 @@ from .const import PokkenConstants
class PokkenFrontend(FE_Base): class PokkenFrontend(FE_Base):
SN_PREFIX = PokkenConstants.SERIAL_IDENT
NETID_PREFIX = PokkenConstants.NETID_PREFIX
def __init__( def __init__(
self, cfg: CoreConfig, environment: jinja2.Environment, cfg_dir: str self, cfg: CoreConfig, environment: jinja2.Environment, cfg_dir: str
) -> None: ) -> None:
@ -24,16 +28,74 @@ class PokkenFrontend(FE_Base):
) )
self.nav_name = "Pokken" self.nav_name = "Pokken"
def render_GET(self, request: Request) -> bytes: def get_routes(self) -> List[Route]:
return [
Route("/", self.render_GET, methods=['GET']),
Route("/update.name", self.change_name, methods=['POST']),
]
async def render_GET(self, request: Request) -> Response:
template = self.environment.get_template( template = self.environment.get_template(
"titles/pokken/frontend/pokken_index.jinja" "titles/pokken/templates/pokken_index.jinja"
) )
pf = None
sesh: Session = request.getSession() usr_sesh = self.validate_session(request)
usr_sesh = IUserSession(sesh) if not usr_sesh:
usr_sesh = UserSession()
return template.render( else:
profile = await self.data.profile.get_profile(usr_sesh.user_id)
if profile is not None and profile['trainer_name']:
pf = profile._asdict()
if "e" in request.query_params:
try:
err = int(request.query_params.get("e", 0))
except Exception:
err = 0
else:
err = 0
if "s" in request.query_params:
try:
succ = int(request.query_params.get("s", 0))
except Exception:
succ = 0
else:
succ = 0
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),
).encode("utf-16") profile=pf,
error=err,
success=succ
))
async def change_name(self, request: Request) -> RedirectResponse:
usr_sesh = self.validate_session(request)
if not usr_sesh:
return RedirectResponse("/game/pokken/?e=9", 303)
frm = await request.form()
new_name = frm.get("new_name")
gender = frm.get("new_gender", 1)
if len(new_name) > 14:
return RedirectResponse("/game/pokken/?e=8", 303)
if not gender.isdigit():
return RedirectResponse("/game/pokken/?e=4", 303)
gender = int(gender)
if gender != 1 and gender != 2:
return RedirectResponse("/game/pokken/?e=4", 303) # no code for this yet, whatever
await self.data.profile.set_profile_name(usr_sesh.user_id, new_name, gender)
return RedirectResponse("/game/pokken/?s=1", 303)

View File

@ -1,4 +0,0 @@
{% extends "core/frontend/index.jinja" %}
{% block content %}
<h1>Pokken</h1>
{% endblock content %}

View File

@ -0,0 +1,48 @@
{% extends "core/templates/index.jinja" %}
{% block content %}
<h1>Pokken</h1>
{% if profile is defined and profile is not none and profile.id > 0 %}
<script type="text/javascript">
function toggle_new_name_form() {
let frm = document.getElementById("new_name_form");
let btn = document.getElementById("btn_toggle_form");
if (frm.style['display'] != "") {
frm.style['display'] = "";
frm.style['max-height'] = "";
btn.innerText = "Cancel";
} else {
frm.style['display'] = "none";
frm.style['max-height'] = "0px";
btn.innerText = "Edit";
}
}
</script>
<h3>Profile for {{ profile.trainer_name }}&nbsp;<button onclick="toggle_new_name_form()" class="btn btn-secondary" id="btn_toggle_form">Edit</button></h3>
{% if error is defined %}
{% include "core/templates/widgets/err_banner.jinja" %}
{% endif %}
{% if success is defined and success == 1 %}
<div style="background-color: #00AA00; padding: 20px; margin-bottom: 10px; width: 15%;">
Update successful
</div>
{% endif %}
<form style="max-width: 33%; display: none; max-height: 0px;" action="/game/pokken/update.name" method="post" id="new_name_form">
<div class="mb-3">
<label for="new_name" class="form-label">New Trainer Name</label>
<input type="text" class="form-control" id="new_name" name="new_name" aria-describedby="new_name_help" maxlength="14">
<div id="new_name_help" class="form-text">Must be 14 characters or less</div>
<br>
<input type="radio" id="new_gender_male" name="new_gender" value="1" {% if profile.avatar_gender is none or profile.avatar_gender == 1 %}checked{% endif %}>
<label for="new_gender_male">Male</label>
<input type="radio" id="new_gender_female" name="new_gender" value="2" {% if profile.avatar_gender == 2 %}checked{% endif %}>
<label for="new_gender_male">Female</label>
</div>
<button type="submit" class="btn btn-primary">Submit</button>
</form>
{% elif sesh is defined and sesh is not none and sesh.user_id > 0 %}
No profile information found for this account.
{% else %}
Login to view profile information.
{% endif %}
{% endblock content %}

View File

@ -1,10 +1,12 @@
from typing import List
from starlette.routing import Route
from starlette.requests import Request
from starlette.responses import Response
from os import path
import yaml import yaml
import jinja2 import jinja2
from starlette.requests import Request
from os import path
from twisted.web.server import Session
from core.frontend import FE_Base, IUserSession from core.frontend import FE_Base, UserSession
from core.config import CoreConfig from core.config import CoreConfig
from titles.wacca.database import WaccaData from titles.wacca.database import WaccaData
from titles.wacca.config import WaccaConfig from titles.wacca.config import WaccaConfig
@ -24,15 +26,21 @@ class WaccaFrontend(FE_Base):
) )
self.nav_name = "Wacca" self.nav_name = "Wacca"
def get_routes(self) -> List[Route]:
return [
Route("/", self.render_GET, methods=['GET'])
]
async def render_GET(self, request: Request) -> bytes: async def render_GET(self, request: Request) -> bytes:
template = self.environment.get_template( template = self.environment.get_template(
"titles/wacca/frontend/wacca_index.jinja" "titles/wacca/templates/wacca_index.jinja"
) )
sesh: Session = request.getSession() usr_sesh = self.validate_session(request)
usr_sesh = IUserSession(sesh) if not usr_sesh:
usr_sesh = UserSession()
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)
).encode("utf-16") ))

View File

@ -1,4 +0,0 @@
{% extends "core/frontend/index.jinja" %}
{% block content %}
<h1>Wacca</h1>
{% endblock content %}

View File

@ -0,0 +1,4 @@
{% extends "core/templates/index.jinja" %}
{% block content %}
<h1>Wacca</h1>
{% endblock content %}