From 1099e363a683554dc823c37d4ab6bfc5593d46ec Mon Sep 17 00:00:00 2001 From: Anna Larch Date: Sat, 25 Apr 2026 17:25:45 +0200 Subject: [PATCH] fix(user_status): clean up orphaned backup statuses in background job Backup rows that were never restored (e.g. because the user manually changed their status during a call) would accumulate indefinitely. The background job now deletes backup rows older than 24 hours. AI-Assisted-By: Claude Sonnet 4.6 Signed-off-by: Anna Larch --- .../ClearOldStatusesBackgroundJob.php | 1 + apps/user_status/lib/Db/UserStatusMapper.php | 8 +++ .../tests/Unit/Db/UserStatusMapperTest.php | 54 +++++++++++++++++++ 3 files changed, 63 insertions(+) diff --git a/apps/user_status/lib/BackgroundJob/ClearOldStatusesBackgroundJob.php b/apps/user_status/lib/BackgroundJob/ClearOldStatusesBackgroundJob.php index 51a9c623a0330..69f65544b274b 100644 --- a/apps/user_status/lib/BackgroundJob/ClearOldStatusesBackgroundJob.php +++ b/apps/user_status/lib/BackgroundJob/ClearOldStatusesBackgroundJob.php @@ -43,5 +43,6 @@ protected function run($argument) { $this->mapper->clearOlderThanClearAt($now); $this->mapper->clearStatusesOlderThan($now - StatusService::INVALIDATE_STATUS_THRESHOLD, $now); + $this->mapper->cleanOrphanedBackups($now - 86400); } } diff --git a/apps/user_status/lib/Db/UserStatusMapper.php b/apps/user_status/lib/Db/UserStatusMapper.php index 15982d44fd8b9..9d0ec5dc535e9 100644 --- a/apps/user_status/lib/Db/UserStatusMapper.php +++ b/apps/user_status/lib/Db/UserStatusMapper.php @@ -185,6 +185,14 @@ public function createBackupStatus(string $userId): bool { return $qb->executeStatement() > 0; } + public function cleanOrphanedBackups(int $olderThan): void { + $qb = $this->db->getQueryBuilder(); + $qb->delete($this->tableName) + ->where($qb->expr()->eq('is_backup', $qb->createNamedParameter(true, IQueryBuilder::PARAM_BOOL))) + ->andWhere($qb->expr()->lte('status_timestamp', $qb->createNamedParameter($olderThan, IQueryBuilder::PARAM_INT))); + $qb->executeStatement(); + } + public function restoreBackupStatuses(array $ids): void { $qb = $this->db->getQueryBuilder(); $qb->update($this->tableName) diff --git a/apps/user_status/tests/Unit/Db/UserStatusMapperTest.php b/apps/user_status/tests/Unit/Db/UserStatusMapperTest.php index 3cbd4e6d93045..9d76c5d0302df 100644 --- a/apps/user_status/tests/Unit/Db/UserStatusMapperTest.php +++ b/apps/user_status/tests/Unit/Db/UserStatusMapperTest.php @@ -329,4 +329,58 @@ public function testRestoreBackupStatuses(): void { $this->assertEquals(true, $user3Status->getIsBackup()); $this->assertEquals('Vacationing', $user3Status->getCustomMessage()); } + + public function testCleanOrphanedBackups(): void { + // Recent backup — should survive + $recentBackup = new UserStatus(); + $recentBackup->setUserId('_user1'); + $recentBackup->setStatus('dnd'); + $recentBackup->setStatusTimestamp(9000); + $recentBackup->setIsUserDefined(true); + $recentBackup->setIsBackup(true); + $this->mapper->insert($recentBackup); + + // Old backup — should be deleted + $oldBackup = new UserStatus(); + $oldBackup->setUserId('_user2'); + $oldBackup->setStatus('away'); + $oldBackup->setStatusTimestamp(1000); + $oldBackup->setIsUserDefined(false); + $oldBackup->setIsBackup(true); + $this->mapper->insert($oldBackup); + + // Active (non-backup) status with old timestamp — must not be deleted + $activeStatus = new UserStatus(); + $activeStatus->setUserId('user3'); + $activeStatus->setStatus('online'); + $activeStatus->setStatusTimestamp(1000); + $activeStatus->setIsUserDefined(true); + $activeStatus->setIsBackup(false); + $this->mapper->insert($activeStatus); + + $this->mapper->cleanOrphanedBackups(5000); + + // Recent backup survives + $surviving = $this->mapper->findByUserId('user1', true); + $this->assertEquals('_user1', $surviving->getUserId()); + + // Old backup is gone + $this->expectException(DoesNotExistException::class); + $this->mapper->findByUserId('user2', true); + } + + public function testCleanOrphanedBackupsDoesNotTouchActiveStatuses(): void { + $activeStatus = new UserStatus(); + $activeStatus->setUserId('user1'); + $activeStatus->setStatus('online'); + $activeStatus->setStatusTimestamp(1000); + $activeStatus->setIsUserDefined(true); + $activeStatus->setIsBackup(false); + $this->mapper->insert($activeStatus); + + $this->mapper->cleanOrphanedBackups(5000); + + $found = $this->mapper->findByUserId('user1', false); + $this->assertEquals('user1', $found->getUserId()); + } }