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, )