From 2c1c384409009134e757110899b6492f4abcfd13 Mon Sep 17 00:00:00 2001 From: Luna Date: Mon, 11 Mar 2019 01:36:52 -0300 Subject: [PATCH] admin_api.features: make features input as a list - add PATCH /:id/features - enums: add Feature enum - schemas: add FEATURES - storage: add Storage.guild_features - types: add return type to timestamp_ --- litecord/blueprints/admin_api/features.py | 70 ++++++++++++++++++----- litecord/enums.py | 15 +++++ litecord/schemas.py | 7 +++ litecord/storage.py | 7 +++ litecord/types.py | 5 +- 5 files changed, 89 insertions(+), 15 deletions(-) diff --git a/litecord/blueprints/admin_api/features.py b/litecord/blueprints/admin_api/features.py index ca14309..b786692 100644 --- a/litecord/blueprints/admin_api/features.py +++ b/litecord/blueprints/admin_api/features.py @@ -17,33 +17,75 @@ along with this program. If not, see . """ -from quart import Blueprint, current_app as app +from quart import Blueprint, current_app as app, jsonify, request from litecord.auth import admin_check from litecord.errors import BadRequest +from litecord.schemas import validate, FEATURES +from litecord.enums import Feature bp = Blueprint('features_admin', __name__) -FEATURES = [ - '' -] -@bp.route('//', methods=['PUT']) -async def insert_feature(guild_id: int, feature: str): +async def _features(guild_id: int): + return jsonify({ + 'features': await app.storage.guild_features(guild_id) + }) + + +async def _update_features(guild_id: int, features: list): + await app.db.execute(""" + UPDATE guilds + SET features = $1 + WHERE id = $2 + """, features, guild_id) + + +@bp.route('//features', methods=['PATCH']) +async def replace_features(guild_id: int): + """Replace the feature list in a guild""" + await admin_check() + j = validate(await request.get_json(), FEATURES) + features = j['features'] + + # yes, we need to pass it to a set and then to a list before + # doing anything, since the api client might just + # shove 200 repeated features to us. + await _update_features(guild_id, list(set(features))) + return await _features(guild_id) + + +@bp.route('//features', methods=['PUT']) +async def insert_features(guild_id: int): """Insert a feature on a guild.""" await admin_check() + j = validate(await request.get_json(), FEATURES) - # TODO - if feature not in FEATURES: - raise BadRequest('invalid feature') + to_add = j['features'] + features = await app.storage.guild_features(guild_id) + features = set(features) - return '', 204 + # i'm assuming set.add is mostly safe + for feature in to_add: + features.add(feature) + + await _update_features(guild_id, list(features)) + return await _features(guild_id) @bp.route('//', methods=['DELETE']) -async def remove_feature(guild_id: int, feature: str): +async def remove_feature(guild_id: int): """Remove a feature from a guild""" await admin_check() - # TODO - await app.db - return '', 204 + j = validate(await request.get_json(), FEATURES) + to_remove = j['features'] + features = await app.storage.guild_features(guild_id) + + for feature in to_remove: + try: + features.remove(feature) + except ValueError: + raise BadRequest('Trying to remove already removed feature.') + + await _update_features(guild_id, features) + return await _features(guild_id) diff --git a/litecord/enums.py b/litecord/enums.py index 4107fd5..0a7c075 100644 --- a/litecord/enums.py +++ b/litecord/enums.py @@ -197,12 +197,27 @@ class RelationshipType(EasyEnum): class MessageNotifications(EasyEnum): + """Message notifications""" ALL = 0 MENTIONS = 1 NOTHING = 2 class PremiumType: + """Premium (Nitro) type.""" TIER_1 = 1 TIER_2 = 2 NONE = None + + +class Feature(Enum): + """Guild features.""" + invite_splash = 'INVITE_SPLASH' + vip = 'VIP_REGIONS' + vanity = 'VANITY_URL' + emoji = 'MORE_EMOJI' + verified = 'VERIFIED' + + # unknown + commerce = 'COMMERCE' + news = 'NEWS' diff --git a/litecord/schemas.py b/litecord/schemas.py index 3683334..40499da 100644 --- a/litecord/schemas.py +++ b/litecord/schemas.py @@ -655,3 +655,10 @@ GET_MENTIONS = { 'everyone': {'coerce': bool, 'default': True}, 'guild_id': {'coerce': int, 'required': False} } + +FEATURES = { + 'features': { + 'type': 'list', 'required': True, + 'schema': {'type': 'guild_feature'} + } +} diff --git a/litecord/storage.py b/litecord/storage.py index 0c8f6d4..d3b2257 100644 --- a/litecord/storage.py +++ b/litecord/storage.py @@ -146,6 +146,13 @@ class Storage: WHERE username = $1 AND discriminator = $2 """, username, discriminator) + async def guild_features(self, guild_id: int) -> Optional[List[str]]: + """Get a list of guild features for the given guild.""" + return await self.db.fetchval(""" + SELECT features FROM guilds + WHERE id = $1 + """, guild_id) + async def get_guild(self, guild_id: int, user_id=None) -> Optional[Dict]: """Get gulid payload.""" row = await self.db.fetchrow(""" diff --git a/litecord/types.py b/litecord/types.py index 1a845f8..2c9c122 100644 --- a/litecord/types.py +++ b/litecord/types.py @@ -17,6 +17,8 @@ along with this program. If not, see . """ +from typing import Optional + # size units KILOBYTES = 1024 @@ -45,5 +47,6 @@ class Color: return self.value -def timestamp_(dt): +def timestamp_(dt) -> Optional[str]: + """safer version for dt.isoformat()""" return f'{dt.isoformat()}+00:00' if dt else None