feat: update folder deletion process to queue document deletions first and handle folder cleanup in Celery task

This commit is contained in:
Anish Sarkar 2026-04-03 04:16:19 +05:30
parent 44e39792da
commit fe7fcaae5d
3 changed files with 42 additions and 23 deletions

View file

@ -367,7 +367,7 @@ async def delete_folder(
session: AsyncSession = Depends(get_async_session), session: AsyncSession = Depends(get_async_session),
user: User = Depends(current_active_user), user: User = Depends(current_active_user),
): ):
"""Delete a folder and cascade-delete subfolders. Documents are async-deleted via Celery.""" """Mark documents for deletion and dispatch Celery to delete docs first, then folders."""
try: try:
folder = await session.get(Folder, folder_id) folder = await session.get(Folder, folder_id)
if not folder: if not folder:
@ -399,30 +399,29 @@ async def delete_folder(
) )
await session.commit() await session.commit()
await session.execute(Folder.__table__.delete().where(Folder.id == folder_id)) try:
await session.commit() from app.tasks.celery_tasks.document_tasks import (
delete_folder_documents_task,
)
if document_ids: delete_folder_documents_task.delay(
try: document_ids, folder_subtree_ids=list(subtree_ids)
from app.tasks.celery_tasks.document_tasks import ( )
delete_folder_documents_task, except Exception as err:
) if document_ids:
delete_folder_documents_task.delay(document_ids)
except Exception as err:
await session.execute( await session.execute(
Document.__table__.update() Document.__table__.update()
.where(Document.id.in_(document_ids)) .where(Document.id.in_(document_ids))
.values(status={"state": "ready"}) .values(status={"state": "ready"})
) )
await session.commit() await session.commit()
raise HTTPException( raise HTTPException(
status_code=503, status_code=503,
detail="Folder deleted but document cleanup could not be queued. Documents have been restored.", detail="Could not queue folder deletion. Documents have been restored.",
) from err ) from err
return { return {
"message": "Folder deleted successfully", "message": "Folder deletion started",
"documents_queued_for_deletion": len(document_ids), "documents_queued_for_deletion": len(document_ids),
} }

View file

@ -142,21 +142,30 @@ async def _delete_document_background(document_id: int) -> None:
retry_backoff_max=300, retry_backoff_max=300,
max_retries=5, max_retries=5,
) )
def delete_folder_documents_task(self, document_ids: list[int]): def delete_folder_documents_task(
"""Celery task to batch-delete documents orphaned by folder deletion.""" self,
document_ids: list[int],
folder_subtree_ids: list[int] | None = None,
):
"""Celery task to delete documents first, then the folder rows."""
loop = asyncio.new_event_loop() loop = asyncio.new_event_loop()
asyncio.set_event_loop(loop) asyncio.set_event_loop(loop)
try: try:
loop.run_until_complete(_delete_folder_documents(document_ids)) loop.run_until_complete(
_delete_folder_documents(document_ids, folder_subtree_ids)
)
finally: finally:
loop.close() loop.close()
async def _delete_folder_documents(document_ids: list[int]) -> None: async def _delete_folder_documents(
"""Delete chunks in batches, then document rows for each orphaned document.""" document_ids: list[int],
folder_subtree_ids: list[int] | None = None,
) -> None:
"""Delete chunks in batches, then document rows, then folder rows."""
from sqlalchemy import delete as sa_delete, select from sqlalchemy import delete as sa_delete, select
from app.db import Chunk, Document from app.db import Chunk, Document, Folder
async with get_celery_session_maker()() as session: async with get_celery_session_maker()() as session:
batch_size = 500 batch_size = 500
@ -178,6 +187,12 @@ async def _delete_folder_documents(document_ids: list[int]) -> None:
await session.delete(doc) await session.delete(doc)
await session.commit() await session.commit()
if folder_subtree_ids:
await session.execute(
sa_delete(Folder).where(Folder.id.in_(folder_subtree_ids))
)
await session.commit()
@celery_app.task( @celery_app.task(
name="delete_search_space_background", name="delete_search_space_background",

View file

@ -188,7 +188,12 @@ export function DocumentsSidebar({
const treeDocuments: DocumentNodeDoc[] = useMemo(() => { const treeDocuments: DocumentNodeDoc[] = useMemo(() => {
const zeroDocs = (zeroAllDocs ?? []) const zeroDocs = (zeroAllDocs ?? [])
.filter((d) => d.title && d.title.trim() !== "") .filter((d) => {
if (!d.title || d.title.trim() === "") return false;
const state = (d.status as { state?: string } | undefined)?.state;
if (state === "deleting") return false;
return true;
})
.map((d) => ({ .map((d) => ({
id: d.id, id: d.id,
title: d.title, title: d.title,