Spaces:
Sleeping
Sleeping
| import asyncio | |
| from fastapi import APIRouter, Form, HTTPException, Request, Depends | |
| from src.core.config import DEFAULT_PINECONE_KEY, IDX_FACES, IDX_OBJECTS | |
| from src.core.security import get_verified_keys | |
| from src.services.db_client import ( | |
| cld_delete_folder_resources, cld_delete_resource, cld_list_folder_images, | |
| cld_remove_folder, cld_root_folders, pinecone_pool, | |
| ) | |
| from src.core.logging import log, warn | |
| from src.common.utils import cld_thumb_url, get_ip, url_to_public_id | |
| router = APIRouter() | |
| async def get_categories( | |
| request: Request, | |
| user_id: str = Form(""), | |
| keys: dict = Depends(get_verified_keys) | |
| ): | |
| ip = get_ip(request) | |
| try: | |
| result = await asyncio.to_thread(cld_root_folders, keys["cloudinary_creds"]) | |
| categories = [f["name"] for f in result.get("folders", [])] | |
| log("INFO", "categories.fetched", user_id=user_id or "anonymous", ip=ip, count=len(categories)) | |
| return {"categories": categories} | |
| except Exception as e: | |
| log("ERROR", "categories.error", user_id=user_id or "anonymous", ip=ip, error=str(e)) | |
| return {"categories": []} | |
| async def list_folder_images( | |
| request: Request, | |
| folder_name: str = Form(...), | |
| user_id: str = Form(""), | |
| next_cursor: str = Form(""), | |
| page_size: int = Form(100), | |
| keys: dict = Depends(get_verified_keys) | |
| ): | |
| ip = get_ip(request) | |
| result = await asyncio.to_thread( | |
| cld_list_folder_images, | |
| folder_name, keys["cloudinary_creds"], next_cursor or None, page_size, | |
| ) | |
| images = [ | |
| { | |
| "url": r["secure_url"], | |
| "thumb_url": cld_thumb_url(r["secure_url"]), | |
| "public_id": r["public_id"], | |
| } | |
| for r in result.get("resources", []) | |
| ] | |
| next_cur = result.get("next_cursor") or "" | |
| log("INFO", "explorer.folder_opened", user_id=user_id or "anonymous", ip=ip, | |
| folder=folder_name, count=len(images), has_more=bool(next_cur)) | |
| return {"images": images, "count": len(images), "next_cursor": next_cur} | |
| async def delete_image( | |
| request: Request, | |
| image_url: str = Form(""), | |
| public_id: str = Form(""), | |
| user_id: str = Form(""), | |
| keys: dict = Depends(get_verified_keys) | |
| ): | |
| ip = get_ip(request) | |
| pid = public_id or url_to_public_id(image_url) | |
| if not pid: | |
| raise HTTPException(400, "Could not determine public_id.") | |
| await asyncio.to_thread(cld_delete_resource, pid, keys["cloudinary_creds"]) | |
| try: | |
| pc = pinecone_pool.get(keys["pinecone_key"]) | |
| for idx_name in [IDX_OBJECTS, IDX_FACES]: | |
| await asyncio.to_thread( | |
| pc.Index(idx_name).delete, filter={"url": {"$eq": image_url}} | |
| ) | |
| except Exception as e: | |
| warn(f"Pinecone delete warning: {e}") | |
| log("INFO", "explorer.image_deleted", user_id=user_id or "anonymous", ip=ip, image_url=image_url, public_id=pid) | |
| return {"message": "Image deleted successfully."} | |
| async def delete_folder( | |
| request: Request, | |
| folder_name: str = Form(...), | |
| user_id: str = Form(""), | |
| keys: dict = Depends(get_verified_keys) | |
| ): | |
| ip = get_ip(request) | |
| all_images, cursor = [], None | |
| while True: | |
| result = await asyncio.to_thread(cld_list_folder_images, folder_name, keys["cloudinary_creds"], cursor) | |
| all_images.extend(result.get("resources", [])) | |
| cursor = result.get("next_cursor") | |
| if not cursor: | |
| break | |
| await asyncio.to_thread(cld_delete_folder_resources, folder_name, keys["cloudinary_creds"]) | |
| await asyncio.to_thread(cld_remove_folder, folder_name, keys["cloudinary_creds"]) | |
| try: | |
| pc = pinecone_pool.get(keys["pinecone_key"]) | |
| for idx_name in [IDX_OBJECTS, IDX_FACES]: | |
| idx = pc.Index(idx_name) | |
| try: | |
| await asyncio.to_thread(idx.delete, filter={"folder": {"$eq": folder_name}}) | |
| except Exception: | |
| for img in all_images: | |
| url = img.get("secure_url", "") | |
| if url: | |
| try: | |
| await asyncio.to_thread(idx.delete, filter={"url": {"$eq": url}}) | |
| except Exception: | |
| pass | |
| except Exception as e: | |
| warn(f"Pinecone folder delete warning: {e}") | |
| log("INFO", "explorer.folder_deleted", user_id=user_id or "anonymous", ip=ip, folder=folder_name, deleted_count=len(all_images)) | |
| return {"message": f"Folder '{folder_name}' and contents deleted.", "deleted_count": len(all_images)} |