Merge commit 'f4c80d70f' into release-v0.99.5

This commit is contained in:
Richard van der Hoff 2019-05-21 17:35:31 +01:00
commit f3ff64e000
5 changed files with 89 additions and 6 deletions

1
changelog.d/5203.feature Normal file
View File

@ -0,0 +1 @@
Add experimental support for relations (aka reactions and edits).

1
changelog.d/5212.feature Normal file
View File

@ -0,0 +1 @@
Add experimental support for relations (aka reactions and edits).

View File

@ -22,7 +22,7 @@ from canonicaljson import encode_canonical_json, json
from twisted.internet import defer from twisted.internet import defer
from twisted.internet.defer import succeed from twisted.internet.defer import succeed
from synapse.api.constants import EventTypes, Membership from synapse.api.constants import EventTypes, Membership, RelationTypes
from synapse.api.errors import ( from synapse.api.errors import (
AuthError, AuthError,
Codes, Codes,
@ -601,6 +601,20 @@ class EventCreationHandler(object):
self.validator.validate_new(event) self.validator.validate_new(event)
# If this event is an annotation then we check that that the sender
# can't annotate the same way twice (e.g. stops users from liking an
# event multiple times).
relation = event.content.get("m.relates_to", {})
if relation.get("rel_type") == RelationTypes.ANNOTATION:
relates_to = relation["event_id"]
aggregation_key = relation["key"]
already_exists = yield self.store.has_user_annotated_event(
relates_to, event.type, aggregation_key, event.sender,
)
if already_exists:
raise SynapseError(400, "Can't send same reaction twice")
logger.debug( logger.debug(
"Created event %s", "Created event %s",
event.event_id, event.event_id,

View File

@ -280,7 +280,7 @@ class RelationsWorkerStore(SQLBaseStore):
having_clause = "" having_clause = ""
sql = """ sql = """
SELECT type, aggregation_key, COUNT(*), MAX(stream_ordering) SELECT type, aggregation_key, COUNT(DISTINCT sender), MAX(stream_ordering)
FROM event_relations FROM event_relations
INNER JOIN events USING (event_id) INNER JOIN events USING (event_id)
WHERE {where_clause} WHERE {where_clause}
@ -350,9 +350,7 @@ class RelationsWorkerStore(SQLBaseStore):
""" """
def _get_applicable_edit_txn(txn): def _get_applicable_edit_txn(txn):
txn.execute( txn.execute(sql, (event_id, RelationTypes.REPLACE))
sql, (event_id, RelationTypes.REPLACE,)
)
row = txn.fetchone() row = txn.fetchone()
if row: if row:
return row[0] return row[0]
@ -367,6 +365,50 @@ class RelationsWorkerStore(SQLBaseStore):
edit_event = yield self.get_event(edit_id, allow_none=True) edit_event = yield self.get_event(edit_id, allow_none=True)
defer.returnValue(edit_event) defer.returnValue(edit_event)
def has_user_annotated_event(self, parent_id, event_type, aggregation_key, sender):
"""Check if a user has already annotated an event with the same key
(e.g. already liked an event).
Args:
parent_id (str): The event being annotated
event_type (str): The event type of the annotation
aggregation_key (str): The aggregation key of the annotation
sender (str): The sender of the annotation
Returns:
Deferred[bool]
"""
sql = """
SELECT 1 FROM event_relations
INNER JOIN events USING (event_id)
WHERE
relates_to_id = ?
AND relation_type = ?
AND type = ?
AND sender = ?
AND aggregation_key = ?
LIMIT 1;
"""
def _get_if_user_has_annotated_event(txn):
txn.execute(
sql,
(
parent_id,
RelationTypes.ANNOTATION,
event_type,
sender,
aggregation_key,
),
)
return bool(txn.fetchone())
return self.runInteraction(
"get_if_user_has_annotated_event", _get_if_user_has_annotated_event
)
class RelationsStore(RelationsWorkerStore): class RelationsStore(RelationsWorkerStore):
def _handle_event_relations(self, txn, event): def _handle_event_relations(self, txn, event):

View File

@ -90,6 +90,15 @@ class RelationsTestCase(unittest.HomeserverTestCase):
channel = self._send_relation(RelationTypes.ANNOTATION, EventTypes.Member) channel = self._send_relation(RelationTypes.ANNOTATION, EventTypes.Member)
self.assertEquals(400, channel.code, channel.json_body) self.assertEquals(400, channel.code, channel.json_body)
def test_deny_double_react(self):
"""Test that we deny relations on membership events
"""
channel = self._send_relation(RelationTypes.ANNOTATION, "m.reaction", "a")
self.assertEquals(200, channel.code, channel.json_body)
channel = self._send_relation(RelationTypes.ANNOTATION, "m.reaction", "a")
self.assertEquals(400, channel.code, channel.json_body)
def test_basic_paginate_relations(self): def test_basic_paginate_relations(self):
"""Tests that calling pagination API corectly the latest relations. """Tests that calling pagination API corectly the latest relations.
""" """
@ -234,14 +243,30 @@ class RelationsTestCase(unittest.HomeserverTestCase):
"""Test that we can paginate within an annotation group. """Test that we can paginate within an annotation group.
""" """
# We need to create ten separate users to send each reaction.
access_tokens = [self.user_token, self.user2_token]
idx = 0
while len(access_tokens) < 10:
user_id, token = self._create_user("test" + str(idx))
idx += 1
self.helper.join(self.room, user=user_id, tok=token)
access_tokens.append(token)
idx = 0
expected_event_ids = [] expected_event_ids = []
for _ in range(10): for _ in range(10):
channel = self._send_relation( channel = self._send_relation(
RelationTypes.ANNOTATION, "m.reaction", key=u"👍" RelationTypes.ANNOTATION,
"m.reaction",
key=u"👍",
access_token=access_tokens[idx],
) )
self.assertEquals(200, channel.code, channel.json_body) self.assertEquals(200, channel.code, channel.json_body)
expected_event_ids.append(channel.json_body["event_id"]) expected_event_ids.append(channel.json_body["event_id"])
idx += 1
# Also send a different type of reaction so that we test we don't see it # Also send a different type of reaction so that we test we don't see it
channel = self._send_relation(RelationTypes.ANNOTATION, "m.reaction", key="a") channel = self._send_relation(RelationTypes.ANNOTATION, "m.reaction", key="a")
self.assertEquals(200, channel.code, channel.json_body) self.assertEquals(200, channel.code, channel.json_body)