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
This commit is contained in:
Luna Mendes 2018-11-05 04:12:16 -03:00
parent 8ffa14d934
commit 7c274f0f70
4 changed files with 111 additions and 17 deletions

View File

@ -7,7 +7,7 @@ from litecord.auth import token_check
from litecord.enums import ChannelType, GUILD_CHANS from litecord.enums import ChannelType, GUILD_CHANS
from litecord.errors import ChannelNotFound from litecord.errors import ChannelNotFound
from litecord.schemas import ( from litecord.schemas import (
validate, CHAN_UPDATE validate, CHAN_UPDATE, CHAN_OVERWRITE
) )
from litecord.blueprints.checks import channel_check, channel_perm_check 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) '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('/<int:channel_id>/permissions/<int:overwrite_id>', 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): async def _update_channel_common(channel_id, guild_id: int, j: dict):
if 'name' in j: if 'name' in j:
await app.db.execute(""" 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 # since theres now an empty slot, move current channel to it
await _update_pos(channel_id, new_pos) 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): 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): 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('/<int:channel_id>', methods=['PUT', 'PATCH']) @bp.route('/<int:channel_id>', methods=['PUT', 'PATCH'])

View File

@ -252,13 +252,10 @@ GUILD_UPDATE = {
CHAN_OVERWRITE = { CHAN_OVERWRITE = {
'type': 'dict',
'schema': {
'id': {'coerce': int}, 'id': {'coerce': int},
'type': {'type': 'string', 'allowed': ['role', 'member']}, 'type': {'type': 'string', 'allowed': ['role', 'member']},
'allow': {'coerce': Permissions}, 'allow': {'coerce': Permissions},
'deny': {'coerce': Permissions} 'deny': {'coerce': Permissions}
}
} }
@ -292,7 +289,7 @@ CHAN_UPDATE = {
'permission_overwrites': { 'permission_overwrites': {
'type': 'list', 'type': 'list',
'schema': CHAN_OVERWRITE, 'schema': {'type': 'dict', 'schema': CHAN_OVERWRITE},
'required': False 'required': False
}, },

View File

@ -324,18 +324,20 @@ class Storage:
def _overwrite_convert(row): def _overwrite_convert(row):
drow = dict(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 0, the overwrite is for a user
# if type is 1, the overwrite is for a role # if type is 1, the overwrite is for a role
drow['id'] = { drow['id'] = {
0: drow['target_user'], 0: drow['target_user'],
1: drow['target_role'], 1: drow['target_role'],
}[drow['type']] }[target_type]
drow['id'] = str(drow['id']) drow['id'] = str(drow['id'])
drow.pop('overwrite_type') drow.pop('target_type')
drow.pop('target_user') drow.pop('target_user')
drow.pop('target_role') drow.pop('target_role')

View File

@ -347,11 +347,15 @@ CREATE TABLE IF NOT EXISTS channel_overwrites (
-- they're bigints (64bits), discord, -- they're bigints (64bits), discord,
-- for now, only needs 53. -- for now, only needs 53.
allow bigint DEFAULT 0, allow bigint DEFAULT 0,
deny bigint DEFAULT 0, deny bigint DEFAULT 0
PRIMARY KEY (channel_id, target_role, target_user)
); );
-- 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 ( CREATE TABLE IF NOT EXISTS features (
id serial PRIMARY KEY, id serial PRIMARY KEY,