refactor: integrate Better-Auth for authentication and remove Supabase dependencies

This commit is contained in:
Jose Selesan
2026-04-16 20:30:01 -03:00
parent f158845279
commit e5002ce440
29 changed files with 165 additions and 211 deletions

View File

@@ -10,10 +10,10 @@ type ComplexIdParams = { complexId: string };
export async function createAdminBookingHandler(c: AppContext) {
const { complexId } = c.req.valid('param' as never) as ComplexIdParams;
const payload = c.req.valid('json' as never) as CreateAdminBookingInput;
const appUserId = c.get('appUserId');
const user = c.get('user');
try {
const booking = await createAdminBooking(appUserId, complexId, payload);
const booking = await createAdminBooking(user.id, complexId, payload);
return c.json(booking, 201);
} catch (error) {
if (error instanceof AdminBookingServiceError) {

View File

@@ -10,10 +10,10 @@ type ComplexIdParams = { complexId: string };
export async function listAdminBookingsHandler(c: AppContext) {
const { complexId } = c.req.valid('param' as never) as ComplexIdParams;
const query = c.req.valid('query' as never) as ListAdminBookingsQuery;
const appUserId = c.get('appUserId');
const user = c.get('user');
try {
const response = await listAdminBookings(appUserId, complexId, query);
const response = await listAdminBookings(user.id, complexId, query);
return c.json(response);
} catch (error) {
if (error instanceof AdminBookingServiceError) {

View File

@@ -10,10 +10,10 @@ type BookingIdParams = { id: string };
export async function updateAdminBookingStatusHandler(c: AppContext) {
const { id } = c.req.valid('param' as never) as BookingIdParams;
const payload = c.req.valid('json' as never) as UpdateAdminBookingStatusInput;
const appUserId = c.get('appUserId');
const user = c.get('user');
try {
const booking = await updateAdminBookingStatus(appUserId, id, payload);
const booking = await updateAdminBookingStatus(user.id, id, payload);
return c.json(booking);
} catch (error) {
if (error instanceof AdminBookingServiceError) {

View File

@@ -126,12 +126,12 @@ function buildSlots(
return slots;
}
async function ensureComplexAccess(complexId: string, appUserId: string) {
async function ensureComplexAccess(complexId: string, userId: string) {
const complexUser = await db.complexUser.findUnique({
where: {
complexId_userId: {
complexId,
userId: appUserId,
userId,
},
},
include: {
@@ -205,11 +205,11 @@ function mapBookingResponse(booking: {
}
export async function listAdminBookings(
appUserId: string,
userId: string,
complexId: string,
query: ListAdminBookingsQuery
) {
await ensureComplexAccess(complexId, appUserId);
await ensureComplexAccess(complexId, userId);
const fromDate = parseIsoDate(query.fromDate);
const bookings = await db.courtBooking.findMany({
@@ -253,11 +253,11 @@ export async function listAdminBookings(
}
export async function createAdminBooking(
appUserId: string,
userId: string,
complexId: string,
input: CreateAdminBookingInput
) {
const complex = await ensureComplexAccess(complexId, appUserId);
const complex = await ensureComplexAccess(complexId, userId);
const bookingDate = parseIsoDate(input.date);
const dayOfWeek = getDayOfWeek(bookingDate);
@@ -436,7 +436,7 @@ export async function createAdminBooking(
}
export async function updateAdminBookingStatus(
appUserId: string,
userId: string,
bookingId: string,
input: UpdateAdminBookingStatusInput
) {
@@ -447,7 +447,7 @@ export async function updateAdminBookingStatus(
complex: {
users: {
some: {
userId: appUserId,
userId,
},
},
},

View File

@@ -1,9 +1,9 @@
import { Hono } from "hono";
import { auth } from "@/lib/auth";
import { AppEnv } from "@/types/hono";
import { auth } from '@/lib/auth';
import { AppEnv } from '@/types/hono';
import { Hono } from 'hono';
export const authRoutes = new Hono<AppEnv>();
authRoutes.on(["POST", "GET"], "/api/auth/*", (c) => {
return auth.handler(c.req.raw);
authRoutes.on(['POST', 'GET'], '/api/auth/*', (c) => {
return auth.handler(c.req.raw);
});

View File

@@ -5,8 +5,8 @@ import type { CreateComplexInput } from '@repo/api-contract';
export async function createComplexHandler(c: AppContext) {
const payload = c.req.valid('json' as never) as CreateComplexInput;
const authUser = c.get('authUser');
const adminEmail = authUser.email;
const user = c.get('user');
const adminEmail = user.email;
if (!adminEmail) {
return c.json({ message: 'El usuario autenticado no tiene email.' }, 400);

View File

@@ -2,7 +2,7 @@ import { listMyComplexes } from '@/modules/complex/services/complex.service';
import type { AppContext } from '@/types/hono';
export async function listMyComplexesHandler(c: AppContext) {
const appUserId = c.get('appUserId');
const complexes = await listMyComplexes(appUserId);
const user = c.get('user');
const complexes = await listMyComplexes(user.id);
return c.json(complexes);
}

View File

@@ -93,9 +93,9 @@ export async function getComplexBySlug(slug: string) {
});
}
export async function listMyComplexes(appUserId: string) {
export async function listMyComplexes(userId: string) {
const complexUsers = await db.complexUser.findMany({
where: { userId: appUserId },
where: { userId },
include: {
complex: true,
},

View File

@@ -7,10 +7,10 @@ type ComplexIdParams = { complexId: string };
export async function createCourtHandler(c: AppContext) {
const { complexId } = c.req.valid('param' as never) as ComplexIdParams;
const payload = c.req.valid('json' as never) as CreateCourtInput;
const appUserId = c.get('appUserId');
const user = c.get('user');
try {
const court = await createCourt(appUserId, complexId, payload);
const court = await createCourt(user.id, complexId, payload);
return c.json(court, 201);
} catch (error) {
if (error instanceof CourtServiceError) {

View File

@@ -5,10 +5,10 @@ type ComplexIdParams = { complexId: string };
export async function listCourtsByComplexHandler(c: AppContext) {
const { complexId } = c.req.valid('param' as never) as ComplexIdParams;
const appUserId = c.get('appUserId');
const user = c.get('user');
try {
const courts = await listCourtsByComplex(complexId, appUserId);
const courts = await listCourtsByComplex(complexId, user.id);
return c.json(courts);
} catch (error) {
if (error instanceof CourtServiceError) {

View File

@@ -7,10 +7,10 @@ type CourtIdParams = { id: string };
export async function updateCourtHandler(c: AppContext) {
const { id } = c.req.valid('param' as never) as CourtIdParams;
const payload = c.req.valid('json' as never) as UpdateCourtInput;
const appUserId = c.get('appUserId');
const user = c.get('user');
try {
const court = await updateCourt(appUserId, id, payload);
const court = await updateCourt(user.id, id, payload);
return c.json(court);
} catch (error) {
if (error instanceof CourtServiceError) {

View File

@@ -61,12 +61,12 @@ function assertAvailabilityRanges(
}
}
async function ensureComplexAccess(complexId: string, appUserId: string) {
async function ensureComplexAccess(complexId: string, userId: string) {
const complexUser = await db.complexUser.findUnique({
where: {
complexId_userId: {
complexId,
userId: appUserId,
userId,
},
},
include: {
@@ -137,14 +137,14 @@ async function enforcePlanCourtLimit(complexId: string) {
}
}
async function getCourtByIdForUser(courtId: string, appUserId: string) {
async function getCourtByIdForUser(courtId: string, userId: string) {
return db.court.findFirst({
where: {
id: courtId,
complex: {
users: {
some: {
userId: appUserId,
userId,
},
},
},
@@ -195,8 +195,8 @@ function mapCourtResponse(court: CourtWithRelations) {
};
}
export async function listCourtsByComplex(complexId: string, appUserId: string) {
await ensureComplexAccess(complexId, appUserId);
export async function listCourtsByComplex(complexId: string, userId: string) {
await ensureComplexAccess(complexId, userId);
const courts = await db.court.findMany({
where: { complexId },
@@ -218,8 +218,8 @@ export async function listCourtsByComplex(complexId: string, appUserId: string)
return courts.map((court) => mapCourtResponse(court));
}
export async function createCourt(appUserId: string, complexId: string, input: CreateCourtInput) {
await ensureComplexAccess(complexId, appUserId);
export async function createCourt(userId: string, complexId: string, input: CreateCourtInput) {
await ensureComplexAccess(complexId, userId);
await ensureActiveSport(input.sportId);
await enforcePlanCourtLimit(complexId);
assertAvailabilityRanges(input.availability);
@@ -249,7 +249,7 @@ export async function createCourt(appUserId: string, complexId: string, input: C
return court;
});
const court = await getCourtByIdForUser(createdCourt.id, appUserId);
const court = await getCourtByIdForUser(createdCourt.id, userId);
if (!court) {
throw new CourtServiceError('No se pudo obtener la cancha creada.', 404);
@@ -258,8 +258,8 @@ export async function createCourt(appUserId: string, complexId: string, input: C
return mapCourtResponse(court);
}
export async function updateCourt(appUserId: string, courtId: string, input: UpdateCourtInput) {
const existingCourt = await getCourtByIdForUser(courtId, appUserId);
export async function updateCourt(userId: string, courtId: string, input: UpdateCourtInput) {
const existingCourt = await getCourtByIdForUser(courtId, userId);
if (!existingCourt) {
throw new CourtServiceError('Cancha no encontrada.', 404);
@@ -305,7 +305,7 @@ export async function updateCourt(appUserId: string, courtId: string, input: Upd
}
});
const updatedCourt = await getCourtByIdForUser(courtId, appUserId);
const updatedCourt = await getCourtByIdForUser(courtId, userId);
if (!updatedCourt) {
throw new CourtServiceError('Cancha no encontrada.', 404);

View File

@@ -1,4 +1,5 @@
import { createHash, randomInt } from 'node:crypto';
import { auth } from '@/lib/auth';
import { sendMail } from '@/lib/mailer';
import { db } from '@/lib/prisma';
import { getSupabaseAdminClient } from '@/lib/supabase-admin';
@@ -10,7 +11,6 @@ import type {
} from '@repo/api-contract';
import type { User as SupabaseAuthUser } from '@supabase/supabase-js';
import { v7 as uuidv7 } from 'uuid';
import { auth } from '@/lib/auth';
const OTP_LENGTH = 6;
const OTP_TTL_MINUTES = Number(Bun.env.ONBOARDING_OTP_TTL_MINUTES ?? 10);
@@ -441,7 +441,6 @@ export async function completeOnboarding(input: OnboardingCompleteInput) {
const complexSlug = await buildUniqueComplexSlug(input.complexName);
const result = await db.$transaction(async (tx) => {
const complex = await tx.complex.create({
data: {
id: uuidv7(),

View File

@@ -2,7 +2,7 @@ import { getUserProfile } from '@/modules/user/services/user.service';
import type { AppContext } from '@/types/hono';
export function getUserProfileHandler(c: AppContext) {
const authUser = c.get('authUser');
const profile = getUserProfile(authUser);
const user = c.get('user');
const profile = getUserProfile(user);
return c.json(profile);
}

View File

@@ -1,23 +1,13 @@
import type { AuthUser } from '@/types/auth-user';
import type { User } from '@/lib/auth';
import type { UserProfile } from '@repo/api-contract';
export function getUserProfile(user: AuthUser): UserProfile {
const fullName =
(typeof user.user_metadata?.full_name === 'string' && user.user_metadata.full_name) ||
(typeof user.user_metadata?.name === 'string' && user.user_metadata.name) ||
(user.email ?? 'Usuario');
const role = (typeof user.app_metadata?.role === 'string' && user.app_metadata.role) || 'member';
const avatarUrl =
(typeof user.user_metadata?.avatar_url === 'string' && user.user_metadata.avatar_url) || null;
export function getUserProfile(user: User): UserProfile {
return {
id: user.id,
fullName,
email: user.email ?? null,
role,
avatarUrl,
createdAt: user.created_at,
fullName: user.name,
email: user.email,
role: (user as any).role || 'member',
avatarUrl: user.image || null,
createdAt: user.createdAt,
};
}