1141 lines
37 KiB
Python
1141 lines
37 KiB
Python
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,
|
|
'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/<int:until>')
|
|
@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/<int:since>')
|
|
@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,
|
|
'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/<int:userid>')
|
|
@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/<int:userid>/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.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'],
|
|
{
|
|
'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/<int:serverid>/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/<int:userid>/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/<int:userid>/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/<int:userid>/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/<int:userid>/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/<int:userid>/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/<int:userid>/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/<int:userid>/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],
|
|
}
|