import { eq, and } from "drizzle-orm"; import * as schema from "~/database/schema"; import { sendEmail, wrapInEmailTemplate, emailParagraph, emailButton, escapeHtml } from "~/lib/email.server"; import { calculatePickInfo } from "~/models/draft-utils"; import { logger } from "~/lib/logger"; import type { database } from "~/database/context"; type DraftSlot = { teamId: string; draftOrder: number }; /** * Sends an "on the clock" email to the team owner whose turn it is. * Reads the current pick number directly from the DB so callers never need * to await a pre-fetch — just fire and forget this after the autodraft chain * has settled. */ export async function sendOnTheClockEmail(params: { seasonId: string; totalTeams: number; draftSlots: DraftSlot[]; db: ReturnType; }): Promise { const { seasonId, totalTeams, draftSlots, db } = params; // Season is fetched first: we need currentPickNumber + draftRounds + leagueId // all in one round-trip, and the pick number must be read after the autodraft // chain has committed its updates to the DB. const season = await db.query.seasons.findFirst({ where: eq(schema.seasons.id, seasonId), }); if (!season) return; const currentPickNumber = season.currentPickNumber ?? 1; const totalPicks = totalTeams * season.draftRounds; if (currentPickNumber > totalPicks) return; const { pickInRound, round } = calculatePickInfo(currentPickNumber, totalTeams); const currentSlot = draftSlots.find((s) => s.draftOrder === pickInRound); if (!currentSlot) return; // Parallel: team, autodraft settings, and league are all independent reads const [team, autodraft, league] = await Promise.all([ db.query.teams.findFirst({ where: eq(schema.teams.id, currentSlot.teamId) }), db.query.autodraftSettings.findFirst({ where: and( eq(schema.autodraftSettings.seasonId, seasonId), eq(schema.autodraftSettings.teamId, currentSlot.teamId) ), }), db.query.leagues.findFirst({ where: eq(schema.leagues.id, season.leagueId) }), ]); // Safety net: skip if autodraft is enabled (pick will be made automatically) if (autodraft?.isEnabled) return; if (!team?.ownerId || !league) return; const ownerId = team.ownerId; const owner = await db.query.users.findFirst({ where: eq(schema.users.id, ownerId), }); if (!owner?.email || !owner.draftEmailNotificationsEnabled) return; // Detect consecutive picks: same team picks the very next slot too let isConsecutive = false; if (currentPickNumber + 1 <= totalPicks) { const { pickInRound: nextPickInRound } = calculatePickInfo(currentPickNumber + 1, totalTeams); const nextSlot = draftSlots.find((s) => s.draftOrder === nextPickInRound); if (nextSlot?.teamId === currentSlot.teamId) { isConsecutive = true; } } const appUrl = process.env.APP_URL ?? "https://brackt.com"; const draftUrl = `${appUrl}/leagues/${season.leagueId}/draft/${seasonId}`; const leagueName = escapeHtml(league.name); const teamName = escapeHtml(team.name); let subject: string; let preheader: string; let bodyHtml: string; if (isConsecutive) { const { round: round2 } = calculatePickInfo(currentPickNumber + 1, totalTeams); subject = `You have back-to-back picks in ${league.name}`; preheader = `${team.name} picks twice in a row — head to the draft room!`; bodyHtml = ` ${emailParagraph(`You're up twice in a row!`)} ${emailParagraph(`${teamName} has back-to-back picks (Round ${round} and Round ${round2}) in ${leagueName}. Head to the draft room to make both picks before time runs out.`)} ${emailButton(draftUrl, "Go to Draft Room")} ${emailParagraph(`You're receiving this because you enabled draft email notifications in your account settings.`)} `; } else { subject = `You're on the clock in ${league.name}`; preheader = `${team.name} is on the clock — make your pick!`; bodyHtml = ` ${emailParagraph(`You're on the clock!`)} ${emailParagraph(`${teamName} is now on the clock in Round ${round} of ${leagueName}. Head to the draft room to make your pick before time runs out.`)} ${emailButton(draftUrl, "Go to Draft Room")} ${emailParagraph(`You're receiving this because you enabled draft email notifications in your account settings.`)} `; } const { error } = await sendEmail({ to: owner.email, subject, html: wrapInEmailTemplate(bodyHtml, preheader), }); if (error) { logger.error(`[DraftEmail] Failed to send on-the-clock email to ${owner.email}:`, error); } }