Files
hackathon-innovaflow5.0-cdf…/backend/app/api/auth.py
shinra32 45ffba69b2 Co-authored-by: eddgranados12 <eddgranados12@users.noreply.github.com>
Co-authored-by: MENDOZA BALLARDO GAEL RICARDO <gael-meb123@users.noreply.github.com>
Co-authored-by: Azareth-Tr <Azareth-Tr@users.noreply.github.com>

modificacion de las vistas principales para el usuario ciudadano, primer avance para el panel admin
2026-05-23 03:13:46 -06:00

166 lines
6.0 KiB
Python

from fastapi import APIRouter, HTTPException, status
from app.schemas.auth import RegisterRequest, LoginRequest, TokenResponse
from app.core.supabase_client import supabase, supabase_admin
router = APIRouter(prefix="/auth", tags=["auth"])
def _fetch_role(user_id: str) -> str:
result = (
supabase_admin.table("users")
.select("role")
.eq("id", user_id)
.maybe_single()
.execute()
)
return result.data["role"] if result.data else "citizen"
def _fetch_route_for_citizen(user_id: str) -> str | None:
"""
Busca la primera dirección verificada del ciudadano y devuelve su `route_id`.
Devuelve None si no hay dirección verificada.
"""
try:
res = (
supabase_admin.table("addresses")
.select("route_id")
.eq("user_id", user_id)
.eq("verified", True)
.limit(1)
.maybe_single()
.execute()
)
if res.data and isinstance(res.data, dict):
return res.data.get("route_id")
except Exception:
return None
return None
@router.post("/register", response_model=TokenResponse, status_code=status.HTTP_201_CREATED)
def register(body: RegisterRequest):
"""
Registro por email o teléfono. Usa el admin client para confirmar automáticamente
sin requerir que el usuario verifique su correo.
"""
if not body.email and not body.phone:
raise HTTPException(status_code=400, detail="Se requiere email o teléfono")
if len(body.password) < 6:
raise HTTPException(status_code=400, detail="La contraseña debe tener al menos 6 caracteres.")
# Crear usuario con confirmación automática vía service_role (bypasea email confirmation)
try:
create_attrs: dict = {"password": body.password}
if body.email:
create_attrs["email"] = body.email
create_attrs["email_confirm"] = True
else:
create_attrs["phone"] = body.phone
create_attrs["phone_confirm"] = True
admin_resp = supabase_admin.auth.admin.create_user(create_attrs)
except Exception as e:
error_msg = str(e)
if "already registered" in error_msg.lower() or "user already exists" in error_msg.lower() or "already been registered" in error_msg.lower():
raise HTTPException(status_code=400, detail="El usuario ya está registrado.")
if "signups are disabled" in error_msg.lower():
raise HTTPException(status_code=400, detail="El registro de nuevos usuarios está deshabilitado temporalmente.")
raise HTTPException(status_code=400, detail=error_msg)
auth_user = admin_resp.user
if not auth_user:
raise HTTPException(status_code=400, detail="No se pudo crear el usuario en Supabase Auth")
# Crear entrada en public.users con el rol elegido
try:
supabase_admin.table("users").upsert(
{"id": str(auth_user.id), "role": body.role}
).execute()
except Exception as e:
raise HTTPException(status_code=500, detail=f"Error al guardar el usuario: {e}")
# Iniciar sesión para obtener el JWT
try:
if body.email:
session_resp = supabase.auth.sign_in_with_password(
{"email": body.email, "password": body.password}
)
else:
session_resp = supabase.auth.sign_in_with_password(
{"phone": body.phone, "password": body.password}
)
except Exception as e:
raise HTTPException(status_code=500, detail=f"Usuario creado pero no se pudo iniciar sesión: {e}")
# Guardar dirección inicial si viene en el payload (evita un segundo HTTP call desde Flutter)
saved_route_id: str | None = None
if body.address_calle and body.address_colonia:
try:
from app.services.simulation import get_colonias
mapping = get_colonias()
match = next(
(c for c in mapping if c.get("colonia", "").lower() == body.address_colonia.lower()),
None,
)
if match:
addr_data: dict = {
"user_id": str(auth_user.id),
"label": body.address_label or "Mi Casa",
"calle": body.address_calle,
"colonia": body.address_colonia,
"route_id": match["routeId"],
"verified": False,
}
if body.address_lat is not None:
addr_data["lat"] = body.address_lat
if body.address_lng is not None:
addr_data["lng"] = body.address_lng
supabase_admin.table("addresses").insert(addr_data).execute()
saved_route_id = match["routeId"]
except Exception as e:
print(f"[register] No se pudo guardar la dirección inicial: {e}")
return TokenResponse(
access_token=session_resp.session.access_token,
user_id=str(auth_user.id),
role=body.role,
route_id=saved_route_id,
)
@router.post("/login", response_model=TokenResponse)
def login(body: LoginRequest):
"""Login por email o teléfono; devuelve JWT de Supabase."""
if not body.email and not body.phone:
raise HTTPException(status_code=400, detail="Se requiere email o teléfono")
try:
if body.email:
resp = supabase.auth.sign_in_with_password(
{"email": body.email, "password": body.password}
)
else:
resp = supabase.auth.sign_in_with_password(
{"phone": body.phone, "password": body.password}
)
except Exception:
raise HTTPException(status_code=401, detail="Credenciales inválidas")
auth_user = resp.user
user_id = str(auth_user.id)
role = _fetch_role(user_id)
route_id = None
if role == 'citizen':
route_id = _fetch_route_for_citizen(user_id)
return TokenResponse(
access_token=resp.session.access_token,
user_id=user_id,
role=role,
route_id=route_id,
)