From cb029b36e335df8a1cdfb742b4ec7d11ed0cd748 Mon Sep 17 00:00:00 2001 From: Luna Date: Mon, 4 Mar 2019 00:37:56 -0300 Subject: [PATCH] add admin api (w/ voice endpoints) - add docs/admin_api.md - add litecord.voice_schemas - auth: add admin_check - voice.manager: add VoiceManager.voice_server_list --- docs/admin_api.md | 45 ++++++++++++ litecord/admin_schemas.py | 31 +++++++++ litecord/auth.py | 18 +++++ litecord/blueprints/admin_api/__init__.py | 22 ++++++ litecord/blueprints/admin_api/voice.py | 83 +++++++++++++++++++++++ litecord/voice/manager.py | 10 +++ run.py | 10 ++- 7 files changed, 216 insertions(+), 3 deletions(-) create mode 100644 docs/admin_api.md create mode 100644 litecord/admin_schemas.py create mode 100644 litecord/blueprints/admin_api/__init__.py create mode 100644 litecord/blueprints/admin_api/voice.py diff --git a/docs/admin_api.md b/docs/admin_api.md new file mode 100644 index 0000000..3c4649c --- /dev/null +++ b/docs/admin_api.md @@ -0,0 +1,45 @@ +# Litecord Admin API + +the base path is `/api/v6/admin`. + +## GET `/voice/regions/` + +Return a list of voice server objects for the region. + +Returns empty list if the region does not exist. + +| field | type | description | +| --: | :-- | :-- | +| hostname | string | the hostname of the voice server | +| last\_health | float | the health of the voice server | + +## PUT `/voice/regions` + +Create a voice region. + +Receives JSON body as input, returns a list of voice region objects as output. + +| field | type | description | +| --: | :-- | :-- | +| id | string | id of the voice region, "brazil", "us-east", "eu-west", etc | +| name | string | name of the voice region | +| vip | Optional[bool] | if voice region is vip-only, default false | +| deprecated | Optional[bool] | if voice region is deprecated, default false | +| custom | Optional[bool] | if voice region is custom-only, default false | + +## PUT `/voice/regions//server` + +Create a voice server for a region. + +Returns empty body with 204 status code on success. + +| field | type | description | +| --: | :-- | :-- | +| hostname | string | the hostname of the voice server | + +## PUT `/voice/regions//deprecate` + +Mark a voice region as deprecated. Disables any voice actions on guilds that are +using the voice region. + +Returns empty body with 204 status code on success. diff --git a/litecord/admin_schemas.py b/litecord/admin_schemas.py new file mode 100644 index 0000000..3896dc9 --- /dev/null +++ b/litecord/admin_schemas.py @@ -0,0 +1,31 @@ +""" + +Litecord +Copyright (C) 2018-2019 Luna Mendes + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, version 3 of the License. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . + +""" + +VOICE_SERVER = { + 'hostname': {'type': 'string', 'maxlength': 255, 'required': True} +} + +VOICE_REGION = { + 'id': {'type': 'string', 'maxlength': 255, 'required': True}, + 'name': {'type': 'string', 'maxlength': 255, 'required': True}, + + 'vip': {'type': 'boolean', 'default': False}, + 'deprecated': {'type': 'boolean', 'default': False}, + 'custom': {'type': 'boolean', 'default': False}, +} diff --git a/litecord/auth.py b/litecord/auth.py index 8294d7e..715865f 100644 --- a/litecord/auth.py +++ b/litecord/auth.py @@ -29,6 +29,7 @@ from quart import request, current_app as app from litecord.errors import Forbidden, Unauthorized, BadRequest from litecord.snowflake import get_snowflake +from litecord.enums import UserFlags log = Logger(__name__) @@ -100,6 +101,23 @@ async def token_check(): return user_id +async def admin_check(): + """Check if the user is an admin.""" + user_id = await token_check() + + flags = await app.db.fetchval(""" + SELECT flags + FROM users + WHERE id = $1 + """, user_id) + + flags = UserFlags.from_int(flags) + if not flags.is_staff: + raise Unauthorized('you are not staff') + + return user_id + + async def hash_data(data: str, loop=None) -> str: """Hash information with bcrypt.""" loop = loop or app.loop diff --git a/litecord/blueprints/admin_api/__init__.py b/litecord/blueprints/admin_api/__init__.py new file mode 100644 index 0000000..d209cf9 --- /dev/null +++ b/litecord/blueprints/admin_api/__init__.py @@ -0,0 +1,22 @@ +""" + +Litecord +Copyright (C) 2018-2019 Luna Mendes + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, version 3 of the License. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . + +""" + +from .voice import bp as voice + +__all__ = ['voice'] diff --git a/litecord/blueprints/admin_api/voice.py b/litecord/blueprints/admin_api/voice.py new file mode 100644 index 0000000..e602972 --- /dev/null +++ b/litecord/blueprints/admin_api/voice.py @@ -0,0 +1,83 @@ +""" + +Litecord +Copyright (C) 2018-2019 Luna Mendes + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, version 3 of the License. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . + +""" + +from quart import Blueprint, jsonify, current_app as app, request + +from litecord.auth import admin_check +from litecord.schemas import validate +from litecord.admin_schemas import VOICE_SERVER, VOICE_REGION + +bp = Blueprint('voice_admin', __name__) + + +@bp.route('/regions/', methods=['GET']) +async def get_region_servers(region): + """Return a list of all servers for a region.""" + _user_id = await admin_check() + servers = await app.voice.voice_server_list(region) + return jsonify(servers) + + +@bp.route('/regions', methods=['PUT']) +async def insert_new_region(): + """Create a voice region.""" + _user_id = await admin_check() + j = validate(await request.get_json(), VOICE_REGION) + + j['id'] = j['id'].lower() + + await app.db.execute(""" + INSERT INTO voice_regions (id, name, vip, deprecated, custom) + VALUES ($1, $2, $3, $4, $5) + """, j['id'], j['name'], j['vip'], j['deprecated'], j['custom']) + + return jsonify( + await app.storage.all_voice_regions() + ) + + +@bp.route('/regions//servers', methods=['PUT']) +async def put_region_server(region): + """Insert a voice server to a region""" + _user_id = await admin_check() + j = validate(await request.get_json(), VOICE_SERVER) + + await app.db.execute(""" + INSERT INTO voice_servers (hostname, region) + VALUES ($1, $2) + """, j['hostname'], region) + + return '', 204 + + +@bp.route('/regions//deprecate', methods=['PUT']) +async def deprecate_region(region): + """Deprecate a voice region.""" + _user_id = await admin_check() + + # TODO: write this + await app.voice.disable_region(region) + + await app.db.execute(""" + UPDATE voice_regions + SET deprecated = true + WHERE id = $1 + """, region) + + return '', 204 diff --git a/litecord/voice/manager.py b/litecord/voice/manager.py index 0d3c687..535b6b2 100644 --- a/litecord/voice/manager.py +++ b/litecord/voice/manager.py @@ -181,3 +181,13 @@ class VoiceManager: async def leave(self, guild_id: int, user_id: int): """Make a user leave a channel IN A GUILD.""" await self.del_state((guild_id, user_id)) + + async def voice_server_list(self, region: str): + """Get a list of voice server objects""" + rows = await self.app.db.fetch(""" + SELECT hostname, last_health + FROM voice_servers + WHERE region_id = $1 + """, region) + + return list(map(dict, rows)) diff --git a/run.py b/run.py index c467aaa..ed3cdb9 100644 --- a/run.py +++ b/run.py @@ -54,8 +54,10 @@ from litecord.blueprints.user import ( user_settings, user_billing, fake_store ) -from litecord.blueprints.user.billing_job import ( - payment_job +from litecord.blueprints.user.billing_job import payment_job + +from litecord.blueprints.admin_api import ( + voice as voice_admin ) from litecord.ratelimits.handler import ratelimit_handler @@ -137,7 +139,9 @@ def set_blueprints(app_): icons: -1, attachments: -1, nodeinfo: -1, - static: -1 + static: -1, + + voice_admin: '/admin/voice' } for bp, suffix in bps.items():