mirror of
https://git.anonymousland.org/anonymousland/synapse.git
synced 2024-12-27 01:59:24 -05:00
Merge commit 'f4c80d70f' into release-v0.99.5
This commit is contained in:
commit
f3ff64e000
1
changelog.d/5203.feature
Normal file
1
changelog.d/5203.feature
Normal file
@ -0,0 +1 @@
|
|||||||
|
Add experimental support for relations (aka reactions and edits).
|
1
changelog.d/5212.feature
Normal file
1
changelog.d/5212.feature
Normal file
@ -0,0 +1 @@
|
|||||||
|
Add experimental support for relations (aka reactions and edits).
|
@ -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,
|
||||||
|
@ -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):
|
||||||
|
@ -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)
|
||||||
|
Loading…
Reference in New Issue
Block a user