"""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)))