From 7c274f0f70571f184964a905f532b492fbd1c509 Mon Sep 17 00:00:00 2001 From: Luna Mendes Date: Mon, 5 Nov 2018 04:12:16 -0300 Subject: [PATCH] channels: add PUT /api/v6/:chan_id/permissions/:overwrite_id This finishes the basics on channel overwrites. SQL for instances: ```sql DROP TABLE channel_overwrites; ``` Then run `schema.sql`. - channels: finish implementations for update_{text,voice}_channel - storage: fix _overwrite_convert - schema.sql: use unique constraint instead of primary key in channel_overwrites --- litecord/blueprints/channels.py | 97 ++++++++++++++++++++++++++++++++- litecord/schemas.py | 13 ++--- litecord/storage.py | 8 ++- schema.sql | 10 +++- 4 files changed, 111 insertions(+), 17 deletions(-) diff --git a/litecord/blueprints/channels.py b/litecord/blueprints/channels.py index 13bbe97..ed5e62e 100644 --- a/litecord/blueprints/channels.py +++ b/litecord/blueprints/channels.py @@ -7,7 +7,7 @@ from litecord.auth import token_check from litecord.enums import ChannelType, GUILD_CHANS from litecord.errors import ChannelNotFound from litecord.schemas import ( - validate, CHAN_UPDATE + validate, CHAN_UPDATE, CHAN_OVERWRITE ) from litecord.blueprints.checks import channel_check, channel_perm_check @@ -231,6 +231,63 @@ async def _mass_chan_update(guild_id, channel_ids: int): 'guild', guild_id, 'CHANNEL_UPDATE', chan) +async def _process_overwrites(channel_id: int, overwrites: list): + for overwrite in overwrites: + + # 0 for user overwrite, 1 for role overwrite + target_type = 0 if overwrite['type'] == 'user' else 1 + target_role = None if target_type == 0 else overwrite['id'] + target_user = overwrite['id'] if target_type == 0 else None + + await app.db.execute( + """ + INSERT INTO channel_overwrites + (channel_id, target_type, target_role, + target_user, allow, deny) + VALUES + ($1, $2, $3, $4, $5, $6) + ON CONFLICT ON CONSTRAINT channel_overwrites_uniq + DO + UPDATE + SET allow = $5, deny = $6 + WHERE channel_overwrites.channel_id = $1 + AND channel_overwrites.target_type = $2 + AND channel_overwrites.target_role = $3 + AND channel_overwrites.target_user = $4 + """, + channel_id, target_type, + target_role, target_user, + overwrite['allow'], overwrite['deny']) + + +@bp.route('//permissions/', methods=['PUT']) +async def put_channel_overwrite(channel_id: int, overwrite_id: int): + """Insert or modify a channel overwrite.""" + user_id = await token_check() + ctype, guild_id = await channel_check(user_id, channel_id) + + if ctype not in GUILD_CHANS: + raise ChannelNotFound('Only usable for guild channels.') + + await channel_perm_check(user_id, guild_id, 'manage_roles') + + j = validate( + # inserting a fake id on the payload so validation passes through + {**await request.get_json(), **{'id': -1}}, + CHAN_OVERWRITE + ) + + await _process_overwrites(channel_id, [{ + 'allow': j['allow'], + 'deny': j['deny'], + 'type': j['type'], + 'id': overwrite_id + }]) + + await _mass_chan_update(guild_id, [channel_id]) + return '', 204 + + async def _update_channel_common(channel_id, guild_id: int, j: dict): if 'name' in j: await app.db.execute(""" @@ -282,13 +339,47 @@ async def _update_channel_common(channel_id, guild_id: int, j: dict): # since theres now an empty slot, move current channel to it await _update_pos(channel_id, new_pos) + if 'channel_overwrites' in j: + overwrites = j['channel_overwrites'] + await _process_overwrites(channel_id, overwrites) + + +async def _common_guild_chan(channel_id, j: dict): + # common updates to the guild_channels table + for field in [field for field in j.keys() + if field in ('nsfw', 'parent_id')]: + await app.db.execute(f""" + UPDATE guild_channels + SET {field} = $1 + WHERE id = $2 + """, j[field], channel_id) + async def _update_text_channel(channel_id: int, j: dict): - pass + # first do the specific ones related to guild_text_channels + for field in [field for field in j.keys() + if field in ('topic', 'rate_limit_per_user')]: + await app.db.execute(f""" + UPDATE guild_text_channels + SET {field} = $1 + WHERE id = $2 + """, j[field], channel_id) + + await _common_guild_chan(channel_id, j) async def _update_voice_channel(channel_id: int, j: dict): - pass + # first do the specific ones in guild_voice_channels + for field in [field for field in j.keys() + if field in ('bitrate', 'user_limit')]: + await app.db.execute(f""" + UPDATE guild_voice_channels + SET {field} = $1 + WHERE id = $2 + """, j[field], channel_id) + + # yes, i'm letting voice channels have nsfw, you cant stop me + await _common_guild_chan(channel_id, j) @bp.route('/', methods=['PUT', 'PATCH']) diff --git a/litecord/schemas.py b/litecord/schemas.py index 901961f..710ccd0 100644 --- a/litecord/schemas.py +++ b/litecord/schemas.py @@ -252,13 +252,10 @@ GUILD_UPDATE = { CHAN_OVERWRITE = { - 'type': 'dict', - 'schema': { - 'id': {'coerce': int}, - 'type': {'type': 'string', 'allowed': ['role', 'member']}, - 'allow': {'coerce': Permissions}, - 'deny': {'coerce': Permissions} - } + 'id': {'coerce': int}, + 'type': {'type': 'string', 'allowed': ['role', 'member']}, + 'allow': {'coerce': Permissions}, + 'deny': {'coerce': Permissions} } @@ -292,7 +289,7 @@ CHAN_UPDATE = { 'permission_overwrites': { 'type': 'list', - 'schema': CHAN_OVERWRITE, + 'schema': {'type': 'dict', 'schema': CHAN_OVERWRITE}, 'required': False }, diff --git a/litecord/storage.py b/litecord/storage.py index ab1f226..b78c2b0 100644 --- a/litecord/storage.py +++ b/litecord/storage.py @@ -324,18 +324,20 @@ class Storage: def _overwrite_convert(row): drow = dict(row) - drow['type'] = drow['target_type'] + + target_type = drow['target_type'] + drow['type'] = 'user' if target_type == 0 else 'role' # if type is 0, the overwrite is for a user # if type is 1, the overwrite is for a role drow['id'] = { 0: drow['target_user'], 1: drow['target_role'], - }[drow['type']] + }[target_type] drow['id'] = str(drow['id']) - drow.pop('overwrite_type') + drow.pop('target_type') drow.pop('target_user') drow.pop('target_role') diff --git a/schema.sql b/schema.sql index 3a654c3..e2823ec 100644 --- a/schema.sql +++ b/schema.sql @@ -347,11 +347,15 @@ CREATE TABLE IF NOT EXISTS channel_overwrites ( -- they're bigints (64bits), discord, -- for now, only needs 53. allow bigint DEFAULT 0, - deny bigint DEFAULT 0, - - PRIMARY KEY (channel_id, target_role, target_user) + deny bigint DEFAULT 0 ); +-- columns in private keys can't have NULL values, +-- so instead we use a custom constraint with UNIQUE + +ALTER TABLE channel_overwrites ADD CONSTRAINT channel_overwrites_uniq + UNIQUE (channel_id, target_role, target_user); + CREATE TABLE IF NOT EXISTS features ( id serial PRIMARY KEY,