blueprints: create dms blueprint

It should be safe for instances to run the `schema.sql` file and update.

 - blueprints.users: s/GUILD_SETTINGS_UPDATE/USER_GUILD_SETTINGS_UPDATE
 - storage: fix get_guild_settings returning a dict instead of a list
 - schema.sql: fix types and syntax
This commit is contained in:
Luna Mendes 2018-10-19 16:21:33 -03:00
parent 692f9ef245
commit ef5d4f30e1
7 changed files with 115 additions and 98 deletions

View File

@ -8,3 +8,4 @@ from .science import bp as science
from .voice import bp as voice from .voice import bp as voice
from .invites import bp as invites from .invites import bp as invites
from .relationships import bp as relationships from .relationships import bp as relationships
from .dms import bp as dms

View File

@ -10,7 +10,7 @@ from ..errors import Forbidden, ChannelNotFound, MessageNotFound
from ..schemas import validate, MESSAGE_CREATE from ..schemas import validate, MESSAGE_CREATE
from .checks import channel_check, guild_check from .checks import channel_check, guild_check
from .users import try_dm_state from .dms import try_dm_state
log = Logger(__name__) log = Logger(__name__)
bp = Blueprint('channels', __name__) bp = Blueprint('channels', __name__)

102
litecord/blueprints/dms.py Normal file
View File

@ -0,0 +1,102 @@
"""
blueprint for direct messages
"""
from asyncpg import UniqueViolationError
from quart import Blueprint, request, current_app as app, jsonify
from logbook import Logger
from ..schemas import validate, CREATE_DM, CREATE_GROUP_DM
from ..enums import ChannelType
from ..snowflake import get_snowflake
from .auth import token_check
log = Logger(__name__)
bp = Blueprint('dms', __name__)
@bp.route('/@me/channels', methods=['GET'])
async def get_dms():
"""Get the open DMs for the user."""
user_id = await token_check()
dms = await app.storage.get_dms(user_id)
return jsonify(dms)
async def try_dm_state(user_id: int, dm_id: int):
"""Try inserting the user into the dm state
for the given DM.
Does not do anything if the user is already
in the dm state.
"""
await app.db.execute("""
INSERT INTO dm_channel_state (user_id, dm_id)
VALUES ($1, $2)
ON CONFLICT DO NOTHING
""", user_id, dm_id)
async def create_dm(user_id, recipient_id):
"""Create a new dm with a user,
or get the existing DM id if it already exists."""
dm_id = get_snowflake()
try:
await app.db.execute("""
INSERT INTO channels (id, channel_type)
VALUES ($1, $2)
""", dm_id, ChannelType.DM.value)
await app.db.execute("""
INSERT INTO dm_channels (id, party1_id, party2_id)
VALUES ($1, $2, $3)
""", dm_id, user_id, recipient_id)
# the dm state is something we use
# to give the currently "open dms"
# on the client.
# we don't open a dm for the peer/recipient
# until the user sends a message.
await try_dm_state(user_id, dm_id)
except UniqueViolationError:
# the dm already exists
dm_id = await app.db.fetchval("""
SELECT id
FROM dm_channels
WHERE (party1_id = $1 OR party2_id = $1) AND
(party2_id = $2 OR party2_id = $2)
""", user_id, recipient_id)
dm = await app.storage.get_dm(dm_id, user_id)
return jsonify(dm)
@bp.route('/@me/channels', methods=['POST'])
async def start_dm():
"""Create a DM with a user."""
user_id = await token_check()
j = validate(await request.get_json(), CREATE_DM)
recipient_id = j['recipient_id']
return await create_dm(user_id, recipient_id)
@bp.route('/<int:p_user_id>/channels', methods=['POST'])
async def create_group_dm(p_user_id: int):
"""Create a DM or a Group DM with user(s)."""
user_id = await token_check()
assert user_id == p_user_id
j = validate(await request.get_json(), CREATE_GROUP_DM)
recipients = j['recipients']
if len(recipients) == 1:
# its a group dm with 1 user... a dm!
return await create_dm(user_id, int(recipients[0]))
# TODO: group dms
return 'group dms not implemented', 500

View File

