Update routes/approvals.py
Browse files- routes/approvals.py +22 -31
routes/approvals.py
CHANGED
@@ -11,6 +11,7 @@ from collections import defaultdict
|
|
11 |
|
12 |
router = APIRouter()
|
13 |
|
|
|
14 |
class ApproveAccountRequest(BaseModel):
|
15 |
user_id: str
|
16 |
|
@@ -18,12 +19,13 @@ class RejectAccountRequest(BaseModel):
|
|
18 |
user_id: str
|
19 |
reason: Optional[str] = None
|
20 |
|
21 |
-
# Configuração
|
22 |
SUPABASE_URL = "https://ussxqnifefkgkaumjann.supabase.co"
|
23 |
SUPABASE_KEY = os.getenv("SUPA_KEY")
|
|
|
24 |
|
25 |
-
if not SUPABASE_KEY:
|
26 |
-
raise ValueError("❌ SUPA_KEY não
|
27 |
|
28 |
SUPABASE_HEADERS = {
|
29 |
"apikey": SUPABASE_KEY,
|
@@ -31,11 +33,17 @@ SUPABASE_HEADERS = {
|
|
31 |
"Content-Type": "application/json"
|
32 |
}
|
33 |
|
34 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
35 |
logging.basicConfig(level=logging.INFO)
|
36 |
logger = logging.getLogger(__name__)
|
37 |
|
38 |
-
# Cache de admin
|
39 |
@lru_cache(maxsize=128)
|
40 |
def get_cached_admin_status(user_id: str) -> bool:
|
41 |
user_data_url = f"{SUPABASE_URL}/rest/v1/User?id=eq.{user_id}"
|
@@ -47,6 +55,7 @@ def get_cached_admin_status(user_id: str) -> bool:
|
|
47 |
user_info = response.json()[0]
|
48 |
return user_info.get("is_admin", False)
|
49 |
|
|
|
50 |
async def verify_admin_token(user_token: str) -> str:
|
51 |
headers = {
|
52 |
"Authorization": f"Bearer {user_token}",
|
@@ -71,8 +80,8 @@ async def verify_admin_token(user_token: str) -> str:
|
|
71 |
|
72 |
return user_id
|
73 |
|
|
|
74 |
async def get_users_pending_approval() -> Dict[str, Any]:
|
75 |
-
"""Obtém todos os usuários que ainda não foram aprovados e suas respostas de onboarding"""
|
76 |
try:
|
77 |
query_users = (
|
78 |
f"{SUPABASE_URL}/rest/v1/User"
|
@@ -87,12 +96,10 @@ async def get_users_pending_approval() -> Dict[str, Any]:
|
|
87 |
headers["Accept"] = "application/json; charset=utf-8"
|
88 |
|
89 |
async with aiohttp.ClientSession() as session:
|
90 |
-
# Busca os usuários pendentes
|
91 |
async with session.get(query_users, headers=headers) as response:
|
92 |
if response.status != 200:
|
93 |
logger.error(f"❌ Erro ao buscar usuários pendentes: {response.status}")
|
94 |
raise HTTPException(status_code=500, detail="Erro ao consultar usuários")
|
95 |
-
|
96 |
users = await response.json()
|
97 |
|
98 |
if not users:
|
@@ -101,7 +108,6 @@ async def get_users_pending_approval() -> Dict[str, Any]:
|
|
101 |
user_ids = [user['id'] for user in users]
|
102 |
user_ids_str = ",".join(user_ids)
|
103 |
|
104 |
-
# Busca as respostas desses usuários
|
105 |
query_answers = (
|
106 |
f"{SUPABASE_URL}/rest/v1/User answers"
|
107 |
f"?select=user_id,question_id,answers"
|
@@ -115,7 +121,6 @@ async def get_users_pending_approval() -> Dict[str, Any]:
|
|
115 |
else:
|
116 |
user_answers = await response.json()
|
117 |
|
118 |
-
# Busca as perguntas
|
119 |
query_questions = f"{SUPABASE_URL}/rest/v1/Onboarding?select=id,title"
|
120 |
async with session.get(query_questions, headers=headers) as response:
|
121 |
if response.status != 200:
|
@@ -124,10 +129,7 @@ async def get_users_pending_approval() -> Dict[str, Any]:
|
|
124 |
else:
|
125 |
questions = await response.json()
|
126 |
|
127 |
-
# Mapeia perguntas por ID
|
128 |
question_map = {q["id"]: q["title"] for q in questions}
|
129 |
-
|
130 |
-
# Agrupa respostas por usuário
|
131 |
grouped_answers = defaultdict(list)
|
132 |
|
133 |
for answer in user_answers:
|
@@ -137,7 +139,6 @@ async def get_users_pending_approval() -> Dict[str, Any]:
|
|
137 |
"answer": answer.get("answers", [])
|
138 |
})
|
139 |
|
140 |
-
# Enriquecer usuários com respostas
|
141 |
for user in users:
|
142 |
user["answers"] = grouped_answers.get(user["id"], [])
|
143 |
|
@@ -147,25 +148,20 @@ async def get_users_pending_approval() -> Dict[str, Any]:
|
|
147 |
logger.error(f"❌ Erro ao buscar usuários e respostas: {str(e)}")
|
148 |
raise HTTPException(status_code=500, detail="Erro interno do servidor")
|
149 |
|
150 |
-
|
151 |
@router.post("/admin/approve-account")
|
152 |
async def approve_account(
|
153 |
body: ApproveAccountRequest,
|
154 |
user_token: str = Header(None, alias="User-key")
|
155 |
):
|
156 |
-
"""
|
157 |
-
Aprova a conta de um usuário.
|
158 |
-
"""
|
159 |
try:
|
160 |
await verify_admin_token(user_token)
|
161 |
|
162 |
update_url = f"{SUPABASE_URL}/rest/v1/User?id=eq.{body.user_id}"
|
163 |
-
payload = {
|
164 |
-
"approved_account": True
|
165 |
-
}
|
166 |
|
167 |
async with aiohttp.ClientSession() as session:
|
168 |
-
async with session.patch(update_url, headers=
|
169 |
if response.status != 204:
|
170 |
logger.error(f"❌ Erro ao aprovar conta: {response.status}")
|
171 |
raise HTTPException(status_code=500, detail="Erro ao aprovar a conta")
|
@@ -178,14 +174,12 @@ async def approve_account(
|
|
178 |
logger.error(f"❌ Erro ao aprovar conta: {str(e)}")
|
179 |
raise HTTPException(status_code=500, detail=str(e))
|
180 |
|
|
|
181 |
@router.post("/admin/reject-account")
|
182 |
async def reject_account(
|
183 |
body: RejectAccountRequest,
|
184 |
user_token: str = Header(None, alias="User-key")
|
185 |
):
|
186 |
-
"""
|
187 |
-
Reprova a conta de um usuário, com motivo.
|
188 |
-
"""
|
189 |
try:
|
190 |
await verify_admin_token(user_token)
|
191 |
|
@@ -196,7 +190,7 @@ async def reject_account(
|
|
196 |
}
|
197 |
|
198 |
async with aiohttp.ClientSession() as session:
|
199 |
-
async with session.patch(update_url, headers=
|
200 |
if response.status != 204:
|
201 |
logger.error(f"❌ Erro ao reprovar conta: {response.status}")
|
202 |
raise HTTPException(status_code=500, detail="Erro ao reprovar a conta")
|
@@ -209,14 +203,11 @@ async def reject_account(
|
|
209 |
logger.error(f"❌ Erro ao reprovar conta: {str(e)}")
|
210 |
raise HTTPException(status_code=500, detail=str(e))
|
211 |
|
|
|
212 |
@router.get("/admin/pending-approvals")
|
213 |
async def pending_approvals_endpoint(
|
214 |
user_token: str = Header(None, alias="User-key")
|
215 |
):
|
216 |
-
"""
|
217 |
-
Endpoint para listar usuários que ainda não foram aprovados,
|
218 |
-
com respostas das perguntas de onboarding.
|
219 |
-
"""
|
220 |
try:
|
221 |
await verify_admin_token(user_token)
|
222 |
return await get_users_pending_approval()
|
@@ -224,4 +215,4 @@ async def pending_approvals_endpoint(
|
|
224 |
raise he
|
225 |
except Exception as e:
|
226 |
logger.error(f"❌ Erro no endpoint de aprovações: {str(e)}")
|
227 |
-
raise HTTPException(status_code=500, detail=str(e))
|
|
|
11 |
|
12 |
router = APIRouter()
|
13 |
|
14 |
+
# 📦 Models
|
15 |
class ApproveAccountRequest(BaseModel):
|
16 |
user_id: str
|
17 |
|
|
|
19 |
user_id: str
|
20 |
reason: Optional[str] = None
|
21 |
|
22 |
+
# 🔥 Supabase Configuração com Secrets
|
23 |
SUPABASE_URL = "https://ussxqnifefkgkaumjann.supabase.co"
|
24 |
SUPABASE_KEY = os.getenv("SUPA_KEY")
|
25 |
+
SUPABASE_ROLE_KEY = os.getenv("SUPA_SERVICE_KEY")
|
26 |
|
27 |
+
if not SUPABASE_KEY or not SUPABASE_ROLE_KEY:
|
28 |
+
raise ValueError("❌ SUPA_KEY ou SUPA_SERVICE_KEY não foram definidos no ambiente!")
|
29 |
|
30 |
SUPABASE_HEADERS = {
|
31 |
"apikey": SUPABASE_KEY,
|
|
|
33 |
"Content-Type": "application/json"
|
34 |
}
|
35 |
|
36 |
+
SUPABASE_ROLE_HEADERS = {
|
37 |
+
"apikey": SUPABASE_ROLE_KEY,
|
38 |
+
"Authorization": f"Bearer {SUPABASE_ROLE_KEY}",
|
39 |
+
"Content-Type": "application/json"
|
40 |
+
}
|
41 |
+
|
42 |
+
# ⚙️ Logging
|
43 |
logging.basicConfig(level=logging.INFO)
|
44 |
logger = logging.getLogger(__name__)
|
45 |
|
46 |
+
# 🔒 Cache de admin
|
47 |
@lru_cache(maxsize=128)
|
48 |
def get_cached_admin_status(user_id: str) -> bool:
|
49 |
user_data_url = f"{SUPABASE_URL}/rest/v1/User?id=eq.{user_id}"
|
|
|
55 |
user_info = response.json()[0]
|
56 |
return user_info.get("is_admin", False)
|
57 |
|
58 |
+
# 🔐 Verificação de token com permissões normais
|
59 |
async def verify_admin_token(user_token: str) -> str:
|
60 |
headers = {
|
61 |
"Authorization": f"Bearer {user_token}",
|
|
|
80 |
|
81 |
return user_id
|
82 |
|
83 |
+
# 📥 Buscar usuários pendentes com respostas
|
84 |
async def get_users_pending_approval() -> Dict[str, Any]:
|
|
|
85 |
try:
|
86 |
query_users = (
|
87 |
f"{SUPABASE_URL}/rest/v1/User"
|
|
|
96 |
headers["Accept"] = "application/json; charset=utf-8"
|
97 |
|
98 |
async with aiohttp.ClientSession() as session:
|
|
|
99 |
async with session.get(query_users, headers=headers) as response:
|
100 |
if response.status != 200:
|
101 |
logger.error(f"❌ Erro ao buscar usuários pendentes: {response.status}")
|
102 |
raise HTTPException(status_code=500, detail="Erro ao consultar usuários")
|
|
|
103 |
users = await response.json()
|
104 |
|
105 |
if not users:
|
|
|
108 |
user_ids = [user['id'] for user in users]
|
109 |
user_ids_str = ",".join(user_ids)
|
110 |
|
|
|
111 |
query_answers = (
|
112 |
f"{SUPABASE_URL}/rest/v1/User answers"
|
113 |
f"?select=user_id,question_id,answers"
|
|
|
121 |
else:
|
122 |
user_answers = await response.json()
|
123 |
|
|
|
124 |
query_questions = f"{SUPABASE_URL}/rest/v1/Onboarding?select=id,title"
|
125 |
async with session.get(query_questions, headers=headers) as response:
|
126 |
if response.status != 200:
|
|
|
129 |
else:
|
130 |
questions = await response.json()
|
131 |
|
|
|
132 |
question_map = {q["id"]: q["title"] for q in questions}
|
|
|
|
|
133 |
grouped_answers = defaultdict(list)
|
134 |
|
135 |
for answer in user_answers:
|
|
|
139 |
"answer": answer.get("answers", [])
|
140 |
})
|
141 |
|
|
|
142 |
for user in users:
|
143 |
user["answers"] = grouped_answers.get(user["id"], [])
|
144 |
|
|
|
148 |
logger.error(f"❌ Erro ao buscar usuários e respostas: {str(e)}")
|
149 |
raise HTTPException(status_code=500, detail="Erro interno do servidor")
|
150 |
|
151 |
+
# ✅ Aprovar conta
|
152 |
@router.post("/admin/approve-account")
|
153 |
async def approve_account(
|
154 |
body: ApproveAccountRequest,
|
155 |
user_token: str = Header(None, alias="User-key")
|
156 |
):
|
|
|
|
|
|
|
157 |
try:
|
158 |
await verify_admin_token(user_token)
|
159 |
|
160 |
update_url = f"{SUPABASE_URL}/rest/v1/User?id=eq.{body.user_id}"
|
161 |
+
payload = { "approved_account": True }
|
|
|
|
|
162 |
|
163 |
async with aiohttp.ClientSession() as session:
|
164 |
+
async with session.patch(update_url, headers=SUPABASE_ROLE_HEADERS, json=payload) as response:
|
165 |
if response.status != 204:
|
166 |
logger.error(f"❌ Erro ao aprovar conta: {response.status}")
|
167 |
raise HTTPException(status_code=500, detail="Erro ao aprovar a conta")
|
|
|
174 |
logger.error(f"❌ Erro ao aprovar conta: {str(e)}")
|
175 |
raise HTTPException(status_code=500, detail=str(e))
|
176 |
|
177 |
+
# ❌ Reprovar conta
|
178 |
@router.post("/admin/reject-account")
|
179 |
async def reject_account(
|
180 |
body: RejectAccountRequest,
|
181 |
user_token: str = Header(None, alias="User-key")
|
182 |
):
|
|
|
|
|
|
|
183 |
try:
|
184 |
await verify_admin_token(user_token)
|
185 |
|
|
|
190 |
}
|
191 |
|
192 |
async with aiohttp.ClientSession() as session:
|
193 |
+
async with session.patch(update_url, headers=SUPABASE_ROLE_HEADERS, json=payload) as response:
|
194 |
if response.status != 204:
|
195 |
logger.error(f"❌ Erro ao reprovar conta: {response.status}")
|
196 |
raise HTTPException(status_code=500, detail="Erro ao reprovar a conta")
|
|
|
203 |
logger.error(f"❌ Erro ao reprovar conta: {str(e)}")
|
204 |
raise HTTPException(status_code=500, detail=str(e))
|
205 |
|
206 |
+
# 🔍 Listar contas pendentes
|
207 |
@router.get("/admin/pending-approvals")
|
208 |
async def pending_approvals_endpoint(
|
209 |
user_token: str = Header(None, alias="User-key")
|
210 |
):
|
|
|
|
|
|
|
|
|
211 |
try:
|
212 |
await verify_admin_token(user_token)
|
213 |
return await get_users_pending_approval()
|
|
|
215 |
raise he
|
216 |
except Exception as e:
|
217 |
logger.error(f"❌ Erro no endpoint de aprovações: {str(e)}")
|
218 |
+
raise HTTPException(status_code=500, detail=str(e))
|