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_
This commit is contained in:
Luna 2019-03-11 01:36:52 -03:00
parent 073f47e0f5
commit 2c1c384409
5 changed files with 89 additions and 15 deletions

View File

@ -17,33 +17,75 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
""" """
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.auth import admin_check
from litecord.errors import BadRequest from litecord.errors import BadRequest
from litecord.schemas import validate, FEATURES
from litecord.enums import Feature
bp = Blueprint('features_admin', __name__) bp = Blueprint('features_admin', __name__)
FEATURES = [
''
]
@bp.route('/<int:guild_id>/<feature>', methods=['PUT']) async def _features(guild_id: int):
async def insert_feature(guild_id: int, feature: str): 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('/<int:guild_id>/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('/<int:guild_id>/features', methods=['PUT'])
async def insert_features(guild_id: int):
"""Insert a feature on a guild.""" """Insert a feature on a guild."""
await admin_check() await admin_check()
j = validate(await request.get_json(), FEATURES)
# TODO to_add = j['features']
if feature not in FEATURES: features = await app.storage.guild_features(guild_id)
raise BadRequest('invalid feature') 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('/<int:guild_id>/<feature>', methods=['DELETE']) @bp.route('/<int:guild_id>/<feature>', methods=['DELETE'])
async def remove_feature(guild_id: int, feature: str): async def remove_feature(guild_id: int):
"""Remove a feature from a guild""" """Remove a feature from a guild"""
await admin_check() await admin_check()
# TODO j = validate(await request.get_json(), FEATURES)
await app.db to_remove = j['features']
return '', 204 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)

View File

@ -197,12 +197,27 @@ class RelationshipType(EasyEnum):
class MessageNotifications(EasyEnum): class MessageNotifications(EasyEnum):
"""Message notifications"""
ALL = 0 ALL = 0
MENTIONS = 1 MENTIONS = 1
NOTHING = 2 NOTHING = 2
class PremiumType: class PremiumType:
"""Premium (Nitro) type."""
TIER_1 = 1 TIER_1 = 1
TIER_2 = 2 TIER_2 = 2
NONE = None 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'

View File

@ -655,3 +655,10 @@ GET_MENTIONS = {
'everyone': {'coerce': bool, 'default': True}, 'everyone': {'coerce': bool, 'default': True},
'guild_id': {'coerce': int, 'required': False} 'guild_id': {'coerce': int, 'required': False}
} }
FEATURES = {
'features': {
'type': 'list', 'required': True,
'schema': {'type': 'guild_feature'}
}
}

View File

@ -146,6 +146,13 @@ class Storage:
WHERE username = $1 AND discriminator = $2 WHERE username = $1 AND discriminator = $2
""", username, discriminator) """, 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]: async def get_guild(self, guild_id: int, user_id=None) -> Optional[Dict]:
"""Get gulid payload.""" """Get gulid payload."""
row = await self.db.fetchrow(""" row = await self.db.fetchrow("""

View File

@ -17,6 +17,8 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
""" """
from typing import Optional
# size units # size units
KILOBYTES = 1024 KILOBYTES = 1024
@ -45,5 +47,6 @@ class Color:
return self.value 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 return f'{dt.isoformat()}+00:00' if dt else None