@ -1,14 +1,12 @@
import random import random
from quart import Blueprint, jsonify, request, current_app as app
from asyncpg import UniqueViolationError from asyncpg import UniqueViolationError
from quart import Blueprint, jsonify, request, current_app as app
from ..auth import token_check from ..auth import token_check
from ..snowflake import get_snowflake from ..errors import Forbidden, BadRequest
from ..errors import Forbidden, BadRequest, Unauthorized
from ..schemas import validate, USER_SETTINGS, \ from ..schemas import validate, USER_SETTINGS, \
CREATE_DM, CREATE_GROUP_DM, USER_UPDATE, GUILD_SETTINGS USER_UPDATE, GUILD_SETTINGS
from ..enums import ChannelType, RelationshipType
from .guilds import guild_check from .guilds import guild_check
from .auth import hash_data, check_password, check_username_usage from .auth import hash_data, check_password, check_username_usage
@ -268,90 +266,6 @@ async def get_connections():
pass pass
@bp.route('/@me/channels', methods=['GET'])
async def get_dms():
user_id = await token_check()
dms = await app.storage.get_dms(user_id)
return jsonify(dms)
async def try_dm_state(user_id, dm_id):
"""Try insertin the user into the dm state
for the given DM."""
try:
await app.db.execute("""
INSERT INTO dm_channel_state (user_id, dm_id)
VALUES ($1, $2)
""", user_id, dm_id)
except UniqueViolationError:
# if already in state, ignore
pass
async def create_dm(user_id, recipient_id):
"""Create a new dm with a user,
or get the existing DM id if it already exists."""
dm_id = get_snowflake()
try:
await app.db.execute("""
INSERT INTO channels (id, channel_type)
VALUES ($1, $2)
""", dm_id, ChannelType.DM.value)
await app.db.execute("""
INSERT INTO dm_channels (id, party1_id, party2_id)
VALUES ($1, $2, $3)
""", dm_id, user_id, recipient_id)
# the dm state is something we use
# to give the currently "open dms"
# on the client.
# we don't open a dm for the peer/recipient
# until the user sends a message.
await try_dm_state(user_id, dm_id)
except UniqueViolationError:
# the dm already exists
dm_id = await app.db.fetchval("""
SELECT id
FROM dm_channels
WHERE (party1_id = $1 OR party2_id = $1) AND
(party2_id = $2 OR party2_id = $2)
""", user_id, recipient_id)
dm = await app.storage.get_dm(dm_id, user_id)
return jsonify(dm)
@bp.route('/@me/channels', methods=['POST'])
async def start_dm():
"""Create a DM with a user."""
user_id = await token_check()
j = validate(await request.get_json(), CREATE_DM)
recipient_id = j['recipient_id']
return await create_dm(user_id, recipient_id)
@bp.route('/<int:p_user_id>/channels', methods=['POST'])
async def create_group_dm(p_user_id: int):
"""Create a DM or a Group DM with user(s)."""
user_id = await token_check()
assert user_id == p_user_id
j = validate(await request.get_json(), CREATE_GROUP_DM)
recipients = j['recipients']
if len(recipients) == 1:
# its a group dm with 1 user... a dm!
return await create_dm(user_id, int(recipients[0]))
# TODO: group dms
return 'group dms not implemented', 500
@bp.route('/@me/notes/<int:target_id>', methods=['PUT']) @bp.route('/@me/notes/<int:target_id>', methods=['PUT'])
async def put_note(target_id: int): async def put_note(target_id: int):
"""Put a note to a user.""" """Put a note to a user."""
@ -536,7 +450,7 @@ async def patch_guild_settings(guild_id: int):
settings = await app.storage.get_guild_settings_one(user_id, guild_id) settings = await app.storage.get_guild_settings_one(user_id, guild_id)
await app.dispatcher.dispatch_user(user_id, 'GUILD_SETTINGS_UPDATE', { await app.dispatcher.dispatch_user(user_id, 'USER_GUILD_SETTINGS_UPDATE', {
**settings, **settings,
**{'guild_id': guild_id} **{'guild_id': guild_id}
}) })

View File

@ -913,7 +913,7 @@ class Storage:
"""Get the specific User Guild Settings, """Get the specific User Guild Settings,
for all guilds a user is on.""" for all guilds a user is on."""
res = {} res = []
settings = await self.db.fetch(""" settings = await self.db.fetch("""
SELECT guild_id, suppress_everyone, muted SELECT guild_id, suppress_everyone, muted
@ -925,7 +925,6 @@ class Storage:
for row in settings: for row in settings:
gid = row['guild_id'] gid = row['guild_id']
drow = dict(row) drow = dict(row)
drow.pop('guild_id')
chan_ids = await self.get_channel_ids(gid) chan_ids = await self.get_channel_ids(gid)
@ -943,9 +942,9 @@ class Storage:
chan_overrides[str(chan_id)] = dict(chan_row) chan_overrides[str(chan_id)] = dict(chan_row)
res[str(gid)] = {**drow, **{ res.append({**drow, **{
'channel_overrides': chan_overrides 'channel_overrides': chan_overrides
}} }})
return res return res

3
run.py
View File

@ -11,7 +11,7 @@ from logbook.compat import redirect_logging
import config import config
from litecord.blueprints import gateway, auth, users, guilds, channels, \ from litecord.blueprints import gateway, auth, users, guilds, channels, \
webhooks, science, voice, invites, relationships webhooks, science, voice, invites, relationships, dms
from litecord.gateway import websocket_handler from litecord.gateway import websocket_handler
from litecord.errors import LitecordError from litecord.errors import LitecordError
from litecord.gateway.state_manager import StateManager from litecord.gateway.state_manager import StateManager
@ -56,6 +56,7 @@ bps = {
science: None, science: None,
voice: '/voice', voice: '/voice',
invites: None, invites: None,
dms: '/users'
} }
for bp, suffix in bps.items(): for bp, suffix in bps.items():

View File

@ -283,11 +283,11 @@ CREATE TABLE IF NOT EXISTS guild_settings (
CREATE TABLE IF NOT EXISTS guild_settings_channel_overrides ( CREATE TABLE IF NOT EXISTS guild_settings_channel_overrides (
user_id bigint REFERENCES users (id) ON DELETE CASCADE, user_id bigint REFERENCES users (id) ON DELETE CASCADE,
guild_id bigint REFERENCES guilds (id) ON DELETE CASCADE guild_id bigint REFERENCES guilds (id) ON DELETE CASCADE,
channel_id bigint REFERENCES channels (id) ON DELETE CASCADE, channel_id bigint REFERENCES channels (id) ON DELETE CASCADE,
muted bool DEFAULT false, muted bool DEFAULT false,
message_notifications bool DEFAULT 0, message_notifications int DEFAULT 0,
PRIMARY KEY (user_id, guild_id, channel_id) PRIMARY KEY (user_id, guild_id, channel_id)
); );