Skip to content

Commit 8165fab

Browse files
committed
feat: Delete chat messages and assignments when user is deleted and stop running assignments
Signed-off-by: Jana Peper <jana.peper@nextcloud.com>
1 parent 72bcc6f commit 8165fab

6 files changed

Lines changed: 125 additions & 0 deletions

File tree

lib/AppInfo/Application.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
use OCA\Assistant\Listener\TaskSuccessfulListener;
2424
use OCA\Assistant\Listener\Text2Image\Text2ImageReferenceListener;
2525
use OCA\Assistant\Listener\Text2Image\Text2StickerListener;
26+
use OCA\Assistant\Listener\UserDeletedListener;
2627
use OCA\Assistant\Notification\Notifier;
2728
use OCA\Assistant\Reference\FreePromptReferenceProvider;
2829
use OCA\Assistant\Reference\SpeechToTextReferenceProvider;
@@ -48,6 +49,7 @@
4849
use OCP\TaskProcessing\Events\TaskFailedEvent;
4950
use OCP\TaskProcessing\Events\TaskSuccessfulEvent;
5051
use OCP\TaskProcessing\IManager;
52+
use OCP\User\Events\UserDeletedEvent;
5153

5254
class Application extends App implements IBootstrap {
5355

@@ -102,6 +104,8 @@ public function register(IRegistrationContext $context): void {
102104

103105
$context->registerEventListener(AddContentSecurityPolicyEvent::class, CSPListener::class);
104106

107+
$context->registerEventListener(UserDeletedEvent::class, UserDeletedListener::class);
108+
105109
if (class_exists('OCP\\TaskProcessing\\TaskTypes\\AudioToAudioChat')) {
106110
$context->registerTaskProcessingProvider(AudioToAudioChatProvider::class);
107111
}

lib/Db/AssignmentMapper.php

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,4 +86,15 @@ public function findDueAssignmentsForUser(string $userId): \Generator {
8686
yield $assignment;
8787
}
8888
}
89+
90+
/**
91+
* @throws \OCP\DB\Exception
92+
*/
93+
public function deleteAllForUser(string $userId): void {
94+
$qb = $this->db->getQueryBuilder();
95+
$qb->delete($this->getTableName())
96+
->where($qb->expr()->eq('user_id', $qb->createPositionalParameter($userId, IQueryBuilder::PARAM_STR)));
97+
98+
$qb->executeStatement();
99+
}
89100
}

lib/Db/ChattyLLM/SessionMapper.php

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,19 @@ public function getUserSessionForAssignment(string $userId, int $assignmentId):
8282
return $this->findEntity($qb);
8383
}
8484

