-
Notifications
You must be signed in to change notification settings - Fork 38
feat: Delete chat messages and assignments when user is deleted #604
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,59 @@ | ||
| <?php | ||
|
|
||
| declare(strict_types=1); | ||
|
|
||
| /** | ||
| * SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors | ||
| * SPDX-License-Identifier: AGPL-3.0-or-later | ||
| */ | ||
|
|
||
| namespace OCA\Assistant\Listener; | ||
|
|
||
| use OCA\Assistant\Service\AssignmentsService; | ||
| use OCA\Assistant\Service\ChatService; | ||
| use OCA\Assistant\Service\InternalException; | ||
| use OCA\Assistant\Service\SessionSummaryService; | ||
| use OCP\EventDispatcher\Event; | ||
| use OCP\EventDispatcher\IEventListener; | ||
| use OCP\User\Events\UserDeletedEvent; | ||
| use Psr\Log\LoggerInterface; | ||
|
|
||
| /** | ||
| * @template-implements IEventListener<UserDeletedEvent> | ||
| */ | ||
| class UserDeletedListener implements IEventListener { | ||
|
|
||
| public function __construct( | ||
| private ChatService $chatService, | ||
| private AssignmentsService $assignmentsService, | ||
| private LoggerInterface $logger, | ||
| private SessionSummaryService $sessionSummaryService, | ||
| ) { | ||
| } | ||
|
|
||
| public function handle(Event $event): void { | ||
| if (!($event instanceof UserDeletedEvent)) { | ||
| return; | ||
| } | ||
|
|
||
| $userId = $event->getUid(); | ||
|
|
||
| try { | ||
| $this->assignmentsService->deleteAllForUser($userId); | ||
| } catch (InternalException $e) { | ||
| $this->logger->error('Error while deleting assignments for user ' . $userId, ['exception' => $e]); | ||
| } | ||
|
|
||
| try { | ||
| $this->chatService->deleteAllUserChatData($userId); | ||
| } catch (InternalException $e) { | ||
| $this->logger->error('Error while deleting chat data for user ' . $userId, ['exception' => $e]); | ||
| } | ||
|
|
||
| try { | ||
| $this->sessionSummaryService->deleteSummaryJobsForUser($userId); | ||
| } catch (InternalException $e) { | ||
| $this->logger->error('Error while deleting summary jobs for user ' . $userId, ['exception' => $e]); | ||
| } | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,110 @@ | ||
| <?php | ||
|
|
||
| declare(strict_types=1); | ||
|
|
||
| /** | ||
| * SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors | ||
| * SPDX-License-Identifier: AGPL-3.0-or-later | ||
| */ | ||
|
|
||
| namespace OCA\Assistant\Migration; | ||
|
|
||
| use Closure; | ||
| use OCA\Assistant\Service\AssignmentsService; | ||
| use OCA\Assistant\Service\ChatService; | ||
| use OCA\Assistant\Service\InternalException; | ||
| use OCA\Assistant\Service\SessionSummaryService; | ||
| use OCP\DB\ISchemaWrapper; | ||
| use OCP\IDBConnection; | ||
| use OCP\IUserManager; | ||
| use OCP\Migration\IOutput; | ||
| use OCP\Migration\SimpleMigrationStep; | ||
| use Override; | ||
| use Psr\Log\LoggerInterface; | ||
|
|
||
| class Version030500Date20260715083046 extends SimpleMigrationStep { | ||
|
|
||
| public function __construct( | ||
| private IDBConnection $db, | ||
| private IUserManager $userManager, | ||
| private AssignmentsService $assignmentsService, | ||
| private ChatService $chatService, | ||
| private SessionSummaryService $sessionSummaryService, | ||
| private LoggerInterface $logger, | ||
| ) { | ||
| } | ||
|
|
||
| /** | ||
| * Clean up chat and assignment data left behind by users deleted before UserDeletedListener existed. | ||
| * | ||
| * @param IOutput $output | ||
| * @param Closure(): ISchemaWrapper $schemaClosure | ||
| * @param array $options | ||
| */ | ||
| #[Override] | ||
| public function postSchemaChange(IOutput $output, Closure $schemaClosure, array $options): void { | ||
| $userIds = $this->getDistinctUserIds(); | ||
| $cleaned = 0; | ||
|
|
||
| foreach ($userIds as $userId) { | ||
| if ($this->userManager->userExists($userId)) { | ||
| continue; | ||
| } | ||
|
|
||
| try { | ||
| $this->assignmentsService->deleteAllForUser($userId); | ||
| } catch (InternalException $e) { | ||
| $this->logger->error('Error while deleting assignments for deleted user ' . $userId, ['exception' => $e]); | ||
| } | ||
|
|
||
| try { | ||
| $this->chatService->deleteAllUserChatData($userId); | ||
| } catch (InternalException $e) { | ||
| $this->logger->error('Error while deleting chat data for deleted user ' . $userId, ['exception' => $e]); | ||
| } | ||
|
|
||
| try { | ||
| $this->sessionSummaryService->deleteSummaryJobsForUser($userId); | ||
| } catch (InternalException $e) { | ||
| $this->logger->error('Error while deleting summary jobs for deleted user ' . $userId, ['exception' => $e]); | ||
| } | ||
|
|
||
| $cleaned++; | ||
| } | ||
|
|
||
| if ($cleaned > 0) { | ||
| $output->info('Cleaned up assistant data for ' . $cleaned . ' deleted user(s)'); | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * @return list<string> | ||
|
Check failure on line 81 in lib/Migration/Version030500Date20260715083046.php
|
||
| */ | ||
| private function getDistinctUserIds(): array { | ||
| $userIds = []; | ||
|
|
||
| $userIdsChat = []; | ||
| $userIdsAssignments = []; | ||
|
|
||
| if ($this->db->tableExists('assistant_chat_sns')) { | ||
| $qb = $this->db->getQueryBuilder(); | ||
| $qb->selectDistinct('user_id') | ||
| ->from('assistant_chat_sns'); | ||
| $result = $qb->executeQuery(); | ||
| $userIdsChat = $result->fetchFirstColumn(); | ||
| $result->closeCursor(); | ||
| } | ||
|
|
||
| if ($this->db->tableExists('assistant_assignments')) { | ||
| $qb = $this->db->getQueryBuilder(); | ||
| $qb->selectDistinct('user_id') | ||
| ->from('assistant_assignments'); | ||
| $result = $qb->executeQuery(); | ||
| $userIdsAssignments = $result->fetchFirstColumn(); | ||
| $result->closeCursor(); | ||
| } | ||
|
|
||
| $userIds = array_unique(array_merge($userIdsChat, $userIdsAssignments)); | ||
| return $userIds; | ||
|
Check failure on line 108 in lib/Migration/Version030500Date20260715083046.php
|
||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -21,6 +21,7 @@ | |
| use OCP\DB\Exception; | ||
| use OCP\IDateTimeZone; | ||
| use OCP\IL10N; | ||
| use OCP\IUserManager; | ||
| use Psr\Log\LoggerInterface; | ||
|
|
||
| class AssignmentsService { | ||
|
|
@@ -33,6 +34,7 @@ public function __construct( | |
| private IJobList $jobList, | ||
| private IL10N $l10n, | ||
| private IDateTimeZone $dateTimeZone, | ||
| private IUserManager $userManager, | ||
| ) { | ||
| } | ||
|
|
||
|
|
@@ -84,13 +86,31 @@ public function createAssignment(?string $userId, string $title, string $prompt, | |
| return $assignment; | ||
| } | ||
|
|
||
| /** | ||
| * @throws InternalException | ||
| */ | ||
| public function deleteAllForUser(string $userId): void { | ||
| try { | ||
| $this->assignmentMapper->deleteAllForUser($userId); | ||
| } catch (Exception $e) { | ||
| throw new InternalException(previous: $e); | ||
| } | ||
| if ($this->jobList->has(RunAssignmentsJob::class, ['userId' => $userId])) { | ||
| $this->jobList->remove(RunAssignmentsJob::class, ['userId' => $userId]); | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * @throws InternalException|UnauthorizedException | ||
| */ | ||
| public function runDueAssignmentsForUser(?string $userId): void { | ||
| if ($userId === null) { | ||
| throw new UnauthorizedException(); | ||
| } | ||
| if ($this->userManager->get($userId) === null) { | ||
| $this->deleteAllForUser($userId); | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should we also clean everything up here? The chat sessions+messages and the summary bg jobs?
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What do you mean, they should already be cleaned up via listener, right?
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If some users where deleted before this PR, there can still be data hanging around.
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fair, but maybe that should be a migration step and not solved here?
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yep, a repair step seems appropriate.
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. A repair step makes sense for this. |
||
| return; | ||
| } | ||
| try { | ||
| foreach ($this->assignmentMapper->findDueAssignmentsForUser($userId) as $assignment) { | ||
| if ($assignment === null) { | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We don't cleanup the jobs created by
SessionSummaryService. I think we should check if there are some jobs scheduled for the user and remove them from the bg job queue.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pushed a new commit that does both.