connect / routes /onboarding.py
habulaj's picture
Update routes/onboarding.py
c2bb33b verified
raw
history blame
11.5 kB
import os
import logging
import aiohttp
from pydantic import BaseModel
from fastapi import APIRouter, HTTPException, Body, Query, Header
from typing import List, Dict, Any, Optional, Union
router = APIRouter()
class UpdateOnboardingQuestion(BaseModel):
id: int
title: Optional[str] = None
question_type: Optional[str] = None
optional: Optional[bool] = None
options: Optional[List[str]] = None
class CreateOnboardingQuestion(BaseModel):
title: Optional[str] = None
question_type: Optional[str] = None
optional: Optional[bool] = None
options: Optional[Union[List[str], str, dict]] = None
target_type: str
# Supabase configs
SUPABASE_URL = "https://ussxqnifefkgkaumjann.supabase.co"
SUPABASE_KEY = os.getenv("SUPA_KEY")
SUPABASE_ROLE_KEY = os.getenv("SUPA_SERVICE_KEY")
if not SUPABASE_KEY or not SUPABASE_ROLE_KEY:
raise ValueError("❌ SUPA_KEY ou SUPA_SERVICE_KEY não foram definidos no ambiente!")
SUPABASE_HEADERS = {
"apikey": SUPABASE_KEY,
"Authorization": f"Bearer {SUPABASE_KEY}",
"Content-Type": "application/json"
}
SUPABASE_ROLE_HEADERS = {
"apikey": SUPABASE_ROLE_KEY,
"Authorization": f"Bearer {SUPABASE_ROLE_KEY}",
"Content-Type": "application/json"
}
# Logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
# Função para verificar token e permissões
async def verify_token_with_permissions(user_token: str, required_permission: Optional[str] = None) -> Dict[str, Any]:
"""Verifica o token e retorna ID do usuário e suas permissões"""
headers = {
"Authorization": f"Bearer {user_token}",
"apikey": SUPABASE_KEY,
"Content-Type": "application/json"
}
async with aiohttp.ClientSession() as session:
async with session.get(f"{SUPABASE_URL}/auth/v1/user", headers=headers) as response:
if response.status != 200:
raise HTTPException(status_code=401, detail="Token inválido ou expirado")
user_data = await response.json()
user_id = user_data.get("id")
if not user_id:
raise HTTPException(status_code=400, detail="ID do usuário não encontrado")
# Obter permissões do usuário
user_data_url = f"{SUPABASE_URL}/rest/v1/User?id=eq.{user_id}&select=is_admin,edit_onboarding"
async with aiohttp.ClientSession() as session:
async with session.get(user_data_url, headers=SUPABASE_HEADERS) as response:
if response.status != 200 or not await response.json():
raise HTTPException(status_code=403, detail="Acesso negado: não foi possível verificar permissões")
user_info = (await response.json())[0]
is_admin = user_info.get("is_admin", False)
if not is_admin:
raise HTTPException(status_code=403, detail="Acesso negado: privilégios de administrador necessários")
# Verificar permissão específica, se requisitada
if required_permission:
has_permission = user_info.get(required_permission, False)
if not has_permission:
raise HTTPException(
status_code=403,
detail=f"Acesso negado: permissão '{required_permission}' necessária"
)
return {
"user_id": user_id,
"is_admin": is_admin,
"permissions": user_info
}
@router.patch("/onboarding/update-question")
async def update_onboarding_question(
payload: UpdateOnboardingQuestion = Body(...),
user_token: str = Header(None, alias="User-key")
):
"""
Atualiza uma pergunta de onboarding com base no ID.
Apenas os campos enviados no payload serão atualizados.
Requer permissão de admin e edit_onboarding=true.
"""
try:
# Verificar se o usuário é admin e tem permissão para editar onboarding
await verify_token_with_permissions(user_token, "edit_onboarding")
update_data = {}
if payload.title is not None:
update_data["title"] = payload.title
if payload.question_type is not None:
update_data["question_type"] = payload.question_type
if payload.optional is not None:
update_data["optional"] = payload.optional
if payload.options is not None:
update_data["options"] = payload.options
if not update_data:
raise HTTPException(status_code=400, detail="Nenhum campo para atualizar foi fornecido.")
query_url = f"{SUPABASE_URL}/rest/v1/Onboarding?id=eq.{payload.id}"
headers = SUPABASE_ROLE_HEADERS.copy()
headers["Prefer"] = "return=representation"
async with aiohttp.ClientSession() as session:
async with session.patch(query_url, json=update_data, headers=headers) as response:
if response.status != 200:
detail = await response.text()
logger.error(f"❌ Erro ao atualizar pergunta: {detail}")
raise HTTPException(status_code=response.status, detail="Erro ao atualizar pergunta")
updated = await response.json()
return {"message": "✅ Pergunta atualizada com sucesso!", "updated": updated}
except HTTPException as he:
raise he
except Exception as e:
logger.error(f"❌ Erro interno ao atualizar pergunta: {str(e)}")
raise HTTPException(status_code=500, detail="Erro interno do servidor")
@router.post("/onboarding/add-question")
async def add_onboarding_question(
payload: CreateOnboardingQuestion = Body(...),
user_token: str = Header(None, alias="User-key")
):
"""
Adiciona uma nova pergunta de onboarding.
Trata casos onde `options` vem como `{}` ou string.
Requer permissão de admin e edit_onboarding=true.
"""
try:
# Verificar se o usuário é admin e tem permissão para editar onboarding
await verify_token_with_permissions(user_token, "edit_onboarding")
# Tratamento de `options`
options = payload.options
if isinstance(options, dict) and not options:
options = None
elif isinstance(options, str):
# Transforma string simples em lista com um item capitalizado
options = [options.strip().capitalize()]
new_question = {
"title": payload.title,
"question_type": payload.question_type,
"optional": payload.optional,
"options": options,
"target_type": payload.target_type
}
query_url = f"{SUPABASE_URL}/rest/v1/Onboarding"
headers = SUPABASE_ROLE_HEADERS.copy()
headers["Prefer"] = "return=representation"
async with aiohttp.ClientSession() as session:
async with session.post(query_url, json=new_question, headers=headers) as response:
if response.status not in (200, 201):
detail = await response.text()
logger.error(f"❌ Erro ao adicionar pergunta: {detail}")
raise HTTPException(status_code=response.status, detail="Erro ao adicionar pergunta")
created = await response.json()
return {"message": "✅ Pergunta adicionada com sucesso!", "created": created}
except HTTPException as he:
raise he
except Exception as e:
logger.error(f"❌ Erro interno ao adicionar pergunta: {str(e)}")
raise HTTPException(status_code=500, detail="Erro interno do servidor")
@router.delete("/onboarding/delete-question")
async def delete_onboarding_question(
id: int = Query(..., description="ID da pergunta a ser deletada"),
user_token: str = Header(None, alias="User-key")
):
"""
Deleta uma pergunta de onboarding com base no ID (passado via query parameter).
Requer permissão de admin e edit_onboarding=true.
"""
try:
# Verificar se o usuário é admin e tem permissão para editar onboarding
await verify_token_with_permissions(user_token, "edit_onboarding")
query_url = f"{SUPABASE_URL}/rest/v1/Onboarding?id=eq.{id}"
headers = SUPABASE_ROLE_HEADERS.copy()
headers["Prefer"] = "return=representation"
async with aiohttp.ClientSession() as session:
async with session.delete(query_url, headers=headers) as response:
if response.status != 200:
detail = await response.text()
logger.error(f"❌ Erro ao deletar pergunta: {detail}")
raise HTTPException(status_code=response.status, detail="Erro ao deletar pergunta")
deleted = await response.json()
if not deleted:
raise HTTPException(status_code=404, detail="Pergunta não encontrada ou já deletada.")
return {"message": "🗑️ Pergunta deletada com sucesso!", "deleted": deleted}
except HTTPException as he:
raise he
except Exception as e:
logger.error(f"❌ Erro interno ao deletar pergunta: {str(e)}")
raise HTTPException(status_code=500, detail="Erro interno do servidor")
@router.get("/onboarding/questions")
async def get_onboarding_questions(user_token: str = Header(None, alias="User-key")) -> Dict[str, List[Dict[str, Any]]]:
"""
Retorna todas as perguntas de onboarding, separadas por target_type (client e stylist).
Requer permissão de admin e edit_onboarding=true.
"""
try:
# Verificar se o usuário é admin e tem permissão para editar onboarding
await verify_token_with_permissions(user_token, "edit_onboarding")
query_url = f"{SUPABASE_URL}/rest/v1/Onboarding?select=id,title,description,question_type,options,target_type,optional,lock&order=created_at.asc"
headers = SUPABASE_HEADERS.copy()
headers["Accept"] = "application/json"
async with aiohttp.ClientSession() as session:
async with session.get(query_url, headers=headers) as response:
if response.status != 200:
logger.error(f"❌ Erro ao buscar onboarding: {response.status}")
raise HTTPException(status_code=response.status, detail="Erro ao buscar onboarding")
data = await response.json()
# Organizar perguntas por tipo de usuário
client_questions = []
stylist_questions = []
for question in data:
formatted = {
"id": question["id"],
"title": question["title"],
"description": question.get("description"),
"question_type": question["question_type"],
"options": question.get("options", []),
"optional": question.get("optional", False),
"lock": question.get("lock", False)
}
if question["target_type"] == "client":
client_questions.append(formatted)
elif question["target_type"] == "stylist":
stylist_questions.append(formatted)
return {
"client_questions": client_questions,
"stylist_questions": stylist_questions
}
except HTTPException as he:
raise he
except Exception as e:
logger.error(f"❌ Erro ao obter perguntas de onboarding: {str(e)}")
raise HTTPException(status_code=500, detail="Erro interno do servidor")