diff --git a/backend/app/routers/nextcloud.py b/backend/app/routers/nextcloud.py index 1461088..bd53543 100644 --- a/backend/app/routers/nextcloud.py +++ b/backend/app/routers/nextcloud.py @@ -333,14 +333,18 @@ async def delete_nextcloud_source_root( ) ) # Folders have a self-referential parent_id FK with no - # ON DELETE rule. A flat DELETE of the whole subtree trips - # `folders_parent_id_fkey` because postgres checks the - # constraint per row, regardless of insertion / list order. - # NULL the parent_id on every folder we're about to delete - # first so the chain breaks cleanly. + # ON DELETE rule. NULL parent_id on every folder that points + # *into* our delete set — that includes children within this + # source root AND any folder under a different SourceRoot whose + # path happens to nest inside this one (e.g. a 'Leóns 1st Year' + # SourceRoot at `.../Taco and Muli - 2024 onward/Leóns 1st Year` + # has folder rows whose parent_id points at folder rows under + # 'Taco and Muli - 2024 onward'). Without this, deleting the + # outer SourceRoot trips folders_parent_id_fkey from the inner + # SourceRoot's still-live rows. await db.execute( update(Folder) - .where(Folder.id.in_(folder_ids)) + .where(Folder.parent_id.in_(folder_ids)) .values(parent_id=None) ) await db.execute(delete(Folder).where(Folder.id.in_(folder_ids))) diff --git a/frontend/src/components/dialogs/SettingsDialog.tsx b/frontend/src/components/dialogs/SettingsDialog.tsx index 1c7c40e..0ff1c8e 100644 --- a/frontend/src/components/dialogs/SettingsDialog.tsx +++ b/frontend/src/components/dialogs/SettingsDialog.tsx @@ -1594,9 +1594,17 @@ function NextcloudIntegrationCard() { variant="ghost" disabled={removeRoot.isPending} onClick={() => removeRoot.mutate(r.id)} - title="Remove this library" + title={ + removeRoot.isPending && removeRoot.variables === r.id + ? 'Removing — cascades through every photo + folder, may take a few seconds' + : 'Remove this library' + } > - + {removeRoot.isPending && removeRoot.variables === r.id ? ( + + ) : ( + + )} ))}