From afd2e727d0e79dbb0cc951914253671a7bc7edaa Mon Sep 17 00:00:00 2001 From: thepra Date: Thu, 1 Oct 2026 11:39:45 +0200 Subject: [PATCH] A remote delete clears what it touched Deleting a remote post now removes its timeline entries and the reblogs of it and lowers its parent's reply count; deleting a remote actor also drops the follows pointing at it and its entries in home timelines. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_012CzABvBkbcFqoHdmi8b9WB --- PrivaPub.Tests/Domain/TimelineTests.cs | 20 +++++++++++++++++++ .../Inbox/Handlers/DeleteHandler.cs | 15 +++++++++++--- 2 files changed, 32 insertions(+), 3 deletions(-) diff --git a/PrivaPub.Tests/Domain/TimelineTests.cs b/PrivaPub.Tests/Domain/TimelineTests.cs index 6420503..8673cc6 100644 --- a/PrivaPub.Tests/Domain/TimelineTests.cs +++ b/PrivaPub.Tests/Domain/TimelineTests.cs @@ -122,6 +122,26 @@ namespace PrivaPub.Tests.Domain Assert.DoesNotContain("

parent

", (await Home(bobRoot, bob.Id)).Select(p => p.ContentHtml)); } + [Fact] + public async Task A_remote_delete_takes_the_post_out_of_home() + { + var (bobRoot, bob) = await _harness.Persona("bob"); + var carol = new RemoteActor(_harness.Peer, "carol"); + await Accepted(bobRoot, bob.Id, carol); + var create = Create(carol, text: "soon gone"); + await _harness.Deliver(carol, "/human-centipede", create); + Assert.Single(await Home(bobRoot, bob.Id)); + + await _harness.Deliver(carol, "/human-centipede", new JsonObject + { + ["id"] = $"{new Uri(carol.Id).GetLeftPart(UriPartial.Authority)}/deletes/{Guid.NewGuid():N}", ["type"] = "Delete", ["actor"] = carol.Id, + ["object"] = new JsonObject { ["id"] = create["object"]!["id"]!.GetValue(), ["type"] = "Tombstone" } + }); + + Assert.Empty(await Home(bobRoot, bob.Id)); + Assert.False(await DB.Default.Find().Match(e => e.AvatarId == bob.Id).ExecuteAnyAsync(TestContext.Current.CancellationToken)); + } + [Fact] public async Task A_mention_notifies_the_persona() { diff --git a/PrivaPub/Federation/Inbox/Handlers/DeleteHandler.cs b/PrivaPub/Federation/Inbox/Handlers/DeleteHandler.cs index 7529b3b..b43d064 100644 --- a/PrivaPub/Federation/Inbox/Handlers/DeleteHandler.cs +++ b/PrivaPub/Federation/Inbox/Handlers/DeleteHandler.cs @@ -7,6 +7,7 @@ using PrivaPub.Federation.Rendering; using PrivaPub.Models.Federation; using PrivaPub.Models.Group; using PrivaPub.Models.Post; +using PrivaPub.Models.Social; using PrivaPub.Models.User; using PrivaPub.StaticServices; @@ -56,11 +57,19 @@ namespace PrivaPub.Federation.Inbox.Handlers if (follower.LocalActorKind == LocalActorKind.Group) await RemoveForeignMember(follower.LocalActorId, actor.ActorURI, token); } + await DB.Default.DeleteAsync(f => f.TargetActorURI == actor.ActorURI); + await DB.Default.DeleteAsync(e => e.AuthorAccountId == actor.ID); return; } - await DB.Default.DeleteAsync(p => p.ObjectURI == objectUri && p.ActorURI == actor.ActorURI); - return; - } + var post = await _dbEntities.Posts.Match(p => p.ObjectURI == objectUri && p.ActorURI == actor.ActorURI).ExecuteFirstAsync(token); + if (post == default) + return; + await DB.Default.DeleteAsync(post.ID); + await DB.Default.DeleteAsync(e => e.PostId == post.ID || e.ReblogOfPostId == post.ID); + await DB.Default.DeleteAsync(p => p.ReblogOfPostId == post.ID); + if (!string.IsNullOrEmpty(post.AnsweringToPostId)) + await DB.Default.Update().MatchID(post.AnsweringToPostId).Modify(b => b.Inc(p => p.RepliesCount, -1)).ExecuteAsync(token); + } } }