File size: 4,999 Bytes
29bfc1f
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
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,
    IDX_FACES_ARCFACE, IDX_FACES_ADAFACE,
    USE_SPLIT_FACE_INDEXES,
)
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()


def _all_index_names() -> list[str]:
    """All possible index names across both modes — used for exhaustive cleanup."""
    if USE_SPLIT_FACE_INDEXES:
        return [IDX_FACES, IDX_OBJECTS, IDX_FACES_ARCFACE, IDX_FACES_ADAFACE]
    return [IDX_FACES, IDX_OBJECTS]


@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_all_indexes():
            existing = {idx.name for idx in pc.list_indexes()}
            for name in _all_index_names():
                if name in existing:
                    pc.delete_index(name)

        await asyncio.to_thread(_delete_all_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."}