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

1""" 

2Delete API Routes 

3 

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

13 

14from flask import Blueprint, jsonify, request, session 

15 

16 

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 

22 

23 

24delete_bp = Blueprint("delete", __name__, url_prefix="/library/api") 

25 

26 

27# ============================================================================= 

28# Document Delete Endpoints 

29# ============================================================================= 

30 

31 

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. 

37 

38 Tooltip: "Permanently delete this document, including PDF and text content. 

39 This cannot be undone." 

40 

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) 

48 

49 if result.get("deleted"): 

50 return jsonify({"success": True, **result}) 

51 else: 

52 return jsonify({"success": False, **result}), 404 

53 

54 except Exception as e: 

55 return handle_api_error("deleting document", e) 

56 

57 

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. 

63 

64 Tooltip: "Remove the PDF file to save space. Text content will be 

65 preserved for searching." 

66 

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) 

74 

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 

82 

83 except Exception as e: 

84 return handle_api_error("deleting document blob", e) 

85 

86 

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. 

92 

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) 

99 

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 

106 

107 except Exception as e: 

108 return handle_api_error("getting document preview", e) 

109 

110 

111# ============================================================================= 

112# Collection Document Endpoints 

113# ============================================================================= 

114 

115 

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. 

124 

125 If the document is not in any other collection, it will be deleted. 

126 

127 Tooltip: "Remove from this collection. If not in any other collection, 

128 the document will be deleted." 

129 

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) 

137 

138 if result.get("unlinked"): 

139 return jsonify({"success": True, **result}) 

140 else: 

141 return jsonify({"success": False, **result}), 404 

142 

143 except Exception as e: 

144 return handle_api_error("removing document from collection", e) 

145 

146 

147# ============================================================================= 

148# Collection Delete Endpoints 

149# ============================================================================= 

150 

151 

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. 

157 

158 Documents are preserved but unlinked. RAG index and chunks are deleted. 

159 

160 Tooltip: "Delete this collection. Documents will remain in the library 

161 but will be unlinked from this collection." 

162 

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) 

170 

171 if result.get("deleted"): 

172 return jsonify({"success": True, **result}) 

173 else: 

174 return jsonify({"success": False, **result}), 404 

175 

176 except Exception as e: 

177 return handle_api_error("deleting collection", e) 

178 

179 

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. 

187 

188 Useful for rebuilding an index from scratch. 

189 

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) 

197 

198 if result.get("deleted"): 

199 return jsonify({"success": True, **result}) 

200 else: 

201 return jsonify({"success": False, **result}), 404 

202 

203 except Exception as e: 

204 return handle_api_error("deleting collection index", e) 

205 

206 

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. 

212 

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) 

219 

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 

226 

227 except Exception as e: 

228 return handle_api_error("getting collection preview", e) 

229 

230 

231# ============================================================================= 

232# Bulk Delete Endpoints 

233# ============================================================================= 

234 

235 

236@delete_bp.route("/documents/bulk", methods=["DELETE"]) 

237@login_required 

238def delete_documents_bulk(): 

239 """ 

240 Delete multiple documents at once. 

241 

242 Tooltip: "Permanently delete all selected documents and their associated data." 

243 

244 Request body: 

245 {"document_ids": ["id1", "id2", ...]} 

246 

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 

259 

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 

268 

269 username = session["username"] 

270 service = BulkDeletionService(username) 

271 result = service.delete_documents(document_ids) 

272 

273 return jsonify({"success": True, **result}) 

274 

275 except Exception as e: 

276 return handle_api_error("bulk deleting documents", e) 

277 

278 

279@delete_bp.route("/documents/blobs", methods=["DELETE"]) 

280@login_required 

281def delete_documents_blobs_bulk(): 

282 """ 

283 Delete PDF binaries for multiple documents. 

284 

285 Tooltip: "Remove PDF files from selected documents to free up database space. 

286 Text content is preserved." 

287 

288 Request body: 

289 {"document_ids": ["id1", "id2", ...]} 

290 

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 

303 

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 

312 

313 username = session["username"] 

314 service = BulkDeletionService(username) 

315 result = service.delete_blobs(document_ids) 

316 

317 return jsonify({"success": True, **result}) 

318 

319 except Exception as e: 

320 return handle_api_error("bulk deleting blobs", e) 

321 

322 

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. 

330 

331 Documents that are not in any other collection will be deleted. 

332 

333 Request body: 

334 {"document_ids": ["id1", "id2", ...]} 

335 

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 

348 

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 

357 

358 username = session["username"] 

359 service = BulkDeletionService(username) 

360 result = service.remove_documents_from_collection( 

361 document_ids, collection_id 

362 ) 

363 

364 return jsonify({"success": True, **result}) 

365 

366 except Exception as e: 

367 return handle_api_error("bulk removing documents from collection", e) 

368 

369 

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. 

375 

376 Request body: 

377 { 

378 "document_ids": ["id1", "id2", ...], 

379 "operation": "delete" or "delete_blobs" 

380 } 

381 

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 

394 

395 document_ids = data["document_ids"] 

396 operation = data.get("operation", "delete") 

397 

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 

405 

406 username = session["username"] 

407 service = BulkDeletionService(username) 

408 result = service.get_bulk_preview(document_ids, operation) 

409 

410 return jsonify({"success": True, **result}) 

411 

412 except Exception as e: 

413 return handle_api_error("getting bulk preview", e)