parent
c3caf26f22
commit
54e7755080
|
@ -928,14 +928,11 @@ class Post(StatorModel):
|
|||
try:
|
||||
cls.by_object_uri(object_uri)
|
||||
except cls.DoesNotExist:
|
||||
InboxMessage.objects.create(
|
||||
message={
|
||||
"type": "__internal__",
|
||||
"object": {
|
||||
"type": "FetchPost",
|
||||
"object": object_uri,
|
||||
"reason": reason,
|
||||
},
|
||||
InboxMessage.create_internal(
|
||||
{
|
||||
"type": "FetchPost",
|
||||
"object": object_uri,
|
||||
"reason": reason,
|
||||
}
|
||||
)
|
||||
|
||||
|
@ -995,7 +992,7 @@ class Post(StatorModel):
|
|||
Handles an internal fetch-request inbox message
|
||||
"""
|
||||
try:
|
||||
uri = data["object"]["object"]
|
||||
uri = data["object"]
|
||||
if "://" in uri:
|
||||
cls.by_object_uri(uri, fetch=True)
|
||||
except (cls.DoesNotExist, KeyError):
|
||||
|
|
|
@ -166,6 +166,30 @@ class TimelineEvent(models.Model):
|
|||
subject_identity_id=interaction.identity_id,
|
||||
).delete()
|
||||
|
||||
### Background tasks ###
|
||||
|
||||
@classmethod
|
||||
def handle_clear_timeline(cls, message):
|
||||
"""
|
||||
Internal stator handler for clearing all events by a user off another
|
||||
user's timeline.
|
||||
"""
|
||||
actor_id = message["actor"]
|
||||
object_id = message["object"]
|
||||
full_erase = message.get("fullErase", False)
|
||||
|
||||
if full_erase:
|
||||
q = (
|
||||
models.Q(subject_post__author_id=object_id)
|
||||
| models.Q(subject_post_interaction__identity_id=object_id)
|
||||
| models.Q(subject_identity_id=object_id)
|
||||
)
|
||||
else:
|
||||
q = models.Q(
|
||||
type=cls.Types.post, subject_post__author_id=object_id
|
||||
) | models.Q(type=cls.Types.boost, subject_identity_id=object_id)
|
||||
TimelineEvent.objects.filter(q, identity_id=actor_id).delete()
|
||||
|
||||
### Mastodon Client API ###
|
||||
|
||||
def to_mastodon_notification_json(self, interactions=None):
|
||||
|
|
|
@ -5,6 +5,7 @@ from activities.models import Post, TimelineEvent
|
|||
from activities.services import PostService
|
||||
from core.ld import format_ld_date
|
||||
from users.models import Block, Follow, Identity, InboxMessage
|
||||
from users.services import IdentityService
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
|
@ -192,3 +193,67 @@ def test_old_new_post(
|
|||
).first()
|
||||
assert event
|
||||
assert "Hello " in event.subject_post.content
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
@pytest.mark.parametrize("full", [True, False])
|
||||
def test_clear_timeline(
|
||||
identity: Identity,
|
||||
remote_identity: Identity,
|
||||
stator,
|
||||
full: bool,
|
||||
):
|
||||
"""
|
||||
Ensures that timeline clearing works as expected.
|
||||
"""
|
||||
# Follow the remote user
|
||||
service = IdentityService(remote_identity)
|
||||
service.follow_from(identity)
|
||||
# Create an inbound new post message mentioning us
|
||||
message = {
|
||||
"id": "test",
|
||||
"type": "Create",
|
||||
"actor": remote_identity.actor_uri,
|
||||
"object": {
|
||||
"id": "https://remote.test/test-post",
|
||||
"type": "Note",
|
||||
"published": format_ld_date(timezone.now()),
|
||||
"attributedTo": remote_identity.actor_uri,
|
||||
"content": f"Hello @{identity.handle}!",
|
||||
"tag": {
|
||||
"type": "Mention",
|
||||
"href": identity.actor_uri,
|
||||
"name": f"@{identity.handle}",
|
||||
},
|
||||
},
|
||||
}
|
||||
InboxMessage.objects.create(message=message)
|
||||
|
||||
# Run stator twice - to make fanouts and then process them
|
||||
stator.run_single_cycle_sync()
|
||||
stator.run_single_cycle_sync()
|
||||
|
||||
# Make sure it appeared on our timeline as a post and a mentioned
|
||||
assert TimelineEvent.objects.filter(
|
||||
type=TimelineEvent.Types.post, identity=identity
|
||||
).exists()
|
||||
assert TimelineEvent.objects.filter(
|
||||
type=TimelineEvent.Types.mentioned, identity=identity
|
||||
).exists()
|
||||
|
||||
# Now, submit either a user block (for full clear) or unfollow (for post clear)
|
||||
if full:
|
||||
service.block_from(identity)
|
||||
else:
|
||||
service.unfollow_from(identity)
|
||||
|
||||
# Run stator once to process the timeline clear message
|
||||
stator.run_single_cycle_sync()
|
||||
|
||||
# Verify that the right things vanished
|
||||
assert not TimelineEvent.objects.filter(
|
||||
type=TimelineEvent.Types.post, identity=identity
|
||||
).exists()
|
||||
assert TimelineEvent.objects.filter(
|
||||
type=TimelineEvent.Types.mentioned, identity=identity
|
||||
).exists() == (not full)
|
||||
|
|
|
@ -14,7 +14,7 @@ class InboxMessageStates(StateGraph):
|
|||
|
||||
@classmethod
|
||||
async def handle_received(cls, instance: "InboxMessage"):
|
||||
from activities.models import Post, PostInteraction
|
||||
from activities.models import Post, PostInteraction, TimelineEvent
|
||||
from users.models import Block, Follow, Identity, Report
|
||||
|
||||
match instance.message_type:
|
||||
|
@ -148,7 +148,11 @@ class InboxMessageStates(StateGraph):
|
|||
match instance.message_object_type:
|
||||
case "fetchpost":
|
||||
await sync_to_async(Post.handle_fetch_internal)(
|
||||
instance.message
|
||||
instance.message["object"]
|
||||
)
|
||||
case "cleartimeline":
|
||||
await sync_to_async(TimelineEvent.handle_clear_timeline)(
|
||||
instance.message["object"]
|
||||
)
|
||||
case unknown:
|
||||
raise ValueError(
|
||||
|
@ -171,6 +175,18 @@ class InboxMessage(StatorModel):
|
|||
|
||||
state = StateField(InboxMessageStates)
|
||||
|
||||
@classmethod
|
||||
def create_internal(cls, payload):
|
||||
"""
|
||||
Creates an internal action message
|
||||
"""
|
||||
cls.objects.create(
|
||||
message={
|
||||
"type": "__internal__",
|
||||
"object": payload,
|
||||
}
|
||||
)
|
||||
|
||||
@property
|
||||
def message_type(self):
|
||||
return self.message["type"].lower()
|
||||
|
|
|
@ -11,6 +11,7 @@ from users.models import (
|
|||
Follow,
|
||||
FollowStates,
|
||||
Identity,
|
||||
InboxMessage,
|
||||
User,
|
||||
)
|
||||
|
||||
|
@ -85,13 +86,29 @@ class IdentityService:
|
|||
existing_follow = Follow.maybe_get(from_identity, self.identity)
|
||||
if existing_follow:
|
||||
existing_follow.transition_perform(FollowStates.undone)
|
||||
InboxMessage.create_internal(
|
||||
{
|
||||
"type": "ClearTimeline",
|
||||
"actor": from_identity.pk,
|
||||
"object": self.identity.pk,
|
||||
}
|
||||
)
|
||||
|
||||
def block_from(self, from_identity: Identity) -> Block:
|
||||
"""
|
||||
Blocks a user.
|
||||
"""
|
||||
self.unfollow_from(from_identity)
|
||||
return Block.create_local_block(from_identity, self.identity)
|
||||
block = Block.create_local_block(from_identity, self.identity)
|
||||
InboxMessage.create_internal(
|
||||
{
|
||||
"type": "ClearTimeline",
|
||||
"actor": from_identity.pk,
|
||||
"object": self.identity.pk,
|
||||
"fullErase": True,
|
||||
}
|
||||
)
|
||||
return block
|
||||
|
||||
def unblock_from(self, from_identity: Identity):
|
||||
"""
|
||||
|
|
Loading…
Reference in New Issue