Update routes/users.py
Browse files- routes/users.py +1 -261
routes/users.py
CHANGED
@@ -13,9 +13,6 @@ router = APIRouter()
|
|
13 |
SUPABASE_URL = "https://ussxqnifefkgkaumjann.supabase.co"
|
14 |
SUPABASE_KEY = os.getenv("SUPA_KEY")
|
15 |
SUPABASE_ROLE_KEY = os.getenv("SUPA_SERVICE_KEY")
|
16 |
-
|
17 |
-
class FeedDeleteRequest(BaseModel):
|
18 |
-
feed_id: int
|
19 |
|
20 |
if not SUPABASE_KEY or not SUPABASE_ROLE_KEY:
|
21 |
raise ValueError("❌ SUPA_KEY ou SUPA_SERVICE_KEY não foram definidos no ambiente!")
|
@@ -198,261 +195,4 @@ async def get_recent_users_endpoint(
|
|
198 |
|
199 |
except Exception as e:
|
200 |
logger.error(f"❌ Erro ao obter usuários: {str(e)}")
|
201 |
-
raise HTTPException(status_code=500, detail=str(e))
|
202 |
-
|
203 |
-
@router.post("/admin/update-user")
|
204 |
-
async def update_user(
|
205 |
-
request: Dict[str, Any],
|
206 |
-
user_token: str = Header(None, alias="User-key")
|
207 |
-
):
|
208 |
-
"""
|
209 |
-
Endpoint para atualizar informações de um usuário específico.
|
210 |
-
Atualmente suporta atualização de name e fee.
|
211 |
-
Usa a chave de serviço para bypassar RLS do Supabase.
|
212 |
-
"""
|
213 |
-
try:
|
214 |
-
# Verificar se o usuário tem permissão para gerenciar usuários
|
215 |
-
user_info = await verify_token_with_permissions(user_token, "manage_users")
|
216 |
-
admin_id = user_info["user_id"]
|
217 |
-
|
218 |
-
# Validar os parâmetros da requisição
|
219 |
-
user_id = request.get("user_id")
|
220 |
-
name = request.get("name")
|
221 |
-
fee = request.get("fee")
|
222 |
-
|
223 |
-
if not user_id:
|
224 |
-
raise HTTPException(status_code=400, detail="ID do usuário é obrigatório")
|
225 |
-
|
226 |
-
if name is None and fee is None:
|
227 |
-
raise HTTPException(status_code=400, detail="Pelo menos um campo para atualização (name ou fee) deve ser fornecido")
|
228 |
-
|
229 |
-
# Verificar se o usuário existe - usando os headers normais para consulta
|
230 |
-
user_query = f"{SUPABASE_URL}/rest/v1/User?select=name,fee&id=eq.{user_id}"
|
231 |
-
|
232 |
-
async with aiohttp.ClientSession() as session:
|
233 |
-
async with session.get(user_query, headers=SUPABASE_HEADERS) as response:
|
234 |
-
if response.status != 200:
|
235 |
-
logger.error(f"❌ Erro ao verificar usuário: {response.status}")
|
236 |
-
raise HTTPException(status_code=response.status, detail="Erro ao consultar usuário")
|
237 |
-
|
238 |
-
user_data = await response.json()
|
239 |
-
|
240 |
-
if not user_data:
|
241 |
-
raise HTTPException(status_code=404, detail="Usuário não encontrado")
|
242 |
-
|
243 |
-
current_user = user_data[0]
|
244 |
-
|
245 |
-
# Preparar os campos para atualização, apenas se forem diferentes
|
246 |
-
update_fields = {}
|
247 |
-
|
248 |
-
if name is not None and name != current_user.get("name"):
|
249 |
-
update_fields["name"] = name
|
250 |
-
|
251 |
-
if fee is not None and isinstance(fee, int) and fee != current_user.get("fee"):
|
252 |
-
update_fields["fee"] = fee
|
253 |
-
|
254 |
-
# Se não há campos para atualizar, retornar sem modificar
|
255 |
-
if not update_fields:
|
256 |
-
return {"message": "Nenhuma alteração necessária", "user_id": user_id}
|
257 |
-
|
258 |
-
# Atualizar o usuário no Supabase usando a chave de serviço para bypassar RLS
|
259 |
-
update_url = f"{SUPABASE_URL}/rest/v1/User?id=eq.{user_id}"
|
260 |
-
|
261 |
-
headers_with_prefer = SUPABASE_ROLE_HEADERS.copy()
|
262 |
-
headers_with_prefer["Prefer"] = "return=representation"
|
263 |
-
|
264 |
-
async with session.patch(update_url, json=update_fields, headers=headers_with_prefer) as update_response:
|
265 |
-
if update_response.status != 200:
|
266 |
-
logger.error(f"❌ Erro ao atualizar usuário: {update_response.status} - {await update_response.text()}")
|
267 |
-
raise HTTPException(status_code=update_response.status, detail="Erro ao atualizar usuário")
|
268 |
-
|
269 |
-
updated_user_data = await update_response.json()
|
270 |
-
raw_user = updated_user_data[0] if updated_user_data else {}
|
271 |
-
updated_user = {
|
272 |
-
"name": raw_user.get("name"),
|
273 |
-
"avatar": raw_user.get("avatar"),
|
274 |
-
"role": raw_user.get("role"),
|
275 |
-
"fee": raw_user.get("fee")
|
276 |
-
}
|
277 |
-
|
278 |
-
logger.info(f"✅ Usuário {user_id} atualizado com sucesso por {admin_id}: {update_fields}")
|
279 |
-
|
280 |
-
return {
|
281 |
-
"message": "Usuário atualizado com sucesso",
|
282 |
-
"user": updated_user,
|
283 |
-
"updated_by": admin_id
|
284 |
-
}
|
285 |
-
|
286 |
-
except HTTPException as he:
|
287 |
-
raise he
|
288 |
-
|
289 |
-
except Exception as e:
|
290 |
-
logger.error(f"❌ Erro ao atualizar usuário: {str(e)}")
|
291 |
-
raise HTTPException(status_code=500, detail="Erro interno do servidor")
|
292 |
-
|
293 |
-
@router.post("/admin/delete-feed")
|
294 |
-
async def delete_feed(
|
295 |
-
request: FeedDeleteRequest,
|
296 |
-
user_token: str = Header(None, alias="User-key")
|
297 |
-
):
|
298 |
-
"""
|
299 |
-
Deleta um feed, seus portfolios relacionados e as imagens no bucket.
|
300 |
-
"""
|
301 |
-
try:
|
302 |
-
await verify_admin_token(user_token)
|
303 |
-
|
304 |
-
feed_id = request.feed_id
|
305 |
-
|
306 |
-
headers = SUPABASE_ROLE_HEADERS.copy()
|
307 |
-
headers["Accept"] = "application/json"
|
308 |
-
|
309 |
-
async with aiohttp.ClientSession() as session:
|
310 |
-
# Buscar o feed pelo ID
|
311 |
-
feed_query = f"{SUPABASE_URL}/rest/v1/Feeds?select=portfolios&limit=1&id=eq.{feed_id}"
|
312 |
-
async with session.get(feed_query, headers=headers) as feed_response:
|
313 |
-
if feed_response.status != 200:
|
314 |
-
raise HTTPException(status_code=feed_response.status, detail="Erro ao buscar feed")
|
315 |
-
|
316 |
-
feed_data = await feed_response.json()
|
317 |
-
if not feed_data:
|
318 |
-
raise HTTPException(status_code=404, detail="Feed não encontrado")
|
319 |
-
|
320 |
-
portfolio_ids = feed_data[0].get("portfolios", [])
|
321 |
-
if not isinstance(portfolio_ids, list):
|
322 |
-
import json
|
323 |
-
portfolio_ids = json.loads(portfolio_ids) # Caso venha como string JSON
|
324 |
-
|
325 |
-
# Buscar os portfolios para pegar os URLs das imagens
|
326 |
-
image_urls = []
|
327 |
-
if portfolio_ids:
|
328 |
-
ids_str = ",".join([str(pid) for pid in portfolio_ids])
|
329 |
-
portfolio_query = f"{SUPABASE_URL}/rest/v1/Portfolio?select=id,image_url&id=in.({ids_str})"
|
330 |
-
async with session.get(portfolio_query, headers=headers) as portfolio_response:
|
331 |
-
if portfolio_response.status == 200:
|
332 |
-
portfolio_data = await portfolio_response.json()
|
333 |
-
image_urls = [item["image_url"] for item in portfolio_data if "image_url" in item]
|
334 |
-
|
335 |
-
# Deletar os portfolios
|
336 |
-
if portfolio_ids:
|
337 |
-
delete_portfolios_url = f"{SUPABASE_URL}/rest/v1/Portfolio?id=in.({','.join(map(str, portfolio_ids))})"
|
338 |
-
async with session.delete(delete_portfolios_url, headers=headers) as delete_response:
|
339 |
-
if delete_response.status != 204:
|
340 |
-
raise HTTPException(status_code=delete_response.status, detail="Erro ao deletar portfolios")
|
341 |
-
|
342 |
-
# Deletar imagens do bucket Supabase Storage
|
343 |
-
async def delete_image_from_storage(image_url: str):
|
344 |
-
from urllib.parse import urlparse
|
345 |
-
path = urlparse(image_url).path
|
346 |
-
# Extraindo bucket e key
|
347 |
-
parts = path.strip("/").split("/")
|
348 |
-
if len(parts) < 3:
|
349 |
-
return
|
350 |
-
bucket_id = parts[1]
|
351 |
-
file_key = "/".join(parts[2:])
|
352 |
-
delete_url = f"{SUPABASE_URL}/storage/v1/object/{bucket_id}/{file_key}"
|
353 |
-
async with session.delete(delete_url, headers=headers) as delete_img_response:
|
354 |
-
if delete_img_response.status not in (200, 204):
|
355 |
-
logger.warning(f"❌ Falha ao deletar imagem: {file_key}")
|
356 |
-
|
357 |
-
await asyncio.gather(*[delete_image_from_storage(url) for url in image_urls])
|
358 |
-
|
359 |
-
# Deletar o feed
|
360 |
-
delete_feed_url = f"{SUPABASE_URL}/rest/v1/Feeds?id=eq.{feed_id}"
|
361 |
-
async with session.delete(delete_feed_url, headers=headers) as delete_feed_response:
|
362 |
-
if delete_feed_response.status != 204:
|
363 |
-
raise HTTPException(status_code=delete_feed_response.status, detail="Erro ao deletar feed")
|
364 |
-
|
365 |
-
logger.info(f"✅ Feed {feed_id} e portfolios relacionados deletados com sucesso.")
|
366 |
-
return {"message": f"Feed {feed_id} e portfolios deletados com sucesso."}
|
367 |
-
|
368 |
-
except HTTPException as he:
|
369 |
-
raise he
|
370 |
-
except Exception as e:
|
371 |
-
logger.error(f"❌ Erro ao deletar feed: {str(e)}")
|
372 |
-
raise HTTPException(status_code=500, detail="Erro interno do servidor")
|
373 |
-
|
374 |
-
@router.get("/admin/user")
|
375 |
-
async def get_user_name(
|
376 |
-
user_id: str = Query(..., description="ID do usuário"),
|
377 |
-
user_token: str = Header(None, alias="User-key")
|
378 |
-
):
|
379 |
-
"""
|
380 |
-
Endpoint para obter informações de um usuário específico a partir do ID,
|
381 |
-
incluindo seus feeds e uma imagem de portfólio para cada feed.
|
382 |
-
"""
|
383 |
-
try:
|
384 |
-
# Verificar se o usuário tem permissão para visualizar usuários
|
385 |
-
user_info = await verify_token_with_permissions(user_token, "view_users")
|
386 |
-
|
387 |
-
# Buscar informações básicas do usuário
|
388 |
-
user_query = f"{SUPABASE_URL}/rest/v1/User?select=name,avatar,role,fee&id=eq.{user_id}"
|
389 |
-
|
390 |
-
# Buscar feeds do usuário
|
391 |
-
feeds_query = f"{SUPABASE_URL}/rest/v1/Feeds?select=id,portfolios,created_at,description,urls,user_id&user_id=eq.{user_id}&order=created_at.desc"
|
392 |
-
|
393 |
-
headers = SUPABASE_HEADERS.copy()
|
394 |
-
headers["Accept"] = "application/json; charset=utf-8"
|
395 |
-
|
396 |
-
async with aiohttp.ClientSession() as session:
|
397 |
-
# Fazer as requisições em paralelo
|
398 |
-
user_task = session.get(user_query, headers=headers)
|
399 |
-
feeds_task = session.get(feeds_query, headers=headers)
|
400 |
-
|
401 |
-
async with user_task as user_response, feeds_task as feeds_response:
|
402 |
-
if user_response.status != 200 or feeds_response.status != 200:
|
403 |
-
status = user_response.status if user_response.status != 200 else feeds_response.status
|
404 |
-
raise HTTPException(status_code=status, detail="Erro ao consultar o Supabase")
|
405 |
-
|
406 |
-
user_data = await user_response.json()
|
407 |
-
feeds_data = await feeds_response.json()
|
408 |
-
|
409 |
-
if not user_data:
|
410 |
-
raise HTTPException(status_code=404, detail="Usuário não encontrado")
|
411 |
-
|
412 |
-
# Processar feeds para buscar imagens de portfólio
|
413 |
-
feeds_with_images = []
|
414 |
-
for feed in feeds_data:
|
415 |
-
feed_info = {
|
416 |
-
"id": feed["id"],
|
417 |
-
"created_at": feed["created_at"],
|
418 |
-
"description": feed["description"],
|
419 |
-
"urls": feed["urls"],
|
420 |
-
"portfolios": feed["portfolios"]
|
421 |
-
}
|
422 |
-
|
423 |
-
# Verificar se há portfolios associados
|
424 |
-
if feed["portfolios"] and len(feed["portfolios"]) > 0:
|
425 |
-
# Pegar o primeiro portfolio do array
|
426 |
-
first_portfolio_id = feed["portfolios"][0]
|
427 |
-
|
428 |
-
# Buscar informações da imagem deste portfolio
|
429 |
-
portfolio_query = f"{SUPABASE_URL}/rest/v1/Portfolio?select=image_url,blurhash,width,height&id=eq.{first_portfolio_id}"
|
430 |
-
|
431 |
-
async with session.get(portfolio_query, headers=headers) as portfolio_response:
|
432 |
-
if portfolio_response.status == 200:
|
433 |
-
portfolio_data = await portfolio_response.json()
|
434 |
-
|
435 |
-
if portfolio_data and len(portfolio_data) > 0:
|
436 |
-
feed_info["thumbnail"] = {
|
437 |
-
"image_url": portfolio_data[0]["image_url"],
|
438 |
-
"blurhash": portfolio_data[0]["blurhash"],
|
439 |
-
"width": portfolio_data[0]["width"],
|
440 |
-
"height": portfolio_data[0]["height"]
|
441 |
-
}
|
442 |
-
|
443 |
-
feeds_with_images.append(feed_info)
|
444 |
-
|
445 |
-
# Construir a resposta completa
|
446 |
-
response = {
|
447 |
-
"user": user_data[0],
|
448 |
-
"feeds": feeds_with_images,
|
449 |
-
"feeds_count": len(feeds_with_images)
|
450 |
-
}
|
451 |
-
|
452 |
-
return response
|
453 |
-
|
454 |
-
except HTTPException as he:
|
455 |
-
raise he
|
456 |
-
except Exception as e:
|
457 |
-
logger.error(f"❌ Erro ao buscar usuário e feeds: {str(e)}")
|
458 |
-
raise HTTPException(status_code=500, detail="Erro interno do servidor")
|
|
|
13 |
SUPABASE_URL = "https://ussxqnifefkgkaumjann.supabase.co"
|
14 |
SUPABASE_KEY = os.getenv("SUPA_KEY")
|
15 |
SUPABASE_ROLE_KEY = os.getenv("SUPA_SERVICE_KEY")
|
|
|
|
|
|
|
16 |
|
17 |
if not SUPABASE_KEY or not SUPABASE_ROLE_KEY:
|
18 |
raise ValueError("❌ SUPA_KEY ou SUPA_SERVICE_KEY não foram definidos no ambiente!")
|
|
|
195 |
|
196 |
except Exception as e:
|
197 |
logger.error(f"❌ Erro ao obter usuários: {str(e)}")
|
198 |
+
raise HTTPException(status_code=500, detail=str(e))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|