File size: 4,388 Bytes
318f9d2
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
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."}