1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
|
"""Deleting a group from the hub, completely.
One implementation for every caller: the owner deleting a group, an
administrator erasing the account that owns it, and the cleanup of groups no
node ever hosted. Each used to carry its own partial cascade, and a row still
pointing at a deleted group is not an orphan on PostgreSQL but a foreign-key
error — the deletion fails. SQLite, which the tests run on, does not enforce
foreign keys by default, so nothing showed it.
The tables are found from the schema rather than listed: every table with a
foreign key to `groups.id` is emptied of the group's rows, including one added
after this was written. A table whose rows must outlive the group goes in
`_DETACHED`, and has its reference set to null instead.
Nothing on a node is touched. The hub does not command those machines; the
administrator's route pushes a signed revocation to the connected ones.
"""
from sqlalchemy import delete, update
from sqlalchemy.ext.asyncio import AsyncSession
from meshbay_hub.db.models import Base, ContentReport, Group
# Rows that outlive their group, detached rather than deleted: a report is
# evidence about content, and it is still needed once the group is gone.
_DETACHED = {ContentReport.__tablename__}
def _referencing() -> list[tuple]:
"""(table, column) for every foreign key onto `groups.id`."""
refs = []
for table in Base.metadata.sorted_tables:
for fk in table.foreign_keys:
if fk.column.table.name == Group.__tablename__ and fk.column.name == "id":
refs.append((table, fk.parent))
return refs
async def purge_groups(db: AsyncSession, group_ids: list[str]) -> None:
"""Delete these groups and everything on the hub that points at them.
The caller commits: this is one step of a larger transaction (an account
erasure deletes its groups and then empties the account row).
"""
if not group_ids:
return
for table, column in _referencing():
if table.name in _DETACHED:
await db.execute(update(table).where(column.in_(group_ids)).values({column.name: None}))
else:
await db.execute(delete(table).where(column.in_(group_ids)))
await db.execute(delete(Group).where(Group.id.in_(group_ids)))
|