feat: add Paperless orphan cleanup endpoint
- POST /maintenance/cleanup/paperless - detect and clean orphaned Paperless documents - Checks indexed documents against Paperless API - Removes vectors and graph nodes for deleted documents - Supports dry_run mode for preview 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -1306,6 +1306,48 @@ Feel free to expand it with more details!
|
||||
logger.error(f"Failed to delete document {document_id} from graph: {e}", exc_info=True)
|
||||
return 0
|
||||
|
||||
async def delete_paperless_document(
|
||||
self,
|
||||
paperless_id: int,
|
||||
user: str
|
||||
) -> int:
|
||||
"""
|
||||
Delete a Paperless document node and all its relationships.
|
||||
|
||||
Args:
|
||||
paperless_id: Paperless-ngx document ID
|
||||
user: User identifier
|
||||
|
||||
Returns:
|
||||
Number of nodes deleted (1 if successful, 0 if not found)
|
||||
"""
|
||||
user_doc_label = get_neo4j_user_label(user)
|
||||
|
||||
delete_query = f"""
|
||||
MATCH (d:{user_doc_label}:Document {{paperless_id: $paperless_id}})
|
||||
DETACH DELETE d
|
||||
RETURN count(d) as deleted_count
|
||||
"""
|
||||
|
||||
try:
|
||||
result = await self.neo4j.execute_query(
|
||||
delete_query,
|
||||
{"paperless_id": paperless_id}
|
||||
)
|
||||
|
||||
deleted_count = result[0]["deleted_count"] if result else 0
|
||||
|
||||
if deleted_count > 0:
|
||||
logger.info(f"Deleted Document node for Paperless document {paperless_id}")
|
||||
else:
|
||||
logger.debug(f"No Document node found for Paperless document {paperless_id}")
|
||||
|
||||
return deleted_count
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to delete Paperless document {paperless_id} from graph: {e}", exc_info=True)
|
||||
return 0
|
||||
|
||||
async def delete_collection_node(
|
||||
self,
|
||||
collection_id: str,
|
||||
|
||||
@@ -389,6 +389,39 @@ class VectorService:
|
||||
logger.error(f"Failed to delete chunks for document {document_id}: {e}", exc_info=True)
|
||||
return 0
|
||||
|
||||
async def delete_paperless_document_chunks(
|
||||
self,
|
||||
paperless_id: int,
|
||||
user: str
|
||||
) -> int:
|
||||
"""
|
||||
Delete all chunks for a Paperless document.
|
||||
|
||||
Args:
|
||||
paperless_id: Paperless-ngx document ID
|
||||
user: User identifier
|
||||
|
||||
Returns:
|
||||
Number of chunks deleted
|
||||
"""
|
||||
collection_name = get_qdrant_collection_name(user)
|
||||
|
||||
try:
|
||||
deleted_count = await self.qdrant.delete_by_filter(
|
||||
collection_name=collection_name,
|
||||
filter_conditions={
|
||||
"doc_type": "document",
|
||||
"paperless_id": paperless_id
|
||||
}
|
||||
)
|
||||
|
||||
logger.info(f"Deleted chunks for Paperless document {paperless_id}")
|
||||
return deleted_count
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to delete chunks for Paperless document {paperless_id}: {e}", exc_info=True)
|
||||
return 0
|
||||
|
||||
async def delete_collection_chunks(
|
||||
self,
|
||||
collection_id: str,
|
||||
|
||||
Reference in New Issue
Block a user