- Add app/lib/logger.ts: dev passes through to console; prod routes errors to Sentry.captureException and warnings to Sentry.captureMessage, with extra context preserved. Uses captureMessage (not captureException) for string-only args to avoid fabricated stack traces. - Add server/logger.ts: dev passes through; prod silences log/info but keeps warn/error on stderr (Sentry not initialized in that process). - Replace all console.* calls across 44 app files and 4 server files. - Upgrade no-console from warn → error in oxlint; exempt logger files and scripts/** via overrides. - Add typescript/no-inferrable-types rule; fix violations in services and simulators. Exempt test files (intentional string widening for switch/if tests would break under literal type inference). Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
4bffa40606
commit
618bc57ec1
50 changed files with 336 additions and 200 deletions
|
|
@ -11,7 +11,7 @@
|
||||||
"suspicious": "warn"
|
"suspicious": "warn"
|
||||||
},
|
},
|
||||||
"rules": {
|
"rules": {
|
||||||
"no-console": "warn",
|
"no-console": "error",
|
||||||
"no-var": "error",
|
"no-var": "error",
|
||||||
"prefer-const": "error",
|
"prefer-const": "error",
|
||||||
"eqeqeq": ["error", "always"],
|
"eqeqeq": ["error", "always"],
|
||||||
|
|
@ -24,18 +24,19 @@
|
||||||
],
|
],
|
||||||
"typescript/no-explicit-any": "error",
|
"typescript/no-explicit-any": "error",
|
||||||
"typescript/no-non-null-assertion": "error",
|
"typescript/no-non-null-assertion": "error",
|
||||||
|
"typescript/no-inferrable-types": "error",
|
||||||
"typescript/consistent-type-imports": [
|
"typescript/consistent-type-imports": [
|
||||||
"error",
|
"error",
|
||||||
{ "prefer": "type-imports" }
|
{ "prefer": "type-imports" }
|
||||||
],
|
],
|
||||||
|
"react/jsx-no-leaked-render": "error",
|
||||||
"react/react-in-jsx-scope": "off",
|
"react/react-in-jsx-scope": "off",
|
||||||
"react/rules-of-hooks": "error",
|
"react/rules-of-hooks": "error",
|
||||||
"react/exhaustive-deps": "error",
|
"react/exhaustive-deps": "error",
|
||||||
"react/jsx-key": "error",
|
"react/jsx-key": "error",
|
||||||
"react/no-array-index-key": "error",
|
"react/no-array-index-key": "error",
|
||||||
"react/self-closing-comp": "warn",
|
"react/self-closing-comp": "warn",
|
||||||
|
"import/no-cycle": "error",
|
||||||
"import/no-duplicates": "error",
|
"import/no-duplicates": "error",
|
||||||
"import/no-unassigned-import": ["error", {
|
"import/no-unassigned-import": ["error", {
|
||||||
"allow": ["**/*.css", "@testing-library/jest-dom", "@testing-library/cypress/add-commands"]
|
"allow": ["**/*.css", "@testing-library/jest-dom", "@testing-library/cypress/add-commands"]
|
||||||
|
|
@ -51,7 +52,14 @@
|
||||||
"files": ["**/__tests__/**", "**/*.test.ts", "**/*.test.tsx", "cypress/**"],
|
"files": ["**/__tests__/**", "**/*.test.ts", "**/*.test.tsx", "cypress/**"],
|
||||||
"rules": {
|
"rules": {
|
||||||
"no-console": "off",
|
"no-console": "off",
|
||||||
"typescript/no-explicit-any": "off"
|
"typescript/no-explicit-any": "off",
|
||||||
|
"typescript/no-inferrable-types": "off"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"files": ["app/lib/logger.ts", "server/logger.ts"],
|
||||||
|
"rules": {
|
||||||
|
"no-console": "off"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
|
@ -61,6 +69,7 @@
|
||||||
"node_modules/**",
|
"node_modules/**",
|
||||||
"drizzle/**",
|
"drizzle/**",
|
||||||
".react-router/**",
|
".react-router/**",
|
||||||
"cypress/fixtures/**"
|
"cypress/fixtures/**",
|
||||||
|
"scripts/**"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
import * as Sentry from "@sentry/react-router";
|
import * as Sentry from "@sentry/react-router";
|
||||||
import { PassThrough } from "node:stream";
|
import { PassThrough } from "node:stream";
|
||||||
|
import { logger } from "~/lib/logger";
|
||||||
|
|
||||||
import type { AppLoadContext, EntryContext } from "react-router";
|
import type { AppLoadContext, EntryContext } from "react-router";
|
||||||
import { createReadableStreamFromReadable } from "@react-router/node";
|
import { createReadableStreamFromReadable } from "@react-router/node";
|
||||||
|
|
@ -76,7 +77,7 @@ async function handleRequest(
|
||||||
// errors encountered during initial shell rendering since they'll
|
// errors encountered during initial shell rendering since they'll
|
||||||
// reject and get logged in handleDocumentRequest.
|
// reject and get logged in handleDocumentRequest.
|
||||||
if (shellRendered) {
|
if (shellRendered) {
|
||||||
console.error(error);
|
logger.error(error);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
import { useCallback, useEffect, useRef, useState } from "react";
|
import { useCallback, useEffect, useRef, useState } from "react";
|
||||||
import { io } from "socket.io-client";
|
import { io } from "socket.io-client";
|
||||||
|
import { logger } from "~/lib/logger";
|
||||||
|
|
||||||
interface UseDraftSocketReturn {
|
interface UseDraftSocketReturn {
|
||||||
isConnected: boolean;
|
isConnected: boolean;
|
||||||
|
|
@ -40,7 +41,7 @@ export function useDraftSocket(seasonId: string, teamId?: string): UseDraftSocke
|
||||||
setSocketVersion((v) => v + 1);
|
setSocketVersion((v) => v + 1);
|
||||||
|
|
||||||
socket.on("connect", () => {
|
socket.on("connect", () => {
|
||||||
console.log("Connected to Socket.IO:", socket.id);
|
logger.log("Connected to Socket.IO:", socket.id);
|
||||||
const isReconnect = hasConnectedOnce.current;
|
const isReconnect = hasConnectedOnce.current;
|
||||||
hasConnectedOnce.current = true;
|
hasConnectedOnce.current = true;
|
||||||
setIsConnected(true);
|
setIsConnected(true);
|
||||||
|
|
@ -53,7 +54,7 @@ export function useDraftSocket(seasonId: string, teamId?: string): UseDraftSocke
|
||||||
});
|
});
|
||||||
|
|
||||||
socket.on("disconnect", (reason) => {
|
socket.on("disconnect", (reason) => {
|
||||||
console.log("Disconnected from Socket.IO:", reason);
|
logger.log("Disconnected from Socket.IO:", reason);
|
||||||
setIsConnected(false);
|
setIsConnected(false);
|
||||||
if (reason === "io server disconnect") {
|
if (reason === "io server disconnect") {
|
||||||
setConnectionError("Server disconnected. Please refresh the page.");
|
setConnectionError("Server disconnected. Please refresh the page.");
|
||||||
|
|
@ -63,7 +64,7 @@ export function useDraftSocket(seasonId: string, teamId?: string): UseDraftSocke
|
||||||
});
|
});
|
||||||
|
|
||||||
socket.on("connect_error", (error) => {
|
socket.on("connect_error", (error) => {
|
||||||
console.error("Socket.IO connection error:", error);
|
logger.error("Socket.IO connection error:", error);
|
||||||
// Don't set connectionError here — reconnect_attempt fires immediately after
|
// Don't set connectionError here — reconnect_attempt fires immediately after
|
||||||
// and would clear it again, causing the error overlay to flicker on every
|
// and would clear it again, causing the error overlay to flicker on every
|
||||||
// retry. Only show a hard error once all attempts are exhausted (reconnect_failed).
|
// retry. Only show a hard error once all attempts are exhausted (reconnect_failed).
|
||||||
|
|
@ -72,13 +73,13 @@ export function useDraftSocket(seasonId: string, teamId?: string): UseDraftSocke
|
||||||
});
|
});
|
||||||
|
|
||||||
socket.io.on("reconnect_attempt", () => {
|
socket.io.on("reconnect_attempt", () => {
|
||||||
console.log("Attempting to reconnect...");
|
logger.log("Attempting to reconnect...");
|
||||||
setIsReconnecting(true);
|
setIsReconnecting(true);
|
||||||
setConnectionError(null);
|
setConnectionError(null);
|
||||||
});
|
});
|
||||||
|
|
||||||
socket.io.on("reconnect_failed", () => {
|
socket.io.on("reconnect_failed", () => {
|
||||||
console.error("Reconnection failed");
|
logger.error("Reconnection failed");
|
||||||
setConnectionError("Failed to reconnect. Please refresh the page.");
|
setConnectionError("Failed to reconnect. Please refresh the page.");
|
||||||
setIsReconnecting(false);
|
setIsReconnecting(false);
|
||||||
});
|
});
|
||||||
|
|
@ -123,7 +124,7 @@ export function useDraftSocket(seasonId: string, teamId?: string): UseDraftSocke
|
||||||
window.removeEventListener("offline", handleOffline);
|
window.removeEventListener("offline", handleOffline);
|
||||||
window.removeEventListener("online", handleReturn);
|
window.removeEventListener("online", handleReturn);
|
||||||
document.removeEventListener("visibilitychange", handleVisibilityChange);
|
document.removeEventListener("visibilitychange", handleVisibilityChange);
|
||||||
console.log("Leaving draft room:", seasonId);
|
logger.log("Leaving draft room:", seasonId);
|
||||||
socket.emit("leave-draft", seasonId);
|
socket.emit("leave-draft", seasonId);
|
||||||
socket.disconnect();
|
socket.disconnect();
|
||||||
};
|
};
|
||||||
|
|
|
||||||
56
app/lib/logger.ts
Normal file
56
app/lib/logger.ts
Normal file
|
|
@ -0,0 +1,56 @@
|
||||||
|
/**
|
||||||
|
* App-level logger. In development, delegates to console. In production,
|
||||||
|
* routes errors/warnings to Sentry and silences debug noise.
|
||||||
|
*/
|
||||||
|
import * as Sentry from "@sentry/react-router";
|
||||||
|
|
||||||
|
const isDev = process.env.NODE_ENV !== "production";
|
||||||
|
|
||||||
|
/** Splits args into the first Error found and everything else (for Sentry context). */
|
||||||
|
function splitArgs(args: unknown[]): { err: Error | undefined; extra: unknown[] } {
|
||||||
|
const err = args.find((a): a is Error => a instanceof Error);
|
||||||
|
const extra = args.filter((a) => a !== err);
|
||||||
|
return { err, extra };
|
||||||
|
}
|
||||||
|
|
||||||
|
function log(...args: unknown[]): void {
|
||||||
|
if (isDev) console.log(...args);
|
||||||
|
}
|
||||||
|
|
||||||
|
function info(...args: unknown[]): void {
|
||||||
|
if (isDev) console.info(...args);
|
||||||
|
}
|
||||||
|
|
||||||
|
function warn(...args: unknown[]): void {
|
||||||
|
if (isDev) {
|
||||||
|
console.warn(...args);
|
||||||
|
} else {
|
||||||
|
const { err, extra } = splitArgs(args);
|
||||||
|
if (err) {
|
||||||
|
Sentry.captureException(err, { level: "warning", extra: { context: extra } });
|
||||||
|
} else {
|
||||||
|
Sentry.captureMessage(String(args[0]), {
|
||||||
|
level: "warning",
|
||||||
|
extra: { args: extra.slice(1) },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function error(...args: unknown[]): void {
|
||||||
|
if (isDev) {
|
||||||
|
console.error(...args);
|
||||||
|
} else {
|
||||||
|
const { err, extra } = splitArgs(args);
|
||||||
|
if (err) {
|
||||||
|
Sentry.captureException(err, extra.length ? { extra: { context: extra } } : undefined);
|
||||||
|
} else {
|
||||||
|
Sentry.captureMessage(String(args[0]), {
|
||||||
|
level: "error",
|
||||||
|
extra: { args: extra.slice(1) },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const logger = { log, info, warn, error };
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
import { database } from "~/database/context";
|
import { database } from "~/database/context";
|
||||||
import * as schema from "~/database/schema";
|
import * as schema from "~/database/schema";
|
||||||
import { eq, and, notInArray, desc, inArray, sql, asc } from "drizzle-orm";
|
import { eq, and, notInArray, desc, inArray, sql, asc } from "drizzle-orm";
|
||||||
|
import { logger } from "~/lib/logger";
|
||||||
import type { InferSelectModel } from "drizzle-orm";
|
import type { InferSelectModel } from "drizzle-orm";
|
||||||
import { getTeamQueue, getAllQueuesForSeason } from "./draft-queue";
|
import { getTeamQueue, getAllQueuesForSeason } from "./draft-queue";
|
||||||
import { isParticipantDrafted, getDraftPicksWithSports, getTeamDraftPicksWithSports } from "./draft-pick";
|
import { isParticipantDrafted, getDraftPicksWithSports, getTeamDraftPicksWithSports } from "./draft-pick";
|
||||||
|
|
@ -50,7 +51,7 @@ export async function checkAndTriggerNextAutodraft(params: {
|
||||||
|
|
||||||
if (!autodraftSettings?.isEnabled) return;
|
if (!autodraftSettings?.isEnabled) return;
|
||||||
|
|
||||||
console.log(
|
logger.log(
|
||||||
`[AutodraftChain] Team ${nextTeamId} has autodraft enabled, triggering immediate pick for pick ${currentPickNumber}`
|
`[AutodraftChain] Team ${nextTeamId} has autodraft enabled, triggering immediate pick for pick ${currentPickNumber}`
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
@ -104,7 +105,7 @@ export async function autoPickForTeam(
|
||||||
allTeams
|
allTeams
|
||||||
);
|
);
|
||||||
|
|
||||||
console.log(
|
logger.log(
|
||||||
`[AutoPick] Team ${teamId} eligible sports:`,
|
`[AutoPick] Team ${teamId} eligible sports:`,
|
||||||
Array.from(eligibility.eligibleSportIds)
|
Array.from(eligibility.eligibleSportIds)
|
||||||
);
|
);
|
||||||
|
|
@ -113,7 +114,7 @@ export async function autoPickForTeam(
|
||||||
const queue = await getTeamQueue(teamId, db);
|
const queue = await getTeamQueue(teamId, db);
|
||||||
|
|
||||||
if (queue.length > 0) {
|
if (queue.length > 0) {
|
||||||
console.log(`[AutoPick] Team ${teamId} has ${queue.length} items in queue`);
|
logger.log(`[AutoPick] Team ${teamId} has ${queue.length} items in queue`);
|
||||||
|
|
||||||
// Get participant details for queue items to check eligibility
|
// Get participant details for queue items to check eligibility
|
||||||
const queueParticipantIds = queue.map((item) => item.participantId);
|
const queueParticipantIds = queue.map((item) => item.participantId);
|
||||||
|
|
@ -134,7 +135,7 @@ export async function autoPickForTeam(
|
||||||
for (const item of queue) {
|
for (const item of queue) {
|
||||||
const participant = queueParticipants.find((p) => p.id === item.participantId);
|
const participant = queueParticipants.find((p) => p.id === item.participantId);
|
||||||
if (!participant) {
|
if (!participant) {
|
||||||
console.log(`[AutoPick] Queue item ${item.id} - participant not found, will remove`);
|
logger.log(`[AutoPick] Queue item ${item.id} - participant not found, will remove`);
|
||||||
ineligibleQueueItemIds.push(item.id);
|
ineligibleQueueItemIds.push(item.id);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
@ -144,7 +145,7 @@ export async function autoPickForTeam(
|
||||||
const isDrafted = await isParticipantDrafted(seasonId, item.participantId, db);
|
const isDrafted = await isParticipantDrafted(seasonId, item.participantId, db);
|
||||||
|
|
||||||
if (isDrafted) {
|
if (isDrafted) {
|
||||||
console.log(
|
logger.log(
|
||||||
`[AutoPick] Queue item ${participant.name} (${participant.sportsSeason.sport.name}) - already drafted, will remove`
|
`[AutoPick] Queue item ${participant.name} (${participant.sportsSeason.sport.name}) - already drafted, will remove`
|
||||||
);
|
);
|
||||||
ineligibleQueueItemIds.push(item.id);
|
ineligibleQueueItemIds.push(item.id);
|
||||||
|
|
@ -152,7 +153,7 @@ export async function autoPickForTeam(
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!isEligible) {
|
if (!isEligible) {
|
||||||
console.log(
|
logger.log(
|
||||||
`[AutoPick] Queue item ${participant.name} (${participant.sportsSeason.sport.name}) - not eligible for this team, will remove`
|
`[AutoPick] Queue item ${participant.name} (${participant.sportsSeason.sport.name}) - not eligible for this team, will remove`
|
||||||
);
|
);
|
||||||
ineligibleQueueItemIds.push(item.id);
|
ineligibleQueueItemIds.push(item.id);
|
||||||
|
|
@ -160,7 +161,7 @@ export async function autoPickForTeam(
|
||||||
}
|
}
|
||||||
|
|
||||||
// Found a valid pick from queue
|
// Found a valid pick from queue
|
||||||
console.log(
|
logger.log(
|
||||||
`[AutoPick] Selecting from queue: ${participant.name} (${participant.sportsSeason.sport.name})`
|
`[AutoPick] Selecting from queue: ${participant.name} (${participant.sportsSeason.sport.name})`
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
@ -169,7 +170,7 @@ export async function autoPickForTeam(
|
||||||
await db
|
await db
|
||||||
.delete(schema.draftQueue)
|
.delete(schema.draftQueue)
|
||||||
.where(inArray(schema.draftQueue.id, ineligibleQueueItemIds));
|
.where(inArray(schema.draftQueue.id, ineligibleQueueItemIds));
|
||||||
console.log(`[AutoPick] Removed ${ineligibleQueueItemIds.length} ineligible items from queue`);
|
logger.log(`[AutoPick] Removed ${ineligibleQueueItemIds.length} ineligible items from queue`);
|
||||||
}
|
}
|
||||||
|
|
||||||
return item.participantId;
|
return item.participantId;
|
||||||
|
|
@ -180,7 +181,7 @@ export async function autoPickForTeam(
|
||||||
await db
|
await db
|
||||||
.delete(schema.draftQueue)
|
.delete(schema.draftQueue)
|
||||||
.where(inArray(schema.draftQueue.id, ineligibleQueueItemIds));
|
.where(inArray(schema.draftQueue.id, ineligibleQueueItemIds));
|
||||||
console.log(
|
logger.log(
|
||||||
`[AutoPick] Removed ${ineligibleQueueItemIds.length} ineligible items from queue (all items were invalid)`
|
`[AutoPick] Removed ${ineligibleQueueItemIds.length} ineligible items from queue (all items were invalid)`
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -188,12 +189,12 @@ export async function autoPickForTeam(
|
||||||
|
|
||||||
// Queue is empty or all queued players drafted/ineligible
|
// Queue is empty or all queued players drafted/ineligible
|
||||||
if (queueOnly) {
|
if (queueOnly) {
|
||||||
console.log(`[AutoPick] No valid queue items and queueOnly constraint is active — will not fall back to highest EV`);
|
logger.log(`[AutoPick] No valid queue items and queueOnly constraint is active — will not fall back to highest EV`);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Pick highest EV available from eligible sports
|
// Pick highest EV available from eligible sports
|
||||||
console.log(`[AutoPick] No valid queue items, selecting highest EV from eligible sports`);
|
logger.log(`[AutoPick] No valid queue items, selecting highest EV from eligible sports`);
|
||||||
return await getTopAvailableParticipant(seasonId, eligibility.eligibleSportIds, db);
|
return await getTopAvailableParticipant(seasonId, eligibility.eligibleSportIds, db);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -407,7 +408,7 @@ export async function pruneIneligibleQueueItems(params: {
|
||||||
for (const item of queue) {
|
for (const item of queue) {
|
||||||
const sportId = participantSportMap.get(item.participantId);
|
const sportId = participantSportMap.get(item.participantId);
|
||||||
if (sportId === undefined) {
|
if (sportId === undefined) {
|
||||||
console.warn(
|
logger.warn(
|
||||||
`[QueuePrune] Team ${teamId}: queue item ${item.id} references participant ${item.participantId} not found in season sports — skipping`
|
`[QueuePrune] Team ${teamId}: queue item ${item.id} references participant ${item.participantId} not found in season sports — skipping`
|
||||||
);
|
);
|
||||||
continue;
|
continue;
|
||||||
|
|
@ -424,7 +425,7 @@ export async function pruneIneligibleQueueItems(params: {
|
||||||
|
|
||||||
const removedParticipantIds = ineligible.map((i) => i.participantId);
|
const removedParticipantIds = ineligible.map((i) => i.participantId);
|
||||||
results.push({ teamId, removedParticipantIds });
|
results.push({ teamId, removedParticipantIds });
|
||||||
console.log(
|
logger.log(
|
||||||
`[QueuePrune] Team ${teamId}: removed ${ineligible.length} ineligible items (sport no longer eligible)`
|
`[QueuePrune] Team ${teamId}: removed ${ineligible.length} ineligible items (sport no longer eligible)`
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -500,7 +501,7 @@ export async function executeAutoPick(params: {
|
||||||
});
|
});
|
||||||
|
|
||||||
if (existingPick) {
|
if (existingPick) {
|
||||||
console.log(`[AutoPick] Pick ${pickNumber} already made, skipping`);
|
logger.log(`[AutoPick] Pick ${pickNumber} already made, skipping`);
|
||||||
return {
|
return {
|
||||||
success: false,
|
success: false,
|
||||||
error: "Pick already made",
|
error: "Pick already made",
|
||||||
|
|
@ -553,7 +554,7 @@ export async function executeAutoPick(params: {
|
||||||
// If queueOnly is set and queue is empty, disable autodraft and return success
|
// If queueOnly is set and queue is empty, disable autodraft and return success
|
||||||
// (timer will fire again and pick highest EV once autodraft is disabled)
|
// (timer will fire again and pick highest EV once autodraft is disabled)
|
||||||
if (triggeredBy === "timer" && queueOnly && autodraftSettings) {
|
if (triggeredBy === "timer" && queueOnly && autodraftSettings) {
|
||||||
console.log(`[AutoPick] Queue empty with queueOnly constraint — disabling autodraft for team ${teamId}`);
|
logger.log(`[AutoPick] Queue empty with queueOnly constraint — disabling autodraft for team ${teamId}`);
|
||||||
await db
|
await db
|
||||||
.update(schema.autodraftSettings)
|
.update(schema.autodraftSettings)
|
||||||
.set({ isEnabled: false, updatedAt: new Date() })
|
.set({ isEnabled: false, updatedAt: new Date() })
|
||||||
|
|
@ -567,7 +568,7 @@ export async function executeAutoPick(params: {
|
||||||
queueOnly: autodraftSettings.queueOnly,
|
queueOnly: autodraftSettings.queueOnly,
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("[AutoPick] Socket.IO autodraft-updated error (queue-empty shutoff):", error);
|
logger.error("[AutoPick] Socket.IO autodraft-updated error (queue-empty shutoff):", error);
|
||||||
}
|
}
|
||||||
|
|
||||||
return { success: true };
|
return { success: true };
|
||||||
|
|
@ -615,7 +616,7 @@ export async function executeAutoPick(params: {
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!currentTimer) {
|
if (!currentTimer) {
|
||||||
console.warn(`[AutoPick] No timer found for team ${teamId} in season ${seasonId}`);
|
logger.warn(`[AutoPick] No timer found for team ${teamId} in season ${seasonId}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create the draft pick
|
// Create the draft pick
|
||||||
|
|
@ -635,7 +636,7 @@ export async function executeAutoPick(params: {
|
||||||
})
|
})
|
||||||
.returning();
|
.returning();
|
||||||
|
|
||||||
console.log(
|
logger.log(
|
||||||
`[AutoPick] Pick created - ${triggeredBy} triggered - Pick ${pickNumber} - Participant ${participantId}`
|
`[AutoPick] Pick created - ${triggeredBy} triggered - Pick ${pickNumber} - Participant ${participantId}`
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
@ -661,13 +662,13 @@ export async function executeAutoPick(params: {
|
||||||
)
|
)
|
||||||
.returning();
|
.returning();
|
||||||
emitTimeRemaining = updatedTimer?.timeRemaining ?? incrementTime;
|
emitTimeRemaining = updatedTimer?.timeRemaining ?? incrementTime;
|
||||||
console.log(
|
logger.log(
|
||||||
`[AutoPick] Reset timer for team ${teamId} to ${emitTimeRemaining}s (standard mode)`
|
`[AutoPick] Reset timer for team ${teamId} to ${emitTimeRemaining}s (standard mode)`
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
// Chess clock: no DB update — auto picks don't earn increment.
|
// Chess clock: no DB update — auto picks don't earn increment.
|
||||||
emitTimeRemaining = currentTimer?.timeRemaining ?? 0;
|
emitTimeRemaining = currentTimer?.timeRemaining ?? 0;
|
||||||
console.log(
|
logger.log(
|
||||||
`[AutoPick] Chess clock pick for team ${teamId}, bank frozen at ${emitTimeRemaining}s (no increment)`
|
`[AutoPick] Chess clock pick for team ${teamId}, bank frozen at ${emitTimeRemaining}s (no increment)`
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -680,7 +681,7 @@ export async function executeAutoPick(params: {
|
||||||
currentPickNumber: nextPickNumber,
|
currentPickNumber: nextPickNumber,
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("[AutoPick] Socket.IO timer-update error:", error);
|
logger.error("[AutoPick] Socket.IO timer-update error:", error);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Next team's timer is unchanged — their bank carries forward as-is
|
// Next team's timer is unchanged — their bank carries forward as-is
|
||||||
|
|
@ -721,12 +722,12 @@ export async function executeAutoPick(params: {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("[AutoPick] Error pruning ineligible queue items:", error);
|
logger.error("[AutoPick] Error pruning ineligible queue items:", error);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle autodraft settings for timer-based picks with "next_pick" mode
|
// Handle autodraft settings for timer-based picks with "next_pick" mode
|
||||||
if (triggeredBy === "timer" && autodraftSettings?.isEnabled && autodraftSettings.mode === "next_pick") {
|
if (triggeredBy === "timer" && autodraftSettings?.isEnabled && autodraftSettings.mode === "next_pick") {
|
||||||
console.log(`[AutoPick] Disabling autodraft for team ${teamId} after next_pick`);
|
logger.log(`[AutoPick] Disabling autodraft for team ${teamId} after next_pick`);
|
||||||
await db
|
await db
|
||||||
.update(schema.autodraftSettings)
|
.update(schema.autodraftSettings)
|
||||||
.set({
|
.set({
|
||||||
|
|
@ -744,7 +745,7 @@ export async function executeAutoPick(params: {
|
||||||
queueOnly: autodraftSettings.queueOnly,
|
queueOnly: autodraftSettings.queueOnly,
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("[AutoPick] Socket.IO autodraft-updated error:", error);
|
logger.error("[AutoPick] Socket.IO autodraft-updated error:", error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -778,7 +779,7 @@ export async function executeAutoPick(params: {
|
||||||
io.to(`draft-${seasonId}`).emit("draft-completed");
|
io.to(`draft-${seasonId}`).emit("draft-completed");
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("[AutoPick] Socket.IO events error:", error);
|
logger.error("[AutoPick] Socket.IO events error:", error);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if next team has autodraft enabled and trigger immediately
|
// Check if next team has autodraft enabled and trigger immediately
|
||||||
|
|
@ -801,7 +802,7 @@ export async function executeAutoPick(params: {
|
||||||
isDraftComplete,
|
isDraftComplete,
|
||||||
};
|
};
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("[AutoPick] Error in executeAutoPick:", error);
|
logger.error("[AutoPick] Error in executeAutoPick:", error);
|
||||||
return {
|
return {
|
||||||
success: false,
|
success: false,
|
||||||
error: error instanceof Error ? error.message : "Unknown error",
|
error: error instanceof Error ? error.message : "Unknown error",
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ import { sendStandingsUpdateNotification, type ScoredMatch } from "~/services/di
|
||||||
import { BRACKET_TEMPLATES } from "~/lib/bracket-templates";
|
import { BRACKET_TEMPLATES } from "~/lib/bracket-templates";
|
||||||
import { getUserDisplayName } from "~/models/user";
|
import { getUserDisplayName } from "~/models/user";
|
||||||
import { createDailySnapshot } from "~/models/standings";
|
import { createDailySnapshot } from "~/models/standings";
|
||||||
|
import { logger } from "~/lib/logger";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Core scoring calculation engine
|
* Core scoring calculation engine
|
||||||
|
|
@ -249,7 +250,7 @@ export async function processPlayoffEvent(
|
||||||
|
|
||||||
if (config === null) {
|
if (config === null) {
|
||||||
// Unrecognized scoring round: losers earn 0 pts (outside top-8).
|
// Unrecognized scoring round: losers earn 0 pts (outside top-8).
|
||||||
console.warn(
|
logger.warn(
|
||||||
`[ScoringCalculator] Unrecognized scoring round "${round}" for event ${eventId}. Losers receive 0 pts.`
|
`[ScoringCalculator] Unrecognized scoring round "${round}" for event ${eventId}. Losers receive 0 pts.`
|
||||||
);
|
);
|
||||||
for (const match of matches) {
|
for (const match of matches) {
|
||||||
|
|
@ -312,11 +313,11 @@ export async function processPlayoffEvent(
|
||||||
// Auto-trigger probability recalculation after result
|
// Auto-trigger probability recalculation after result
|
||||||
try {
|
try {
|
||||||
await updateProbabilitiesAfterResult(event.sportsSeasonId, true);
|
await updateProbabilitiesAfterResult(event.sportsSeasonId, true);
|
||||||
console.log(
|
logger.log(
|
||||||
`[ScoringCalculator] Auto-updated probabilities for sports season ${event.sportsSeasonId}`
|
`[ScoringCalculator] Auto-updated probabilities for sports season ${event.sportsSeasonId}`
|
||||||
);
|
);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(
|
logger.error(
|
||||||
`[ScoringCalculator] Failed to auto-update probabilities for sports season ${event.sportsSeasonId}:`,
|
`[ScoringCalculator] Failed to auto-update probabilities for sports season ${event.sportsSeasonId}:`,
|
||||||
error
|
error
|
||||||
);
|
);
|
||||||
|
|
@ -369,7 +370,7 @@ export async function processMatchResult(
|
||||||
|
|
||||||
if (config === null) {
|
if (config === null) {
|
||||||
// Unrecognized scoring round: loser earns 0 pts, winner gets fallback T5–T8 floor.
|
// Unrecognized scoring round: loser earns 0 pts, winner gets fallback T5–T8 floor.
|
||||||
console.warn(
|
logger.warn(
|
||||||
`[ScoringCalculator] processMatchResult: unrecognized round "${round}". Loser receives 0 pts.`
|
`[ScoringCalculator] processMatchResult: unrecognized round "${round}". Loser receives 0 pts.`
|
||||||
);
|
);
|
||||||
await upsertParticipantResult(loserId, sportsSeasonId, 0, db);
|
await upsertParticipantResult(loserId, sportsSeasonId, 0, db);
|
||||||
|
|
@ -393,7 +394,7 @@ export async function processMatchResult(
|
||||||
try {
|
try {
|
||||||
await updateProbabilitiesAfterResult(sportsSeasonId, true);
|
await updateProbabilitiesAfterResult(sportsSeasonId, true);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(
|
logger.error(
|
||||||
`[ScoringCalculator] Failed to auto-update probabilities for sports season ${sportsSeasonId}:`,
|
`[ScoringCalculator] Failed to auto-update probabilities for sports season ${sportsSeasonId}:`,
|
||||||
error
|
error
|
||||||
);
|
);
|
||||||
|
|
@ -557,7 +558,7 @@ export async function processQualifyingEvent(
|
||||||
.where(eq(schema.sportsSeasons.id, event.sportsSeasonId));
|
.where(eq(schema.sportsSeasons.id, event.sportsSeasonId));
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log(
|
logger.log(
|
||||||
`[ScoringCalculator] Processed qualifying event ${eventId}: awarded QP to ${results.length} participants`
|
`[ScoringCalculator] Processed qualifying event ${eventId}: awarded QP to ${results.length} participants`
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -677,17 +678,17 @@ export async function finalizeQualifyingPoints(
|
||||||
// Auto-trigger probability recalculation after result
|
// Auto-trigger probability recalculation after result
|
||||||
try {
|
try {
|
||||||
await updateProbabilitiesAfterResult(sportsSeasonId, true);
|
await updateProbabilitiesAfterResult(sportsSeasonId, true);
|
||||||
console.log(
|
logger.log(
|
||||||
`[ScoringCalculator] Auto-updated probabilities for sports season ${sportsSeasonId}`
|
`[ScoringCalculator] Auto-updated probabilities for sports season ${sportsSeasonId}`
|
||||||
);
|
);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(
|
logger.error(
|
||||||
`[ScoringCalculator] Failed to auto-update probabilities for sports season ${sportsSeasonId}:`,
|
`[ScoringCalculator] Failed to auto-update probabilities for sports season ${sportsSeasonId}:`,
|
||||||
error
|
error
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log(
|
logger.log(
|
||||||
`[ScoringCalculator] Finalized qualifying points for sports season ${sportsSeasonId}: ${standings.length} participants processed`
|
`[ScoringCalculator] Finalized qualifying points for sports season ${sportsSeasonId}: ${standings.length} participants processed`
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -787,17 +788,17 @@ export async function processSeasonStandings(
|
||||||
// Auto-trigger probability recalculation after result
|
// Auto-trigger probability recalculation after result
|
||||||
try {
|
try {
|
||||||
await updateProbabilitiesAfterResult(sportsSeasonId, true);
|
await updateProbabilitiesAfterResult(sportsSeasonId, true);
|
||||||
console.log(
|
logger.log(
|
||||||
`[ScoringCalculator] Auto-updated probabilities for sports season ${sportsSeasonId}`
|
`[ScoringCalculator] Auto-updated probabilities for sports season ${sportsSeasonId}`
|
||||||
);
|
);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(
|
logger.error(
|
||||||
`[ScoringCalculator] Failed to auto-update probabilities for sports season ${sportsSeasonId}:`,
|
`[ScoringCalculator] Failed to auto-update probabilities for sports season ${sportsSeasonId}:`,
|
||||||
error
|
error
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log(
|
logger.log(
|
||||||
`[ScoringCalculator] Processed season standings for sports season ${sportsSeasonId}`
|
`[ScoringCalculator] Processed season standings for sports season ${sportsSeasonId}`
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -1208,7 +1209,7 @@ export async function recalculateStandings(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log(`[ScoringCalculator] Recalculated standings for season ${seasonId}: ${teams.length} teams ranked`);
|
logger.log(`[ScoringCalculator] Recalculated standings for season ${seasonId}: ${teams.length} teams ranked`);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -1289,7 +1290,7 @@ export async function recalculateAffectedLeagues(
|
||||||
try {
|
try {
|
||||||
await createDailySnapshot(seasonId, db);
|
await createDailySnapshot(seasonId, db);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error(`[ScoringCalculator] Snapshot failed for season ${seasonId}:`, err);
|
logger.error(`[ScoringCalculator] Snapshot failed for season ${seasonId}:`, err);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if any team's score changed — if so, send Discord notification
|
// Check if any team's score changed — if so, send Discord notification
|
||||||
|
|
@ -1413,11 +1414,11 @@ export async function recalculateAffectedLeagues(
|
||||||
});
|
});
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
// Log but don't fail the scoring pipeline if Discord is unreachable
|
// Log but don't fail the scoring pipeline if Discord is unreachable
|
||||||
console.error(`[ScoringCalculator] Discord notification failed for season ${seasonId}:`, err);
|
logger.error(`[ScoringCalculator] Discord notification failed for season ${seasonId}:`, err);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log(
|
logger.log(
|
||||||
`[ScoringCalculator] Recalculated ${seasonIds.length} affected season(s) for sports season ${sportsSeasonId}`
|
`[ScoringCalculator] Recalculated ${seasonIds.length} affected season(s) for sports season ${sportsSeasonId}`
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ import * as schema from "~/database/schema";
|
||||||
import { eq, and } from "drizzle-orm";
|
import { eq, and } from "drizzle-orm";
|
||||||
import type { TeamStanding, TeamStandingSnapshot, TeamStandingWithChange } from "~/types/standings";
|
import type { TeamStanding, TeamStandingSnapshot, TeamStandingWithChange } from "~/types/standings";
|
||||||
import { calculateBracketPoints, calculateFantasyPoints } from "~/models/scoring-rules";
|
import { calculateBracketPoints, calculateFantasyPoints } from "~/models/scoring-rules";
|
||||||
|
import { logger } from "~/lib/logger";
|
||||||
|
|
||||||
// Re-export types from shared types file
|
// Re-export types from shared types file
|
||||||
export type { TeamStanding, TeamStandingSnapshot, TeamStandingWithChange } from "~/types/standings";
|
export type { TeamStanding, TeamStandingSnapshot, TeamStandingWithChange } from "~/types/standings";
|
||||||
|
|
@ -374,7 +375,7 @@ export async function createDailySnapshot(
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
console.log(`[Standings] Upserted daily snapshot for season ${seasonId}`);
|
logger.log(`[Standings] Upserted daily snapshot for season ${seasonId}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ import { useState } from "react";
|
||||||
import { useFetcher } from "react-router";
|
import { useFetcher } from "react-router";
|
||||||
import type { Route } from "./+types/admin.data-sync";
|
import type { Route } from "./+types/admin.data-sync";
|
||||||
|
|
||||||
|
import { logger } from "~/lib/logger";
|
||||||
import { findAllSports } from "~/models/sport";
|
import { findAllSports } from "~/models/sport";
|
||||||
import { findAllSportsSeasons } from "~/models/sports-season";
|
import { findAllSportsSeasons } from "~/models/sports-season";
|
||||||
import { findAllSeasonTemplates } from "~/models/season-template";
|
import { findAllSeasonTemplates } from "~/models/season-template";
|
||||||
|
|
@ -77,7 +78,7 @@ export async function action({ request }: Route.ActionArgs) {
|
||||||
message: `Import complete! Created: ${result.created}, Updated: ${result.updated}, Skipped: ${result.skipped}`,
|
message: `Import complete! Created: ${result.created}, Updated: ${result.updated}, Skipped: ${result.skipped}`,
|
||||||
};
|
};
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Import error:", error);
|
logger.error("Import error:", error);
|
||||||
return {
|
return {
|
||||||
success: false,
|
success: false,
|
||||||
error: error instanceof Error ? error.message : "Import failed",
|
error: error instanceof Error ? error.message : "Import failed",
|
||||||
|
|
@ -116,7 +117,7 @@ export default function DataSync({ loaderData }: Route.ComponentProps) {
|
||||||
document.body.removeChild(a);
|
document.body.removeChild(a);
|
||||||
URL.revokeObjectURL(url);
|
URL.revokeObjectURL(url);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Export failed:", error);
|
logger.error("Export failed:", error);
|
||||||
alert("Export failed. Please try again.");
|
alert("Export failed. Please try again.");
|
||||||
} finally {
|
} finally {
|
||||||
setIsExporting(false);
|
setIsExporting(false);
|
||||||
|
|
|
||||||
|
|
@ -40,6 +40,7 @@ import {
|
||||||
getAdvancingParticipantIds,
|
getAdvancingParticipantIds,
|
||||||
getEliminatedParticipantIds,
|
getEliminatedParticipantIds,
|
||||||
} from "~/models/tournament-group";
|
} from "~/models/tournament-group";
|
||||||
|
import { logger } from "~/lib/logger";
|
||||||
import { database } from "~/database/context";
|
import { database } from "~/database/context";
|
||||||
import * as schema from "~/database/schema";
|
import * as schema from "~/database/schema";
|
||||||
import { eq } from "drizzle-orm";
|
import { eq } from "drizzle-orm";
|
||||||
|
|
@ -173,7 +174,7 @@ export async function action({ request, params }: Route.ActionArgs) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log(`[BracketGeneration] Marked ${allParticipants.length - participantIds.length} participants as eliminated`);
|
logger.log(`[BracketGeneration] Marked ${allParticipants.length - participantIds.length} participants as eliminated`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update the event to store the template ID, scoring start round, and region config
|
// Update the event to store the template ID, scoring start round, and region config
|
||||||
|
|
@ -185,7 +186,7 @@ export async function action({ request, params }: Route.ActionArgs) {
|
||||||
|
|
||||||
return { success: "Bracket generated successfully" };
|
return { success: "Bracket generated successfully" };
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error generating bracket:", error);
|
logger.error("Error generating bracket:", error);
|
||||||
return {
|
return {
|
||||||
error:
|
error:
|
||||||
error instanceof Error
|
error instanceof Error
|
||||||
|
|
@ -223,7 +224,7 @@ export async function action({ request, params }: Route.ActionArgs) {
|
||||||
.where(eq(schema.scoringEvents.id, eventId));
|
.where(eq(schema.scoringEvents.id, eventId));
|
||||||
|
|
||||||
await processPlayoffEvent(eventId, db, { skipRecalculate: true });
|
await processPlayoffEvent(eventId, db, { skipRecalculate: true });
|
||||||
console.log(`[AutoComplete] Round "${round}" auto-completed for event ${eventId}`);
|
logger.log(`[AutoComplete] Round "${round}" auto-completed for event ${eventId}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (intent === "set-winner") {
|
if (intent === "set-winner") {
|
||||||
|
|
@ -275,7 +276,7 @@ export async function action({ request, params }: Route.ActionArgs) {
|
||||||
error instanceof Error &&
|
error instanceof Error &&
|
||||||
error.message.includes("already filled")
|
error.message.includes("already filled")
|
||||||
) {
|
) {
|
||||||
console.warn("Match already filled, skipping advancement");
|
logger.warn("Match already filled, skipping advancement");
|
||||||
} else {
|
} else {
|
||||||
throw error; // Re-throw unexpected errors
|
throw error; // Re-throw unexpected errors
|
||||||
}
|
}
|
||||||
|
|
@ -303,7 +304,7 @@ export async function action({ request, params }: Route.ActionArgs) {
|
||||||
|
|
||||||
return { success: "Winner set successfully" };
|
return { success: "Winner set successfully" };
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error setting winner:", error);
|
logger.error("Error setting winner:", error);
|
||||||
return {
|
return {
|
||||||
error:
|
error:
|
||||||
error instanceof Error ? error.message : "Failed to set winner",
|
error instanceof Error ? error.message : "Failed to set winner",
|
||||||
|
|
@ -384,7 +385,7 @@ export async function action({ request, params }: Route.ActionArgs) {
|
||||||
error instanceof Error &&
|
error instanceof Error &&
|
||||||
error.message.includes("already filled")
|
error.message.includes("already filled")
|
||||||
) {
|
) {
|
||||||
console.warn("Match already filled, skipping advancement");
|
logger.warn("Match already filled, skipping advancement");
|
||||||
} else {
|
} else {
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
|
|
@ -408,7 +409,7 @@ export async function action({ request, params }: Route.ActionArgs) {
|
||||||
successCount++;
|
successCount++;
|
||||||
processedMatchIds.push(matchId);
|
processedMatchIds.push(matchId);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(`Error setting winner for match ${matchId}:`, error);
|
logger.error(`Error setting winner for match ${matchId}:`, error);
|
||||||
errors.push(
|
errors.push(
|
||||||
`Match ${matchId}: ${error instanceof Error ? error.message : "Unknown error"}`
|
`Match ${matchId}: ${error instanceof Error ? error.message : "Unknown error"}`
|
||||||
);
|
);
|
||||||
|
|
@ -424,7 +425,7 @@ export async function action({ request, params }: Route.ActionArgs) {
|
||||||
try {
|
try {
|
||||||
await updateProbabilitiesAfterResult(event.sportsSeasonId, true);
|
await updateProbabilitiesAfterResult(event.sportsSeasonId, true);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(`Error updating probabilities after batch round winners:`, error);
|
logger.error(`Error updating probabilities after batch round winners:`, error);
|
||||||
}
|
}
|
||||||
await autoCompleteRoundIfDone(event.id, round, event.sportsSeasonId, db);
|
await autoCompleteRoundIfDone(event.id, round, event.sportsSeasonId, db);
|
||||||
}
|
}
|
||||||
|
|
@ -442,7 +443,7 @@ export async function action({ request, params }: Route.ActionArgs) {
|
||||||
|
|
||||||
return { success: `Successfully set ${successCount} winner(s) for ${round}` };
|
return { success: `Successfully set ${successCount} winner(s) for ${round}` };
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error setting round winners:", error);
|
logger.error("Error setting round winners:", error);
|
||||||
return {
|
return {
|
||||||
error:
|
error:
|
||||||
error instanceof Error ? error.message : "Failed to set winners",
|
error instanceof Error ? error.message : "Failed to set winners",
|
||||||
|
|
@ -545,7 +546,7 @@ export async function action({ request, params }: Route.ActionArgs) {
|
||||||
success: `${round} completed and placements calculated successfully`,
|
success: `${round} completed and placements calculated successfully`,
|
||||||
};
|
};
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error completing round:", error);
|
logger.error("Error completing round:", error);
|
||||||
return {
|
return {
|
||||||
error:
|
error:
|
||||||
error instanceof Error ? error.message : "Failed to complete round",
|
error instanceof Error ? error.message : "Failed to complete round",
|
||||||
|
|
@ -591,13 +592,13 @@ export async function action({ request, params }: Route.ActionArgs) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log(`[ReprocessEliminations] Marked ${eliminatedCount} participants as eliminated`);
|
logger.log(`[ReprocessEliminations] Marked ${eliminatedCount} participants as eliminated`);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
success: `Successfully marked ${eliminatedCount} participant(s) as eliminated`,
|
success: `Successfully marked ${eliminatedCount} participant(s) as eliminated`,
|
||||||
};
|
};
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error reprocessing eliminations:", error);
|
logger.error("Error reprocessing eliminations:", error);
|
||||||
return {
|
return {
|
||||||
error:
|
error:
|
||||||
error instanceof Error ? error.message : "Failed to reprocess eliminations",
|
error instanceof Error ? error.message : "Failed to reprocess eliminations",
|
||||||
|
|
@ -685,7 +686,7 @@ export async function action({ request, params }: Route.ActionArgs) {
|
||||||
success: `Bracket finalized! All placements calculated and standings updated.`,
|
success: `Bracket finalized! All placements calculated and standings updated.`,
|
||||||
};
|
};
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error finalizing bracket:", error);
|
logger.error("Error finalizing bracket:", error);
|
||||||
return {
|
return {
|
||||||
error:
|
error:
|
||||||
error instanceof Error ? error.message : "Failed to finalize bracket",
|
error instanceof Error ? error.message : "Failed to finalize bracket",
|
||||||
|
|
@ -749,7 +750,7 @@ export async function action({ request, params }: Route.ActionArgs) {
|
||||||
|
|
||||||
return { success: "Groups and knockout bracket structure created successfully" };
|
return { success: "Groups and knockout bracket structure created successfully" };
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error generating groups:", error);
|
logger.error("Error generating groups:", error);
|
||||||
return {
|
return {
|
||||||
error: error instanceof Error ? error.message : "Failed to generate groups",
|
error: error instanceof Error ? error.message : "Failed to generate groups",
|
||||||
};
|
};
|
||||||
|
|
@ -771,7 +772,7 @@ export async function action({ request, params }: Route.ActionArgs) {
|
||||||
: "Team reinstated",
|
: "Team reinstated",
|
||||||
};
|
};
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error toggling elimination:", error);
|
logger.error("Error toggling elimination:", error);
|
||||||
return {
|
return {
|
||||||
error: error instanceof Error ? error.message : "Failed to toggle elimination",
|
error: error instanceof Error ? error.message : "Failed to toggle elimination",
|
||||||
};
|
};
|
||||||
|
|
@ -831,13 +832,13 @@ export async function action({ request, params }: Route.ActionArgs) {
|
||||||
await setParticipantResult(participantId, event.sportsSeasonId, 0);
|
await setParticipantResult(participantId, event.sportsSeasonId, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log(
|
logger.log(
|
||||||
`[PopulateKnockout] Assigned 32 participants to knockout, marked ${eliminatedIds.length} as eliminated`
|
`[PopulateKnockout] Assigned 32 participants to knockout, marked ${eliminatedIds.length} as eliminated`
|
||||||
);
|
);
|
||||||
|
|
||||||
return { success: "Knockout bracket populated successfully" };
|
return { success: "Knockout bracket populated successfully" };
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error populating knockout:", error);
|
logger.error("Error populating knockout:", error);
|
||||||
return {
|
return {
|
||||||
error: error instanceof Error ? error.message : "Failed to populate knockout",
|
error: error instanceof Error ? error.message : "Failed to populate knockout",
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
import type { Route } from "./+types/admin.sports-seasons.$id.events.$eventId";
|
import type { Route } from "./+types/admin.sports-seasons.$id.events.$eventId";
|
||||||
|
import { logger } from "~/lib/logger";
|
||||||
import { findSportsSeasonById } from "~/models/sports-season";
|
import { findSportsSeasonById } from "~/models/sports-season";
|
||||||
import { findParticipantsBySportsSeasonId } from "~/models/participant";
|
import { findParticipantsBySportsSeasonId } from "~/models/participant";
|
||||||
import {
|
import {
|
||||||
|
|
@ -93,7 +94,7 @@ export async function action({ request, params }: Route.ActionArgs) {
|
||||||
|
|
||||||
return { success: "Event marked as qualifying event!" };
|
return { success: "Event marked as qualifying event!" };
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error marking event as qualifying:", error);
|
logger.error("Error marking event as qualifying:", error);
|
||||||
return { error: "Failed to mark event as qualifying" };
|
return { error: "Failed to mark event as qualifying" };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -103,7 +104,7 @@ export async function action({ request, params }: Route.ActionArgs) {
|
||||||
await processQualifyingEvent(params.eventId);
|
await processQualifyingEvent(params.eventId);
|
||||||
return { success: "Qualifying points processed and awarded!" };
|
return { success: "Qualifying points processed and awarded!" };
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error processing qualifying points:", error);
|
logger.error("Error processing qualifying points:", error);
|
||||||
return { error: error instanceof Error ? error.message : "Failed to process qualifying points" };
|
return { error: error instanceof Error ? error.message : "Failed to process qualifying points" };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -131,7 +132,7 @@ export async function action({ request, params }: Route.ActionArgs) {
|
||||||
|
|
||||||
return { success: "Event marked as completed" };
|
return { success: "Event marked as completed" };
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error completing event:", error);
|
logger.error("Error completing event:", error);
|
||||||
return { error: "Failed to complete event" };
|
return { error: "Failed to complete event" };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -141,7 +142,7 @@ export async function action({ request, params }: Route.ActionArgs) {
|
||||||
await updateScoringEvent(params.eventId, { isComplete: false });
|
await updateScoringEvent(params.eventId, { isComplete: false });
|
||||||
return { success: "Event marked as not updated" };
|
return { success: "Event marked as not updated" };
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error uncompleting event:", error);
|
logger.error("Error uncompleting event:", error);
|
||||||
return { error: "Failed to update event" };
|
return { error: "Failed to update event" };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -178,7 +179,7 @@ export async function action({ request, params }: Route.ActionArgs) {
|
||||||
});
|
});
|
||||||
return { success: "Event updated" };
|
return { success: "Event updated" };
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error updating event:", error);
|
logger.error("Error updating event:", error);
|
||||||
return { error: "Failed to update event" };
|
return { error: "Failed to update event" };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -210,7 +211,7 @@ export async function action({ request, params }: Route.ActionArgs) {
|
||||||
await createEventResult(resultData);
|
await createEventResult(resultData);
|
||||||
return { success: "Result added successfully" };
|
return { success: "Result added successfully" };
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error adding result:", error);
|
logger.error("Error adding result:", error);
|
||||||
return { error: "Failed to add result" };
|
return { error: "Failed to add result" };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -240,7 +241,7 @@ export async function action({ request, params }: Route.ActionArgs) {
|
||||||
await updateEventResult(resultId, updateData);
|
await updateEventResult(resultId, updateData);
|
||||||
return { success: "Result updated successfully" };
|
return { success: "Result updated successfully" };
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error updating result:", error);
|
logger.error("Error updating result:", error);
|
||||||
return { error: "Failed to update result" };
|
return { error: "Failed to update result" };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -256,7 +257,7 @@ export async function action({ request, params }: Route.ActionArgs) {
|
||||||
await deleteEventResult(resultId);
|
await deleteEventResult(resultId);
|
||||||
return { success: "Result deleted successfully" };
|
return { success: "Result deleted successfully" };
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error deleting result:", error);
|
logger.error("Error deleting result:", error);
|
||||||
return { error: "Failed to delete result" };
|
return { error: "Failed to delete result" };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -317,7 +318,7 @@ export async function action({ request, params }: Route.ActionArgs) {
|
||||||
|
|
||||||
return { success: `Updated standings for ${participantPoints.length} participants (positions auto-calculated from points)` };
|
return { success: `Updated standings for ${participantPoints.length} participants (positions auto-calculated from points)` };
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error updating season standings:", error);
|
logger.error("Error updating season standings:", error);
|
||||||
return { error: "Failed to update season standings" };
|
return { error: "Failed to update season standings" };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
import type { Route } from "./+types/admin.sports-seasons.$id.events";
|
import type { Route } from "./+types/admin.sports-seasons.$id.events";
|
||||||
import { redirect } from "react-router";
|
import { redirect } from "react-router";
|
||||||
|
import { logger } from "~/lib/logger";
|
||||||
import { findSportsSeasonById } from "~/models/sports-season";
|
import { findSportsSeasonById } from "~/models/sports-season";
|
||||||
import {
|
import {
|
||||||
getScoringEventsForSportsSeason,
|
getScoringEventsForSportsSeason,
|
||||||
|
|
@ -52,7 +53,7 @@ export async function action({ request, params }: Route.ActionArgs) {
|
||||||
await finalizeQualifyingPoints(params.id);
|
await finalizeQualifyingPoints(params.id);
|
||||||
return { success: "Qualifying points finalized and fantasy placements assigned to top 8!" };
|
return { success: "Qualifying points finalized and fantasy placements assigned to top 8!" };
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error finalizing qualifying points:", error);
|
logger.error("Error finalizing qualifying points:", error);
|
||||||
return { error: error instanceof Error ? error.message : "Failed to finalize qualifying points" };
|
return { error: error instanceof Error ? error.message : "Failed to finalize qualifying points" };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -110,7 +111,7 @@ export async function action({ request, params }: Route.ActionArgs) {
|
||||||
await bulkCreateScoringEvents(params.id, validEvents);
|
await bulkCreateScoringEvents(params.id, validEvents);
|
||||||
return { success: `Created ${validEvents.length} event${validEvents.length !== 1 ? "s" : ""} successfully` };
|
return { success: `Created ${validEvents.length} event${validEvents.length !== 1 ? "s" : ""} successfully` };
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error bulk creating events:", error);
|
logger.error("Error bulk creating events:", error);
|
||||||
return { error: "Failed to create events. Please try again." };
|
return { error: "Failed to create events. Please try again." };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -126,7 +127,7 @@ export async function action({ request, params }: Route.ActionArgs) {
|
||||||
await deleteScoringEvent(eventId);
|
await deleteScoringEvent(eventId);
|
||||||
return { success: "Event deleted successfully" };
|
return { success: "Event deleted successfully" };
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error deleting event:", error);
|
logger.error("Error deleting event:", error);
|
||||||
return { error: "Failed to delete event" };
|
return { error: "Failed to delete event" };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -181,7 +182,7 @@ export async function action({ request, params }: Route.ActionArgs) {
|
||||||
const event = await createScoringEvent(eventData);
|
const event = await createScoringEvent(eventData);
|
||||||
return redirect(`/admin/sports-seasons/${params.id}/events/${event.id}`);
|
return redirect(`/admin/sports-seasons/${params.id}/events/${event.id}`);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error creating scoring event:", error);
|
logger.error("Error creating scoring event:", error);
|
||||||
return { error: "Failed to create scoring event. Please try again." };
|
return { error: "Failed to create scoring event. Please try again." };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
import type { Route } from "./+types/admin.sports-seasons.$id.expected-values";
|
import type { Route } from "./+types/admin.sports-seasons.$id.expected-values";
|
||||||
|
import { logger } from "~/lib/logger";
|
||||||
import { findSportsSeasonById } from "~/models/sports-season";
|
import { findSportsSeasonById } from "~/models/sports-season";
|
||||||
import { findParticipantsBySportsSeasonId } from "~/models/participant";
|
import { findParticipantsBySportsSeasonId } from "~/models/participant";
|
||||||
import {
|
import {
|
||||||
|
|
@ -78,7 +79,7 @@ export async function action({ request, params }: Route.ActionArgs) {
|
||||||
|
|
||||||
return { success: true, totalEV };
|
return { success: true, totalEV };
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error saving probabilities:", error);
|
logger.error("Error saving probabilities:", error);
|
||||||
return {
|
return {
|
||||||
error: error instanceof Error ? error.message : "Failed to save probabilities"
|
error: error instanceof Error ? error.message : "Failed to save probabilities"
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
import { Form, redirect, useLoaderData, useActionData, useNavigation } from 'react-router';
|
import { Form, redirect, useLoaderData, useActionData, useNavigation } from 'react-router';
|
||||||
import type { Route } from './+types/admin.sports-seasons.$id.futures-odds';
|
import type { Route } from './+types/admin.sports-seasons.$id.futures-odds';
|
||||||
|
|
||||||
|
import { logger } from '~/lib/logger';
|
||||||
import { findSportsSeasonById, updateSportsSeason } from '~/models/sports-season';
|
import { findSportsSeasonById, updateSportsSeason } from '~/models/sports-season';
|
||||||
import { findParticipantsBySportsSeasonId } from '~/models/participant';
|
import { findParticipantsBySportsSeasonId } from '~/models/participant';
|
||||||
import { getAllParticipantEVsForSeason, batchSaveSourceOdds, batchUpsertParticipantEVs } from '~/models/participant-expected-value';
|
import { getAllParticipantEVsForSeason, batchSaveSourceOdds, batchUpsertParticipantEVs } from '~/models/participant-expected-value';
|
||||||
|
|
@ -155,7 +156,7 @@ export async function action({ request, params }: Route.ActionArgs) {
|
||||||
await updateSportsSeason(sportsSeasonId, { simulationStatus: 'idle' });
|
await updateSportsSeason(sportsSeasonId, { simulationStatus: 'idle' });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
await updateSportsSeason(sportsSeasonId, { simulationStatus: 'failed' });
|
await updateSportsSeason(sportsSeasonId, { simulationStatus: 'failed' });
|
||||||
console.error('Error running simulation:', error);
|
logger.error('Error running simulation:', error);
|
||||||
return {
|
return {
|
||||||
success: false,
|
success: false,
|
||||||
message: error instanceof Error ? error.message : 'Simulation failed',
|
message: error instanceof Error ? error.message : 'Simulation failed',
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
import { Form, Link } from "react-router";
|
import { Form, Link } from "react-router";
|
||||||
import type { Route } from "./+types/admin.sports-seasons.$id.participants";
|
import type { Route } from "./+types/admin.sports-seasons.$id.participants";
|
||||||
|
|
||||||
|
import { logger } from "~/lib/logger";
|
||||||
import { findSportsSeasonById } from "~/models/sports-season";
|
import { findSportsSeasonById } from "~/models/sports-season";
|
||||||
import {
|
import {
|
||||||
findParticipantsBySportsSeasonId,
|
findParticipantsBySportsSeasonId,
|
||||||
|
|
@ -90,7 +91,7 @@ export async function action({ request, params }: Route.ActionArgs) {
|
||||||
|
|
||||||
return { success: true, count: names.length };
|
return { success: true, count: names.length };
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error creating participants:", error);
|
logger.error("Error creating participants:", error);
|
||||||
return { error: "Failed to add participants. Please try again." };
|
return { error: "Failed to add participants. Please try again." };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -113,7 +114,7 @@ export async function action({ request, params }: Route.ActionArgs) {
|
||||||
|
|
||||||
return { success: true };
|
return { success: true };
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error creating participant:", error);
|
logger.error("Error creating participant:", error);
|
||||||
return { error: "Failed to add participant. Please try again." };
|
return { error: "Failed to add participant. Please try again." };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
import { Form, Link } from "react-router";
|
import { Form, Link } from "react-router";
|
||||||
import type { Route } from "./+types/admin.sports-seasons.$id.regular-standings";
|
import type { Route } from "./+types/admin.sports-seasons.$id.regular-standings";
|
||||||
|
|
||||||
|
import { logger } from "~/lib/logger";
|
||||||
import { findSportsSeasonById } from "~/models/sports-season";
|
import { findSportsSeasonById } from "~/models/sports-season";
|
||||||
import { findParticipantsBySportsSeasonId } from "~/models/participant";
|
import { findParticipantsBySportsSeasonId } from "~/models/participant";
|
||||||
import { getRegularSeasonStandings, upsertManualStanding } from "~/models/regular-season-standings";
|
import { getRegularSeasonStandings, upsertManualStanding } from "~/models/regular-season-standings";
|
||||||
|
|
@ -103,7 +104,7 @@ export async function action({ request, params }: Route.ActionArgs) {
|
||||||
}
|
}
|
||||||
return { success: true };
|
return { success: true };
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error saving regular season standings:", error);
|
logger.error("Error saving regular season standings:", error);
|
||||||
return { error: "Failed to save standings. Please try again." };
|
return { error: "Failed to save standings. Please try again." };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
import { Form, Link } from "react-router";
|
import { Form, Link } from "react-router";
|
||||||
import type { Route } from "./+types/admin.sports-seasons.$id.standings";
|
import type { Route } from "./+types/admin.sports-seasons.$id.standings";
|
||||||
|
|
||||||
|
import { logger } from "~/lib/logger";
|
||||||
import { findSportsSeasonById } from "~/models/sports-season";
|
import { findSportsSeasonById } from "~/models/sports-season";
|
||||||
import { findParticipantsBySportsSeasonId } from "~/models/participant";
|
import { findParticipantsBySportsSeasonId } from "~/models/participant";
|
||||||
import { getSeasonResults, upsertSeasonResultsBulk } from "~/models/participant-season-result";
|
import { getSeasonResults, upsertSeasonResultsBulk } from "~/models/participant-season-result";
|
||||||
|
|
@ -74,7 +75,7 @@ export async function action({ request, params }: Route.ActionArgs) {
|
||||||
await upsertSeasonResultsBulk(updates);
|
await upsertSeasonResultsBulk(updates);
|
||||||
return { success: true };
|
return { success: true };
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error updating standings:", error);
|
logger.error("Error updating standings:", error);
|
||||||
return { error: "Failed to save standings. Please try again." };
|
return { error: "Failed to save standings. Please try again." };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ import { getAuth } from "@clerk/react-router/server";
|
||||||
import { isUserAdminByClerkId } from "~/models/user";
|
import { isUserAdminByClerkId } from "~/models/user";
|
||||||
import type { Route } from "./+types/admin.sports-seasons.$id";
|
import type { Route } from "./+types/admin.sports-seasons.$id";
|
||||||
|
|
||||||
|
import { logger } from "~/lib/logger";
|
||||||
import { findSportsSeasonById, updateSportsSeason, deleteSportsSeason, type NewSportsSeason } from "~/models/sports-season";
|
import { findSportsSeasonById, updateSportsSeason, deleteSportsSeason, type NewSportsSeason } from "~/models/sports-season";
|
||||||
import { processSeasonStandings, recalculateStandings } from "~/models/scoring-calculator";
|
import { processSeasonStandings, recalculateStandings } from "~/models/scoring-calculator";
|
||||||
import { createDailySnapshot } from "~/models/standings";
|
import { createDailySnapshot } from "~/models/standings";
|
||||||
|
|
@ -127,7 +128,7 @@ export async function action(args: Route.ActionArgs) {
|
||||||
await Promise.all(links.map((link) => createDailySnapshot(link.seasonId, db)));
|
await Promise.all(links.map((link) => createDailySnapshot(link.seasonId, db)));
|
||||||
return { success: true, intent: "rescore", message: `Rescored ${links.length} linked season(s).` };
|
return { success: true, intent: "rescore", message: `Rescored ${links.length} linked season(s).` };
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error rescoring:", error);
|
logger.error("Error rescoring:", error);
|
||||||
return { error: "Failed to rescore. Please try again." };
|
return { error: "Failed to rescore. Please try again." };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -137,7 +138,7 @@ export async function action(args: Route.ActionArgs) {
|
||||||
const result = await syncStandings(params.id);
|
const result = await syncStandings(params.id);
|
||||||
return { success: true, intent: "sync-standings", syncResult: result };
|
return { success: true, intent: "sync-standings", syncResult: result };
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error syncing standings:", error);
|
logger.error("Error syncing standings:", error);
|
||||||
return {
|
return {
|
||||||
syncError:
|
syncError:
|
||||||
error instanceof Error ? error.message : "Failed to sync standings. Please try again.",
|
error instanceof Error ? error.message : "Failed to sync standings. Please try again.",
|
||||||
|
|
@ -197,7 +198,7 @@ export async function action(args: Route.ActionArgs) {
|
||||||
|
|
||||||
return { success: true, intent: "resolve-mapping", resolvedTeam: String(standingData.teamName ?? "") };
|
return { success: true, intent: "resolve-mapping", resolvedTeam: String(standingData.teamName ?? "") };
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error resolving mapping:", error);
|
logger.error("Error resolving mapping:", error);
|
||||||
return { error: "Failed to resolve mapping. Please try again." };
|
return { error: "Failed to resolve mapping. Please try again." };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -208,7 +209,7 @@ export async function action(args: Route.ActionArgs) {
|
||||||
await updateSportsSeason(params.id, { status: "completed" });
|
await updateSportsSeason(params.id, { status: "completed" });
|
||||||
return { success: true, intent: "finalize-standings", message: "Standings finalized and fantasy placements assigned!" };
|
return { success: true, intent: "finalize-standings", message: "Standings finalized and fantasy placements assigned!" };
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error finalizing standings:", error);
|
logger.error("Error finalizing standings:", error);
|
||||||
return { error: "Failed to finalize standings. Please try again." };
|
return { error: "Failed to finalize standings. Please try again." };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -277,7 +278,7 @@ export async function action(args: Route.ActionArgs) {
|
||||||
|
|
||||||
return { success: true, message: "Sports season updated successfully!" };
|
return { success: true, message: "Sports season updated successfully!" };
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error updating sports season:", error);
|
logger.error("Error updating sports season:", error);
|
||||||
return { error: "Failed to update sports season. Please try again." };
|
return { error: "Failed to update sports season. Please try again." };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
import { Form, Link, redirect } from "react-router";
|
import { Form, Link, redirect } from "react-router";
|
||||||
import type { Route } from "./+types/admin.sports-seasons.new";
|
import type { Route } from "./+types/admin.sports-seasons.new";
|
||||||
|
|
||||||
|
import { logger } from "~/lib/logger";
|
||||||
import { createSportsSeason, type NewSportsSeason } from "~/models/sports-season";
|
import { createSportsSeason, type NewSportsSeason } from "~/models/sports-season";
|
||||||
import { findAllSports } from "~/models/sport";
|
import { findAllSports } from "~/models/sport";
|
||||||
import { Button } from "~/components/ui/button";
|
import { Button } from "~/components/ui/button";
|
||||||
|
|
@ -100,7 +101,7 @@ export async function action({ request }: Route.ActionArgs) {
|
||||||
|
|
||||||
return redirect("/admin/sports-seasons");
|
return redirect("/admin/sports-seasons");
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error creating sports season:", error);
|
logger.error("Error creating sports season:", error);
|
||||||
return { error: "Failed to create sports season. Please try again." };
|
return { error: "Failed to create sports season. Please try again." };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
import { Form, Link, redirect } from "react-router";
|
import { Form, Link, redirect } from "react-router";
|
||||||
import type { Route } from "./+types/admin.sports.$id";
|
import type { Route } from "./+types/admin.sports.$id";
|
||||||
|
|
||||||
|
import { logger } from "~/lib/logger";
|
||||||
import { findSportById, updateSport } from "~/models/sport";
|
import { findSportById, updateSport } from "~/models/sport";
|
||||||
import { writeFile, mkdir } from "node:fs/promises";
|
import { writeFile, mkdir } from "node:fs/promises";
|
||||||
import { join } from "node:path";
|
import { join } from "node:path";
|
||||||
|
|
@ -94,7 +95,7 @@ export async function action({ request, params }: Route.ActionArgs) {
|
||||||
const buffer = Buffer.from(bytes);
|
const buffer = Buffer.from(bytes);
|
||||||
await writeFile(filepath, buffer);
|
await writeFile(filepath, buffer);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error saving icon file:", error);
|
logger.error("Error saving icon file:", error);
|
||||||
return { error: "Failed to save icon file" };
|
return { error: "Failed to save icon file" };
|
||||||
}
|
}
|
||||||
} else if (keepExistingIcon !== "true") {
|
} else if (keepExistingIcon !== "true") {
|
||||||
|
|
@ -120,7 +121,7 @@ export async function action({ request, params }: Route.ActionArgs) {
|
||||||
|
|
||||||
return redirect("/admin/sports");
|
return redirect("/admin/sports");
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error updating sport:", error);
|
logger.error("Error updating sport:", error);
|
||||||
const message = error instanceof Error ? error.message : String(error);
|
const message = error instanceof Error ? error.message : String(error);
|
||||||
const isUniqueViolation = message.includes("unique") || message.includes("duplicate");
|
const isUniqueViolation = message.includes("unique") || message.includes("duplicate");
|
||||||
return {
|
return {
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
import { Form, Link, redirect } from "react-router";
|
import { Form, Link, redirect } from "react-router";
|
||||||
import type { Route } from "./+types/admin.sports.new";
|
import type { Route } from "./+types/admin.sports.new";
|
||||||
|
|
||||||
|
import { logger } from "~/lib/logger";
|
||||||
import { createSport } from "~/models/sport";
|
import { createSport } from "~/models/sport";
|
||||||
import { writeFile, mkdir } from "node:fs/promises";
|
import { writeFile, mkdir } from "node:fs/promises";
|
||||||
import { join } from "node:path";
|
import { join } from "node:path";
|
||||||
|
|
@ -79,7 +80,7 @@ export async function action({ request }: Route.ActionArgs) {
|
||||||
const buffer = Buffer.from(bytes);
|
const buffer = Buffer.from(bytes);
|
||||||
await writeFile(filepath, buffer);
|
await writeFile(filepath, buffer);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error saving icon file:", error);
|
logger.error("Error saving icon file:", error);
|
||||||
return { error: "Failed to save icon file" };
|
return { error: "Failed to save icon file" };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -95,7 +96,7 @@ export async function action({ request }: Route.ActionArgs) {
|
||||||
|
|
||||||
return redirect("/admin/sports");
|
return redirect("/admin/sports");
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error creating sport:", error);
|
logger.error("Error creating sport:", error);
|
||||||
return { error: "Failed to create sport. The slug might already exist." };
|
return { error: "Failed to create sport. The slug might already exist." };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
import { Form } from "react-router";
|
import { Form } from "react-router";
|
||||||
import type { Route } from "./+types/admin.standings-snapshots";
|
import type { Route } from "./+types/admin.standings-snapshots";
|
||||||
|
|
||||||
|
import { logger } from "~/lib/logger";
|
||||||
import { database } from "~/database/context";
|
import { database } from "~/database/context";
|
||||||
import { eq, or } from "drizzle-orm";
|
import { eq, or } from "drizzle-orm";
|
||||||
import * as schema from "~/database/schema";
|
import * as schema from "~/database/schema";
|
||||||
|
|
@ -104,7 +105,7 @@ export async function action({ request }: Route.ActionArgs) {
|
||||||
message: "Invalid intent",
|
message: "Invalid intent",
|
||||||
};
|
};
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error creating snapshot:", error);
|
logger.error("Error creating snapshot:", error);
|
||||||
return {
|
return {
|
||||||
success: false,
|
success: false,
|
||||||
message: error instanceof Error ? error.message : "Unknown error",
|
message: error instanceof Error ? error.message : "Unknown error",
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ import { Form, Link, redirect } from "react-router";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import type { Route } from "./+types/admin.templates.$id";
|
import type { Route } from "./+types/admin.templates.$id";
|
||||||
|
|
||||||
|
import { logger } from "~/lib/logger";
|
||||||
import {
|
import {
|
||||||
findSeasonTemplateWithSportsSeasons,
|
findSeasonTemplateWithSportsSeasons,
|
||||||
updateSeasonTemplate,
|
updateSeasonTemplate,
|
||||||
|
|
@ -125,7 +126,7 @@ export async function action({ request, params }: Route.ActionArgs) {
|
||||||
|
|
||||||
return { success: true };
|
return { success: true };
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error updating template:", error);
|
logger.error("Error updating template:", error);
|
||||||
return { error: "Failed to update template. Please try again." };
|
return { error: "Failed to update template. Please try again." };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
import { Form, Link, redirect } from "react-router";
|
import { Form, Link, redirect } from "react-router";
|
||||||
import type { Route } from "./+types/admin.templates.new";
|
import type { Route } from "./+types/admin.templates.new";
|
||||||
|
|
||||||
|
import { logger } from "~/lib/logger";
|
||||||
import { createSeasonTemplate } from "~/models/season-template";
|
import { createSeasonTemplate } from "~/models/season-template";
|
||||||
import { Button } from "~/components/ui/button";
|
import { Button } from "~/components/ui/button";
|
||||||
import { Input } from "~/components/ui/input";
|
import { Input } from "~/components/ui/input";
|
||||||
|
|
@ -48,7 +49,7 @@ export async function action({ request }: Route.ActionArgs) {
|
||||||
|
|
||||||
return redirect(`/admin/templates/${template.id}`);
|
return redirect(`/admin/templates/${template.id}`);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error creating template:", error);
|
logger.error("Error creating template:", error);
|
||||||
return { error: "Failed to create template. Please try again." };
|
return { error: "Failed to create template. Please try again." };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ import { eq, and, asc } from "drizzle-orm";
|
||||||
import { getSocketIO } from "~/server/socket";
|
import { getSocketIO } from "~/server/socket";
|
||||||
import { isUserAdminByClerkId } from "~/models/user";
|
import { isUserAdminByClerkId } from "~/models/user";
|
||||||
import { getTeamForPick } from "~/lib/draft-order";
|
import { getTeamForPick } from "~/lib/draft-order";
|
||||||
|
import { logger } from "~/lib/logger";
|
||||||
|
|
||||||
import type { ActionFunctionArgs } from "react-router";
|
import type { ActionFunctionArgs } from "react-router";
|
||||||
export async function action(args: ActionFunctionArgs) {
|
export async function action(args: ActionFunctionArgs) {
|
||||||
|
|
@ -131,7 +132,7 @@ export async function action(args: ActionFunctionArgs) {
|
||||||
db,
|
db,
|
||||||
});
|
});
|
||||||
}).catch((err) => {
|
}).catch((err) => {
|
||||||
console.error("[AutodraftUpdate] Mid-turn autopick failed:", err);
|
logger.error("[AutodraftUpdate] Mid-turn autopick failed:", err);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ import { database } from "~/database/context";
|
||||||
import * as schema from "~/database/schema";
|
import * as schema from "~/database/schema";
|
||||||
import { isCommissioner } from "~/models/commissioner";
|
import { isCommissioner } from "~/models/commissioner";
|
||||||
import { getSocketIO } from "../../../server/socket";
|
import { getSocketIO } from "../../../server/socket";
|
||||||
|
import { logger } from "~/lib/logger";
|
||||||
|
|
||||||
import type { ActionFunctionArgs } from "react-router";
|
import type { ActionFunctionArgs } from "react-router";
|
||||||
export async function action(args: ActionFunctionArgs) {
|
export async function action(args: ActionFunctionArgs) {
|
||||||
|
|
@ -85,7 +86,7 @@ export async function action(args: ActionFunctionArgs) {
|
||||||
currentPickNumber: season.currentPickNumber ?? 1,
|
currentPickNumber: season.currentPickNumber ?? 1,
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Socket.IO error:", error);
|
logger.error("Socket.IO error:", error);
|
||||||
}
|
}
|
||||||
|
|
||||||
return Response.json({ success: true, timeRemaining: newTime });
|
return Response.json({ success: true, timeRemaining: newTime });
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@ import { getParticipantsForSeasonWithSports } from "~/models/participant";
|
||||||
import { getSeasonSportsSimple } from "~/models/season-sport";
|
import { getSeasonSportsSimple } from "~/models/season-sport";
|
||||||
import { calculatePickInfo, checkAndTriggerNextAutodraft } from "~/models/draft-utils";
|
import { calculatePickInfo, checkAndTriggerNextAutodraft } from "~/models/draft-utils";
|
||||||
import { getSocketIO } from "../../../server/socket";
|
import { getSocketIO } from "../../../server/socket";
|
||||||
|
import { logger } from "~/lib/logger";
|
||||||
import type { ActionFunctionArgs } from "react-router";
|
import type { ActionFunctionArgs } from "react-router";
|
||||||
|
|
||||||
export async function action(args: ActionFunctionArgs) {
|
export async function action(args: ActionFunctionArgs) {
|
||||||
|
|
@ -208,7 +209,7 @@ export async function action(args: ActionFunctionArgs) {
|
||||||
currentPickNumber: nextPickNumber,
|
currentPickNumber: nextPickNumber,
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Socket.IO timer-update error:", error);
|
logger.error("Socket.IO timer-update error:", error);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Next team's timer is unchanged — their bank carries forward as-is.
|
// Next team's timer is unchanged — their bank carries forward as-is.
|
||||||
|
|
@ -239,7 +240,7 @@ export async function action(args: ActionFunctionArgs) {
|
||||||
participantId,
|
participantId,
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Socket.IO participant-removed-from-queues error:", error);
|
logger.error("Socket.IO participant-removed-from-queues error:", error);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Emit socket event
|
// Emit socket event
|
||||||
|
|
@ -265,7 +266,7 @@ export async function action(args: ActionFunctionArgs) {
|
||||||
io.to(`draft-${seasonId}`).emit("draft-completed");
|
io.to(`draft-${seasonId}`).emit("draft-completed");
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Socket.IO error:", error);
|
logger.error("Socket.IO error:", error);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if next team has autodraft enabled and trigger immediately
|
// Check if next team has autodraft enabled and trigger immediately
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@ import { getParticipantsForSeasonWithSports } from "~/models/participant";
|
||||||
import { getSeasonSportsSimple } from "~/models/season-sport";
|
import { getSeasonSportsSimple } from "~/models/season-sport";
|
||||||
import { calculatePickInfo, checkAndTriggerNextAutodraft, pruneIneligibleQueueItems } from "~/models/draft-utils";
|
import { calculatePickInfo, checkAndTriggerNextAutodraft, pruneIneligibleQueueItems } from "~/models/draft-utils";
|
||||||
import { getSocketIO } from "../../../server/socket";
|
import { getSocketIO } from "../../../server/socket";
|
||||||
|
import { logger } from "~/lib/logger";
|
||||||
|
|
||||||
import type { ActionFunctionArgs } from "react-router";
|
import type { ActionFunctionArgs } from "react-router";
|
||||||
export async function action(args: ActionFunctionArgs) {
|
export async function action(args: ActionFunctionArgs) {
|
||||||
|
|
@ -174,7 +175,7 @@ export async function action(args: ActionFunctionArgs) {
|
||||||
participantId,
|
participantId,
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Socket.IO participant-removed-from-queues error:", error);
|
logger.error("Socket.IO participant-removed-from-queues error:", error);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Proactively prune queue items that are now ineligible due to this pick
|
// Proactively prune queue items that are now ineligible due to this pick
|
||||||
|
|
@ -194,7 +195,7 @@ export async function action(args: ActionFunctionArgs) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Queue pruning error after pick:", error);
|
logger.error("Queue pruning error after pick:", error);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Calculate next pick info (before updating season)
|
// Calculate next pick info (before updating season)
|
||||||
|
|
@ -262,7 +263,7 @@ export async function action(args: ActionFunctionArgs) {
|
||||||
currentPickNumber: nextPickNumber,
|
currentPickNumber: nextPickNumber,
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Socket.IO timer-update error:", error);
|
logger.error("Socket.IO timer-update error:", error);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Next team's timer is unchanged — their bank carries forward as-is
|
// Next team's timer is unchanged — their bank carries forward as-is
|
||||||
|
|
@ -297,7 +298,7 @@ export async function action(args: ActionFunctionArgs) {
|
||||||
getSocketIO().to(`draft-${seasonId}`).emit("draft-completed");
|
getSocketIO().to(`draft-${seasonId}`).emit("draft-completed");
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Socket.IO error:", error);
|
logger.error("Socket.IO error:", error);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if next team has autodraft enabled and trigger immediately
|
// Check if next team has autodraft enabled and trigger immediately
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ import { database } from "~/database/context";
|
||||||
import * as schema from "~/database/schema";
|
import * as schema from "~/database/schema";
|
||||||
import { isCommissioner } from "~/models/commissioner";
|
import { isCommissioner } from "~/models/commissioner";
|
||||||
import { getSocketIO } from "../../../server/socket";
|
import { getSocketIO } from "../../../server/socket";
|
||||||
|
import { logger } from "~/lib/logger";
|
||||||
|
|
||||||
import type { ActionFunctionArgs } from "react-router";
|
import type { ActionFunctionArgs } from "react-router";
|
||||||
export async function action(args: ActionFunctionArgs) {
|
export async function action(args: ActionFunctionArgs) {
|
||||||
|
|
@ -61,7 +62,7 @@ export async function action(args: ActionFunctionArgs) {
|
||||||
paused: true,
|
paused: true,
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Socket.IO error:", error);
|
logger.error("Socket.IO error:", error);
|
||||||
}
|
}
|
||||||
|
|
||||||
return Response.json({ success: true, paused: true });
|
return Response.json({ success: true, paused: true });
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ import { getDraftPicksWithSports, getTeamDraftPicksWithSports } from "~/models/d
|
||||||
import { getParticipantsForSeasonWithSports } from "~/models/participant";
|
import { getParticipantsForSeasonWithSports } from "~/models/participant";
|
||||||
import { getSeasonSportsSimple } from "~/models/season-sport";
|
import { getSeasonSportsSimple } from "~/models/season-sport";
|
||||||
import { getSocketIO } from "../../../server/socket";
|
import { getSocketIO } from "../../../server/socket";
|
||||||
|
import { logger } from "~/lib/logger";
|
||||||
|
|
||||||
import type { ActionFunctionArgs } from "react-router";
|
import type { ActionFunctionArgs } from "react-router";
|
||||||
export async function action(args: ActionFunctionArgs) {
|
export async function action(args: ActionFunctionArgs) {
|
||||||
|
|
@ -179,7 +180,7 @@ export async function action(args: ActionFunctionArgs) {
|
||||||
participantId,
|
participantId,
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Socket.IO error:", error);
|
logger.error("Socket.IO error:", error);
|
||||||
}
|
}
|
||||||
|
|
||||||
return Response.json({ success: true });
|
return Response.json({ success: true });
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ import { database } from "~/database/context";
|
||||||
import * as schema from "~/database/schema";
|
import * as schema from "~/database/schema";
|
||||||
import { isCommissioner } from "~/models/commissioner";
|
import { isCommissioner } from "~/models/commissioner";
|
||||||
import { getSocketIO } from "../../../server/socket";
|
import { getSocketIO } from "../../../server/socket";
|
||||||
|
import { logger } from "~/lib/logger";
|
||||||
|
|
||||||
import type { ActionFunctionArgs } from "react-router";
|
import type { ActionFunctionArgs } from "react-router";
|
||||||
export async function action(args: ActionFunctionArgs) {
|
export async function action(args: ActionFunctionArgs) {
|
||||||
|
|
@ -61,7 +62,7 @@ export async function action(args: ActionFunctionArgs) {
|
||||||
paused: false,
|
paused: false,
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Socket.IO error:", error);
|
logger.error("Socket.IO error:", error);
|
||||||
}
|
}
|
||||||
|
|
||||||
return Response.json({ success: true, paused: false });
|
return Response.json({ success: true, paused: false });
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ import * as schema from "~/database/schema";
|
||||||
import { eq, and, gte } from "drizzle-orm";
|
import { eq, and, gte } from "drizzle-orm";
|
||||||
import { isCommissioner } from "~/models/commissioner";
|
import { isCommissioner } from "~/models/commissioner";
|
||||||
import { getSocketIO } from "../../../server/socket";
|
import { getSocketIO } from "../../../server/socket";
|
||||||
|
import { logger } from "~/lib/logger";
|
||||||
|
|
||||||
import type { ActionFunctionArgs } from "react-router";
|
import type { ActionFunctionArgs } from "react-router";
|
||||||
export async function action(args: ActionFunctionArgs) {
|
export async function action(args: ActionFunctionArgs) {
|
||||||
|
|
@ -92,7 +93,7 @@ export async function action(args: ActionFunctionArgs) {
|
||||||
teamId: rollbackSlot?.teamId,
|
teamId: rollbackSlot?.teamId,
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Socket.IO error:", error);
|
logger.error("Socket.IO error:", error);
|
||||||
}
|
}
|
||||||
|
|
||||||
return Response.json({ success: true, pickNumber });
|
return Response.json({ success: true, pickNumber });
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ import { eq } from "drizzle-orm";
|
||||||
import { deleteSeasonTimers, initializeDraftTimers } from "~/models/draft-timer";
|
import { deleteSeasonTimers, initializeDraftTimers } from "~/models/draft-timer";
|
||||||
import { isCommissioner } from "~/models/commissioner";
|
import { isCommissioner } from "~/models/commissioner";
|
||||||
import { getSocketIO } from "../../../server/socket";
|
import { getSocketIO } from "../../../server/socket";
|
||||||
|
import { logger } from "~/lib/logger";
|
||||||
|
|
||||||
import type { ActionFunctionArgs } from "react-router";
|
import type { ActionFunctionArgs } from "react-router";
|
||||||
export async function action(args: ActionFunctionArgs) {
|
export async function action(args: ActionFunctionArgs) {
|
||||||
|
|
@ -83,7 +84,7 @@ export async function action(args: ActionFunctionArgs) {
|
||||||
currentPickNumber: 1,
|
currentPickNumber: 1,
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Socket.IO error:", error);
|
logger.error("Socket.IO error:", error);
|
||||||
}
|
}
|
||||||
|
|
||||||
return Response.json({ success: true });
|
return Response.json({ success: true });
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
import { Webhook } from "svix";
|
import { Webhook } from "svix";
|
||||||
import type { Route } from "./+types/clerk";
|
import type { Route } from "./+types/clerk";
|
||||||
import { findOrCreateUser, getUserDisplayName } from "~/models/user";
|
import { findOrCreateUser, getUserDisplayName } from "~/models/user";
|
||||||
|
import { logger } from "~/lib/logger";
|
||||||
|
|
||||||
export async function action({ request }: Route.ActionArgs) {
|
export async function action({ request }: Route.ActionArgs) {
|
||||||
// Get the webhook secret from environment
|
// Get the webhook secret from environment
|
||||||
|
|
@ -36,13 +37,13 @@ export async function action({ request }: Route.ActionArgs) {
|
||||||
"svix-signature": svix_signature,
|
"svix-signature": svix_signature,
|
||||||
}) as typeof evt;
|
}) as typeof evt;
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error("Error verifying webhook:", err);
|
logger.error("Error verifying webhook:", err);
|
||||||
return new Response("Error: Verification failed", { status: 400 });
|
return new Response("Error: Verification failed", { status: 400 });
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle the webhook
|
// Handle the webhook
|
||||||
const eventType = evt.type;
|
const eventType = evt.type;
|
||||||
console.log(`Webhook received: ${eventType}`);
|
logger.log(`Webhook received: ${eventType}`);
|
||||||
|
|
||||||
if (eventType === "user.created" || eventType === "user.updated") {
|
if (eventType === "user.created" || eventType === "user.updated") {
|
||||||
const { id, email_addresses, username, first_name, last_name, image_url } = evt.data;
|
const { id, email_addresses, username, first_name, last_name, image_url } = evt.data;
|
||||||
|
|
@ -61,7 +62,7 @@ export async function action({ request }: Route.ActionArgs) {
|
||||||
lastName: last_name,
|
lastName: last_name,
|
||||||
imageUrl: image_url,
|
imageUrl: image_url,
|
||||||
});
|
});
|
||||||
console.log(
|
logger.log(
|
||||||
`User created in database: ${user.id} (${getUserDisplayName(user)})`
|
`User created in database: ${user.id} (${getUserDisplayName(user)})`
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -77,12 +78,12 @@ export async function action({ request }: Route.ActionArgs) {
|
||||||
lastName: last_name,
|
lastName: last_name,
|
||||||
imageUrl: image_url,
|
imageUrl: image_url,
|
||||||
});
|
});
|
||||||
console.log(
|
logger.log(
|
||||||
`User updated in database: ${user.id} (${getUserDisplayName(user)})`
|
`User updated in database: ${user.id} (${getUserDisplayName(user)})`
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(`Error handling ${eventType}:`, error);
|
logger.error(`Error handling ${eventType}:`, error);
|
||||||
return new Response(
|
return new Response(
|
||||||
`Error: Failed to ${eventType === "user.created" ? "create" : "update"} user`,
|
`Error: Failed to ${eventType === "user.created" ? "create" : "update"} user`,
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ import { eq, and, asc, desc, inArray } from "drizzle-orm";
|
||||||
import { database } from "~/database/context";
|
import { database } from "~/database/context";
|
||||||
import * as schema from "~/database/schema";
|
import * as schema from "~/database/schema";
|
||||||
import { useDraftSocket } from "~/hooks/useDraftSocket";
|
import { useDraftSocket } from "~/hooks/useDraftSocket";
|
||||||
|
import { logger } from "~/lib/logger";
|
||||||
import { useCallback, useEffect, useState, useMemo, useRef } from "react";
|
import { useCallback, useEffect, useState, useMemo, useRef } from "react";
|
||||||
import { Button } from "~/components/ui/button";
|
import { Button } from "~/components/ui/button";
|
||||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from "~/components/ui/tabs";
|
import { Tabs, TabsContent, TabsList, TabsTrigger } from "~/components/ui/tabs";
|
||||||
|
|
@ -185,7 +186,7 @@ export async function loader(args: Route.LoaderArgs) {
|
||||||
// Load user's team queue if they have a team
|
// Load user's team queue if they have a team
|
||||||
const userQueue = userTeam
|
const userQueue = userTeam
|
||||||
? await getTeamQueue(userTeam.id).catch((err) => {
|
? await getTeamQueue(userTeam.id).catch((err) => {
|
||||||
console.error("[DraftLoader] Failed to load queue, defaulting to empty:", err);
|
logger.error("[DraftLoader] Failed to load queue, defaulting to empty:", err);
|
||||||
return [];
|
return [];
|
||||||
})
|
})
|
||||||
: [];
|
: [];
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ import { Form, Link, redirect, useNavigation } from "react-router";
|
||||||
import { getAuth } from "@clerk/react-router/server";
|
import { getAuth } from "@clerk/react-router/server";
|
||||||
import { format } from "date-fns";
|
import { format } from "date-fns";
|
||||||
import { CalendarIcon } from "lucide-react";
|
import { CalendarIcon } from "lucide-react";
|
||||||
|
import { logger } from "~/lib/logger";
|
||||||
import { findLeagueById, updateLeague, deleteLeague } from "~/models/league";
|
import { findLeagueById, updateLeague, deleteLeague } from "~/models/league";
|
||||||
import { sendStandingsUpdateNotification } from "~/services/discord";
|
import { sendStandingsUpdateNotification } from "~/services/discord";
|
||||||
import {
|
import {
|
||||||
|
|
@ -222,7 +223,7 @@ export async function action(args: Route.ActionArgs) {
|
||||||
discordWebhookUrl: webhookUrl || null,
|
discordWebhookUrl: webhookUrl || null,
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error updating league:", error);
|
logger.error("Error updating league:", error);
|
||||||
return { error: "Failed to update league. Please try again." };
|
return { error: "Failed to update league. Please try again." };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -260,7 +261,7 @@ export async function action(args: Route.ActionArgs) {
|
||||||
});
|
});
|
||||||
return { testSuccess: true };
|
return { testSuccess: true };
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error("Discord test webhook failed:", err);
|
logger.error("Discord test webhook failed:", err);
|
||||||
return { error: "Failed to send test notification. Check your webhook URL." };
|
return { error: "Failed to send test notification. Check your webhook URL." };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -358,7 +359,7 @@ export async function action(args: Route.ActionArgs) {
|
||||||
await removeTeamOwner(teamId);
|
await removeTeamOwner(teamId);
|
||||||
return { success: true, message: "Owner removed successfully" };
|
return { success: true, message: "Owner removed successfully" };
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error removing team owner:", error);
|
logger.error("Error removing team owner:", error);
|
||||||
return { error: "Failed to remove owner. Please try again." };
|
return { error: "Failed to remove owner. Please try again." };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -403,7 +404,7 @@ export async function action(args: Route.ActionArgs) {
|
||||||
|
|
||||||
return { success: true, message: "Owner assigned successfully" };
|
return { success: true, message: "Owner assigned successfully" };
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error assigning team owner:", error);
|
logger.error("Error assigning team owner:", error);
|
||||||
return { error: "Failed to assign owner. Please try again." };
|
return { error: "Failed to assign owner. Please try again." };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -435,7 +436,7 @@ export async function action(args: Route.ActionArgs) {
|
||||||
|
|
||||||
return { success: true, message: "Draft has been reset successfully. Draft order preserved." };
|
return { success: true, message: "Draft has been reset successfully. Draft order preserved." };
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error resetting draft:", error);
|
logger.error("Error resetting draft:", error);
|
||||||
return { error: "Failed to reset draft. Please try again." };
|
return { error: "Failed to reset draft. Please try again." };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -588,7 +589,7 @@ export async function action(args: Route.ActionArgs) {
|
||||||
|
|
||||||
return redirect(`/leagues/${leagueId}?updated=true`);
|
return redirect(`/leagues/${leagueId}?updated=true`);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error updating season settings:", error);
|
logger.error("Error updating season settings:", error);
|
||||||
return { error: "Failed to update season settings. Please try again." };
|
return { error: "Failed to update season settings. Please try again." };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -611,7 +612,7 @@ export async function action(args: Route.ActionArgs) {
|
||||||
await createCommissioner({ leagueId, userId: userClerkId });
|
await createCommissioner({ leagueId, userId: userClerkId });
|
||||||
return { success: true, message: "Commissioner added successfully" };
|
return { success: true, message: "Commissioner added successfully" };
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error adding commissioner:", error);
|
logger.error("Error adding commissioner:", error);
|
||||||
return { error: "Failed to add commissioner. Please try again." };
|
return { error: "Failed to add commissioner. Please try again." };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -638,7 +639,7 @@ export async function action(args: Route.ActionArgs) {
|
||||||
await removeCommissionerByLeagueAndUser(leagueId, commissionerUserId);
|
await removeCommissionerByLeagueAndUser(leagueId, commissionerUserId);
|
||||||
return { success: true, message: "Commissioner removed successfully" };
|
return { success: true, message: "Commissioner removed successfully" };
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error removing commissioner:", error);
|
logger.error("Error removing commissioner:", error);
|
||||||
return { error: "Failed to remove commissioner. Please try again." };
|
return { error: "Failed to remove commissioner. Please try again." };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ import * as schema from "~/database/schema";
|
||||||
import { Button } from "~/components/ui/button";
|
import { Button } from "~/components/ui/button";
|
||||||
import { Card, CardContent, CardHeader, CardTitle } from "~/components/ui/card";
|
import { Card, CardContent, CardHeader, CardTitle } from "~/components/ui/card";
|
||||||
import { StandingsTable } from "~/components/standings/StandingsTable";
|
import { StandingsTable } from "~/components/standings/StandingsTable";
|
||||||
|
import { logger } from "~/lib/logger";
|
||||||
import { getSevenDayStandingsChange, getSeasonPointProgression } from "~/models/standings";
|
import { getSevenDayStandingsChange, getSeasonPointProgression } from "~/models/standings";
|
||||||
import { isSeasonComplete, getSeasonCompletionPercentage } from "~/lib/season-helpers.server";
|
import { isSeasonComplete, getSeasonCompletionPercentage } from "~/lib/season-helpers.server";
|
||||||
import { PointProgressionChart } from "~/components/standings/PointProgressionChart";
|
import { PointProgressionChart } from "~/components/standings/PointProgressionChart";
|
||||||
|
|
@ -113,8 +114,8 @@ export async function loader(args: Route.LoaderArgs) {
|
||||||
completionPercentage,
|
completionPercentage,
|
||||||
};
|
};
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error loading standings:", error);
|
logger.error("Error loading standings:", error);
|
||||||
console.error("Error details:", {
|
logger.error("Error details:", {
|
||||||
message: error instanceof Error ? error.message : String(error),
|
message: error instanceof Error ? error.message : String(error),
|
||||||
stack: error instanceof Error ? error.stack : undefined,
|
stack: error instanceof Error ? error.stack : undefined,
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ import { Form, redirect, Link } from "react-router";
|
||||||
import { getAuth } from "@clerk/react-router/server";
|
import { getAuth } from "@clerk/react-router/server";
|
||||||
import type { Route } from "./+types/new";
|
import type { Route } from "./+types/new";
|
||||||
|
|
||||||
|
import { logger } from "~/lib/logger";
|
||||||
import { createLeague, setCurrentSeason } from "~/models/league";
|
import { createLeague, setCurrentSeason } from "~/models/league";
|
||||||
import { createCommissioner } from "~/models/commissioner";
|
import { createCommissioner } from "~/models/commissioner";
|
||||||
import { createSeason } from "~/models/season";
|
import { createSeason } from "~/models/season";
|
||||||
|
|
@ -193,7 +194,7 @@ export async function action(args: Route.ActionArgs) {
|
||||||
// Redirect to league homepage
|
// Redirect to league homepage
|
||||||
return redirect(`/leagues/${league.id}`);
|
return redirect(`/leagues/${league.id}`);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error creating league:", error);
|
logger.error("Error creating league:", error);
|
||||||
return { error: "Failed to create league. Please try again." };
|
return { error: "Failed to create league. Please try again." };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
import { useState, useEffect } from "react";
|
import { useState, useEffect } from "react";
|
||||||
import { useDraftSocket } from "~/hooks/useDraftSocket";
|
import { useDraftSocket } from "~/hooks/useDraftSocket";
|
||||||
|
import { logger } from "~/lib/logger";
|
||||||
|
|
||||||
export function meta() {
|
export function meta() {
|
||||||
return [{ title: "Socket Test - Brackt" }];
|
return [{ title: "Socket Test - Brackt" }];
|
||||||
|
|
@ -12,7 +13,7 @@ export default function TestSocket() {
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// Listen for test messages
|
// Listen for test messages
|
||||||
const handleTestMessage = (data: unknown) => {
|
const handleTestMessage = (data: unknown) => {
|
||||||
console.log("Received test message:", data);
|
logger.log("Received test message:", data);
|
||||||
setMessages((prev) => [...prev, JSON.stringify(data)]);
|
setMessages((prev) => [...prev, JSON.stringify(data)]);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -209,7 +209,7 @@ function countsToDistributions(
|
||||||
export async function simulateBracket(
|
export async function simulateBracket(
|
||||||
teams: TeamForSimulation[],
|
teams: TeamForSimulation[],
|
||||||
format: BracketFormat = 'nhl-8',
|
format: BracketFormat = 'nhl-8',
|
||||||
simulations: number = 100000,
|
simulations = 100000,
|
||||||
onProgress?: (current: number, total: number) => void
|
onProgress?: (current: number, total: number) => void
|
||||||
): Promise<SimulationResults> {
|
): Promise<SimulationResults> {
|
||||||
// Validate input
|
// Validate input
|
||||||
|
|
@ -280,7 +280,7 @@ export async function simulateBracket(
|
||||||
export function simulateBracketSync(
|
export function simulateBracketSync(
|
||||||
teams: TeamForSimulation[],
|
teams: TeamForSimulation[],
|
||||||
format: BracketFormat = 'nhl-8',
|
format: BracketFormat = 'nhl-8',
|
||||||
simulations: number = 100000
|
simulations = 100000
|
||||||
): SimulationResults {
|
): SimulationResults {
|
||||||
// Validate input
|
// Validate input
|
||||||
if (format === 'nhl-8' && teams.length !== 8) {
|
if (format === 'nhl-8' && teams.length !== 8) {
|
||||||
|
|
|
||||||
|
|
@ -79,7 +79,7 @@ export function calculateEV(
|
||||||
*/
|
*/
|
||||||
export function validateProbabilities(
|
export function validateProbabilities(
|
||||||
probabilities: ProbabilityDistribution,
|
probabilities: ProbabilityDistribution,
|
||||||
tolerance: number = 0.01
|
tolerance = 0.01
|
||||||
): boolean {
|
): boolean {
|
||||||
const sum =
|
const sum =
|
||||||
probabilities.probFirst +
|
probabilities.probFirst +
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
import { logger } from "~/lib/logger";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* ICM (Independent Chip Model) Calculator - Monte Carlo Simulation
|
* ICM (Independent Chip Model) Calculator - Monte Carlo Simulation
|
||||||
*
|
*
|
||||||
|
|
@ -104,8 +106,8 @@ function simulateTournament(
|
||||||
*/
|
*/
|
||||||
export function calculateICM(
|
export function calculateICM(
|
||||||
participants: ParticipantChips[],
|
participants: ParticipantChips[],
|
||||||
scoringPlaces: number = 8,
|
scoringPlaces = 8,
|
||||||
iterations: number = 100000
|
iterations = 100000
|
||||||
): Map<string, ICMResult> {
|
): Map<string, ICMResult> {
|
||||||
if (participants.length === 0) {
|
if (participants.length === 0) {
|
||||||
return new Map();
|
return new Map();
|
||||||
|
|
@ -118,7 +120,7 @@ export function calculateICM(
|
||||||
championshipProbability: totalProb > 0 ? p.championshipProbability / totalProb : 1 / participants.length,
|
championshipProbability: totalProb > 0 ? p.championshipProbability / totalProb : 1 / participants.length,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
console.log(`[ICM Simulation] Starting ${iterations.toLocaleString()} simulations for ${normalized.length} participants`);
|
logger.log(`[ICM Simulation] Starting ${iterations.toLocaleString()} simulations for ${normalized.length} participants`);
|
||||||
const startTime = Date.now();
|
const startTime = Date.now();
|
||||||
|
|
||||||
// Initialize counters for each participant
|
// Initialize counters for each participant
|
||||||
|
|
@ -142,7 +144,7 @@ export function calculateICM(
|
||||||
const progress = ((iter + 1) / iterations * 100).toFixed(0);
|
const progress = ((iter + 1) / iterations * 100).toFixed(0);
|
||||||
const elapsed = Date.now() - startTime;
|
const elapsed = Date.now() - startTime;
|
||||||
const rate = (iter + 1) / (elapsed / 1000);
|
const rate = (iter + 1) / (elapsed / 1000);
|
||||||
console.log(`[ICM Simulation] ${progress}% complete (${(iter + 1).toLocaleString()}/${iterations.toLocaleString()}) - ${rate.toFixed(0)} sims/sec`);
|
logger.log(`[ICM Simulation] ${progress}% complete (${(iter + 1).toLocaleString()}/${iterations.toLocaleString()}) - ${rate.toFixed(0)} sims/sec`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -169,7 +171,7 @@ export function calculateICM(
|
||||||
}
|
}
|
||||||
|
|
||||||
const elapsed = Date.now() - startTime;
|
const elapsed = Date.now() - startTime;
|
||||||
console.log(`[ICM Simulation] Completed in ${(elapsed / 1000).toFixed(1)}s (${(iterations / (elapsed / 1000)).toFixed(0)} sims/sec)`);
|
logger.log(`[ICM Simulation] Completed in ${(elapsed / 1000).toFixed(1)}s (${(iterations / (elapsed / 1000)).toFixed(0)} sims/sec)`);
|
||||||
|
|
||||||
return results;
|
return results;
|
||||||
}
|
}
|
||||||
|
|
@ -228,7 +230,7 @@ function convertAmericanOddsToProbability(odds: number): number {
|
||||||
*/
|
*/
|
||||||
export function calculateICMFromOdds(
|
export function calculateICMFromOdds(
|
||||||
futuresOdds: Array<{ participantId: string; odds: number }>,
|
futuresOdds: Array<{ participantId: string; odds: number }>,
|
||||||
scoringPlaces: number = 8
|
scoringPlaces = 8
|
||||||
): Map<string, ICMResult> {
|
): Map<string, ICMResult> {
|
||||||
// Convert odds to championship probabilities
|
// Convert odds to championship probabilities
|
||||||
const participants: ParticipantChips[] = futuresOdds.map(({ participantId, odds }) => ({
|
const participants: ParticipantChips[] = futuresOdds.map(({ participantId, odds }) => ({
|
||||||
|
|
|
||||||
|
|
@ -111,7 +111,7 @@ function createFinishedProbabilities(finalPosition: number): number[] {
|
||||||
*/
|
*/
|
||||||
export async function updateProbabilitiesAfterResult(
|
export async function updateProbabilitiesAfterResult(
|
||||||
sportsSeasonId: string,
|
sportsSeasonId: string,
|
||||||
recalculateUnfinished: boolean = true
|
recalculateUnfinished = true
|
||||||
): Promise<ProbabilityUpdateResult> {
|
): Promise<ProbabilityUpdateResult> {
|
||||||
const errors: string[] = [];
|
const errors: string[] = [];
|
||||||
let updated = 0;
|
let updated = 0;
|
||||||
|
|
|
||||||
|
|
@ -47,6 +47,7 @@ import * as schema from "~/database/schema";
|
||||||
import type { BracketRegion } from "~/lib/bracket-templates";
|
import type { BracketRegion } from "~/lib/bracket-templates";
|
||||||
import { buildNCAA68SlotMap, matchIndexForSeedSlot, NCAA_68 } from "~/lib/bracket-templates";
|
import { buildNCAA68SlotMap, matchIndexForSeedSlot, NCAA_68 } from "~/lib/bracket-templates";
|
||||||
import type { Simulator, SimulationResult } from "./types";
|
import type { Simulator, SimulationResult } from "./types";
|
||||||
|
import { logger } from "~/lib/logger";
|
||||||
|
|
||||||
// ─── Simulation parameters ────────────────────────────────────────────────────
|
// ─── Simulation parameters ────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
|
@ -500,7 +501,7 @@ export class NCAAMSimulator implements Simulator {
|
||||||
if (participantRows.length < participantIds.length) {
|
if (participantRows.length < participantIds.length) {
|
||||||
const foundIds = new Set(participantRows.map((r) => r.id));
|
const foundIds = new Set(participantRows.map((r) => r.id));
|
||||||
const missing = participantIds.filter((id) => !foundIds.has(id));
|
const missing = participantIds.filter((id) => !foundIds.has(id));
|
||||||
console.warn(
|
logger.warn(
|
||||||
`[NCAAMSimulator] ${missing.length} participant ID(s) not found in DB: ${missing.join(", ")}. ` +
|
`[NCAAMSimulator] ${missing.length} participant ID(s) not found in DB: ${missing.join(", ")}. ` +
|
||||||
`They will be treated as average strength (KenPom 0.0).`
|
`They will be treated as average strength (KenPom 0.0).`
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -45,6 +45,7 @@ import * as schema from "~/database/schema";
|
||||||
import type { BracketRegion } from "~/lib/bracket-templates";
|
import type { BracketRegion } from "~/lib/bracket-templates";
|
||||||
import { buildNCAA68SlotMap, matchIndexForSeedSlot, NCAA_68 } from "~/lib/bracket-templates";
|
import { buildNCAA68SlotMap, matchIndexForSeedSlot, NCAA_68 } from "~/lib/bracket-templates";
|
||||||
import type { Simulator, SimulationResult } from "./types";
|
import type { Simulator, SimulationResult } from "./types";
|
||||||
|
import { logger } from "~/lib/logger";
|
||||||
|
|
||||||
// ─── Simulation parameters ────────────────────────────────────────────────────
|
// ─── Simulation parameters ────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
|
@ -463,7 +464,7 @@ export class NCAAWSimulator implements Simulator {
|
||||||
if (participantRows.length < participantIds.length) {
|
if (participantRows.length < participantIds.length) {
|
||||||
const foundIds = new Set(participantRows.map((r) => r.id));
|
const foundIds = new Set(participantRows.map((r) => r.id));
|
||||||
const missing = participantIds.filter((id) => !foundIds.has(id));
|
const missing = participantIds.filter((id) => !foundIds.has(id));
|
||||||
console.warn(
|
logger.warn(
|
||||||
`[NCAAWSimulator] ${missing.length} participant ID(s) not found in DB: ${missing.join(", ")}. ` +
|
`[NCAAWSimulator] ${missing.length} participant ID(s) not found in DB: ${missing.join(", ")}. ` +
|
||||||
`They will be treated as average strength (Barthag ${BARTHAG_FALLBACK}).`
|
`They will be treated as average strength (Barthag ${BARTHAG_FALLBACK}).`
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -54,6 +54,7 @@ import { database } from "~/database/context";
|
||||||
import { eq } from "drizzle-orm";
|
import { eq } from "drizzle-orm";
|
||||||
import * as schema from "~/database/schema";
|
import * as schema from "~/database/schema";
|
||||||
import type { Simulator, SimulationResult } from "./types";
|
import type { Simulator, SimulationResult } from "./types";
|
||||||
|
import { logger } from "~/lib/logger";
|
||||||
|
|
||||||
// ─── Simulation parameters ────────────────────────────────────────────────────
|
// ─── Simulation parameters ────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
|
@ -353,7 +354,7 @@ export class NHLSimulator implements Simulator {
|
||||||
// These are excluded entirely rather than silently landing in the wildcard pool.
|
// These are excluded entirely rather than silently landing in the wildcard pool.
|
||||||
const unrecognized = teams.filter((t) => !t.data);
|
const unrecognized = teams.filter((t) => !t.data);
|
||||||
if (unrecognized.length > 0) {
|
if (unrecognized.length > 0) {
|
||||||
console.warn(
|
logger.warn(
|
||||||
`[NHLSimulator] ${unrecognized.length} participant(s) not found in TEAMS_DATA and will be excluded: ` +
|
`[NHLSimulator] ${unrecognized.length} participant(s) not found in TEAMS_DATA and will be excluded: ` +
|
||||||
unrecognized.map((t) => t.name).join(", ")
|
unrecognized.map((t) => t.name).join(", ")
|
||||||
);
|
);
|
||||||
|
|
|
||||||
11
server.ts
11
server.ts
|
|
@ -9,6 +9,7 @@ import { fileURLToPath } from "url";
|
||||||
import { drizzle } from "drizzle-orm/postgres-js";
|
import { drizzle } from "drizzle-orm/postgres-js";
|
||||||
import { migrate } from "drizzle-orm/postgres-js/migrator";
|
import { migrate } from "drizzle-orm/postgres-js/migrator";
|
||||||
import postgres from "postgres";
|
import postgres from "postgres";
|
||||||
|
import { logger } from "./server/logger";
|
||||||
|
|
||||||
// ESM module resolution helpers
|
// ESM module resolution helpers
|
||||||
const __filename = fileURLToPath(import.meta.url);
|
const __filename = fileURLToPath(import.meta.url);
|
||||||
|
|
@ -31,7 +32,7 @@ async function runMigrations(): Promise<void> {
|
||||||
try {
|
try {
|
||||||
const db = drizzle(client);
|
const db = drizzle(client);
|
||||||
await migrate(db, { migrationsFolder: path.join(PROJECT_ROOT, "drizzle") });
|
await migrate(db, { migrationsFolder: path.join(PROJECT_ROOT, "drizzle") });
|
||||||
console.log("Migrations complete");
|
logger.log("Migrations complete");
|
||||||
} finally {
|
} finally {
|
||||||
await client.end();
|
await client.end();
|
||||||
}
|
}
|
||||||
|
|
@ -44,7 +45,7 @@ async function createAppServer(): Promise<void> {
|
||||||
app.disable("x-powered-by");
|
app.disable("x-powered-by");
|
||||||
|
|
||||||
if (DEVELOPMENT) {
|
if (DEVELOPMENT) {
|
||||||
console.log("Starting development server");
|
logger.log("Starting development server");
|
||||||
|
|
||||||
// Dynamic import Vite only in development
|
// Dynamic import Vite only in development
|
||||||
const { createServer: createViteServer } = await import("vite");
|
const { createServer: createViteServer } = await import("vite");
|
||||||
|
|
@ -67,7 +68,7 @@ async function createAppServer(): Promise<void> {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
console.log("Starting production server");
|
logger.log("Starting production server");
|
||||||
|
|
||||||
app.use(
|
app.use(
|
||||||
"/assets",
|
"/assets",
|
||||||
|
|
@ -94,7 +95,7 @@ async function createAppServer(): Promise<void> {
|
||||||
|
|
||||||
// Start server
|
// Start server
|
||||||
httpServer.listen(PORT, () => {
|
httpServer.listen(PORT, () => {
|
||||||
console.log(`Server is running on http://localhost:${PORT}`);
|
logger.log(`Server is running on http://localhost:${PORT}`);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -102,6 +103,6 @@ async function createAppServer(): Promise<void> {
|
||||||
(DEVELOPMENT ? Promise.resolve() : runMigrations())
|
(DEVELOPMENT ? Promise.resolve() : runMigrations())
|
||||||
.then(createAppServer)
|
.then(createAppServer)
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
console.error("Failed to start server:", error);
|
logger.error("Failed to start server:", error);
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
});
|
});
|
||||||
|
|
|
||||||
26
server/logger.ts
Normal file
26
server/logger.ts
Normal file
|
|
@ -0,0 +1,26 @@
|
||||||
|
/**
|
||||||
|
* Server-side logger. In development, delegates to console. In production,
|
||||||
|
* silences debug noise but keeps warnings and errors visible in server
|
||||||
|
* stdout/stderr. Sentry is not initialized in this process — if you need
|
||||||
|
* server-side Sentry coverage, initialize @sentry/node in server.ts.
|
||||||
|
*/
|
||||||
|
|
||||||
|
const isDev = process.env.NODE_ENV !== "production";
|
||||||
|
|
||||||
|
function log(...args: unknown[]): void {
|
||||||
|
if (isDev) console.log(...args);
|
||||||
|
}
|
||||||
|
|
||||||
|
function info(...args: unknown[]): void {
|
||||||
|
if (isDev) console.info(...args);
|
||||||
|
}
|
||||||
|
|
||||||
|
function warn(...args: unknown[]): void {
|
||||||
|
console.warn(...args);
|
||||||
|
}
|
||||||
|
|
||||||
|
function error(...args: unknown[]): void {
|
||||||
|
console.error(...args);
|
||||||
|
}
|
||||||
|
|
||||||
|
export const logger = { log, info, warn, error };
|
||||||
|
|
@ -3,6 +3,7 @@ import postgres from "postgres";
|
||||||
import * as schema from "~/database/schema";
|
import * as schema from "~/database/schema";
|
||||||
import { eq, or } from "drizzle-orm";
|
import { eq, or } from "drizzle-orm";
|
||||||
import { createDailySnapshot } from "~/models/standings";
|
import { createDailySnapshot } from "~/models/standings";
|
||||||
|
import { logger } from "./logger";
|
||||||
|
|
||||||
// Create a dedicated database connection for the snapshot system
|
// Create a dedicated database connection for the snapshot system
|
||||||
const connectionString = process.env.DATABASE_URL;
|
const connectionString = process.env.DATABASE_URL;
|
||||||
|
|
@ -21,7 +22,7 @@ const CHECK_INTERVAL = 24 * 60 * 60 * 1000; // Check once per day (in millisecon
|
||||||
*/
|
*/
|
||||||
export function startSnapshotSystem(): void {
|
export function startSnapshotSystem(): void {
|
||||||
if (snapshotInterval) {
|
if (snapshotInterval) {
|
||||||
console.log("[Snapshots] Snapshot system already running");
|
logger.log("[Snapshots] Snapshot system already running");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -33,11 +34,11 @@ export function startSnapshotSystem(): void {
|
||||||
try {
|
try {
|
||||||
await createDailySnapshots();
|
await createDailySnapshots();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("[Snapshots] Error creating daily snapshots:", error);
|
logger.error("[Snapshots] Error creating daily snapshots:", error);
|
||||||
}
|
}
|
||||||
}, CHECK_INTERVAL);
|
}, CHECK_INTERVAL);
|
||||||
|
|
||||||
console.log("[Snapshots] Daily snapshot system started (runs once per day)");
|
logger.log("[Snapshots] Daily snapshot system started (runs once per day)");
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -47,7 +48,7 @@ export function stopSnapshotSystem(): void {
|
||||||
if (snapshotInterval) {
|
if (snapshotInterval) {
|
||||||
clearInterval(snapshotInterval);
|
clearInterval(snapshotInterval);
|
||||||
snapshotInterval = null;
|
snapshotInterval = null;
|
||||||
console.log("[Snapshots] Snapshot system stopped");
|
logger.log("[Snapshots] Snapshot system stopped");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -59,7 +60,7 @@ async function createDailySnapshots(): Promise<void> {
|
||||||
const now = new Date();
|
const now = new Date();
|
||||||
const today = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, "0")}-${String(now.getDate()).padStart(2, "0")}`;
|
const today = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, "0")}-${String(now.getDate()).padStart(2, "0")}`;
|
||||||
|
|
||||||
console.log(`[Snapshots] Checking for snapshots to create (${today})`);
|
logger.log(`[Snapshots] Checking for snapshots to create (${today})`);
|
||||||
|
|
||||||
// Get all seasons that are active or in draft (we want to track standings for these)
|
// Get all seasons that are active or in draft (we want to track standings for these)
|
||||||
const activeSeasons = await db.query.seasons.findMany({
|
const activeSeasons = await db.query.seasons.findMany({
|
||||||
|
|
@ -70,22 +71,22 @@ async function createDailySnapshots(): Promise<void> {
|
||||||
});
|
});
|
||||||
|
|
||||||
if (activeSeasons.length === 0) {
|
if (activeSeasons.length === 0) {
|
||||||
console.log("[Snapshots] No active seasons found");
|
logger.log("[Snapshots] No active seasons found");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log(`[Snapshots] Found ${activeSeasons.length} active season(s)`);
|
logger.log(`[Snapshots] Found ${activeSeasons.length} active season(s)`);
|
||||||
|
|
||||||
for (const season of activeSeasons) {
|
for (const season of activeSeasons) {
|
||||||
try {
|
try {
|
||||||
await createDailySnapshot(season.id, db);
|
await createDailySnapshot(season.id, db);
|
||||||
console.log(`[Snapshots] ✅ Upserted snapshot for season ${season.id}`);
|
logger.log(`[Snapshots] ✅ Upserted snapshot for season ${season.id}`);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(`[Snapshots] Error creating snapshot for season ${season.id}:`, error);
|
logger.error(`[Snapshots] Error creating snapshot for season ${season.id}:`, error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log("[Snapshots] Daily snapshot check complete");
|
logger.log("[Snapshots] Daily snapshot check complete");
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -93,14 +94,14 @@ async function createDailySnapshots(): Promise<void> {
|
||||||
* Useful for admin tools or manual triggers
|
* Useful for admin tools or manual triggers
|
||||||
*/
|
*/
|
||||||
export async function createSnapshotsForSeasons(seasonIds: string[]): Promise<void> {
|
export async function createSnapshotsForSeasons(seasonIds: string[]): Promise<void> {
|
||||||
console.log(`[Snapshots] Manual trigger for ${seasonIds.length} season(s)`);
|
logger.log(`[Snapshots] Manual trigger for ${seasonIds.length} season(s)`);
|
||||||
|
|
||||||
for (const seasonId of seasonIds) {
|
for (const seasonId of seasonIds) {
|
||||||
try {
|
try {
|
||||||
await createDailySnapshot(seasonId, db);
|
await createDailySnapshot(seasonId, db);
|
||||||
console.log(`[Snapshots] ✅ Created snapshot for season ${seasonId}`);
|
logger.log(`[Snapshots] ✅ Created snapshot for season ${seasonId}`);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(`[Snapshots] Error creating snapshot for season ${seasonId}:`, error);
|
logger.error(`[Snapshots] Error creating snapshot for season ${seasonId}:`, error);
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -111,6 +112,6 @@ export async function createSnapshotsForSeasons(seasonIds: string[]): Promise<vo
|
||||||
* Useful for testing or manual refreshes
|
* Useful for testing or manual refreshes
|
||||||
*/
|
*/
|
||||||
export async function createSnapshotsForAllSeasons(): Promise<void> {
|
export async function createSnapshotsForAllSeasons(): Promise<void> {
|
||||||
console.log("[Snapshots] Manual trigger for all active seasons");
|
logger.log("[Snapshots] Manual trigger for all active seasons");
|
||||||
await createDailySnapshots();
|
await createDailySnapshots();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ import type { PostgresJsDatabase } from "drizzle-orm/postgres-js";
|
||||||
import postgres from "postgres";
|
import postgres from "postgres";
|
||||||
import * as schema from "~/database/schema";
|
import * as schema from "~/database/schema";
|
||||||
import { eq, and, asc } from "drizzle-orm";
|
import { eq, and, asc } from "drizzle-orm";
|
||||||
|
import { logger } from "./logger";
|
||||||
|
|
||||||
// Lazy-initialized DB for socket-level validation queries (team ownership checks)
|
// Lazy-initialized DB for socket-level validation queries (team ownership checks)
|
||||||
let _socketDb: PostgresJsDatabase<typeof schema> | null = null;
|
let _socketDb: PostgresJsDatabase<typeof schema> | null = null;
|
||||||
|
|
@ -100,7 +101,7 @@ const connectedTeams = new Map<string, Set<string>>(); // seasonId -> Set<teamId
|
||||||
*/
|
*/
|
||||||
export function initializeSocketIO(httpServer: HTTPServer): SocketIOServer {
|
export function initializeSocketIO(httpServer: HTTPServer): SocketIOServer {
|
||||||
if (io) {
|
if (io) {
|
||||||
console.log("Socket.IO already initialized");
|
logger.log("Socket.IO already initialized");
|
||||||
return io;
|
return io;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -123,7 +124,7 @@ export function initializeSocketIO(httpServer: HTTPServer): SocketIOServer {
|
||||||
|
|
||||||
// Connection handling
|
// Connection handling
|
||||||
io.on("connection", (socket: Socket<ClientToServerEvents, ServerToClientEvents>) => {
|
io.on("connection", (socket: Socket<ClientToServerEvents, ServerToClientEvents>) => {
|
||||||
console.log("Client connected:", socket.id);
|
logger.log("Client connected:", socket.id);
|
||||||
|
|
||||||
// Store team ID for this socket
|
// Store team ID for this socket
|
||||||
let currentTeamId: string | undefined;
|
let currentTeamId: string | undefined;
|
||||||
|
|
@ -131,12 +132,12 @@ export function initializeSocketIO(httpServer: HTTPServer): SocketIOServer {
|
||||||
|
|
||||||
socket.on("join-draft", async (seasonId: string, teamId?: string) => {
|
socket.on("join-draft", async (seasonId: string, teamId?: string) => {
|
||||||
if (!seasonId) {
|
if (!seasonId) {
|
||||||
console.error("No seasonId provided for join-draft");
|
logger.error("No seasonId provided for join-draft");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
socket.join(`draft-${seasonId}`);
|
socket.join(`draft-${seasonId}`);
|
||||||
currentSeasonId = seasonId;
|
currentSeasonId = seasonId;
|
||||||
console.log(`Socket ${socket.id} joined draft-${seasonId}`);
|
logger.log(`Socket ${socket.id} joined draft-${seasonId}`);
|
||||||
|
|
||||||
// If teamId provided, validate it belongs to this season before tracking
|
// If teamId provided, validate it belongs to this season before tracking
|
||||||
if (teamId) {
|
if (teamId) {
|
||||||
|
|
@ -146,11 +147,11 @@ export function initializeSocketIO(httpServer: HTTPServer): SocketIOServer {
|
||||||
where: and(eq(schema.teams.id, teamId), eq(schema.teams.seasonId, seasonId)),
|
where: and(eq(schema.teams.id, teamId), eq(schema.teams.seasonId, seasonId)),
|
||||||
});
|
});
|
||||||
if (!team) {
|
if (!team) {
|
||||||
console.warn(`[Socket] join-draft rejected: team ${teamId} does not belong to season ${seasonId}`);
|
logger.warn(`[Socket] join-draft rejected: team ${teamId} does not belong to season ${seasonId}`);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error("[Socket] join-draft team validation failed:", err);
|
logger.error("[Socket] join-draft team validation failed:", err);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -174,7 +175,7 @@ export function initializeSocketIO(httpServer: HTTPServer): SocketIOServer {
|
||||||
|
|
||||||
// Broadcast to everyone else that this team connected
|
// Broadcast to everyone else that this team connected
|
||||||
io?.to(`draft-${seasonId}`).emit("team-connected", { teamId });
|
io?.to(`draft-${seasonId}`).emit("team-connected", { teamId });
|
||||||
console.log(`Team ${teamId} connected to draft-${seasonId}. Total connected: ${seasonConnectedTeams.size}`);
|
logger.log(`Team ${teamId} connected to draft-${seasonId}. Total connected: ${seasonConnectedTeams.size}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Send full draft state to the joining client so it can sync immediately.
|
// Send full draft state to the joining client so it can sync immediately.
|
||||||
|
|
@ -244,7 +245,7 @@ export function initializeSocketIO(httpServer: HTTPServer): SocketIOServer {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error("[Socket] draft-state-sync query failed:", err);
|
logger.error("[Socket] draft-state-sync query failed:", err);
|
||||||
// Non-fatal — the client will fall back to HTTP revalidation
|
// Non-fatal — the client will fall back to HTTP revalidation
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
@ -252,7 +253,7 @@ export function initializeSocketIO(httpServer: HTTPServer): SocketIOServer {
|
||||||
socket.on("leave-draft", (seasonId: string) => {
|
socket.on("leave-draft", (seasonId: string) => {
|
||||||
if (!seasonId) return;
|
if (!seasonId) return;
|
||||||
socket.leave(`draft-${seasonId}`);
|
socket.leave(`draft-${seasonId}`);
|
||||||
console.log(`Socket ${socket.id} left draft-${seasonId}`);
|
logger.log(`Socket ${socket.id} left draft-${seasonId}`);
|
||||||
|
|
||||||
// Emit disconnection event if team was tracked
|
// Emit disconnection event if team was tracked
|
||||||
if (currentTeamId) {
|
if (currentTeamId) {
|
||||||
|
|
@ -262,7 +263,7 @@ export function initializeSocketIO(httpServer: HTTPServer): SocketIOServer {
|
||||||
const seasonConnectedTeams = connectedTeams.get(seasonId);
|
const seasonConnectedTeams = connectedTeams.get(seasonId);
|
||||||
if (seasonConnectedTeams) {
|
if (seasonConnectedTeams) {
|
||||||
seasonConnectedTeams.delete(currentTeamId);
|
seasonConnectedTeams.delete(currentTeamId);
|
||||||
console.log(`Team ${currentTeamId} removed from tracking. Remaining: ${seasonConnectedTeams.size}`);
|
logger.log(`Team ${currentTeamId} removed from tracking. Remaining: ${seasonConnectedTeams.size}`);
|
||||||
|
|
||||||
// Clean up empty sets
|
// Clean up empty sets
|
||||||
if (seasonConnectedTeams.size === 0) {
|
if (seasonConnectedTeams.size === 0) {
|
||||||
|
|
@ -271,12 +272,12 @@ export function initializeSocketIO(httpServer: HTTPServer): SocketIOServer {
|
||||||
}
|
}
|
||||||
|
|
||||||
io?.to(`draft-${seasonId}`).emit("team-disconnected", { teamId: currentTeamId });
|
io?.to(`draft-${seasonId}`).emit("team-disconnected", { teamId: currentTeamId });
|
||||||
console.log(`Team ${currentTeamId} disconnected from draft-${seasonId}`);
|
logger.log(`Team ${currentTeamId} disconnected from draft-${seasonId}`);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
socket.on("test-event", (data: unknown) => {
|
socket.on("test-event", (data: unknown) => {
|
||||||
console.log("📨 Received test-event from client:", socket.id, data);
|
logger.log("📨 Received test-event from client:", socket.id, data);
|
||||||
|
|
||||||
socket.emit("test-message", {
|
socket.emit("test-message", {
|
||||||
originalMessage: data,
|
originalMessage: data,
|
||||||
|
|
@ -285,11 +286,11 @@ export function initializeSocketIO(httpServer: HTTPServer): SocketIOServer {
|
||||||
socketId: socket.id,
|
socketId: socket.id,
|
||||||
});
|
});
|
||||||
|
|
||||||
console.log("✅ Sent test-message response to client:", socket.id);
|
logger.log("✅ Sent test-message response to client:", socket.id);
|
||||||
});
|
});
|
||||||
|
|
||||||
socket.on("disconnect", () => {
|
socket.on("disconnect", () => {
|
||||||
console.log("Client disconnected:", socket.id);
|
logger.log("Client disconnected:", socket.id);
|
||||||
|
|
||||||
// Emit disconnection event if team was tracked
|
// Emit disconnection event if team was tracked
|
||||||
if (currentTeamId && currentSeasonId) {
|
if (currentTeamId && currentSeasonId) {
|
||||||
|
|
@ -297,7 +298,7 @@ export function initializeSocketIO(httpServer: HTTPServer): SocketIOServer {
|
||||||
const seasonConnectedTeams = connectedTeams.get(currentSeasonId);
|
const seasonConnectedTeams = connectedTeams.get(currentSeasonId);
|
||||||
if (seasonConnectedTeams) {
|
if (seasonConnectedTeams) {
|
||||||
seasonConnectedTeams.delete(currentTeamId);
|
seasonConnectedTeams.delete(currentTeamId);
|
||||||
console.log(`Team ${currentTeamId} removed from tracking on disconnect. Remaining: ${seasonConnectedTeams.size}`);
|
logger.log(`Team ${currentTeamId} removed from tracking on disconnect. Remaining: ${seasonConnectedTeams.size}`);
|
||||||
|
|
||||||
// Clean up empty sets
|
// Clean up empty sets
|
||||||
if (seasonConnectedTeams.size === 0) {
|
if (seasonConnectedTeams.size === 0) {
|
||||||
|
|
@ -306,7 +307,7 @@ export function initializeSocketIO(httpServer: HTTPServer): SocketIOServer {
|
||||||
}
|
}
|
||||||
|
|
||||||
io?.to(`draft-${currentSeasonId}`).emit("team-disconnected", { teamId: currentTeamId });
|
io?.to(`draft-${currentSeasonId}`).emit("team-disconnected", { teamId: currentTeamId });
|
||||||
console.log(`Team ${currentTeamId} disconnected from draft-${currentSeasonId}`);
|
logger.log(`Team ${currentTeamId} disconnected from draft-${currentSeasonId}`);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
@ -314,20 +315,20 @@ export function initializeSocketIO(httpServer: HTTPServer): SocketIOServer {
|
||||||
// Store globally for route handlers
|
// Store globally for route handlers
|
||||||
global.__socketIO = io;
|
global.__socketIO = io;
|
||||||
|
|
||||||
console.log("Socket.IO initialized");
|
logger.log("Socket.IO initialized");
|
||||||
|
|
||||||
// Start the draft timer system (async import but don't await)
|
// Start the draft timer system (async import but don't await)
|
||||||
import("./timer").then(({ startDraftTimerSystem }) => {
|
import("./timer").then(({ startDraftTimerSystem }) => {
|
||||||
startDraftTimerSystem();
|
startDraftTimerSystem();
|
||||||
}).catch((error) => {
|
}).catch((error) => {
|
||||||
console.error("Failed to start timer system:", error);
|
logger.error("Failed to start timer system:", error);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Start the daily snapshot system
|
// Start the daily snapshot system
|
||||||
import("./snapshots").then(({ startSnapshotSystem }) => {
|
import("./snapshots").then(({ startSnapshotSystem }) => {
|
||||||
startSnapshotSystem();
|
startSnapshotSystem();
|
||||||
}).catch((error) => {
|
}).catch((error) => {
|
||||||
console.error("Failed to start snapshot system:", error);
|
logger.error("Failed to start snapshot system:", error);
|
||||||
});
|
});
|
||||||
|
|
||||||
return io;
|
return io;
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ import { eq, and, asc, sql } from "drizzle-orm";
|
||||||
import type { InferSelectModel } from "drizzle-orm";
|
import type { InferSelectModel } from "drizzle-orm";
|
||||||
import { getSocketIO } from "./socket";
|
import { getSocketIO } from "./socket";
|
||||||
import { executeAutoPick, calculatePickInfo } from "~/models/draft-utils";
|
import { executeAutoPick, calculatePickInfo } from "~/models/draft-utils";
|
||||||
|
import { logger } from "./logger";
|
||||||
|
|
||||||
// Create a dedicated database connection for the timer
|
// Create a dedicated database connection for the timer
|
||||||
const connectionString = process.env.DATABASE_URL;
|
const connectionString = process.env.DATABASE_URL;
|
||||||
|
|
@ -22,7 +23,7 @@ let timerInterval: NodeJS.Timeout | null = null;
|
||||||
*/
|
*/
|
||||||
export function startDraftTimerSystem(): void {
|
export function startDraftTimerSystem(): void {
|
||||||
if (timerInterval) {
|
if (timerInterval) {
|
||||||
console.log("[Timer] Timer system already running");
|
logger.log("[Timer] Timer system already running");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -30,11 +31,11 @@ export function startDraftTimerSystem(): void {
|
||||||
try {
|
try {
|
||||||
await updateDraftTimers();
|
await updateDraftTimers();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("[Timer] Error updating draft timers:", error);
|
logger.error("[Timer] Error updating draft timers:", error);
|
||||||
}
|
}
|
||||||
}, 1000);
|
}, 1000);
|
||||||
|
|
||||||
console.log("[Timer] Draft timer system started");
|
logger.log("[Timer] Draft timer system started");
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -44,7 +45,7 @@ export function stopDraftTimerSystem(): void {
|
||||||
if (timerInterval) {
|
if (timerInterval) {
|
||||||
clearInterval(timerInterval);
|
clearInterval(timerInterval);
|
||||||
timerInterval = null;
|
timerInterval = null;
|
||||||
console.log("[Timer] Draft timer system stopped");
|
logger.log("[Timer] Draft timer system stopped");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -101,7 +102,7 @@ async function updateDraftTimers(): Promise<void> {
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!timer) {
|
if (!timer) {
|
||||||
console.warn(
|
logger.warn(
|
||||||
`[Timer] No timer found for team ${currentTeamId} in season ${season.id}, creating with initial time`
|
`[Timer] No timer found for team ${currentTeamId} in season ${season.id}, creating with initial time`
|
||||||
);
|
);
|
||||||
const initialTime = season.draftInitialTime || 120;
|
const initialTime = season.draftInitialTime || 120;
|
||||||
|
|
@ -127,7 +128,7 @@ async function updateDraftTimers(): Promise<void> {
|
||||||
|
|
||||||
// If timer is at 0 or below, check autodraft settings and trigger auto-pick
|
// If timer is at 0 or below, check autodraft settings and trigger auto-pick
|
||||||
if (timer.timeRemaining <= 0) {
|
if (timer.timeRemaining <= 0) {
|
||||||
console.log(
|
logger.log(
|
||||||
`[Timer] ⚠️ Timer expired for team ${currentTeamId} in season ${season.id} (pick ${currentPickNumber})`
|
`[Timer] ⚠️ Timer expired for team ${currentTeamId} in season ${season.id} (pick ${currentPickNumber})`
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
@ -144,7 +145,7 @@ async function updateDraftTimers(): Promise<void> {
|
||||||
const success = await triggerAutoPick(season.id, currentTeamId, currentPickNumber, shouldAutodraft ? (autodraftSettings ?? null) : null);
|
const success = await triggerAutoPick(season.id, currentTeamId, currentPickNumber, shouldAutodraft ? (autodraftSettings ?? null) : null);
|
||||||
|
|
||||||
if (!success) {
|
if (!success) {
|
||||||
console.error(`[Timer] Pausing draft ${season.id} — auto-pick failed for team ${currentTeamId} pick ${currentPickNumber}`);
|
logger.error(`[Timer] Pausing draft ${season.id} — auto-pick failed for team ${currentTeamId} pick ${currentPickNumber}`);
|
||||||
await db.update(schema.seasons).set({ draftPaused: true }).where(eq(schema.seasons.id, season.id));
|
await db.update(schema.seasons).set({ draftPaused: true }).where(eq(schema.seasons.id, season.id));
|
||||||
io.to(`draft-${season.id}`).emit("draft-paused", { seasonId: season.id, paused: true });
|
io.to(`draft-${season.id}`).emit("draft-paused", { seasonId: season.id, paused: true });
|
||||||
}
|
}
|
||||||
|
|
@ -186,7 +187,7 @@ async function updateDraftTimers(): Promise<void> {
|
||||||
const success = await triggerAutoPick(season.id, currentTeamId, currentPickNumber, shouldAutodraft ? (autodraftSettings ?? null) : null);
|
const success = await triggerAutoPick(season.id, currentTeamId, currentPickNumber, shouldAutodraft ? (autodraftSettings ?? null) : null);
|
||||||
|
|
||||||
if (!success) {
|
if (!success) {
|
||||||
console.error(`[Timer] Pausing draft ${season.id} — auto-pick failed for team ${currentTeamId} pick ${currentPickNumber}`);
|
logger.error(`[Timer] Pausing draft ${season.id} — auto-pick failed for team ${currentTeamId} pick ${currentPickNumber}`);
|
||||||
await db.update(schema.seasons).set({ draftPaused: true }).where(eq(schema.seasons.id, season.id));
|
await db.update(schema.seasons).set({ draftPaused: true }).where(eq(schema.seasons.id, season.id));
|
||||||
io.to(`draft-${season.id}`).emit("draft-paused", { seasonId: season.id, paused: true });
|
io.to(`draft-${season.id}`).emit("draft-paused", { seasonId: season.id, paused: true });
|
||||||
}
|
}
|
||||||
|
|
@ -220,13 +221,13 @@ async function triggerAutoPick(
|
||||||
if (result.error === "Pick already made") {
|
if (result.error === "Pick already made") {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
console.error(`[Timer] Auto-pick failed: ${result.error}`);
|
logger.error(`[Timer] Auto-pick failed: ${result.error}`);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("[Timer] Error in triggerAutoPick:", error);
|
logger.error("[Timer] Error in triggerAutoPick:", error);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue