feat(email-validation): implement email verification checks for booking creation and resend functionality

This commit is contained in:
Jose Selesan
2026-06-02 15:47:36 -03:00
parent 88ea7e70da
commit 0e69759549
6 changed files with 173 additions and 71 deletions

View File

@@ -31,11 +31,14 @@ export const auth = betterAuth({
sendOnSignUp: true,
autoSignInAfterVerification: true,
sendVerificationEmail: async ({ user, url }) => {
const verificationUrl = new URL(url);
const appUrl = process.env.APP_BASE_URL ?? 'http://localhost:5173';
verificationUrl.searchParams.set('callbackURL', appUrl);
await sendMail({
to: user.email,
subject: 'Verificá tu email en Playzer',
html: `Hacé click para verificar tu email: <a href="${url}">${url}</a>`,
text: `Hacé click para verificar tu email: ${url}`,
html: `Hacé click para verificar tu email: <a href="${verificationUrl.toString()}">${verificationUrl.toString()}</a>`,
text: `Hacé click para verificar tu email: ${verificationUrl.toString()}`,
});
},
},

View File

@@ -258,6 +258,16 @@ export async function createAdminBooking(
input: CreateAdminBookingInput
) {
const complex = await ensureComplexAccess(complexId, userId);
const adminUser = await db.user.findUnique({
where: { id: userId },
select: { emailVerified: true },
});
if (!adminUser?.emailVerified) {
throw new AdminBookingServiceError('Debés verificar tu email para poder crear reservas.', 403);
}
const bookingDate = parseIsoDate(input.date);
const dayOfWeek = getDayOfWeek(bookingDate);

View File

@@ -329,9 +329,34 @@ export async function getPublicBookingConfirmation(complexSlug: string, bookingC
};
}
async function hasVerifiedAdmin(complexId: string): Promise<boolean> {
const verifiedAdmin = await db.complexUser.findFirst({
where: {
complexId,
role: 'ADMIN',
user: { emailVerified: true },
},
});
return verifiedAdmin !== null;
}
export async function listPublicAvailability(complexSlug: string, query: PublicAvailabilityQuery) {
const { bookingDate, dayOfWeek } = parseIsoDate(query.date);
const complex = await getComplexWithBookingData(complexSlug);
if (!(await hasVerifiedAdmin(complex.id))) {
return {
complexId: complex.id,
complexName: complex.complexName,
complexAddress: complex.physicalAddress ?? null,
complexSlug: complex.complexSlug,
date: query.date,
sportSelectionRequired: false,
sports: [],
courts: [],
};
}
const sports = resolveSports(complex);
const sportSelectionRequired = sports.length > 1;
@@ -427,6 +452,14 @@ export async function listPublicAvailability(complexSlug: string, query: PublicA
export async function createPublicBooking(complexSlug: string, input: CreatePublicBookingInput) {
const { bookingDate, dayOfWeek } = parseIsoDate(input.date);
const complex = await getComplexWithBookingData(complexSlug);
if (!(await hasVerifiedAdmin(complex.id))) {
throw new PublicBookingServiceError(
'El complejo no puede recibir reservas hasta que un administrador verifique su email.',
403
);
}
const sports = resolveSports(complex);
const { sportSelectionRequired } = validateSportSelection(sports, input.sportId);