import random from typing import Dict, Tuple, Any, Optional from flask import Blueprint, request, Response, render_template, url_for from bemani.backend.base import Base from bemani.common import ( CardCipher, CardCipherException, GameConstants, RegionConstants, ValidatedDict, ) from bemani.data import Arcade, Machine, User, UserID, News, Event, Server, Client from bemani.data.api.client import APIClient, NotAuthorizedAPIException, APIException from bemani.frontend.app import ( adminrequired, jsonify, valid_email, valid_username, valid_pin, render_react, ) from bemani.frontend.gamesettings import get_game_settings from bemani.frontend.iidx.iidx import IIDXFrontend from bemani.frontend.jubeat.jubeat import JubeatFrontend from bemani.frontend.popn.popn import PopnMusicFrontend from bemani.frontend.templates import templates_location from bemani.frontend.static import static_location from bemani.frontend.types import g admin_pages = Blueprint( "admin_pages", __name__, url_prefix="/admin", template_folder=templates_location, static_folder=static_location, ) def format_arcade(arcade: Arcade) -> Dict[str, Any]: owners = [] for owner in arcade.owners: user = g.data.local.user.get_user(owner) if user is not None: owners.append(user.username) return { "id": arcade.id, "name": arcade.name, "description": arcade.description, "region": arcade.region, "area": arcade.area or "", "paseli_enabled": arcade.data.get_bool("paseli_enabled"), "paseli_infinite": arcade.data.get_bool("paseli_infinite"), "mask_services_url": arcade.data.get_bool("mask_services_url"), "owners": owners, } def format_machine(machine: Machine) -> Dict[str, Any]: return { "id": machine.id, "pcbid": machine.pcbid, "name": machine.name, "description": machine.description, "arcade": machine.arcade, "port": machine.port, "game": machine.game.value if machine.game else "any", "version": machine.version, } def format_card(card: Tuple[str, Optional[UserID]]) -> Dict[str, Any]: owner = None if card[1] is not None: user = g.data.local.user.get_user(card[1]) if user is not None: owner = user.username try: return { "number": CardCipher.encode(card[0]), "owner": owner, "id": card[1], } except CardCipherException: return { "number": "????????????????", "owner": owner, "id": card[1], } def format_user(user: User) -> Dict[str, Any]: return { "id": user.id, "username": user.username, "email": user.email, "admin": user.admin, } def format_news(news: News) -> Dict[str, Any]: return { "id": news.id, "timestamp": news.timestamp, "title": news.title, "body": news.body, } def format_event(event: Event) -> Dict[str, Any]: return { "id": event.id, "timestamp": event.timestamp, "userid": event.userid, "arcadeid": event.arcadeid, "type": event.type, "data": event.data, } def format_client(client: Client) -> Dict[str, Any]: return { "id": client.id, "name": client.name, "token": client.token, } def format_server(server: Server) -> Dict[str, Any]: return { "id": server.id, "uri": server.uri, "token": server.token, "allow_stats": server.allow_stats, "allow_scores": server.allow_scores, } @admin_pages.route("/") @adminrequired def viewsettings() -> Response: return Response( render_template( "admin/settings.html", **{ "title": "Network Settings", "config": g.config, "region": RegionConstants.LUT, }, ) ) @admin_pages.route("/events") @adminrequired def viewevents() -> Response: iidx = IIDXFrontend(g.data, g.config, g.cache) jubeat = JubeatFrontend(g.data, g.config, g.cache) pnm = PopnMusicFrontend(g.data, g.config, g.cache) return render_react( "Events", "admin/events.react.js", { "events": [ format_event(event) for event in g.data.local.network.get_events(limit=100) ], "users": { user.id: user.username for user in g.data.local.user.get_all_users() }, "arcades": { arcade.id: arcade.name for arcade in g.data.local.machine.get_all_arcades() }, "iidxsongs": iidx.get_all_songs(), "jubeatsongs": jubeat.get_all_songs(), "pnmsongs": pnm.get_all_songs(), "iidxversions": { version: name for (game, version, name) in iidx.all_games() }, "jubeatversions": { version: name for (game, version, name) in jubeat.all_games() }, "pnmversions": {version: name for (game, version, name) in pnm.all_games()}, }, { "refresh": url_for("admin_pages.listevents", since=-1), "backfill": url_for("admin_pages.backfillevents", until=-1), "viewuser": url_for("admin_pages.viewuser", userid=-1), "jubeatsong": url_for("jubeat_pages.viewtopscores", musicid=-1) if GameConstants.JUBEAT in g.config.support else None, "iidxsong": url_for("iidx_pages.viewtopscores", musicid=-1) if GameConstants.IIDX in g.config.support else None, "pnmsong": url_for("popn_pages.viewtopscores", musicid=-1) if GameConstants.POPN_MUSIC in g.config.support else None, }, ) @admin_pages.route("/events/backfill/") @jsonify @adminrequired def backfillevents(until: int) -> Dict[str, Any]: return { "events": [ format_event(event) for event in g.data.local.network.get_events(until_id=until, limit=1000) ], } @admin_pages.route("/events/list/") @jsonify @adminrequired def listevents(since: int) -> Dict[str, Any]: return { "events": [ format_event(event) for event in g.data.local.network.get_events(since_id=since) ], "users": {user.id: user.username for user in g.data.local.user.get_all_users()}, "arcades": { arcade.id: arcade.name for arcade in g.data.local.machine.get_all_arcades() }, } @admin_pages.route("/api") @adminrequired def viewapi() -> Response: return render_react( "Data API", "admin/api.react.js", { "clients": [ format_client(client) for client in g.data.local.api.get_all_clients() ], "servers": [ format_server(server) for server in g.data.local.api.get_all_servers() ], }, { "addclient": url_for("admin_pages.addclient"), "updateclient": url_for("admin_pages.updateclient"), "removeclient": url_for("admin_pages.removeclient"), "addserver": url_for("admin_pages.addserver"), "updateserver": url_for("admin_pages.updateserver"), "removeserver": url_for("admin_pages.removeserver"), "queryserver": url_for("admin_pages.queryserver", serverid=-1), }, ) @admin_pages.route("/arcades") @adminrequired def viewarcades() -> Response: return render_react( "Arcades", "admin/arcades.react.js", { "arcades": [ format_arcade(arcade) for arcade in g.data.local.machine.get_all_arcades() ], "regions": RegionConstants.LUT, "usernames": g.data.local.user.get_all_usernames(), "paseli_enabled": g.config.paseli.enabled, "paseli_infinite": g.config.paseli.infinite, "default_region": g.config.server.region, "default_area": g.config.server.area, "mask_services_url": False, }, { "addarcade": url_for("admin_pages.addarcade"), "updatearcade": url_for("admin_pages.updatearcade"), "removearcade": url_for("admin_pages.removearcade"), }, ) @admin_pages.route("/pcbids") @adminrequired def viewmachines() -> Response: games: Dict[str, Dict[int, str]] = {} for (game, version, name) in Base.all_games(): if game.value not in games: games[game.value] = {} games[game.value][version] = name return render_react( "Machines", "admin/machines.react.js", { "machines": [ format_machine(machine) for machine in g.data.local.machine.get_all_machines() ], "arcades": { arcade.id: arcade.name for arcade in g.data.local.machine.get_all_arcades() }, "series": { GameConstants.BISHI_BASHI.value: "BishiBashi", GameConstants.DDR.value: "DDR", GameConstants.IIDX.value: "IIDX", GameConstants.JUBEAT.value: "Jubeat", GameConstants.MGA.value: "Metal Gear Arcade", GameConstants.MUSECA.value: "MÚSECA", GameConstants.POPN_MUSIC.value: "Pop'n Music", GameConstants.REFLEC_BEAT.value: "Reflec Beat", GameConstants.SDVX.value: "SDVX", }, "games": games, "enforcing": g.config.server.enforce_pcbid, }, { "refresh": url_for("admin_pages.listmachines"), "generatepcbid": url_for("admin_pages.generatepcbid"), "addpcbid": url_for("admin_pages.addpcbid"), "updatepcbid": url_for("admin_pages.updatepcbid"), "removepcbid": url_for("admin_pages.removepcbid"), }, ) @admin_pages.route("/cards") @adminrequired def viewcards() -> Response: return render_react( "Cards", "admin/cards.react.js", { "cards": [format_card(card) for card in g.data.local.user.get_all_cards()], "usernames": g.data.local.user.get_all_usernames(), }, { "addcard": url_for("admin_pages.addcard"), "removecard": url_for("admin_pages.removecard"), "viewuser": url_for("admin_pages.viewuser", userid=-1), }, ) @admin_pages.route("/users") @adminrequired def viewusers() -> Response: return render_react( "Users", "admin/users.react.js", { "users": [format_user(user) for user in g.data.local.user.get_all_users()], }, { "searchusers": url_for("admin_pages.searchusers"), "viewuser": url_for("admin_pages.viewuser", userid=-1), }, ) @admin_pages.route("/news") @adminrequired def viewnews() -> Response: return render_react( "News", "admin/news.react.js", { "news": [format_news(news) for news in g.data.local.network.get_all_news()], }, { "removenews": url_for("admin_pages.removenews"), "addnews": url_for("admin_pages.addnews"), "updatenews": url_for("admin_pages.updatenews"), }, ) @admin_pages.route("/gamesettings") @adminrequired def viewgamesettings() -> Response: return render_react( "Game Settings", "admin/gamesettings.react.js", { "game_settings": get_game_settings( g.data, g.data.local.machine.DEFAULT_SETTINGS_ARCADE ), }, { "update_settings": url_for("admin_pages.updatesettings"), }, ) @admin_pages.route("/users/") @adminrequired def viewuser(userid: int) -> Response: # Cast the userID. userid = UserID(userid) user = g.data.local.user.get_user(userid) def __format_card(card: str) -> str: try: return CardCipher.encode(card) except CardCipherException: return "????????????????" cards = [__format_card(card) for card in g.data.local.user.get_cards(userid)] arcades = g.data.local.machine.get_all_arcades() return render_react( "User", "admin/user.react.js", { "user": { "email": user.email, "username": user.username, }, "cards": cards, "arcades": {arcade.id: arcade.name for arcade in arcades}, "balances": { arcade.id: g.data.local.user.get_balance(userid, arcade.id) for arcade in arcades }, "events": [ format_event(event) for event in g.data.local.network.get_events( userid=userid, event="paseli_transaction" ) ], }, { "refresh": url_for("admin_pages.listuser", userid=userid), "removeusercard": url_for("admin_pages.removeusercard", userid=userid), "addusercard": url_for("admin_pages.addusercard", userid=userid), "updatebalance": url_for("admin_pages.updatebalance", userid=userid), "updateusername": url_for("admin_pages.updateusername", userid=userid), "updateemail": url_for("admin_pages.updateemail", userid=userid), "updatepin": url_for("admin_pages.updatepin", userid=userid), "updatepassword": url_for("admin_pages.updatepassword", userid=userid), }, ) @admin_pages.route("/users//list") @jsonify @adminrequired def listuser(userid: int) -> Dict[str, Any]: # Cast the userID. userid = UserID(userid) def __format_card(card: str) -> str: try: return CardCipher.encode(card) except CardCipherException: return "????????????????" cards = [__format_card(card) for card in g.data.local.user.get_cards(userid)] arcades = g.data.local.machine.get_all_arcades() return { "cards": cards, "arcades": {arcade.id: arcade.name for arcade in arcades}, "balances": { arcade.id: g.data.local.user.get_balance(userid, arcade.id) for arcade in arcades }, "events": [ format_event(event) for event in g.data.local.network.get_events( userid=userid, event="paseli_transaction" ) ], } @admin_pages.route("/arcades/list") @jsonify @adminrequired def listmachines() -> Dict[str, Any]: return { "machines": [ format_machine(machine) for machine in g.data.local.machine.get_all_machines() ], "arcades": { arcade.id: arcade.name for arcade in g.data.local.machine.get_all_arcades() }, } @admin_pages.route("/arcades/update", methods=["POST"]) @jsonify @adminrequired def updatearcade() -> Dict[str, Any]: # Attempt to look this arcade up new_values = request.get_json()["arcade"] arcade = g.data.local.machine.get_arcade(new_values["id"]) if arcade is None: raise Exception("Unable to find arcade to update!") arcade.name = new_values["name"] arcade.description = new_values["description"] arcade.region = new_values["region"] arcade.area = new_values["area"] or None arcade.data.replace_bool("paseli_enabled", new_values["paseli_enabled"]) arcade.data.replace_bool("paseli_infinite", new_values["paseli_infinite"]) arcade.data.replace_bool("mask_services_url", new_values["mask_services_url"]) owners = [] for owner in new_values["owners"]: ownerid = g.data.local.user.from_username(owner) if ownerid is not None: owners.append(ownerid) owners = list(set(owners)) arcade.owners = owners g.data.local.machine.put_arcade(arcade) # Just return all arcades for ease of updating return { "arcades": [ format_arcade(arcade) for arcade in g.data.local.machine.get_all_arcades() ], } @admin_pages.route("/arcades/add", methods=["POST"]) @jsonify @adminrequired def addarcade() -> Dict[str, Any]: # Attempt to look this arcade up new_values = request.get_json()["arcade"] if len(new_values["name"]) == 0: raise Exception("Please name your new arcade!") if len(new_values["description"]) == 0: raise Exception("Please describe your new arcade!") owners = [] for owner in new_values["owners"]: ownerid = g.data.local.user.from_username(owner) if ownerid is not None: owners.append(ownerid) owners = list(set(owners)) g.data.local.machine.create_arcade( new_values["name"], new_values["description"], new_values["region"], new_values["area"] or None, { "paseli_enabled": new_values["paseli_enabled"], "paseli_infinite": new_values["paseli_infinite"], "mask_services_url": new_values["mask_services_url"], }, owners, ) # Just return all arcades for ease of updating return { "arcades": [ format_arcade(arcade) for arcade in g.data.local.machine.get_all_arcades() ], } @admin_pages.route("/arcades/remove", methods=["POST"]) @jsonify @adminrequired def removearcade() -> Dict[str, Any]: # Attempt to look this arcade up arcadeid = request.get_json()["arcadeid"] arcade = g.data.local.machine.get_arcade(arcadeid) if arcade is None: raise Exception("Unable to find arcade to delete!") g.data.local.machine.destroy_arcade(arcadeid) # Just return all arcades for ease of updating return { "arcades": [ format_arcade(arcade) for arcade in g.data.local.machine.get_all_arcades() ], } @admin_pages.route("/clients/update", methods=["POST"]) @jsonify @adminrequired def updateclient() -> Dict[str, Any]: # Attempt to look this client up new_values = request.get_json()["client"] client = g.data.local.api.get_client(new_values["id"]) if client is None: raise Exception("Unable to find client to update!") if len(new_values["name"]) == 0: raise Exception("Client names must be at least one character long!") client.name = new_values["name"] g.data.local.api.put_client(client) # Just return all clients for ease of updating return { "clients": [ format_client(client) for client in g.data.local.api.get_all_clients() ], } @admin_pages.route("/clients/add", methods=["POST"]) @jsonify @adminrequired def addclient() -> Dict[str, Any]: # Attempt to look this client up new_values = request.get_json()["client"] if len(new_values["name"]) == 0: raise Exception("Please name your new client!") g.data.local.api.create_client( new_values["name"], ) # Just return all clientss for ease of updating return { "clients": [ format_client(client) for client in g.data.local.api.get_all_clients() ], } @admin_pages.route("/clients/remove", methods=["POST"]) @jsonify @adminrequired def removeclient() -> Dict[str, Any]: # Attempt to look this client up clientid = request.get_json()["clientid"] client = g.data.local.api.get_client(clientid) if client is None: raise Exception("Unable to find client to delete!") g.data.local.api.destroy_client(clientid) # Just return all clients for ease of updating return { "clients": [ format_client(client) for client in g.data.local.api.get_all_clients() ], } @admin_pages.route("/server//info") @jsonify @adminrequired def queryserver(serverid: int) -> Dict[str, Any]: # Attempt to look this server up server = g.data.local.api.get_server(serverid) if server is None: raise Exception("Unable to find server to query!") client = APIClient(server.uri, server.token, False, False) try: serverinfo = client.get_server_info() info = { "name": serverinfo["name"], "email": serverinfo["email"], } info["status"] = ( "ok" if APIClient.API_VERSION in serverinfo["versions"] else "badversion" ) except NotAuthorizedAPIException: info = { "name": "unknown", "email": "unknown", "status": "badauth", } except APIException: info = { "name": "unknown", "email": "unknown", "status": "error", } return info @admin_pages.route("/servers/update", methods=["POST"]) @jsonify @adminrequired def updateserver() -> Dict[str, Any]: # Attempt to look this server up new_values = request.get_json()["server"] server = g.data.local.api.get_server(new_values["id"]) if server is None: raise Exception("Unable to find server to update!") if len(new_values["uri"]) == 0: raise Exception("Please provide a valid connection URI for this server!") if len(new_values["token"]) == 0 or len(new_values["token"]) > 64: raise Exception("Please provide a valid connection token for this server!") server.uri = new_values["uri"] server.token = new_values["token"] server.allow_stats = new_values["allow_stats"] server.allow_scores = new_values["allow_scores"] g.data.local.api.put_server(server) # Just return all servers for ease of updating return { "servers": [ format_server(server) for server in g.data.local.api.get_all_servers() ], } @admin_pages.route("/servers/add", methods=["POST"]) @jsonify @adminrequired def addserver() -> Dict[str, Any]: # Attempt to look this server up new_values = request.get_json()["server"] if len(new_values["uri"]) == 0: raise Exception("Please provide a connection URI for the new server!") if len(new_values["token"]) == 0 or len(new_values["token"]) > 64: raise Exception("Please provide a valid connection token for the new server!") g.data.local.api.create_server( new_values["uri"], new_values["token"], ) # Just return all serverss for ease of updating return { "servers": [ format_server(server) for server in g.data.local.api.get_all_servers() ], } @admin_pages.route("/servers/remove", methods=["POST"]) @jsonify @adminrequired def removeserver() -> Dict[str, Any]: # Attempt to look this server up serverid = request.get_json()["serverid"] server = g.data.local.api.get_server(serverid) if server is None: raise Exception("Unable to find server to delete!") g.data.local.api.destroy_server(serverid) # Just return all servers for ease of updating return { "servers": [ format_server(server) for server in g.data.local.api.get_all_servers() ], } @admin_pages.route("/pcbids/generate", methods=["POST"]) @jsonify @adminrequired def generatepcbid() -> Dict[str, Any]: # Attempt to look this arcade up new_pcbid = request.get_json()["machine"] if new_pcbid["arcade"] is not None: arcade = g.data.local.machine.get_arcade(new_pcbid["arcade"]) if arcade is None: raise Exception("Unable to find arcade to link PCBID to!") # Will be set by the game on boot. name: str = "なし" pcbid: Optional[str] = None while pcbid is None: # Generate a new PCBID, check for uniqueness potential_pcbid = "01201000000000" + "".join( [random.choice("0123456789ABCDEF") for _ in range(6)] ) if g.data.local.machine.get_machine(potential_pcbid) is None: pcbid = potential_pcbid g.data.local.machine.create_machine( pcbid, name, new_pcbid["description"], new_pcbid["arcade"] ) # Just return all machines for ease of updating return { "machines": [ format_machine(machine) for machine in g.data.local.machine.get_all_machines() ], } @admin_pages.route("/pcbids/add", methods=["POST"]) @jsonify @adminrequired def addpcbid() -> Dict[str, Any]: # Attempt to look this arcade up new_pcbid = request.get_json()["machine"] if new_pcbid["arcade"] is not None: arcade = g.data.local.machine.get_arcade(new_pcbid["arcade"]) if arcade is None: raise Exception("Unable to find arcade to link PCBID to!") # Verify that the PCBID is valid potential_pcbid = "".join( [c for c in new_pcbid["pcbid"].upper() if c in "0123456789ABCDEF"] ) if len(potential_pcbid) != len(new_pcbid["pcbid"]): raise Exception("Invalid characters in PCBID!") if len(potential_pcbid) != 20: raise Exception("PCBID has invalid length!") if g.data.local.machine.get_machine(potential_pcbid) is not None: raise Exception("PCBID already exists!") # Will be set by the game on boot. name = "なし" g.data.local.machine.create_machine( potential_pcbid, name, new_pcbid["description"], new_pcbid["arcade"] ) # Just return all machines for ease of updating return { "machines": [ format_machine(machine) for machine in g.data.local.machine.get_all_machines() ], } @admin_pages.route("/pcbids/update", methods=["POST"]) @jsonify @adminrequired def updatepcbid() -> Dict[str, Any]: # Attempt to look this machine up machine = request.get_json()["machine"] if machine["arcade"] is not None: arcade = g.data.local.machine.get_arcade(machine["arcade"]) if arcade is None: raise Exception("Unable to find arcade to link PCBID to!") # Make sure we don't duplicate port assignments other_pcbid = g.data.local.machine.from_port(machine["port"]) if other_pcbid is not None and other_pcbid != machine["pcbid"]: raise Exception(f"The specified port is already in use by '{other_pcbid}'!") if machine["port"] < 1 or machine["port"] > 65535: raise Exception("The specified port is out of range!") current_machine = g.data.local.machine.get_machine(machine["pcbid"]) current_machine.description = machine["description"] current_machine.arcade = machine["arcade"] current_machine.port = machine["port"] current_machine.game = ( None if machine["game"] == "any" else GameConstants(machine["game"]) ) current_machine.version = None if machine["game"] == "any" else machine["version"] g.data.local.machine.put_machine(current_machine) # Just return all machines for ease of updating return { "machines": [ format_machine(machine) for machine in g.data.local.machine.get_all_machines() ], } @admin_pages.route("/pcbids/remove", methods=["POST"]) @jsonify @adminrequired def removepcbid() -> Dict[str, Any]: # Attempt to look this machine up pcbid = request.get_json()["pcbid"] if g.data.local.machine.get_machine(pcbid) is None: raise Exception("Unable to find PCBID to delete!") g.data.local.machine.destroy_machine(pcbid) # Just return all machines for ease of updating return { "machines": [ format_machine(machine) for machine in g.data.local.machine.get_all_machines() ], } @admin_pages.route("/cards/remove", methods=["POST"]) @jsonify @adminrequired def removecard() -> Dict[str, Any]: # Grab card, convert it card = request.get_json()["card"] try: cardid = CardCipher.decode(card) except CardCipherException: raise Exception("Invalid card number!") # Make sure it is our card userid = g.data.local.user.from_cardid(cardid) # Remove it from the user's account g.data.local.user.destroy_card(userid, cardid) # Return new card list return { "cards": [format_card(card) for card in g.data.local.user.get_all_cards()], } @admin_pages.route("/cards/add", methods=["POST"]) @jsonify @adminrequired def addcard() -> Dict[str, Any]: # Grab card, convert it card = request.get_json()["card"] try: cardid = CardCipher.decode(card["number"]) except CardCipherException: raise Exception("Invalid card number!") # Make sure it is our card userid = g.data.local.user.from_username(card["owner"]) if userid is None: raise Exception("Cannot find user to add card to!") # See if it is already claimed curuserid = g.data.local.user.from_cardid(cardid) if curuserid is not None: raise Exception("This card is already in use!") # Add it to the user's account g.data.local.user.add_card(userid, cardid) # Return new card list return { "cards": [format_card(card) for card in g.data.local.user.get_all_cards()], } @admin_pages.route("/users/search", methods=["POST"]) @jsonify @adminrequired def searchusers() -> Dict[str, Any]: # Grab card, convert it searchdetails = request.get_json()["user_search"] if len(searchdetails["card"]) > 0: try: cardid = CardCipher.decode(searchdetails["card"]) actual_userid = g.data.local.user.from_cardid(cardid) if actual_userid is None: # Force a non-match below actual_userid = UserID(-1) except CardCipherException: actual_userid = UserID(-1) else: actual_userid = None def match(user: User) -> bool: if actual_userid is not None: return user.id == actual_userid else: return True return { "users": [ format_user(user) for user in g.data.local.user.get_all_users() if match(user) ], } @admin_pages.route("/users//balance/update", methods=["POST"]) @jsonify @adminrequired def updatebalance(userid: int) -> Dict[str, Any]: # Cast the userID. userid = UserID(userid) credits = request.get_json()["credits"] user = g.data.local.user.get_user(userid) arcades = g.data.local.machine.get_all_arcades() # Make sure the user ID is valid if user is None: raise Exception("Cannot find user to update!") # Update balances for arcadeid in credits: balance = g.data.local.user.update_balance(userid, arcadeid, credits[arcadeid]) if balance is not None: g.data.local.network.put_event( "paseli_transaction", { "delta": credits[arcadeid], "balance": balance, "reason": "admin adjustment", }, userid=userid, arcadeid=arcadeid, ) return { "arcades": {arcade.id: arcade.name for arcade in arcades}, "balances": { arcade.id: g.data.local.user.get_balance(userid, arcade.id) for arcade in arcades }, "events": [ format_event(event) for event in g.data.local.network.get_events( userid=userid, event="paseli_transaction" ) ], } @admin_pages.route("/users//username/update", methods=["POST"]) @jsonify @adminrequired def updateusername(userid: int) -> Dict[str, Any]: # Cast the userID. userid = UserID(userid) username = request.get_json()["username"] user = g.data.local.user.get_user(userid) # Make sure the user ID is valid if user is None: raise Exception("Cannot find user to update!") if not valid_username(username): raise Exception("Invalid username!") # Make sure this user ID isn't taken potential_userid = g.data.local.user.from_username(username) if potential_userid is not None and potential_userid != userid: raise Exception("That username is already taken!") # Update the user user.username = username g.data.local.user.put_user(user) return { "username": username, } @admin_pages.route("/users//email/update", methods=["POST"]) @jsonify @adminrequired def updateemail(userid: int) -> Dict[str, Any]: # Cast the userID. userid = UserID(userid) email = request.get_json()["email"] user = g.data.local.user.get_user(userid) # Make sure the user ID is valid if user is None: raise Exception("Cannot find user to update!") if not valid_email(email): raise Exception("Invalid email!") # Update the user user.email = email g.data.local.user.put_user(user) return { "email": email, } @admin_pages.route("/users//pin/update", methods=["POST"]) @jsonify @adminrequired def updatepin(userid: int) -> Dict[str, Any]: # Cast the userID. userid = UserID(userid) pin = request.get_json()["pin"] user = g.data.local.user.get_user(userid) # Make sure the user ID is valid if user is None: raise Exception("Cannot find user to update!") if not valid_pin(pin, "card"): raise Exception("Invalid pin, must be exactly 4 digits!") # Update the user g.data.local.user.update_pin(userid, pin) return {} @admin_pages.route("/users//password/update", methods=["POST"]) @jsonify @adminrequired def updatepassword(userid: int) -> Dict[str, Any]: # Cast the userID. userid = UserID(userid) new1 = request.get_json()["new1"] new2 = request.get_json()["new2"] user = g.data.local.user.get_user(userid) # Make sure the user ID is valid if user is None: raise Exception("Cannot find user to update!") # Now, make sure that the passwords match if new1 != new2: raise Exception("Passwords do not match each other!") # Now, make sure passwords are long enough if len(new1) < 6: raise Exception("Password is not long enough!") # Update the user g.data.local.user.update_password(userid, new1) return {} @admin_pages.route("/users//cards/remove", methods=["POST"]) @jsonify @adminrequired def removeusercard(userid: int) -> Dict[str, Any]: # Cast the userID. userid = UserID(userid) # Grab card, convert it card = request.get_json()["card"] try: cardid = CardCipher.decode(card) except CardCipherException: raise Exception("Invalid card number!") user = g.data.local.user.get_user(userid) # Make sure the user ID is valid if user is None: raise Exception("Cannot find user to update!") # Remove it from the user's account g.data.local.user.destroy_card(userid, cardid) # Return new card list return { "cards": [ CardCipher.encode(card) for card in g.data.local.user.get_cards(userid) ], } @admin_pages.route("/users//cards/add", methods=["POST"]) @jsonify @adminrequired def addusercard(userid: int) -> Dict[str, Any]: # Cast the userID. userid = UserID(userid) # Grab card, convert it card = request.get_json()["card"] try: cardid = CardCipher.decode(card) except CardCipherException: raise Exception("Invalid card number!") user = g.data.local.user.get_user(userid) # Make sure the user ID is valid if user is None: raise Exception("Cannot find user to update!") # See if it is already claimed curuserid = g.data.local.user.from_cardid(cardid) if curuserid is not None: raise Exception("This card is already in use!") # Add it to the user's account g.data.local.user.add_card(userid, cardid) # Return new card list return { "cards": [ CardCipher.encode(card) for card in g.data.local.user.get_cards(userid) ], } @admin_pages.route("/news/add", methods=["POST"]) @jsonify @adminrequired def addnews() -> Dict[str, Any]: news = request.get_json()["news"] if len(news["title"]) == 0: raise Exception("Please provide a title!") if len(news["body"]) == 0: raise Exception("Please provide a body!") g.data.local.network.create_news(news["title"], news["body"]) return { "news": [format_news(news) for news in g.data.local.network.get_all_news()], } @admin_pages.route("/news/remove", methods=["POST"]) @jsonify @adminrequired def removenews() -> Dict[str, Any]: newsid = request.get_json()["newsid"] if g.data.local.network.get_news(newsid) is None: raise Exception("Unable to find entry to delete!") g.data.local.network.destroy_news(newsid) return { "news": [format_news(news) for news in g.data.local.network.get_all_news()], } @admin_pages.route("/news/update", methods=["POST"]) @jsonify @adminrequired def updatenews() -> Dict[str, Any]: new_news = request.get_json()["news"] if g.data.local.network.get_news(new_news["id"]) is None: raise Exception("Unable to find entry to update!") if len(new_news["title"]) == 0: raise Exception("Please provide a title!") if len(new_news["body"]) == 0: raise Exception("Please provide a body!") news = g.data.local.network.get_news(new_news["id"]) news.title = new_news["title"] news.body = new_news["body"] g.data.local.network.put_news(news) return { "news": [format_news(news) for news in g.data.local.network.get_all_news()], } @admin_pages.route("/gamesettings/update", methods=["POST"]) @jsonify @adminrequired def updatesettings() -> Dict[str, Any]: # Cast the ID for type safety. arcadeid = g.data.local.machine.DEFAULT_SETTINGS_ARCADE game = GameConstants(request.get_json()["game"]) version = request.get_json()["version"] for setting_type, update_function in [ ("bools", "replace_bool"), ("ints", "replace_int"), ("strs", "replace_str"), ("longstrs", "replace_str"), ]: for game_setting in request.get_json()[setting_type]: # Grab the value to update category = game_setting["category"] setting = game_setting["setting"] new_value = game_setting["value"] # Update the value current_settings = g.data.local.machine.get_settings( arcadeid, game, version, category ) if current_settings is None: current_settings = ValidatedDict() getattr(current_settings, update_function)(setting, new_value) # Save it back g.data.local.machine.put_settings( arcadeid, game, version, category, current_settings ) # Return the updated value return { "game_settings": [ gs for gs in get_game_settings(g.data, arcadeid) if gs["game"] == game.value and gs["version"] == version ][0], }