refactor: migrate authentication from Supabase to Better Auth and update project configuration
This commit is contained in:
@@ -18,11 +18,7 @@ WORKDIR /app
|
|||||||
COPY apps/frontend ./apps/frontend
|
COPY apps/frontend ./apps/frontend
|
||||||
COPY packages ./packages
|
COPY packages ./packages
|
||||||
ARG VITE_API_BASE_URL
|
ARG VITE_API_BASE_URL
|
||||||
ARG VITE_SUPABASE_URL
|
|
||||||
ARG VITE_SUPABASE_ANON_KEY
|
|
||||||
ENV VITE_API_BASE_URL=${VITE_API_BASE_URL}
|
ENV VITE_API_BASE_URL=${VITE_API_BASE_URL}
|
||||||
ENV VITE_SUPABASE_URL=${VITE_SUPABASE_URL}
|
|
||||||
ENV VITE_SUPABASE_ANON_KEY=${VITE_SUPABASE_ANON_KEY}
|
|
||||||
RUN bun run build:frontend
|
RUN bun run build:frontend
|
||||||
|
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
|
|||||||
183
README.md
183
README.md
@@ -1,184 +1,105 @@
|
|||||||
# Monorepo (Frontend + Backend + Contrato API)
|
# Playzer Monorepo
|
||||||
|
|
||||||
Este repositorio contiene:
|
Este repositorio contiene:
|
||||||
|
|
||||||
- `apps/frontend`: app web en React + Vite + TanStack Router/Query.
|
- `apps/frontend`: React + Vite + TanStack Router/Query.
|
||||||
- `apps/backend`: API en Bun + Hono + Prisma + Supabase Auth.
|
- `apps/backend`: Bun + Hono + Prisma + Better Auth.
|
||||||
- `packages/api-contract`: contratos compartidos (schemas/types/rutas) entre frontend y backend.
|
- `packages/api-contract`: contratos Zod/tipos compartidos.
|
||||||
|
|
||||||
## Objetivo de este README
|
## Requisitos
|
||||||
|
|
||||||
Dejarte productivo/a en pocos minutos para que puedas:
|
- Bun reciente
|
||||||
|
- PostgreSQL disponible
|
||||||
1. Levantar frontend y backend en local.
|
|
||||||
2. Configurar variables de entorno.
|
|
||||||
3. Ejecutar migraciones de base de datos.
|
|
||||||
4. Entender la estructura y el flujo de trabajo del proyecto.
|
|
||||||
|
|
||||||
## Stack técnico
|
|
||||||
|
|
||||||
- Runtime/package manager: `bun`
|
|
||||||
- Frontend: `React 19`, `Vite`, `TanStack Router`, `TanStack Query`
|
|
||||||
- Backend: `Bun`, `Hono`, `Prisma`, `PostgreSQL`
|
|
||||||
- Auth: `Supabase Auth` (token Bearer validado en backend)
|
|
||||||
- Validación de contratos: `zod` en `@repo/api-contract`
|
|
||||||
|
|
||||||
## Prerrequisitos
|
|
||||||
|
|
||||||
- `bun` instalado (versión reciente).
|
|
||||||
- `PostgreSQL` corriendo localmente (o accesible remotamente).
|
|
||||||
- Proyecto de `Supabase` para autenticación (URL + anon key).
|
|
||||||
|
|
||||||
## Setup rápido
|
## Setup rápido
|
||||||
|
|
||||||
1. Instalar dependencias desde la raíz:
|
1. Instalar dependencias:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
bun install
|
bun install
|
||||||
```
|
```
|
||||||
|
|
||||||
2. Configurar variables de entorno del backend:
|
2. Configurar backend:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
cp apps/backend/.env.example apps/backend/.env
|
cp apps/backend/.env.example apps/backend/.env
|
||||||
```
|
```
|
||||||
|
|
||||||
3. Configurar variables de entorno del frontend:
|
3. Configurar frontend:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
cp apps/frontend/.env.example apps/frontend/.env
|
cp apps/frontend/.env.example apps/frontend/.env
|
||||||
```
|
```
|
||||||
|
|
||||||
4. Revisar/editar valores en ambos `.env` según tu entorno.
|
4. Ejecutar migraciones:
|
||||||
|
|
||||||
## Variables de entorno
|
|
||||||
|
|
||||||
### Backend (`apps/backend/.env`)
|
|
||||||
|
|
||||||
Variables esperadas:
|
|
||||||
|
|
||||||
- `SUPABASE_URL`
|
|
||||||
- `SUPABASE_ANON_KEY`
|
|
||||||
- `CORS_ORIGIN` (por defecto local frontend)
|
|
||||||
- `LOG_LEVEL`
|
|
||||||
- `DATABASE_URL` (PostgreSQL)
|
|
||||||
|
|
||||||
Notas:
|
|
||||||
|
|
||||||
- El backend también acepta `VITE_SUPABASE_URL` y `VITE_SUPABASE_ANON_KEY` como fallback.
|
|
||||||
- Si falta `DATABASE_URL`, Prisma no puede inicializarse.
|
|
||||||
|
|
||||||
### Frontend (`apps/frontend/.env`)
|
|
||||||
|
|
||||||
Variables esperadas:
|
|
||||||
|
|
||||||
- `VITE_SUPABASE_URL`
|
|
||||||
- `VITE_SUPABASE_ANON_KEY`
|
|
||||||
- `VITE_API_BASE_URL` (por defecto: `http://localhost:3000`)
|
|
||||||
|
|
||||||
## Base de datos (Prisma)
|
|
||||||
|
|
||||||
Antes de arrancar backend por primera vez, corré migraciones:
|
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
bun --filter backend prisma:migrate
|
bun --filter backend prisma:migrate
|
||||||
```
|
```
|
||||||
|
|
||||||
Si cambiás el schema, regenerá cliente:
|
## Variables de entorno
|
||||||
|
|
||||||
```sh
|
### Backend (`apps/backend/.env`)
|
||||||
bun --filter backend prisma:generate
|
|
||||||
```
|
|
||||||
|
|
||||||
## Ejecutar en desarrollo
|
- `DATABASE_URL`
|
||||||
|
- `BETTER_AUTH_SECRET`
|
||||||
|
- `BETTER_AUTH_URL`
|
||||||
|
- `APP_BASE_URL`
|
||||||
|
- `CORS_ORIGIN`
|
||||||
|
|
||||||
Terminal 1 (backend):
|
### Frontend (`apps/frontend/.env`)
|
||||||
|
|
||||||
|
- `VITE_API_BASE_URL`
|
||||||
|
|
||||||
|
## Desarrollo
|
||||||
|
|
||||||
|
Backend:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
bun run dev:backend
|
bun run dev:backend
|
||||||
```
|
```
|
||||||
|
|
||||||
Terminal 2 (frontend):
|
Frontend:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
bun run dev:frontend
|
bun run dev:frontend
|
||||||
```
|
```
|
||||||
|
|
||||||
Puertos por defecto:
|
Todo junto:
|
||||||
|
|
||||||
- Frontend: `http://localhost:5173`
|
```sh
|
||||||
- Backend: `http://localhost:3000`
|
bun run dev
|
||||||
|
|
||||||
## Scripts útiles
|
|
||||||
|
|
||||||
Desde la raíz:
|
|
||||||
|
|
||||||
- `bun run dev:frontend`
|
|
||||||
- `bun run dev:backend`
|
|
||||||
- `bun run build:frontend`
|
|
||||||
- `bun run lint:frontend`
|
|
||||||
|
|
||||||
Desde `apps/backend`:
|
|
||||||
|
|
||||||
- `bun run dev`
|
|
||||||
- `bun run start`
|
|
||||||
- `bun run prisma:migrate`
|
|
||||||
- `bun run prisma:generate`
|
|
||||||
|
|
||||||
## Estructura del repo
|
|
||||||
|
|
||||||
```txt
|
|
||||||
.
|
|
||||||
├─ apps/
|
|
||||||
│ ├─ frontend/ # UI y navegación
|
|
||||||
│ └─ backend/ # API, auth middleware, acceso a DB
|
|
||||||
└─ packages/
|
|
||||||
└─ api-contract/ # Schemas/tipos/rutas compartidos
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## Flujo de autenticación (resumen)
|
## Prisma
|
||||||
|
|
||||||
1. Frontend autentica con Supabase.
|
Regenerar cliente después de cambios de schema:
|
||||||
2. Frontend guarda el access token y lo envía como `Authorization: Bearer ...` al backend.
|
|
||||||
3. Backend valida token con Supabase (`requireAuth` middleware).
|
|
||||||
4. Si el token es válido, habilita rutas protegidas (`/api/user/*`, `/api/tenants/*`).
|
|
||||||
|
|
||||||
## Endpoints principales
|
```sh
|
||||||
|
bun --filter backend prisma:generate
|
||||||
|
```
|
||||||
|
|
||||||
- `GET /api/user/profile`
|
## Autenticación
|
||||||
- `POST /api/tenants`
|
|
||||||
- `GET /api/tenants/:id`
|
|
||||||
- `PATCH /api/tenants/:id`
|
|
||||||
|
|
||||||
## Convenciones recomendadas para el equipo
|
El proyecto usa **Better Auth** con sesión por cookie.
|
||||||
|
|
||||||
- Mantener contratos de API en `packages/api-contract` (evitar duplicar tipos).
|
- Backend: `c.get('user')` y `c.get('session')` en contexto Hono.
|
||||||
- Si agregás/cambiás endpoint:
|
- Frontend: `authClient` con `fetchOptions.credentials = 'include'`.
|
||||||
1. Actualizá schema/tipos en `api-contract`.
|
- Frontend API: Axios con `withCredentials = true`.
|
||||||
2. Implementá handler/ruta en backend.
|
- Backend CORS: `credentials: true`.
|
||||||
3. Consumí el contrato desde frontend.
|
|
||||||
- Validar payloads con Zod tanto en contrato como en rutas.
|
|
||||||
- Mantener PRs chicos y enfocados.
|
|
||||||
|
|
||||||
## Troubleshooting rápido
|
## Deploy (Docker)
|
||||||
|
|
||||||
- Error de CORS:
|
- Build context: raíz del monorepo (`./`)
|
||||||
- Revisar `CORS_ORIGIN` en backend y que incluya `http://localhost:5173`.
|
- Dockerfile: `./Dockerfile`
|
||||||
- `403 Missing bearer token`:
|
- Build args requeridos:
|
||||||
- Verificar login en frontend y envío del header `Authorization`.
|
- `DATABASE_URL`
|
||||||
- Error de Supabase env vars:
|
- `VITE_API_BASE_URL`
|
||||||
- Confirmar `SUPABASE_URL`/`SUPABASE_ANON_KEY` (backend) y `VITE_*` (frontend).
|
- `BETTER_AUTH_SECRET`
|
||||||
- Error de Prisma/DB:
|
- `BETTER_AUTH_URL`
|
||||||
- Confirmar `DATABASE_URL`, conectividad a PostgreSQL y migraciones aplicadas.
|
|
||||||
|
|
||||||
## Primer día sugerido para onboarding
|
## Flujo recomendado de cambios de API
|
||||||
|
|
||||||
1. Levantar backend y frontend en local.
|
1. Actualizar contrato en `packages/api-contract`.
|
||||||
2. Crear usuario o iniciar sesión desde la pantalla de onboard.
|
2. Implementar backend en `apps/backend`.
|
||||||
3. Crear un tenant de prueba.
|
3. Consumir contrato desde frontend (`@repo/api-contract`).
|
||||||
4. Probar lectura/edición de tenant.
|
|
||||||
5. Revisar `packages/api-contract` para entender cómo se comparten tipos y rutas.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
Si te trabás en el setup, empezá verificando `.env`, estado de PostgreSQL y que ambos procesos (`dev:backend` y `dev:frontend`) estén corriendo.
|
|
||||||
|
|||||||
@@ -1,9 +1,5 @@
|
|||||||
SUPABASE_URL=https://your-project-ref.supabase.co
|
BETTER_AUTH_SECRET=your-random-secret
|
||||||
SUPABASE_ANON_KEY=your-anon-key
|
BETTER_AUTH_URL=http://localhost:3000
|
||||||
SUPABASE_SERVICE_ROLE_KEY=your-service-role-key
|
|
||||||
# Fallbacks accepted too:
|
|
||||||
# VITE_SUPABASE_URL=https://your-project-ref.supabase.co
|
|
||||||
# VITE_SUPABASE_ANON_KEY=your-anon-key
|
|
||||||
CORS_ORIGIN=http://localhost:5173,http://127.0.0.1:5173
|
CORS_ORIGIN=http://localhost:5173,http://127.0.0.1:5173
|
||||||
LOG_LEVEL=debug
|
LOG_LEVEL=debug
|
||||||
DATABASE_URL=postgresql://postgres:postgres@localhost:5432/monorepo
|
DATABASE_URL=postgresql://postgres:postgres@localhost:5432/monorepo
|
||||||
|
|||||||
@@ -1,11 +1 @@
|
|||||||
// model User {
|
// User model is defined in auth.prisma (Better Auth schema).
|
||||||
// id String @id @db.Uuid
|
|
||||||
// supabaseUserId String @unique @map("supabase_user_id") @db.Uuid
|
|
||||||
// email String @unique
|
|
||||||
// fullName String @map("full_name")
|
|
||||||
// createdAt DateTime @default(now()) @map("created_at")
|
|
||||||
// updatedAt DateTime @updatedAt @map("updated_at")
|
|
||||||
// complexes ComplexUser[]
|
|
||||||
|
|
||||||
// @@map("users")
|
|
||||||
// }
|
|
||||||
|
|||||||
@@ -1,26 +0,0 @@
|
|||||||
import { createClient } from '@supabase/supabase-js';
|
|
||||||
|
|
||||||
let cachedClient: ReturnType<typeof createClient> | null = null;
|
|
||||||
|
|
||||||
export function getSupabaseAdminClient() {
|
|
||||||
if (cachedClient) return cachedClient;
|
|
||||||
|
|
||||||
const supabaseUrl = Bun.env.SUPABASE_URL ?? Bun.env.VITE_SUPABASE_URL;
|
|
||||||
const supabaseServiceRoleKey = Bun.env.SUPABASE_SERVICE_ROLE_KEY;
|
|
||||||
|
|
||||||
if (!supabaseUrl || !supabaseServiceRoleKey) {
|
|
||||||
throw new Error(
|
|
||||||
'Missing Supabase admin env vars. Set SUPABASE_URL and SUPABASE_SERVICE_ROLE_KEY in backend environment.'
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
cachedClient = createClient(supabaseUrl, supabaseServiceRoleKey, {
|
|
||||||
auth: {
|
|
||||||
autoRefreshToken: false,
|
|
||||||
persistSession: false,
|
|
||||||
detectSessionInUrl: false,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
return cachedClient;
|
|
||||||
}
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
import { createClient } from '@supabase/supabase-js';
|
|
||||||
|
|
||||||
const supabaseUrl = Bun.env.SUPABASE_URL ?? Bun.env.VITE_SUPABASE_URL;
|
|
||||||
const supabaseAnonKey = Bun.env.SUPABASE_ANON_KEY ?? Bun.env.VITE_SUPABASE_ANON_KEY;
|
|
||||||
|
|
||||||
if (!supabaseUrl || !supabaseAnonKey) {
|
|
||||||
throw new Error(
|
|
||||||
'Missing Supabase env vars. Set SUPABASE_URL/SUPABASE_ANON_KEY (or VITE_SUPABASE_URL/VITE_SUPABASE_ANON_KEY) in backend environment.'
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export const supabase = createClient(supabaseUrl, supabaseAnonKey, {
|
|
||||||
auth: {
|
|
||||||
autoRefreshToken: false,
|
|
||||||
persistSession: false,
|
|
||||||
detectSessionInUrl: false,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
@@ -2,14 +2,12 @@ import { createHash, randomInt } from 'node:crypto';
|
|||||||
import { auth } from '@/lib/auth';
|
import { auth } from '@/lib/auth';
|
||||||
import { sendMail } from '@/lib/mailer';
|
import { sendMail } from '@/lib/mailer';
|
||||||
import { db } from '@/lib/prisma';
|
import { db } from '@/lib/prisma';
|
||||||
import { getSupabaseAdminClient } from '@/lib/supabase-admin';
|
|
||||||
import type {
|
import type {
|
||||||
OnboardingCompleteInput,
|
OnboardingCompleteInput,
|
||||||
OnboardingResendOtpInput,
|
OnboardingResendOtpInput,
|
||||||
OnboardingStartInput,
|
OnboardingStartInput,
|
||||||
OnboardingVerifyOtpInput,
|
OnboardingVerifyOtpInput,
|
||||||
} from '@repo/api-contract';
|
} from '@repo/api-contract';
|
||||||
import type { User as SupabaseAuthUser } from '@supabase/supabase-js';
|
|
||||||
import { v7 as uuidv7 } from 'uuid';
|
import { v7 as uuidv7 } from 'uuid';
|
||||||
|
|
||||||
const OTP_LENGTH = 6;
|
const OTP_LENGTH = 6;
|
||||||
@@ -126,77 +124,6 @@ async function sendOtpEmail(email: string, otpCode: string) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async function findSupabaseUserByEmail(email: string): Promise<SupabaseAuthUser | null> {
|
|
||||||
let page = 1;
|
|
||||||
const perPage = 100;
|
|
||||||
|
|
||||||
while (page <= 10) {
|
|
||||||
const { data, error } = await getSupabaseAdminClient().auth.admin.listUsers({
|
|
||||||
page,
|
|
||||||
perPage,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (error) {
|
|
||||||
throw new Error(`No se pudo consultar usuarios en Supabase: ${error.message}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
const found = data.users.find((user) => user.email?.toLowerCase() === email.toLowerCase());
|
|
||||||
|
|
||||||
if (found) {
|
|
||||||
return found;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (data.users.length < perPage) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
page += 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
async function ensureSupabaseUser(params: {
|
|
||||||
email: string;
|
|
||||||
fullName: string;
|
|
||||||
password: string;
|
|
||||||
}): Promise<string> {
|
|
||||||
const existingUser = await findSupabaseUserByEmail(params.email);
|
|
||||||
|
|
||||||
if (existingUser) {
|
|
||||||
const { error } = await getSupabaseAdminClient().auth.admin.updateUserById(existingUser.id, {
|
|
||||||
password: params.password,
|
|
||||||
email_confirm: true,
|
|
||||||
user_metadata: {
|
|
||||||
full_name: params.fullName,
|
|
||||||
name: params.fullName,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
if (error) {
|
|
||||||
throw new Error(`No se pudo actualizar usuario existente en Supabase: ${error.message}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
return existingUser.id;
|
|
||||||
}
|
|
||||||
|
|
||||||
const { data, error } = await getSupabaseAdminClient().auth.admin.createUser({
|
|
||||||
email: params.email,
|
|
||||||
password: params.password,
|
|
||||||
email_confirm: true,
|
|
||||||
user_metadata: {
|
|
||||||
full_name: params.fullName,
|
|
||||||
name: params.fullName,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
if (error || !data.user) {
|
|
||||||
throw new Error(`No se pudo crear usuario en Supabase: ${error?.message ?? 'unknown error'}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
return data.user.id;
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function startOnboarding(input: OnboardingStartInput): Promise<StartOnboardingResult> {
|
export async function startOnboarding(input: OnboardingStartInput): Promise<StartOnboardingResult> {
|
||||||
const email = normalizeEmail(input.email);
|
const email = normalizeEmail(input.email);
|
||||||
const otpCode = createOtpCode();
|
const otpCode = createOtpCode();
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { createHash, randomInt } from 'node:crypto';
|
import { createHash, randomInt } from 'node:crypto';
|
||||||
|
import { auth } from '@/lib/auth';
|
||||||
import { sendMail } from '@/lib/mailer';
|
import { sendMail } from '@/lib/mailer';
|
||||||
import { db } from '@/lib/prisma';
|
import { db } from '@/lib/prisma';
|
||||||
import { getSupabaseAdminClient } from '@/lib/supabase-admin';
|
|
||||||
import type {
|
import type {
|
||||||
PasswordResetResendInput,
|
PasswordResetResendInput,
|
||||||
PasswordResetStartInput,
|
PasswordResetStartInput,
|
||||||
@@ -189,34 +189,17 @@ async function sendPasswordChangedEmail(
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async function findSupabaseUserByEmail(email: string): Promise<{ id: string } | null> {
|
async function findBetterAuthUserByEmail(email: string): Promise<{ id: string } | null> {
|
||||||
let page = 1;
|
const user = await db.user.findUnique({
|
||||||
const perPage = 100;
|
where: { email },
|
||||||
|
select: { id: true },
|
||||||
while (page <= 10) {
|
|
||||||
const { data, error } = await getSupabaseAdminClient().auth.admin.listUsers({
|
|
||||||
page,
|
|
||||||
perPage,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
if (error) {
|
if (!user) {
|
||||||
throw new Error(`No se pudo consultar usuarios en Supabase: ${error.message}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
const found = data.users.find((user) => user.email?.toLowerCase() === email.toLowerCase());
|
|
||||||
|
|
||||||
if (found) {
|
|
||||||
return { id: found.id };
|
|
||||||
}
|
|
||||||
|
|
||||||
if (data.users.length < perPage) {
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
page += 1;
|
return { id: user.id };
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function startPasswordReset(
|
export async function startPasswordReset(
|
||||||
@@ -224,7 +207,7 @@ export async function startPasswordReset(
|
|||||||
): Promise<PasswordResetStartResult> {
|
): Promise<PasswordResetStartResult> {
|
||||||
const email = normalizeEmail(input.email);
|
const email = normalizeEmail(input.email);
|
||||||
|
|
||||||
const existingUser = await findSupabaseUserByEmail(email);
|
const existingUser = await findBetterAuthUserByEmail(email);
|
||||||
if (!existingUser) {
|
if (!existingUser) {
|
||||||
return {
|
return {
|
||||||
message: 'Si el email existe, recibirás un código de verificación.',
|
message: 'Si el email existe, recibirás un código de verificación.',
|
||||||
@@ -369,13 +352,30 @@ export async function verifyOtpAndResetPassword(
|
|||||||
throw new PasswordResetError('Código incorrecto.', 400);
|
throw new PasswordResetError('Código incorrecto.', 400);
|
||||||
}
|
}
|
||||||
|
|
||||||
const existingUser = await findSupabaseUserByEmail(request.email);
|
const existingUser = await findBetterAuthUserByEmail(request.email);
|
||||||
if (!existingUser) {
|
if (!existingUser) {
|
||||||
throw new PasswordResetError('Usuario no encontrado.', 404);
|
throw new PasswordResetError('Usuario no encontrado.', 404);
|
||||||
}
|
}
|
||||||
|
|
||||||
await getSupabaseAdminClient().auth.admin.updateUserById(existingUser.id, {
|
const resetToken = uuidv7();
|
||||||
password: newPassword,
|
const identifier = `reset-password:${resetToken}`;
|
||||||
|
const expiresAt = nowPlusMinutes(OTP_TTL_MINUTES);
|
||||||
|
|
||||||
|
await db.verification.create({
|
||||||
|
data: {
|
||||||
|
id: uuidv7(),
|
||||||
|
identifier,
|
||||||
|
value: existingUser.id,
|
||||||
|
expiresAt,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
await auth.api.resetPassword({
|
||||||
|
asResponse: false,
|
||||||
|
body: {
|
||||||
|
token: resetToken,
|
||||||
|
newPassword,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
await db.passwordResetRequest.update({
|
await db.passwordResetRequest.update({
|
||||||
|
|||||||
@@ -1,3 +0,0 @@
|
|||||||
import type { User } from '@supabase/supabase-js';
|
|
||||||
|
|
||||||
export type AuthUser = User;
|
|
||||||
@@ -1,3 +1 @@
|
|||||||
VITE_SUPABASE_URL=https://your-project-ref.supabase.co
|
|
||||||
VITE_SUPABASE_ANON_KEY=your-anon-key
|
|
||||||
VITE_API_BASE_URL=http://localhost:3000
|
VITE_API_BASE_URL=http://localhost:3000
|
||||||
|
|||||||
@@ -18,7 +18,6 @@
|
|||||||
"@radix-ui/react-popover": "^1.1.2",
|
"@radix-ui/react-popover": "^1.1.2",
|
||||||
"@radix-ui/react-slot": "^1.2.4",
|
"@radix-ui/react-slot": "^1.2.4",
|
||||||
"@repo/api-contract": "workspace:*",
|
"@repo/api-contract": "workspace:*",
|
||||||
"@supabase/supabase-js": "^2.101.1",
|
|
||||||
"@tanstack/react-query": "^5.96.1",
|
"@tanstack/react-query": "^5.96.1",
|
||||||
"@tanstack/react-query-devtools": "^5.96.1",
|
"@tanstack/react-query-devtools": "^5.96.1",
|
||||||
"@tanstack/react-router": "^1.168.10",
|
"@tanstack/react-router": "^1.168.10",
|
||||||
|
|||||||
@@ -22,9 +22,6 @@ export default defineConfig({
|
|||||||
if (id.includes('node_modules/@tanstack/react-router')) {
|
if (id.includes('node_modules/@tanstack/react-router')) {
|
||||||
return 'router-vendor';
|
return 'router-vendor';
|
||||||
}
|
}
|
||||||
if (id.includes('node_modules/@supabase/supabase-js')) {
|
|
||||||
return 'supabase-vendor';
|
|
||||||
}
|
|
||||||
if (id.includes('node_modules/radix-ui') || id.includes('node_modules/lucide-react')) {
|
if (id.includes('node_modules/radix-ui') || id.includes('node_modules/lucide-react')) {
|
||||||
return 'ui-vendor';
|
return 'ui-vendor';
|
||||||
}
|
}
|
||||||
|
|||||||
21
bun.lock
21
bun.lock
@@ -47,7 +47,6 @@
|
|||||||
"@radix-ui/react-popover": "^1.1.2",
|
"@radix-ui/react-popover": "^1.1.2",
|
||||||
"@radix-ui/react-slot": "^1.2.4",
|
"@radix-ui/react-slot": "^1.2.4",
|
||||||
"@repo/api-contract": "workspace:*",
|
"@repo/api-contract": "workspace:*",
|
||||||
"@supabase/supabase-js": "^2.101.1",
|
|
||||||
"@tanstack/react-query": "^5.96.1",
|
"@tanstack/react-query": "^5.96.1",
|
||||||
"@tanstack/react-query-devtools": "^5.96.1",
|
"@tanstack/react-query-devtools": "^5.96.1",
|
||||||
"@tanstack/react-router": "^1.168.10",
|
"@tanstack/react-router": "^1.168.10",
|
||||||
@@ -515,20 +514,6 @@
|
|||||||
|
|
||||||
"@standard-schema/utils": ["@standard-schema/utils@0.3.0", "", {}, "sha512-e7Mew686owMaPJVNNLs55PUvgz371nKgwsc4vxE49zsODpJEnxgxRo2y/OKrqueavXgZNMDVj3DdHFlaSAeU8g=="],
|
"@standard-schema/utils": ["@standard-schema/utils@0.3.0", "", {}, "sha512-e7Mew686owMaPJVNNLs55PUvgz371nKgwsc4vxE49zsODpJEnxgxRo2y/OKrqueavXgZNMDVj3DdHFlaSAeU8g=="],
|
||||||
|
|
||||||
"@supabase/auth-js": ["@supabase/auth-js@2.101.1", "", { "dependencies": { "tslib": "2.8.1" } }, "sha512-Kd0Wey+RkFHgyVep7adS6UOE2pN6MJ3mZ32PAXSvfw6IjUkFRC7IQpdZZjUOcUe5pXr1ejufCRgF6lsGINe4Tw=="],
|
|
||||||
|
|
||||||
"@supabase/functions-js": ["@supabase/functions-js@2.101.1", "", { "dependencies": { "tslib": "2.8.1" } }, "sha512-OZWU7YtaG+NNNFZK8p/FuJ6gpq7pFyrG2fLOopP73HAIDHDGpOttPJapvO8ADu3RkqfQfkwrB354vPkSBbZ20A=="],
|
|
||||||
|
|
||||||
"@supabase/phoenix": ["@supabase/phoenix@0.4.0", "", {}, "sha512-RHSx8bHS02xwfHdAbX5Lpbo6PXbgyf7lTaXTlwtFDPwOIw64NnVRwFAXGojHhjtVYI+PEPNSWwkL90f4agN3bw=="],
|
|
||||||
|
|
||||||
"@supabase/postgrest-js": ["@supabase/postgrest-js@2.101.1", "", { "dependencies": { "tslib": "2.8.1" } }, "sha512-UW1RajH5jbZoK+ldAJ1I6VZ+HWwZ2oaKjEQ6Gn+AQ67CHQVxGl8wNQoLYyumbyaExm41I+wn7arulcY1eHeZJw=="],
|
|
||||||
|
|
||||||
"@supabase/realtime-js": ["@supabase/realtime-js@2.101.1", "", { "dependencies": { "@supabase/phoenix": "^0.4.0", "@types/ws": "^8.18.1", "tslib": "2.8.1", "ws": "^8.18.2" } }, "sha512-Oa6dno0OB9I+hv5do5zsZHbFu41ViZnE9IWjmkeeF/8fPmB5fWoHGqeTYEC3/0DAgtpUoFJa4FpvzFH0SBHo1Q=="],
|
|
||||||
|
|
||||||
"@supabase/storage-js": ["@supabase/storage-js@2.101.1", "", { "dependencies": { "iceberg-js": "^0.8.1", "tslib": "2.8.1" } }, "sha512-WhTaUOBgeEvnKLy95Cdlp6+D5igSF/65yC727w1olxbet5nzUvMlajKUWyzNtQu2efrz2cQ7FcdVBdQqgT9YKQ=="],
|
|
||||||
|
|
||||||
"@supabase/supabase-js": ["@supabase/supabase-js@2.101.1", "", { "dependencies": { "@supabase/auth-js": "2.101.1", "@supabase/functions-js": "2.101.1", "@supabase/postgrest-js": "2.101.1", "@supabase/realtime-js": "2.101.1", "@supabase/storage-js": "2.101.1" } }, "sha512-Jnhm3LfuACwjIzvk2pfUbGQn7pa7hi6MFzfSyPrRYWVCCu69RPLCFyHSBl7HSBwadbQ3UZOznnD3gPca3ePrRA=="],
|
|
||||||
|
|
||||||
"@tailwindcss/node": ["@tailwindcss/node@4.2.2", "", { "dependencies": { "@jridgewell/remapping": "^2.3.5", "enhanced-resolve": "^5.19.0", "jiti": "^2.6.1", "lightningcss": "1.32.0", "magic-string": "^0.30.21", "source-map-js": "^1.2.1", "tailwindcss": "4.2.2" } }, "sha512-pXS+wJ2gZpVXqFaUEjojq7jzMpTGf8rU6ipJz5ovJV6PUGmlJ+jvIwGrzdHdQ80Sg+wmQxUFuoW1UAAwHNEdFA=="],
|
"@tailwindcss/node": ["@tailwindcss/node@4.2.2", "", { "dependencies": { "@jridgewell/remapping": "^2.3.5", "enhanced-resolve": "^5.19.0", "jiti": "^2.6.1", "lightningcss": "1.32.0", "magic-string": "^0.30.21", "source-map-js": "^1.2.1", "tailwindcss": "4.2.2" } }, "sha512-pXS+wJ2gZpVXqFaUEjojq7jzMpTGf8rU6ipJz5ovJV6PUGmlJ+jvIwGrzdHdQ80Sg+wmQxUFuoW1UAAwHNEdFA=="],
|
||||||
|
|
||||||
"@tailwindcss/oxide": ["@tailwindcss/oxide@4.2.2", "", { "optionalDependencies": { "@tailwindcss/oxide-android-arm64": "4.2.2", "@tailwindcss/oxide-darwin-arm64": "4.2.2", "@tailwindcss/oxide-darwin-x64": "4.2.2", "@tailwindcss/oxide-freebsd-x64": "4.2.2", "@tailwindcss/oxide-linux-arm-gnueabihf": "4.2.2", "@tailwindcss/oxide-linux-arm64-gnu": "4.2.2", "@tailwindcss/oxide-linux-arm64-musl": "4.2.2", "@tailwindcss/oxide-linux-x64-gnu": "4.2.2", "@tailwindcss/oxide-linux-x64-musl": "4.2.2", "@tailwindcss/oxide-wasm32-wasi": "4.2.2", "@tailwindcss/oxide-win32-arm64-msvc": "4.2.2", "@tailwindcss/oxide-win32-x64-msvc": "4.2.2" } }, "sha512-qEUA07+E5kehxYp9BVMpq9E8vnJuBHfJEC0vPC5e7iL/hw7HR61aDKoVoKzrG+QKp56vhNZe4qwkRmMC0zDLvg=="],
|
"@tailwindcss/oxide": ["@tailwindcss/oxide@4.2.2", "", { "optionalDependencies": { "@tailwindcss/oxide-android-arm64": "4.2.2", "@tailwindcss/oxide-darwin-arm64": "4.2.2", "@tailwindcss/oxide-darwin-x64": "4.2.2", "@tailwindcss/oxide-freebsd-x64": "4.2.2", "@tailwindcss/oxide-linux-arm-gnueabihf": "4.2.2", "@tailwindcss/oxide-linux-arm64-gnu": "4.2.2", "@tailwindcss/oxide-linux-arm64-musl": "4.2.2", "@tailwindcss/oxide-linux-x64-gnu": "4.2.2", "@tailwindcss/oxide-linux-x64-musl": "4.2.2", "@tailwindcss/oxide-wasm32-wasi": "4.2.2", "@tailwindcss/oxide-win32-arm64-msvc": "4.2.2", "@tailwindcss/oxide-win32-x64-msvc": "4.2.2" } }, "sha512-qEUA07+E5kehxYp9BVMpq9E8vnJuBHfJEC0vPC5e7iL/hw7HR61aDKoVoKzrG+QKp56vhNZe4qwkRmMC0zDLvg=="],
|
||||||
@@ -611,8 +596,6 @@
|
|||||||
|
|
||||||
"@types/validate-npm-package-name": ["@types/validate-npm-package-name@4.0.2", "", {}, "sha512-lrpDziQipxCEeK5kWxvljWYhUvOiB2A9izZd9B2AFarYAkqZshb4lPbRs7zKEic6eGtH8V/2qJW+dPp9OtF6bw=="],
|
"@types/validate-npm-package-name": ["@types/validate-npm-package-name@4.0.2", "", {}, "sha512-lrpDziQipxCEeK5kWxvljWYhUvOiB2A9izZd9B2AFarYAkqZshb4lPbRs7zKEic6eGtH8V/2qJW+dPp9OtF6bw=="],
|
||||||
|
|
||||||
"@types/ws": ["@types/ws@8.18.1", "", { "dependencies": { "@types/node": "*" } }, "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg=="],
|
|
||||||
|
|
||||||
"@vitejs/plugin-react": ["@vitejs/plugin-react@6.0.1", "", { "dependencies": { "@rolldown/pluginutils": "1.0.0-rc.7" }, "peerDependencies": { "@rolldown/plugin-babel": "^0.1.7 || ^0.2.0", "babel-plugin-react-compiler": "^1.0.0", "vite": "^8.0.0" }, "optionalPeers": ["@rolldown/plugin-babel", "babel-plugin-react-compiler"] }, "sha512-l9X/E3cDb+xY3SWzlG1MOGt2usfEHGMNIaegaUGFsLkb3RCn/k8/TOXBcab+OndDI4TBtktT8/9BwwW8Vi9KUQ=="],
|
"@vitejs/plugin-react": ["@vitejs/plugin-react@6.0.1", "", { "dependencies": { "@rolldown/pluginutils": "1.0.0-rc.7" }, "peerDependencies": { "@rolldown/plugin-babel": "^0.1.7 || ^0.2.0", "babel-plugin-react-compiler": "^1.0.0", "vite": "^8.0.0" }, "optionalPeers": ["@rolldown/plugin-babel", "babel-plugin-react-compiler"] }, "sha512-l9X/E3cDb+xY3SWzlG1MOGt2usfEHGMNIaegaUGFsLkb3RCn/k8/TOXBcab+OndDI4TBtktT8/9BwwW8Vi9KUQ=="],
|
||||||
|
|
||||||
"accepts": ["accepts@2.0.0", "", { "dependencies": { "mime-types": "^3.0.0", "negotiator": "^1.0.0" } }, "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng=="],
|
"accepts": ["accepts@2.0.0", "", { "dependencies": { "mime-types": "^3.0.0", "negotiator": "^1.0.0" } }, "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng=="],
|
||||||
@@ -945,8 +928,6 @@
|
|||||||
|
|
||||||
"human-signals": ["human-signals@8.0.1", "", {}, "sha512-eKCa6bwnJhvxj14kZk5NCPc6Hb6BdsU9DZcOnmQKSnO1VKrfV0zCvtttPZUsBvjmNDn8rpcJfpwSYnHBjc95MQ=="],
|
"human-signals": ["human-signals@8.0.1", "", {}, "sha512-eKCa6bwnJhvxj14kZk5NCPc6Hb6BdsU9DZcOnmQKSnO1VKrfV0zCvtttPZUsBvjmNDn8rpcJfpwSYnHBjc95MQ=="],
|
||||||
|
|
||||||
"iceberg-js": ["iceberg-js@0.8.1", "", {}, "sha512-1dhVQZXhcHje7798IVM+xoo/1ZdVfzOMIc8/rgVSijRK38EDqOJoGula9N/8ZI5RD8QTxNQtK/Gozpr+qUqRRA=="],
|
|
||||||
|
|
||||||
"iconv-lite": ["iconv-lite@0.7.2", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw=="],
|
"iconv-lite": ["iconv-lite@0.7.2", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw=="],
|
||||||
|
|
||||||
"ignore": ["ignore@5.3.2", "", {}, "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g=="],
|
"ignore": ["ignore@5.3.2", "", {}, "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g=="],
|
||||||
@@ -1461,8 +1442,6 @@
|
|||||||
|
|
||||||
"wrappy": ["wrappy@1.0.2", "", {}, "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="],
|
"wrappy": ["wrappy@1.0.2", "", {}, "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="],
|
||||||
|
|
||||||
"ws": ["ws@8.20.0", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-sAt8BhgNbzCtgGbt2OxmpuryO63ZoDk/sqaB/znQm94T4fCEsy/yV+7CdC1kJhOU9lboAEU7R3kquuycDoibVA=="],
|
|
||||||
|
|
||||||
"wsl-utils": ["wsl-utils@0.3.1", "", { "dependencies": { "is-wsl": "^3.1.0", "powershell-utils": "^0.1.0" } }, "sha512-g/eziiSUNBSsdDJtCLB8bdYEUMj4jR7AGeUo96p/3dTafgjHhpF4RiCFPiRILwjQoDXx5MqkBr4fwWtR3Ky4Wg=="],
|
"wsl-utils": ["wsl-utils@0.3.1", "", { "dependencies": { "is-wsl": "^3.1.0", "powershell-utils": "^0.1.0" } }, "sha512-g/eziiSUNBSsdDJtCLB8bdYEUMj4jR7AGeUo96p/3dTafgjHhpF4RiCFPiRILwjQoDXx5MqkBr4fwWtR3Ky4Wg=="],
|
||||||
|
|
||||||
"xtend": ["xtend@4.0.2", "", {}, "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ=="],
|
"xtend": ["xtend@4.0.2", "", {}, "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ=="],
|
||||||
|
|||||||
@@ -93,7 +93,6 @@ export const onboardingCompleteResponseSchema = z.object({
|
|||||||
userId: z.uuid(),
|
userId: z.uuid(),
|
||||||
complexId: z.uuid(),
|
complexId: z.uuid(),
|
||||||
complexSlug: z.string(),
|
complexSlug: z.string(),
|
||||||
supabaseUserId: z.uuid(),
|
|
||||||
})
|
})
|
||||||
|
|
||||||
export type OnboardingStartInput = z.infer<typeof onboardingStartSchema>
|
export type OnboardingStartInput = z.infer<typeof onboardingStartSchema>
|
||||||
|
|||||||
Reference in New Issue
Block a user