| """ |
| Admin API β /api/v1/collections, /api/v1/delete |
| Management endpoints for tenant collections. |
| """ |
|
|
| from fastapi import APIRouter, Depends, HTTPException |
| from pydantic import BaseModel, Field |
| from loguru import logger |
|
|
| from app.core.qdrant_client import qdrant_manager |
| from app.core.security import verify_api_key |
|
|
| router = APIRouter() |
|
|
|
|
| |
|
|
| @router.get("/collections") |
| async def list_collections(_: str = Depends(verify_api_key)): |
| """ |
| List all Qdrant collections currently in the instance. |
| Returns both kb_* tenant collections and any other collections. |
| |
| **Required Header:** `X-RAG-API-Key` |
| """ |
| try: |
| collections = await qdrant_manager.list_all_collections() |
| tenant_collections = [c for c in collections if c.startswith("kb_")] |
| return { |
| "total": len(collections), |
| "tenant_collections": tenant_collections, |
| "all_collections": collections, |
| } |
| except Exception as e: |
| raise HTTPException(status_code=500, detail=str(e)) |
|
|
|
|
| |
|
|
| @router.get("/collections/{company_id}") |
| async def get_collection_info( |
| company_id: str, |
| _: str = Depends(verify_api_key), |
| ): |
| """ |
| Get stats for a specific tenant's collection. |
| Returns vector count, point count, and status. |
| |
| **Required Header:** `X-RAG-API-Key` |
| """ |
| try: |
| info = await qdrant_manager.get_collection_info(company_id) |
| return info |
| except Exception as e: |
| raise HTTPException(status_code=404, detail=f"Collection not found: {str(e)}") |
|
|
|
|
| |
|
|
| class DeleteRequest(BaseModel): |
| company_id: str = Field(..., description="Tenant to permanently remove.") |
| confirm: bool = Field( |
| ..., |
| description="Must be `true` to confirm deletion. Irreversible.", |
| ) |
|
|
|
|
| @router.post("/delete") |
| async def delete_tenant( |
| body: DeleteRequest, |
| _: str = Depends(verify_api_key), |
| ): |
| """ |
| Permanently delete a tenant's entire knowledge base collection. |
| **This is irreversible.** All embedded documents are lost. |
| |
| Requires `confirm: true` in the request body as a safety check. |
| |
| Uses POST instead of DELETE to avoid request body stripping by proxies/CDNs. |
| |
| **Required Header:** `X-RAG-API-Key` |
| """ |
| if not body.confirm: |
| raise HTTPException( |
| status_code=400, |
| detail="Deletion not confirmed. Set 'confirm': true to proceed.", |
| ) |
|
|
| try: |
| result = await qdrant_manager.delete_tenant_collection(body.company_id) |
| logger.info(f"Admin deleted collection for '{body.company_id}'") |
| return { |
| "status": "deleted", |
| "company_id": body.company_id, |
| "collection": result["collection"], |
| "message": f"Collection '{result['collection']}' permanently deleted.", |
| } |
| except Exception as e: |
| logger.error(f"Delete failed for '{body.company_id}': {e}") |
| raise HTTPException(status_code=500, detail=str(e)) |
|
|
|
|
| |
|
|
| class DeleteSourceRequest(BaseModel): |
| company_id: str = Field(...) |
| source: str = Field(..., description="Filename or URL to remove from the collection.") |
|
|
|
|
| @router.post("/delete/source") |
| async def delete_source( |
| body: DeleteSourceRequest, |
| _: str = Depends(verify_api_key), |
| ): |
| """ |
| Delete all chunks belonging to a specific source (file or URL) |
| from a tenant's collection, without deleting the entire collection. |
| |
| Use this when a company updates a document β delete old version, re-ingest new. |
| |
| Uses POST instead of DELETE to avoid request body stripping by proxies/CDNs. |
| |
| **Required Header:** `X-RAG-API-Key` |
| """ |
| try: |
| await qdrant_manager.delete_chunks_by_source(body.company_id, body.source) |
| return { |
| "status": "deleted", |
| "company_id": body.company_id, |
| "source": body.source, |
| "message": f"All chunks for source '{body.source}' deleted from 'kb_{body.company_id}'.", |
| } |
| except Exception as e: |
| raise HTTPException(status_code=500, detail=str(e)) |