85+
/**
86+
* @return \Generator<array-key, Session>
87+
* @throws \OCP\DB\Exception
88+
*/
89+
public function getAllUserSessions(string $userId): \Generator {
90+
$qb = $this->db->getQueryBuilder();
91+
$qb->select(Session::$columns)
92+
->from($this->getTableName())
93+
->where($qb->expr()->eq('user_id', $qb->createPositionalParameter($userId, IQueryBuilder::PARAM_STR)));
94+
95+
yield from $this->yieldEntities($qb);
96+
}
97+
8598
/**
8699
* @param string $userId
87100
* @param bool $isAssignment
@@ -205,6 +218,17 @@ public function deleteSession(string $userId, int $sessionId) {
205218
$qb->executeStatement();
206219
}
207220

221+
/**
222+
* @throws \OCP\DB\Exception
223+
*/
224+
public function deleteAllSessionsForUser(string $userId): void {
225+
$qb = $this->db->getQueryBuilder();
226+
$qb->delete($this->getTableName())
227+
->where($qb->expr()->eq('user_id', $qb->createPositionalParameter($userId, IQueryBuilder::PARAM_STR)));
228+
229+
$qb->executeStatement();
230+
}
231+
208232
public function updateSessionIsRemembered(?string $userId, int $sessionId, bool $is_remembered) {
209233
$session = $this->getUserSession($userId, $sessionId);
210234
$session->setIsRemembered($is_remembered);
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
/**
6+
* SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors
7+
* SPDX-License-Identifier: AGPL-3.0-or-later
8+
*/
9+
10+
namespace OCA\Assistant\Listener;
11+
12+
use OCA\Assistant\Service\AssignmentsService;
13+
use OCA\Assistant\Service\ChatService;
14+
use OCA\Assistant\Service\InternalException;
15+
use OCP\EventDispatcher\Event;
16+
use OCP\EventDispatcher\IEventListener;
17+
use OCP\User\Events\UserDeletedEvent;
18+
use Psr\Log\LoggerInterface;
19+
20+
/**
21+
* @template-implements IEventListener<UserDeletedEvent>
22+
*/
23+
class UserDeletedListener implements IEventListener {
24+
25+
public function __construct(
26+
private ChatService $chatService,
27+
private AssignmentsService $assignmentsService,
28+
private LoggerInterface $logger,
29+
) {
30+
}
31+
32+
public function handle(Event $event): void {
33+
if (!($event instanceof UserDeletedEvent)) {
34+
return;
35+
}
36+
37+
$userId = $event->getUid();
38+
39+
try {
40+
$this->assignmentsService->deleteAllForUser($userId);
41+
} catch (InternalException $e) {
42+
$this->logger->error('Error while deleting assignments for user ' . $userId, ['exception' => $e]);
43+
}
44+
45+
try {
46+
$this->chatService->deleteAllUserChatData($userId);
47+
} catch (InternalException $e) {
48+
$this->logger->error('Error while deleting chat data for user ' . $userId, ['exception' => $e]);
49+
}
50+
}
51+
}

lib/Service/AssignmentsService.php

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
use OCP\DB\Exception;
2222
use OCP\IDateTimeZone;
2323
use OCP\IL10N;
24+
use OCP\IUserManager;
2425
use Psr\Log\LoggerInterface;
2526

2627
class AssignmentsService {
@@ -33,6 +34,7 @@ public function __construct(
3334
private IJobList $jobList,
3435
private IL10N $l10n,
3536
private IDateTimeZone $dateTimeZone,
37+
private IUserManager $userManager,
3638
) {
3739
}
3840

@@ -84,13 +86,31 @@ public function createAssignment(?string $userId, string $title, string $prompt,
8486
return $assignment;
8587
}
8688

89+
/**
90+
* @throws InternalException
91+
*/
92+
public function deleteAllForUser(string $userId): void {
93+
try {
94+
$this->assignmentMapper->deleteAllForUser($userId);
95+
} catch (Exception $e) {
96+
throw new InternalException(previous: $e);
97+
}
98+
if ($this->jobList->has(RunAssignmentsJob::class, ['userId' => $userId])) {
99+
$this->jobList->remove(RunAssignmentsJob::class, ['userId' => $userId]);
100+
}
101+
}
102+
87103
/**
88104
* @throws InternalException|UnauthorizedException
89105
*/
90106
public function runDueAssignmentsForUser(?string $userId): void {
91107
if ($userId === null) {
92108
throw new UnauthorizedException();
93109
}
110+
if ($this->userManager->get($userId) === null) {
111+
$this->deleteAllForUser($userId);
112+
return;
113+
}
94114
try {
95115
foreach ($this->assignmentMapper->findDueAssignmentsForUser($userId) as $assignment) {
96116
if ($assignment === null) {

lib/Service/ChatService.php

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,21 @@ public function deleteSession(?string $userId, int $sessionId): void {
152152
}
153153
}
154154

155+
/**
156+
* @throws InternalException
157+
*/
158+
public function deleteAllUserChatData(string $userId): void {
159+
try {
160+
$sessions = $this->sessionMapper->getAllUserSessions($userId);
161+
foreach ($sessions as $session) {
162+
$this->messageMapper->deleteMessagesBySession($session->getId());
163+
}
164+
$this->sessionMapper->deleteAllSessionsForUser($userId);
165+
} catch (Exception|\RuntimeException $e) {
166+
throw new InternalException(previous: $e);
167+
}
168+
}
169+
155170
/**
156171
* @return list<Session>
157172
* @throws InternalException

0 commit comments

Comments
 (0)