From c7be40ad2d5c4d7226b1b55771803c7ca8d84249 Mon Sep 17 00:00:00 2001 From: Jana Peper Date: Wed, 15 Jul 2026 16:11:37 +0200 Subject: [PATCH 1/5] feat: Delete chat messages when user is deleted Signed-off-by: Jana Peper --- lib/AppInfo/Application.php | 4 + lib/Db/ChattyLLM/SessionMapper.php | 11 +++ lib/Listener/UserDeletedListener.php | 51 ++++++++++ .../Version030500Date20260715083046.php | 93 +++++++++++++++++++ lib/Service/ChatService.php | 38 ++++++++ lib/Service/InternalException.php | 6 ++ lib/Service/SessionSummaryService.php | 9 ++ 7 files changed, 212 insertions(+) create mode 100644 lib/Listener/UserDeletedListener.php create mode 100644 lib/Migration/Version030500Date20260715083046.php create mode 100644 lib/Service/ChatService.php create mode 100644 lib/Service/InternalException.php diff --git a/lib/AppInfo/Application.php b/lib/AppInfo/Application.php index b1b2771f3..fac780b05 100644 --- a/lib/AppInfo/Application.php +++ b/lib/AppInfo/Application.php @@ -23,6 +23,7 @@ use OCA\Assistant\Listener\TaskSuccessfulListener; use OCA\Assistant\Listener\Text2Image\Text2ImageReferenceListener; use OCA\Assistant\Listener\Text2Image\Text2StickerListener; +use OCA\Assistant\Listener\UserDeletedListener; use OCA\Assistant\Notification\Notifier; use OCA\Assistant\Reference\FreePromptReferenceProvider; use OCA\Assistant\Reference\SpeechToTextReferenceProvider; @@ -47,6 +48,7 @@ use OCP\TaskProcessing\Events\TaskFailedEvent; use OCP\TaskProcessing\Events\TaskSuccessfulEvent; use OCP\TaskProcessing\IManager; +use OCP\User\Events\UserDeletedEvent; class Application extends App implements IBootstrap { @@ -99,6 +101,8 @@ public function register(IRegistrationContext $context): void { $context->registerNotifierService(Notifier::class); $context->registerEventListener(AddContentSecurityPolicyEvent::class, CSPListener::class); + + $context->registerEventListener(UserDeletedEvent::class, UserDeletedListener::class); if (class_exists('OCP\\TaskProcessing\\TaskTypes\\AudioToAudioChat')) { $context->registerTaskProcessingProvider(AudioToAudioChatProvider::class); diff --git a/lib/Db/ChattyLLM/SessionMapper.php b/lib/Db/ChattyLLM/SessionMapper.php index 9006bcf63..660543c09 100644 --- a/lib/Db/ChattyLLM/SessionMapper.php +++ b/lib/Db/ChattyLLM/SessionMapper.php @@ -180,6 +180,17 @@ public function deleteSession(string $userId, int $sessionId) { $qb->executeStatement(); } + /** + * @throws \OCP\DB\Exception + */ + public function deleteAllSessionsForUser(string $userId): void { + $qb = $this->db->getQueryBuilder(); + $qb->delete($this->getTableName()) + ->where($qb->expr()->eq('user_id', $qb->createPositionalParameter($userId, IQueryBuilder::PARAM_STR))); + + $qb->executeStatement(); + } + public function updateSessionIsRemembered(?string $userId, int $sessionId, bool $is_remembered) { $session = $this->getUserSession($userId, $sessionId); $session->setIsRemembered($is_remembered); diff --git a/lib/Listener/UserDeletedListener.php b/lib/Listener/UserDeletedListener.php new file mode 100644 index 000000000..73cb60f90 --- /dev/null +++ b/lib/Listener/UserDeletedListener.php @@ -0,0 +1,51 @@ + + */ +class UserDeletedListener implements IEventListener { + + public function __construct( + private ChatService $chatService, + private LoggerInterface $logger, + private SessionSummaryService $sessionSummaryService, + ) { + } + + public function handle(Event $event): void { + if (!($event instanceof UserDeletedEvent)) { + return; + } + + $userId = $event->getUid(); + + 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]); + } + } +} \ No newline at end of file diff --git a/lib/Migration/Version030500Date20260715083046.php b/lib/Migration/Version030500Date20260715083046.php new file mode 100644 index 000000000..0afce1750 --- /dev/null +++ b/lib/Migration/Version030500Date20260715083046.php @@ -0,0 +1,93 @@ +getDistinctUserIds(); + $cleaned = 0; + + foreach ($userIds as $userId) { + if ($this->userManager->userExists($userId)) { + continue; + } + + 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 + */ + 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(); + } + + $userIds = array_unique(array_merge($userIdsChat, $userIdsAssignments)); + return $userIds; + } +} \ No newline at end of file diff --git a/lib/Service/ChatService.php b/lib/Service/ChatService.php new file mode 100644 index 000000000..a88d12a11 --- /dev/null +++ b/lib/Service/ChatService.php @@ -0,0 +1,38 @@ +sessionMapper->getUserSessions($userId); + foreach ($sessions as $session) { + $this->messageMapper->deleteMessagesBySession($session->getId()); + } + $this->sessionMapper->deleteAllSessionsForUser($userId); + } catch (Exception|\RuntimeException $e) { + throw new InternalException(previous: $e); + } + } + +} \ No newline at end of file diff --git a/lib/Service/InternalException.php b/lib/Service/InternalException.php new file mode 100644 index 000000000..c844f6b52 --- /dev/null +++ b/lib/Service/InternalException.php @@ -0,0 +1,6 @@ +jobList->has(GenerateNewChatSummaries::class, ['userId' => $userId])) { + $this->jobList->remove(GenerateNewChatSummaries::class, ['userId' => $userId]); + } + if ($this->jobList->has(RegenerateOutdatedChatSummariesJob::class, ['userId' => $userId])) { + $this->jobList->remove(RegenerateOutdatedChatSummariesJob::class, ['userId' => $userId]); + } + } + private function generateSummaries(array $sessions): void { foreach ($sessions as $session) { try { From 485fe6b954667c8af688c24b6738f99b4feff7d1 Mon Sep 17 00:00:00 2001 From: Jana Peper Date: Wed, 15 Jul 2026 16:23:44 +0200 Subject: [PATCH 2/5] chore: bump version nr Signed-off-by: Jana Peper --- appinfo/info.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/appinfo/info.xml b/appinfo/info.xml index b3884af95..dbc31e7f0 100644 --- a/appinfo/info.xml +++ b/appinfo/info.xml @@ -62,7 +62,7 @@ Known providers: More details on how to set this up in the [admin docs](https://docs.nextcloud.com/server/latest/admin_manual/ai/index.html) ]]> - 3.4.3 + 3.5.0.-dev.1 agpl Julien Veyssier Assistant From 7f52837cf9a5d5d54a23d2377ccb9f4fb319b400 Mon Sep 17 00:00:00 2001 From: Jana Peper Date: Wed, 15 Jul 2026 18:54:54 +0200 Subject: [PATCH 3/5] chore: fix cs Signed-off-by: Jana Peper --- lib/AppInfo/Application.php | 2 +- lib/Listener/UserDeletedListener.php | 2 +- lib/Migration/Version030500Date20260715083046.php | 2 +- lib/Service/ChatService.php | 4 ++-- lib/Service/InternalException.php | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/lib/AppInfo/Application.php b/lib/AppInfo/Application.php index fac780b05..07296100e 100644 --- a/lib/AppInfo/Application.php +++ b/lib/AppInfo/Application.php @@ -101,7 +101,7 @@ public function register(IRegistrationContext $context): void { $context->registerNotifierService(Notifier::class); $context->registerEventListener(AddContentSecurityPolicyEvent::class, CSPListener::class); - + $context->registerEventListener(UserDeletedEvent::class, UserDeletedListener::class); if (class_exists('OCP\\TaskProcessing\\TaskTypes\\AudioToAudioChat')) { diff --git a/lib/Listener/UserDeletedListener.php b/lib/Listener/UserDeletedListener.php index 73cb60f90..acbc4b799 100644 --- a/lib/Listener/UserDeletedListener.php +++ b/lib/Listener/UserDeletedListener.php @@ -48,4 +48,4 @@ public function handle(Event $event): void { $this->logger->error('Error while deleting summary jobs for user ' . $userId, ['exception' => $e]); } } -} \ No newline at end of file +} diff --git a/lib/Migration/Version030500Date20260715083046.php b/lib/Migration/Version030500Date20260715083046.php index 0afce1750..a327bca4b 100644 --- a/lib/Migration/Version030500Date20260715083046.php +++ b/lib/Migration/Version030500Date20260715083046.php @@ -90,4 +90,4 @@ private function getDistinctUserIds(): array { $userIds = array_unique(array_merge($userIdsChat, $userIdsAssignments)); return $userIds; } -} \ No newline at end of file +} diff --git a/lib/Service/ChatService.php b/lib/Service/ChatService.php index a88d12a11..97e325c5f 100644 --- a/lib/Service/ChatService.php +++ b/lib/Service/ChatService.php @@ -20,7 +20,7 @@ public function __construct( ) { } - /** + /** * @throws InternalException */ public function deleteAllUserChatData(string $userId): void { @@ -35,4 +35,4 @@ public function deleteAllUserChatData(string $userId): void { } } -} \ No newline at end of file +} diff --git a/lib/Service/InternalException.php b/lib/Service/InternalException.php index c844f6b52..ad4463c3c 100644 --- a/lib/Service/InternalException.php +++ b/lib/Service/InternalException.php @@ -3,4 +3,4 @@ namespace OCA\Assistant\Service; class InternalException extends \Exception { -} \ No newline at end of file +} From c6af3e7c074dbb113b3077670444789421ae3ff2 Mon Sep 17 00:00:00 2001 From: Jana Peper Date: Wed, 15 Jul 2026 18:56:02 +0200 Subject: [PATCH 4/5] chore: fix reuse Signed-off-by: Jana Peper --- lib/Service/InternalException.php | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/lib/Service/InternalException.php b/lib/Service/InternalException.php index ad4463c3c..28cd5c0d1 100644 --- a/lib/Service/InternalException.php +++ b/lib/Service/InternalException.php @@ -1,5 +1,10 @@ Date: Wed, 15 Jul 2026 18:57:17 +0200 Subject: [PATCH 5/5] chore: fix psalm Signed-off-by: Jana Peper --- lib/Migration/Version030500Date20260715083046.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/Migration/Version030500Date20260715083046.php b/lib/Migration/Version030500Date20260715083046.php index a327bca4b..60da26478 100644 --- a/lib/Migration/Version030500Date20260715083046.php +++ b/lib/Migration/Version030500Date20260715083046.php @@ -70,7 +70,7 @@ public function postSchemaChange(IOutput $output, Closure $schemaClosure, array } /** - * @return list + * @return array */ private function getDistinctUserIds(): array { $userIds = [];