183 lines
6.7 KiB
Python
183 lines
6.7 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 y nombre elegidos
|
|
try:
|
|
supabase_admin.table("users").upsert(
|
|
{"id": str(auth_user.id), "role": body.role, "name": body.name}
|
|
).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
|
|
|
|
calle = body.address_calle or body.addressCalle
|
|
colonia = body.address_colonia or body.addressColonia
|
|
label = body.address_label or body.addressLabel or "Mi Casa"
|
|
lat = body.address_lat if body.address_lat is not None else body.addressLat
|
|
lng = body.address_lng if body.address_lng is not None else body.addressLng
|
|
|
|
if calle and colonia:
|
|
try:
|
|
from app.services.simulation import get_colonias
|
|
mapping = get_colonias()
|
|
match = next(
|
|
(c for c in mapping if c.get("colonia", "").lower() == colonia.lower() or c.get("nombre", "").lower() == colonia.lower()),
|
|
None,
|
|
)
|
|
if match:
|
|
addr_data: dict = {
|
|
"user_id": str(auth_user.id),
|
|
"label": label,
|
|
"calle": calle,
|
|
"colonia": colonia,
|
|
"route_id": match["routeId"],
|
|
"verified": False,
|
|
}
|
|
if lat is not None:
|
|
addr_data["lat"] = lat
|
|
if lng is not None:
|
|
addr_data["lng"] = lng
|
|
|
|
try:
|
|
supabase_admin.table("addresses").insert(addr_data).execute()
|
|
saved_route_id = match["routeId"]
|
|
except Exception as db_err:
|
|
if "PGRST204" in str(db_err) or "lat" in str(db_err):
|
|
addr_data.pop("lat", None)
|
|
addr_data.pop("lng", None)
|
|
supabase_admin.table("addresses").insert(addr_data).execute()
|
|
saved_route_id = match["routeId"]
|
|
else:
|
|
raise db_err
|
|
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,
|
|
)
|