diff --git a/app/models/scoring-calculator.ts b/app/models/scoring-calculator.ts index 23902b2..f2b6dfc 100644 --- a/app/models/scoring-calculator.ts +++ b/app/models/scoring-calculator.ts @@ -310,13 +310,14 @@ export async function processMatchResult( isScoring: boolean; sportsSeasonId: string; bracketTemplateId?: string | null; + eventId?: string; eventName?: string; skipSideEffects?: boolean; }, providedDb?: ReturnType ): Promise { const db = providedDb || database(); - const { round, winnerId, loserId, isScoring, sportsSeasonId, bracketTemplateId, eventName, skipSideEffects } = params; + const { round, winnerId, loserId, isScoring, sportsSeasonId, bracketTemplateId, eventId, eventName, skipSideEffects } = params; if (!isScoring) { // Non-scoring (pre-bracket) round: loser permanently eliminated (0 pts), @@ -346,7 +347,7 @@ export async function processMatchResult( } if (!skipSideEffects) { - await recalculateAffectedLeagues(sportsSeasonId, db, eventName ? { eventName } : undefined); + await recalculateAffectedLeagues(sportsSeasonId, db, (eventName || eventId) ? { eventName, eventId } : undefined); try { await updateProbabilitiesAfterResult(sportsSeasonId, true); } catch (error) { @@ -629,7 +630,7 @@ export async function finalizeQualifyingPoints( .where(eq(schema.sportsSeasons.id, sportsSeasonId)); // Trigger recalculation for all affected leagues - await recalculateAffectedLeagues(sportsSeasonId, db); + await recalculateAffectedLeagues(sportsSeasonId, db, { eventName: "Final Standings" }); // Auto-trigger probability recalculation after result try { @@ -739,7 +740,7 @@ export async function processSeasonStandings( } // Trigger recalculation for all affected leagues - await recalculateAffectedLeagues(sportsSeasonId, db); + await recalculateAffectedLeagues(sportsSeasonId, db, { eventName: "Season Complete" }); // Auto-trigger probability recalculation after result try { @@ -1215,7 +1216,6 @@ export async function recalculateAffectedLeagues( } } - const standings = afterStandings.map((s) => ({ teamId: s.teamId, teamName: s.team.name, diff --git a/app/routes/admin.sports-seasons.$id.events.$eventId.bracket.server.ts b/app/routes/admin.sports-seasons.$id.events.$eventId.bracket.server.ts index d9f4b1f..5533e76 100644 --- a/app/routes/admin.sports-seasons.$id.events.$eventId.bracket.server.ts +++ b/app/routes/admin.sports-seasons.$id.events.$eventId.bracket.server.ts @@ -257,6 +257,7 @@ export async function action({ request, params }: Route.ActionArgs) { isScoring: match.isScoring ?? true, sportsSeasonId: event.sportsSeasonId, bracketTemplateId: event.bracketTemplateId, + eventId: event.id, eventName: event.name ?? undefined, }); @@ -375,7 +376,7 @@ export async function action({ request, params }: Route.ActionArgs) { // Run side effects once for the whole batch (avoids N redundant recalcs). if (successCount > 0) { const db = database(); - await recalculateAffectedLeagues(event.sportsSeasonId, db, event.name ? { eventName: event.name } : undefined); + await recalculateAffectedLeagues(event.sportsSeasonId, db, { eventId: event.id, eventName: event.name ?? undefined }); try { await updateProbabilitiesAfterResult(event.sportsSeasonId, true); } catch (error) { diff --git a/app/routes/leagues/$leagueId.settings.tsx b/app/routes/leagues/$leagueId.settings.tsx index ba6596d..9b0dcd2 100644 --- a/app/routes/leagues/$leagueId.settings.tsx +++ b/app/routes/leagues/$leagueId.settings.tsx @@ -224,9 +224,11 @@ export async function action(args: Route.ActionArgs) { return { error: "Invalid Discord webhook URL" }; } try { + const testSeason = await findCurrentSeasonWithSports(leagueId); + const seasonName = testSeason ? `${league.name} ${testSeason.year}` : league.name; await sendStandingsUpdateNotification({ webhookUrl, - seasonName: league.name, + seasonName, standings: [ { teamId: "1", teamName: "Team Alpha", totalPoints: 150, rank: 1 }, { teamId: "2", teamName: "Team Beta", totalPoints: 125, rank: 2 }, diff --git a/app/services/__tests__/discord.test.ts b/app/services/__tests__/discord.test.ts index 2b596fa..d3c8846 100644 --- a/app/services/__tests__/discord.test.ts +++ b/app/services/__tests__/discord.test.ts @@ -137,8 +137,8 @@ describe("sendStandingsUpdateNotification", () => { const desc = getDescription(); expect(desc).toContain("**Scored Matches**"); - expect(desc).toContain("⚽ **Real Madrid** def. Bayern Munich"); - expect(desc).toContain("⚽ **Arsenal** def. PSG"); + expect(desc).toContain("• **Real Madrid** def. Bayern Munich"); + expect(desc).toContain("• **Arsenal** def. PSG"); // Scored matches section should appear before standings expect(desc.indexOf("Scored Matches")).toBeLessThan(desc.indexOf("Current Standings")); }); diff --git a/app/services/discord.ts b/app/services/discord.ts index 5f78de2..94b12bc 100644 --- a/app/services/discord.ts +++ b/app/services/discord.ts @@ -69,7 +69,7 @@ export async function sendStandingsUpdateNotification({ if (scoredMatches && scoredMatches.length > 0) { sections.push("**Scored Matches**"); for (const match of scoredMatches) { - sections.push(`⚽ **${match.winnerName}** def. ${match.loserName}`); + sections.push(`• **${match.winnerName}** def. ${match.loserName}`); } } @@ -77,19 +77,27 @@ export async function sendStandingsUpdateNotification({ sections.push("**Current Standings**"); for (const s of standings) { const prev = previousStandings.get(s.teamId); - const delta = - prev !== undefined && prev !== s.totalPoints - ? ` **(+${Math.round(s.totalPoints - prev)} pts)**` - : ""; + let delta = ""; + if (prev !== undefined && prev !== s.totalPoints) { + const diff = Math.round(s.totalPoints - prev); + const sign = diff > 0 ? "+" : ""; + delta = ` **(${sign}${diff} pts)**`; + } const medal = RANK_MEDALS[s.rank - 1] ?? `${s.rank}.`; sections.push(`${medal} ${s.teamName} — ${Math.round(s.totalPoints)} pts${delta}`); } + const MAX_DESCRIPTION = 4096; + let description = sections.join("\n"); + if (description.length > MAX_DESCRIPTION) { + description = description.slice(0, MAX_DESCRIPTION - 3) + "..."; + } + await sendDiscordWebhook(webhookUrl, { embeds: [ { title: `📊 Standings Update — ${seasonName}`, - description: sections.join("\n"), + description, color: 0x5865f2, // Discord blurple footer: { text: "brackt.com" }, },