Coverage for src / local_deep_research / research_library / deletion / routes / delete_routes.py: 21%
157 statements
« prev ^ index » next coverage.py v7.12.0, created at 2026-01-11 00:51 +0000
« prev ^ index » next coverage.py v7.12.0, created at 2026-01-11 00:51 +0000
1"""
2Delete API Routes
4Provides endpoints for delete operations:
5- Delete document
6- Delete document blob only
7- Delete documents in bulk
8- Delete blobs in bulk
9- Remove document from collection
10- Delete collection
11- Delete collection index only
12"""
14from flask import Blueprint, jsonify, request, session
17from ....web.auth.decorators import login_required
18from ...utils import handle_api_error
19from ..services.document_deletion import DocumentDeletionService
20from ..services.collection_deletion import CollectionDeletionService
21from ..services.bulk_deletion import BulkDeletionService
24delete_bp = Blueprint("delete", __name__, url_prefix="/library/api")
27# =============================================================================
28# Document Delete Endpoints
29# =============================================================================
32@delete_bp.route("/document/<string:document_id>", methods=["DELETE"])
33@login_required
34def delete_document(document_id):
35 """
36 Delete a document and all related data.
38 Tooltip: "Permanently delete this document, including PDF and text content.
39 This cannot be undone."
41 Returns:
42 JSON with deletion details including chunks deleted, blob size freed
43 """
44 try:
45 username = session["username"]
46 service = DocumentDeletionService(username)
47 result = service.delete_document(document_id)
49 if result.get("deleted"):
50 return jsonify({"success": True, **result})
51 else:
52 return jsonify({"success": False, **result}), 404
54 except Exception as e:
55 return handle_api_error("deleting document", e)
58@delete_bp.route("/document/<string:document_id>/blob", methods=["DELETE"])
59@login_required
60def delete_document_blob(document_id):
61 """
62 Delete PDF binary but keep document metadata and text content.
64 Tooltip: "Remove the PDF file to save space. Text content will be
65 preserved for searching."
67 Returns:
68 JSON with bytes freed
69 """
70 try:
71 username = session["username"]
72 service = DocumentDeletionService(username)
73 result = service.delete_blob_only(document_id)
75 if result.get("deleted"):
76 return jsonify({"success": True, **result})
77 else:
78 error_code = (
79 404 if "not found" in result.get("error", "").lower() else 400
80 )
81 return jsonify({"success": False, **result}), error_code
83 except Exception as e:
84 return handle_api_error("deleting document blob", e)
87@delete_bp.route("/document/<string:document_id>/preview", methods=["GET"])
88@login_required
89def get_document_deletion_preview(document_id):
90 """
91 Get a preview of what will be deleted.
93 Returns information about the document to help user confirm deletion.
94 """
95 try:
96 username = session["username"]
97 service = DocumentDeletionService(username)
98 result = service.get_deletion_preview(document_id)
100 if result.get("found"):
101 return jsonify({"success": True, **result})
102 else:
103 return jsonify(
104 {"success": False, "error": "Document not found"}
105 ), 404
107 except Exception as e:
108 return handle_api_error("getting document preview", e)
111# =============================================================================
112# Collection Document Endpoints
113# =============================================================================
116@delete_bp.route(
117 "/collection/<string:collection_id>/document/<string:document_id>",
118 methods=["DELETE"],
119)
120@login_required
121def remove_document_from_collection(collection_id, document_id):
122 """
123 Remove document from a collection.
125 If the document is not in any other collection, it will be deleted.
127 Tooltip: "Remove from this collection. If not in any other collection,
128 the document will be deleted."
130 Returns:
131 JSON with unlink status and whether document was deleted
132 """
133 try:
134 username = session["username"]
135 service = DocumentDeletionService(username)
136 result = service.remove_from_collection(document_id, collection_id)
138 if result.get("unlinked"):
139 return jsonify({"success": True, **result})
140 else:
141 return jsonify({"success": False, **result}), 404
143 except Exception as e:
144 return handle_api_error("removing document from collection", e)
147# =============================================================================
148# Collection Delete Endpoints
149# =============================================================================
152@delete_bp.route("/collections/<string:collection_id>", methods=["DELETE"])
153@login_required
154def delete_collection(collection_id):
155 """
156 Delete a collection and clean up all related data.
158 Documents are preserved but unlinked. RAG index and chunks are deleted.
160 Tooltip: "Delete this collection. Documents will remain in the library
161 but will be unlinked from this collection."
163 Returns:
164 JSON with deletion details
165 """
166 try:
167 username = session["username"]
168 service = CollectionDeletionService(username)
169 result = service.delete_collection(collection_id)
171 if result.get("deleted"):
172 return jsonify({"success": True, **result})
173 else:
174 return jsonify({"success": False, **result}), 404
176 except Exception as e:
177 return handle_api_error("deleting collection", e)
180@delete_bp.route(
181 "/collections/<string:collection_id>/index", methods=["DELETE"]
182)
183@login_required
184def delete_collection_index(collection_id):
185 """
186 Delete only the RAG index for a collection, keeping the collection itself.
188 Useful for rebuilding an index from scratch.
190 Returns:
191 JSON with deletion details
192 """
193 try:
194 username = session["username"]
195 service = CollectionDeletionService(username)
196 result = service.delete_collection_index_only(collection_id)
198 if result.get("deleted"):
199 return jsonify({"success": True, **result})
200 else:
201 return jsonify({"success": False, **result}), 404
203 except Exception as e:
204 return handle_api_error("deleting collection index", e)
207@delete_bp.route("/collections/<string:collection_id>/preview", methods=["GET"])
208@login_required
209def get_collection_deletion_preview(collection_id):
210 """
211 Get a preview of what will be deleted.
213 Returns information about the collection to help user confirm deletion.
214 """
215 try:
216 username = session["username"]
217 service = CollectionDeletionService(username)
218 result = service.get_deletion_preview(collection_id)
220 if result.get("found"):
221 return jsonify({"success": True, **result})
222 else:
223 return jsonify(
224 {"success": False, "error": "Collection not found"}
225 ), 404
227 except Exception as e:
228 return handle_api_error("getting collection preview", e)
231# =============================================================================
232# Bulk Delete Endpoints
233# =============================================================================
236@delete_bp.route("/documents/bulk", methods=["DELETE"])
237@login_required
238def delete_documents_bulk():
239 """
240 Delete multiple documents at once.
242 Tooltip: "Permanently delete all selected documents and their associated data."
244 Request body:
245 {"document_ids": ["id1", "id2", ...]}
247 Returns:
248 JSON with bulk deletion results
249 """
250 try:
251 data = request.get_json()
252 if not data or "document_ids" not in data:
253 return jsonify(
254 {
255 "success": False,
256 "error": "document_ids required in request body",
257 }
258 ), 400
260 document_ids = data["document_ids"]
261 if not isinstance(document_ids, list) or not document_ids:
262 return jsonify(
263 {
264 "success": False,
265 "error": "document_ids must be a non-empty list",
266 }
267 ), 400
269 username = session["username"]
270 service = BulkDeletionService(username)
271 result = service.delete_documents(document_ids)
273 return jsonify({"success": True, **result})
275 except Exception as e:
276 return handle_api_error("bulk deleting documents", e)
279@delete_bp.route("/documents/blobs", methods=["DELETE"])
280@login_required
281def delete_documents_blobs_bulk():
282 """
283 Delete PDF binaries for multiple documents.
285 Tooltip: "Remove PDF files from selected documents to free up database space.
286 Text content is preserved."
288 Request body:
289 {"document_ids": ["id1", "id2", ...]}
291 Returns:
292 JSON with bulk blob deletion results
293 """
294 try:
295 data = request.get_json()
296 if not data or "document_ids" not in data:
297 return jsonify(
298 {
299 "success": False,
300 "error": "document_ids required in request body",
301 }
302 ), 400
304 document_ids = data["document_ids"]
305 if not isinstance(document_ids, list) or not document_ids:
306 return jsonify(
307 {
308 "success": False,
309 "error": "document_ids must be a non-empty list",
310 }
311 ), 400
313 username = session["username"]
314 service = BulkDeletionService(username)
315 result = service.delete_blobs(document_ids)
317 return jsonify({"success": True, **result})
319 except Exception as e:
320 return handle_api_error("bulk deleting blobs", e)
323@delete_bp.route(
324 "/collection/<string:collection_id>/documents/bulk", methods=["DELETE"]
325)
326@login_required
327def remove_documents_from_collection_bulk(collection_id):
328 """
329 Remove multiple documents from a collection.
331 Documents that are not in any other collection will be deleted.
333 Request body:
334 {"document_ids": ["id1", "id2", ...]}
336 Returns:
337 JSON with bulk removal results
338 """
339 try:
340 data = request.get_json()
341 if not data or "document_ids" not in data:
342 return jsonify(
343 {
344 "success": False,
345 "error": "document_ids required in request body",
346 }
347 ), 400
349 document_ids = data["document_ids"]
350 if not isinstance(document_ids, list) or not document_ids:
351 return jsonify(
352 {
353 "success": False,
354 "error": "document_ids must be a non-empty list",
355 }
356 ), 400
358 username = session["username"]
359 service = BulkDeletionService(username)
360 result = service.remove_documents_from_collection(
361 document_ids, collection_id
362 )
364 return jsonify({"success": True, **result})
366 except Exception as e:
367 return handle_api_error("bulk removing documents from collection", e)
370@delete_bp.route("/documents/preview", methods=["POST"])
371@login_required
372def get_bulk_deletion_preview():
373 """
374 Get a preview of what will be affected by a bulk operation.
376 Request body:
377 {
378 "document_ids": ["id1", "id2", ...],
379 "operation": "delete" or "delete_blobs"
380 }
382 Returns:
383 JSON with preview information
384 """
385 try:
386 data = request.get_json()
387 if not data or "document_ids" not in data:
388 return jsonify(
389 {
390 "success": False,
391 "error": "document_ids required in request body",
392 }
393 ), 400
395 document_ids = data["document_ids"]
396 operation = data.get("operation", "delete")
398 if not isinstance(document_ids, list) or not document_ids:
399 return jsonify(
400 {
401 "success": False,
402 "error": "document_ids must be a non-empty list",
403 }
404 ), 400
406 username = session["username"]
407 service = BulkDeletionService(username)
408 result = service.get_bulk_preview(document_ids, operation)
410 return jsonify({"success": True, **result})
412 except Exception as e:
413 return handle_api_error("getting bulk preview", e)