From 9fd1e0f87cefddbd0e8b3c9db0073d2bb0a38048 Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Wed, 22 Oct 2025 18:56:41 +0300 Subject: [PATCH] bridgev2/networkinterface: allow deleting children in chat delete event --- bridgev2/networkinterface.go | 5 ++++ bridgev2/portal.go | 49 ++++++++++++++++++++++++++++++++++++ bridgev2/simplevent/chat.go | 7 +++++- 3 files changed, 60 insertions(+), 1 deletion(-) diff --git a/bridgev2/networkinterface.go b/bridgev2/networkinterface.go index 9ca2dc43..da505435 100644 --- a/bridgev2/networkinterface.go +++ b/bridgev2/networkinterface.go @@ -1135,6 +1135,11 @@ type RemoteChatDelete interface { RemoteDeleteOnlyForMe } +type RemoteChatDeleteWithChildren interface { + RemoteChatDelete + DeleteChildren() bool +} + type RemoteEventThatMayCreatePortal interface { RemoteEvent ShouldCreatePortal() bool diff --git a/bridgev2/portal.go b/bridgev2/portal.go index 566847fb..0bd23b9e 100644 --- a/bridgev2/portal.go +++ b/bridgev2/portal.go @@ -284,6 +284,16 @@ func (br *Bridge) GetDMPortalsWith(ctx context.Context, otherUserID networkid.Us return br.loadManyPortals(ctx, rows) } +func (br *Bridge) GetChildPortals(ctx context.Context, parent networkid.PortalKey) ([]*Portal, error) { + br.cacheLock.Lock() + defer br.cacheLock.Unlock() + rows, err := br.DB.Portal.GetChildren(ctx, parent) + if err != nil { + return nil, err + } + return br.loadManyPortals(ctx, rows) +} + func (br *Bridge) GetDMPortal(ctx context.Context, receiver networkid.UserLoginID, otherUserID networkid.UserID) (*Portal, error) { br.cacheLock.Lock() defer br.cacheLock.Unlock() @@ -3514,6 +3524,20 @@ func (portal *Portal) findOtherLogins(ctx context.Context, source *UserLogin) (o return } +type childDeleteProxy struct { + RemoteChatDeleteWithChildren + child networkid.PortalKey + done func() +} + +func (cdp *childDeleteProxy) AddLogContext(c zerolog.Context) zerolog.Context { + return cdp.RemoteChatDeleteWithChildren.AddLogContext(c).Str("subaction", "delete children") +} +func (cdp *childDeleteProxy) GetPortalKey() networkid.PortalKey { return cdp.child } +func (cdp *childDeleteProxy) ShouldCreatePortal() bool { return false } +func (cdp *childDeleteProxy) PreHandle(ctx context.Context, portal *Portal) {} +func (cdp *childDeleteProxy) PostHandle(ctx context.Context, portal *Portal) { cdp.done() } + func (portal *Portal) handleRemoteChatDelete(ctx context.Context, source *UserLogin, evt RemoteChatDelete) EventHandlingResult { log := zerolog.Ctx(ctx) if portal.Receiver == "" && evt.DeleteOnlyForMe() { @@ -3549,6 +3573,31 @@ func (portal *Portal) handleRemoteChatDelete(ctx context.Context, source *UserLo } } } + if childDeleter, ok := evt.(RemoteChatDeleteWithChildren); ok && childDeleter.DeleteChildren() && portal.RoomType == database.RoomTypeSpace { + children, err := portal.Bridge.GetChildPortals(ctx, portal.PortalKey) + if err != nil { + log.Err(err).Msg("Failed to fetch children to delete") + return EventHandlingResultFailed.WithError(err) + } + log.Debug(). + Int("portal_count", len(children)). + Msg("Deleting child portals before remote chat delete") + var wg sync.WaitGroup + wg.Add(len(children)) + for _, child := range children { + child.queueEvent(ctx, &portalRemoteEvent{ + evt: &childDeleteProxy{ + RemoteChatDeleteWithChildren: childDeleter, + child: child.PortalKey, + done: wg.Done, + }, + source: source, + evtType: RemoteEventChatDelete, + }) + } + wg.Wait() + log.Debug().Msg("Finished deleting child portals") + } err := portal.Delete(ctx) if err != nil { log.Err(err).Msg("Failed to delete portal from database") diff --git a/bridgev2/simplevent/chat.go b/bridgev2/simplevent/chat.go index c725141b..56e3a6b1 100644 --- a/bridgev2/simplevent/chat.go +++ b/bridgev2/simplevent/chat.go @@ -65,14 +65,19 @@ func (evt *ChatResync) GetChatInfo(ctx context.Context, portal *bridgev2.Portal) type ChatDelete struct { EventMeta OnlyForMe bool + Children bool } -var _ bridgev2.RemoteChatDelete = (*ChatDelete)(nil) +var _ bridgev2.RemoteChatDeleteWithChildren = (*ChatDelete)(nil) func (evt *ChatDelete) DeleteOnlyForMe() bool { return evt.OnlyForMe } +func (evt *ChatDelete) DeleteChildren() bool { + return evt.Children +} + // ChatInfoChange is a simple implementation of [bridgev2.RemoteChatInfoChange]. type ChatInfoChange struct { EventMeta