visual-search-api2 / src /api /danger.py
AdarshDRC's picture
Create api/danger.py
318f9d2 verified
import asyncio
import time
from fastapi import APIRouter, Form, HTTPException, Request, Depends
from src.core.config import DEFAULT_CLOUDINARY_URL, DEFAULT_PINECONE_KEY, IDX_FACES, IDX_OBJECTS
from src.core.security import get_verified_keys
from src.services.db_client import (
cld_delete_all_paginated, cld_remove_folder, cld_root_folders,
delete_and_recreate_indexes, pinecone_pool,
)
from src.core.logging import log, warn
from src.common.utils import get_ip, is_default_key
router = APIRouter()
@router.post("/api/reset-database")
async def reset_database(
request: Request,
user_id: str = Form(""),
keys: dict = Depends(get_verified_keys)
):
ip = get_ip(request)
start = time.perf_counter()
log("WARNING", "danger.reset_database.attempt", user_id=user_id or "anonymous", ip=ip)
if is_default_key(keys["pinecone_key"], DEFAULT_PINECONE_KEY) or is_default_key(keys["cloudinary_url"], DEFAULT_CLOUDINARY_URL):
log("WARNING", "danger.reset_database.blocked", user_id=user_id or "anonymous", ip=ip)
raise HTTPException(403, "Reset is not allowed on the shared demo database.")
try:
deleted = await asyncio.to_thread(cld_delete_all_paginated, keys["cloudinary_creds"])
log("INFO", "danger.reset_database.cloudinary_wiped", deleted=deleted)
except Exception as e:
warn(f"Cloudinary wipe error: {e}")
try:
folders_res = await asyncio.to_thread(cld_root_folders, keys["cloudinary_creds"])
folder_tasks = [
asyncio.to_thread(cld_remove_folder, f["name"], keys["cloudinary_creds"])
for f in folders_res.get("folders", [])
]
if folder_tasks:
await asyncio.gather(*folder_tasks, return_exceptions=True)
except Exception as e:
warn(f"Cloudinary folder cleanup error: {e}")
try:
pc = pinecone_pool.get(keys["pinecone_key"])
await asyncio.to_thread(delete_and_recreate_indexes, pc)
except Exception as e:
log("ERROR", "danger.reset_database.pinecone_error", user_id=user_id or "anonymous", ip=ip, error=str(e))
raise HTTPException(500, f"Pinecone reset error: {e}")
log("WARNING", "danger.reset_database.complete", user_id=user_id or "anonymous", ip=ip, duration_ms=round((time.perf_counter() - start) * 1000))
return {"message": "Database reset complete. All data wiped and indexes recreated."}
@router.post("/api/delete-account")
async def delete_account(
request: Request,
user_id: str = Form(""),
keys: dict = Depends(get_verified_keys)
):
ip = get_ip(request)
start = time.perf_counter()
log("WARNING", "danger.delete_account.attempt", user_id=user_id or "anonymous", ip=ip)
if is_default_key(keys["pinecone_key"], DEFAULT_PINECONE_KEY) or is_default_key(keys["cloudinary_url"], DEFAULT_CLOUDINARY_URL):
log("WARNING", "danger.delete_account.blocked", user_id=user_id or "anonymous", ip=ip)
raise HTTPException(403, "Account deletion is not allowed on the shared demo database.")
try:
deleted = await asyncio.to_thread(cld_delete_all_paginated, keys["cloudinary_creds"])
log("INFO", "danger.delete_account.cloudinary_wiped", deleted=deleted)
except Exception as e:
warn(f"Account delete Cloudinary error: {e}")
try:
folders_res = await asyncio.to_thread(cld_root_folders, keys["cloudinary_creds"])
folder_tasks = [
asyncio.to_thread(cld_remove_folder, f["name"], keys["cloudinary_creds"])
for f in folders_res.get("folders", [])
]
if folder_tasks:
await asyncio.gather(*folder_tasks, return_exceptions=True)
except Exception as e:
warn(f"Account delete folders error: {e}")
try:
pc = pinecone_pool.get(keys["pinecone_key"])
def _delete_indexes():
existing = {idx.name for idx in pc.list_indexes()}
for name in [IDX_OBJECTS, IDX_FACES]:
if name in existing:
pc.delete_index(name)
await asyncio.to_thread(_delete_indexes)
except Exception as e:
warn(f"Account delete Pinecone error: {e}")
log("WARNING", "danger.delete_account.complete", user_id=user_id or "anonymous", ip=ip, duration_ms=round((time.perf_counter() - start) * 1000))
return {"message": "Account data deleted. Sign out initiated."}