fix(dm): purge DMs with zero live members (exclude-uid, scoped) so dead-both threads clean up (S2)
This commit is contained in:
@@ -56,4 +56,19 @@ describe('tombstoneUser — DM membership partition', () => {
|
||||
// Survivor's 1-on-1 channel still exists and still has the survivor
|
||||
expect(testDb.select().from(schema.dmChannels).where(eq(schema.dmChannels.id, 'dm_1on1')).get()).toBeTruthy();
|
||||
});
|
||||
|
||||
it('keeps a Deleted<->Survivor 1-on-1, purges a Deleted<->Deleted 1-on-1', async () => {
|
||||
const { tombstoneUser } = await import('./userDeletion.js');
|
||||
seedUser('alreadyDead', { isDeleted: 1 });
|
||||
seedUser('victim'); seedUser('survivor');
|
||||
// Survivor thread — must survive
|
||||
seedDm('dm_live', null); seedMember('dm_live', 'victim'); seedMember('dm_live', 'survivor');
|
||||
// Both-dead thread — victim + an already-deleted partner → must be purged
|
||||
seedDm('dm_dead', null); seedMember('dm_dead', 'victim'); seedMember('dm_dead', 'alreadyDead');
|
||||
|
||||
tombstoneUser('victim', { purgeContent: false });
|
||||
|
||||
expect(testDb.select().from(schema.dmChannels).where(eq(schema.dmChannels.id, 'dm_live')).get()).toBeTruthy();
|
||||
expect(testDb.select().from(schema.dmChannels).where(eq(schema.dmChannels.id, 'dm_dead')).get()).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -185,20 +185,24 @@ export function tombstoneUser(uid: string, options?: TombstoneOptions): string[]
|
||||
}
|
||||
}
|
||||
|
||||
// Clean up orphaned DM channels (zero members after our removal) — always runs,
|
||||
// orphaned channels are unreachable garbage regardless of purge mode
|
||||
const orphanedDmIds = tx.select({ id: schema.dmChannels.id })
|
||||
.from(schema.dmChannels)
|
||||
.all()
|
||||
.filter(dc => {
|
||||
const memberCount = tx.select({ id: schema.dmMembers.dmChannelId })
|
||||
.from(schema.dmMembers)
|
||||
.where(eq(schema.dmMembers.dmChannelId, dc.id))
|
||||
.all()
|
||||
.length;
|
||||
return memberCount === 0;
|
||||
})
|
||||
.map(dc => dc.id);
|
||||
// Purge DMs that are dead after this deletion: among the channels this user
|
||||
// was in, those with zero LIVE members. The uid being tombstoned right now
|
||||
// still reads isDeleted=0 (its row is updated below), so it is excluded
|
||||
// explicitly — this keeps a Deleted<->Survivor 1-on-1 but purges a
|
||||
// Deleted<->Deleted one. Scoped to the user's channels: only their
|
||||
// membership changed here, so only these can newly become dead.
|
||||
const orphanedDmIds = userDmChannelIds.filter(dmId => {
|
||||
const liveOthers = tx.select({ userId: schema.dmMembers.userId })
|
||||
.from(schema.dmMembers)
|
||||
.innerJoin(schema.users, eq(schema.dmMembers.userId, schema.users.id))
|
||||
.where(and(
|
||||
eq(schema.dmMembers.dmChannelId, dmId),
|
||||
eq(schema.users.isDeleted, 0),
|
||||
))
|
||||
.all()
|
||||
.filter(m => m.userId !== uid);
|
||||
return liveOthers.length === 0;
|
||||
});
|
||||
|
||||
for (const dmId of orphanedDmIds) {
|
||||
const msgIds = tx.select({ id: schema.dmMessages.id })
|
||||
|
||||
Reference in New Issue
Block a user