Skip to content

Commit faf39c4

Browse files
feat(archiving): add mapper support for archive override cleanup
Add UserArchiveMapper::findUserIdsForNode() and deleteAllForUser(), plus ShareMapper::findNodesByReceiver(), as building blocks for cleaning up per-user archive overrides when access is lost. Assisted-by: ClaudeCode:claude-fable-5 Signed-off-by: Andy Scherzinger <info@andy-scherzinger.de>
1 parent 1dddabb commit faf39c4

3 files changed

Lines changed: 94 additions & 0 deletions

File tree

lib/Db/ShareMapper.php

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -301,6 +301,28 @@ public function changeReceiverForNode(string $nodeType, int $nodeId, string $new
301301
->executeStatement();
302302
}
303303

304+
/**
305+
* Find the distinct nodes shared with a given receiver.
306+
*
307+
* @return list<array{nodeType: string, nodeId: int}>
308+
* @throws Exception
309+
*/
310+
public function findNodesByReceiver(string $receiver, string $receiverType): array {
311+
$qb = $this->db->getQueryBuilder();
312+
$qb->selectDistinct(['node_type', 'node_id'])
313+
->from($this->table)
314+
->where($qb->expr()->eq('receiver', $qb->createNamedParameter($receiver, IQueryBuilder::PARAM_STR)))
315+
->andWhere($qb->expr()->eq('receiver_type', $qb->createNamedParameter($receiverType, IQueryBuilder::PARAM_STR)));
316+
317+
$result = $qb->executeQuery();
318+
$nodes = [];
319+
while ($row = $result->fetchAssociative()) {
320+
$nodes[] = ['nodeType' => (string)$row['node_type'], 'nodeId' => (int)$row['node_id']];
321+
}
322+
$result->closeCursor();
323+
return $nodes;
324+
}
325+
304326
/**
305327
* @throws Exception
306328
*/

lib/Db/UserArchiveMapper.php

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,41 @@ public function upsert(string $userId, int $nodeType, int $nodeId, bool $archive
9797
]);
9898
}
9999

100+
/**
101+
* Fetch the IDs of all users holding an archive override for a node.
102+
*
103+
* @return string[]
104+
* @throws Exception
105+
*/
106+
public function findUserIdsForNode(int $nodeType, int $nodeId): array {
107+
$qb = $this->db->getQueryBuilder();
108+
$qb->select('user_id')
109+
->from($this->table)
110+
->where($qb->expr()->eq('node_type', $qb->createNamedParameter($nodeType, IQueryBuilder::PARAM_INT)))
111+
->andWhere($qb->expr()->eq('node_id', $qb->createNamedParameter($nodeId, IQueryBuilder::PARAM_INT)));
112+
113+
$result = $qb->executeQuery();
114+
$userIds = array_map(static fn (array $row) => (string)$row['user_id'], $result->fetchAllAssociative());
115+
$result->closeCursor();
116+
return $userIds;
117+
}
118+
119+
/**
120+
* Remove every archive override a user holds, on any node.
121+
*
122+
* Called when the user account is deleted, so no orphaned rows remain
123+
* for nodes owned by other users.
124+
*
125+
* @throws Exception
126+
*/
127+
public function deleteAllForUser(string $userId): void {
128+
$qb = $this->db->getQueryBuilder();
129+
$qb->delete($this->table)
130+
->where($qb->expr()->eq('user_id', $qb->createNamedParameter($userId, IQueryBuilder::PARAM_STR)));
131+
132+
$qb->executeStatement();
133+
}
134+
100135
/**
101136
* Remove the per-user archive override for a single user.
102137
*

tests/unit/Db/UserArchiveMapperTest.php

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,43 @@ public function testFindAllOverridesForUserIgnoresOtherNodeTypes(): void {
148148
$this->assertEmpty($results);
149149
}
150150

151+
// -------------------------------------------------------------------------
152+
// findUserIdsForNode
153+
// -------------------------------------------------------------------------
154+
155+
public function testFindUserIdsForNodeReturnsAllHolders(): void {
156+
$this->mapper->upsert('alice', Application::NODE_TYPE_TABLE, 5, true);
157+
$this->mapper->upsert('bob', Application::NODE_TYPE_TABLE, 5, false);
158+
$this->mapper->upsert('carol', Application::NODE_TYPE_TABLE, 6, true);
159+
160+
$userIds = $this->mapper->findUserIdsForNode(Application::NODE_TYPE_TABLE, 5);
161+
162+
sort($userIds);
163+
$this->assertSame(['alice', 'bob'], $userIds);
164+
}
165+
166+
public function testFindUserIdsForNodeReturnsEmptyWhenNone(): void {
167+
$this->assertSame([], $this->mapper->findUserIdsForNode(Application::NODE_TYPE_TABLE, 999));
168+
}
169+
170+
// -------------------------------------------------------------------------
171+
// deleteAllForUser
172+
// -------------------------------------------------------------------------
173+
174+
public function testDeleteAllForUserRemovesAllNodesAndTypes(): void {
175+
$this->mapper->upsert('alice', Application::NODE_TYPE_TABLE, 1, true);
176+
$this->mapper->upsert('alice', Application::NODE_TYPE_TABLE, 2, false);
177+
$this->mapper->upsert('alice', Application::NODE_TYPE_CONTEXT, 3, true);
178+
$this->mapper->upsert('bob', Application::NODE_TYPE_TABLE, 1, true);
179+
180+
$this->mapper->deleteAllForUser('alice');
181+
182+
$this->assertNull($this->mapper->findForUser('alice', Application::NODE_TYPE_TABLE, 1));
183+
$this->assertNull($this->mapper->findForUser('alice', Application::NODE_TYPE_TABLE, 2));
184+
$this->assertNull($this->mapper->findForUser('alice', Application::NODE_TYPE_CONTEXT, 3));
185+
$this->assertNotNull($this->mapper->findForUser('bob', Application::NODE_TYPE_TABLE, 1));
186+
}
187+
151188
// -------------------------------------------------------------------------
152189
// deleteForUser
153190
// -------------------------------------------------------------------------

0 commit comments

Comments
 (0)