From db8ee48e36026e696771a85b38f38fdf3ac4e555 Mon Sep 17 00:00:00 2001 From: JackAttack-365 <142643773+jackattack-4@users.noreply.github.com> Date: Sat, 29 Aug 2026 18:37:54 -0700 Subject: [PATCH 1/8] Add Match and MatchParticipant. Create better Match importer and dev import command --- package.json | 3 +- prisma/schema.prisma | 113 ++++- src/handler/manager/addTournamentMatches.ts | 41 +- src/handler/manager/managerConstants.ts | 35 ++ src/lib/importAllTournaments.ts | 36 ++ src/lib/importTournamentMatches.ts | 439 ++++++++++++++++++++ src/runImportAllTournaments.ts | 14 + 7 files changed, 634 insertions(+), 47 deletions(-) create mode 100644 src/lib/importAllTournaments.ts create mode 100644 src/lib/importTournamentMatches.ts create mode 100644 src/runImportAllTournaments.ts diff --git a/package.json b/package.json index 5b390e47..21c2669c 100644 --- a/package.json +++ b/package.json @@ -9,7 +9,8 @@ "start": "node dist/src/server.js --omit=dev", "test": "tsc", "lint": "eslint .", - "format": "prettier --write ." + "format": "prettier --write .", + "import-all-matches": "tsx src/runImportAllTournaments.ts" }, "keywords": [], "author": "", diff --git a/prisma/schema.prisma b/prisma/schema.prisma index b0402a70..921c6889 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -1,6 +1,6 @@ generator client { - provider = "prisma-client-js" - engineType = "binary" + provider = "prisma-client-js" + engineType = "binary" } datasource db { @@ -26,14 +26,99 @@ model FeatureToggle { enabled Boolean @default(true) } -model TeamMatchData { - key String @id +model Match { + key String @id tournamentKey String - matchNumber Int @db.SmallInt - teamNumber Int - matchType MatchType - scoutReports ScoutReport[] - tournament Tournament @relation(fields: [tournamentKey], references: [key], onDelete: Cascade) + year Int + compLevel MatchType + setNumber Int + matchNumber Int + scheduledTime DateTime? + actualTime DateTime? + redScore Int? + blueScore Int? + rawTba Json + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + + tournament Tournament @relation(fields: [tournamentKey], references: [key], onDelete: Cascade) + participants MatchParticipant[] + breakdowns OfficialAllianceBreakdown[] + videos MatchVideo[] + + @@index([tournamentKey, compLevel, matchNumber]) +} + +model MatchParticipant { + matchKey String + teamNumber Int + alliance AllianceColor + station Int + teamMatchKey String? @unique + + match Match @relation(fields: [matchKey], references: [key], onDelete: Cascade) + teamMatchData TeamMatchData? @relation(fields: [teamMatchKey], references: [key]) + + @@id([matchKey, teamNumber]) + @@index([teamNumber]) +} + +model OfficialAllianceBreakdown { + matchKey String + alliance AllianceColor + + // autoCoralCount Int? + // autoCoralPoints Int? + // teleopCoralCount Int? + // teleopCoralPoints Int? + + // autoL1 Int? + // autoL2 Int? + // autoL3 Int? + // autoL4 Int? + + // teleopL1 Int? + // teleopL2 Int? + // teleopL3 Int? + // teleopL4 Int? + + isConsistent Boolean? + raw Json + fetchedAt DateTime @default(now()) + updatedAt DateTime @updatedAt + + match Match @relation(fields: [matchKey], references: [key], onDelete: Cascade) + + @@id([matchKey, alliance]) +} + +model MatchVideo { + uuid String @id @default(uuid()) + matchKey String + type String + externalKey String + createdAt DateTime @default(now()) + + match Match @relation(fields: [matchKey], references: [key], onDelete: Cascade) + + @@unique([matchKey, type, externalKey]) + @@index([matchKey]) +} + +enum AllianceColor { + RED + BLUE +} + +model TeamMatchData { + key String @id + tournamentKey String + matchNumber Int @db.SmallInt + teamNumber Int + matchType MatchType + scoutReports ScoutReport[] + matchParticipant MatchParticipant? + tournament Tournament @relation(fields: [tournamentKey], references: [key], onDelete: Cascade) @@index([tournamentKey, teamNumber]) } @@ -197,6 +282,7 @@ model Tournament { location String? date String? mutablePicklists MutablePicklist[] + matches Match[] scouterScheduleShifts ScouterScheduleShift[] teamMatchData TeamMatchData[] latestFetchETag String? @@ -234,6 +320,15 @@ model CachedAnalysis { tournamentDependencies String[] @default([]) } +model DataFetch { + key String @id + etag String? + createdAt DateTime @default(now()) + lastTried DateTime? + lastFetched DateTime? + data String? +} + enum Position { LEFT_TRENCH LEFT_BUMP diff --git a/src/handler/manager/addTournamentMatches.ts b/src/handler/manager/addTournamentMatches.ts index a8f31cfe..5033ee28 100644 --- a/src/handler/manager/addTournamentMatches.ts +++ b/src/handler/manager/addTournamentMatches.ts @@ -2,6 +2,10 @@ import prismaClient from "../../prismaClient.js"; import z from "zod"; import axios from "axios"; import type { AxiosResponse } from "axios"; +import { + eightTeamDoubleElimPlayoffMatchOrder, + fourTeamDoubleElimPlayoffMatchOrder, +} from "./managerConstants.js"; interface TbaAlliance { team_keys: string[]; @@ -88,39 +92,6 @@ export const addTournamentMatches = async ( }, }); - // playoff formats come from tba's event.playoff_type - // Double Elim 8 team is 10 - // Double Elim 4 team is 11 - // as per https://github.com/the-blue-alliance/the-blue-alliance/blob/main/src/backend/common/consts/playoff_type.py - - const eightTeamDoubleElimPlayoffMatchOrder = new Map([ - ["sf1m1", 1], - ["sf2m1", 2], - ["sf3m1", 3], - ["sf4m1", 4], - ["sf5m1", 5], - ["sf6m1", 6], - ["sf7m1", 7], - ["sf8m1", 8], - ["sf9m1", 9], - ["sf10m1", 10], - ["sf11m1", 11], - ["sf12m1", 12], - ["sf13m1", 13], - ["f1m1", 14], - ["f1m2", 15], - ]); - - const fourTeamDoubleElimPlayoffMatchOrder = new Map([ - ["sf1m1", 1], - ["sf2m1", 2], - ["sf3m1", 3], - ["sf4m1", 4], - ["sf5m1", 5], - ["f1m1", 6], - ["f1m2", 7], - ]); - const playoffMatchOrder = event.playoff_type === 10 ? eightTeamDoubleElimPlayoffMatchOrder @@ -139,12 +110,8 @@ export const addTournamentMatches = async ( ...match.alliances.red.team_keys, ...match.alliances.blue.team_keys, ]; - let matchesString = ``; //make matches with trailing _0, _1, _2 etc for (let k = 0; k < teams.length; k++) { - matchesString = - matchesString + - `('${tournamentKey}_qm${match.match_number}_${k}', '${tournamentKey}', ${match.match_number}, '${teams[k]}', '${match.comp_level}'), `; const currMatchKey = `${tournamentKey}_qm${match.match_number}_${k}`; const fakeTeamKey = teams[k]; // The one TBA sends you which is potentially "fake", like frc6418B diff --git a/src/handler/manager/managerConstants.ts b/src/handler/manager/managerConstants.ts index c4b06dbd..4704a280 100644 --- a/src/handler/manager/managerConstants.ts +++ b/src/handler/manager/managerConstants.ts @@ -104,6 +104,39 @@ const MatchEnumToAbrivation: Record = { [MatchType.ELIMINATION]: "em", }; +// playoff formats come from tba's event.playoff_type +// Double Elim 8 team is 10 +// Double Elim 4 team is 11 +// as per https://github.com/the-blue-alliance/the-blue-alliance/blob/main/src/backend/common/consts/playoff_type.py + +const eightTeamDoubleElimPlayoffMatchOrder = new Map([ + ["sf1m1", 1], + ["sf2m1", 2], + ["sf3m1", 3], + ["sf4m1", 4], + ["sf5m1", 5], + ["sf6m1", 6], + ["sf7m1", 7], + ["sf8m1", 8], + ["sf9m1", 9], + ["sf10m1", 10], + ["sf11m1", 11], + ["sf12m1", 12], + ["sf13m1", 13], + ["f1m1", 14], + ["f1m2", 15], +]); + +const fourTeamDoubleElimPlayoffMatchOrder = new Map([ + ["sf1m1", 1], + ["sf2m1", 2], + ["sf3m1", 3], + ["sf4m1", 4], + ["sf5m1", 5], + ["f1m1", 6], + ["f1m2", 7], +]); + export { EventActionMap, PositionMap, @@ -118,4 +151,6 @@ export { EndgameClimbReverseMap, AutoClimbReverseMap, FeederTypeReverseMap, + eightTeamDoubleElimPlayoffMatchOrder, + fourTeamDoubleElimPlayoffMatchOrder, }; diff --git a/src/lib/importAllTournaments.ts b/src/lib/importAllTournaments.ts new file mode 100644 index 00000000..a55faaab --- /dev/null +++ b/src/lib/importAllTournaments.ts @@ -0,0 +1,36 @@ +import prisma from "../prismaClient.js"; +import { importTournamentMatches } from "./importTournamentMatches.js"; + +export default async function importAllTournaments(): Promise { + const tournaments = await prisma.tournament.findMany({ + where: { + key: { + startsWith: "2026", + }, + }, + select: { + key: true, + }, + orderBy: { + key: "asc", + }, + }); + + let success = 0; + let failed = 0; + + for (const tournament of tournaments) { + try { + await importTournamentMatches(tournament.key); + success++; + console.log(`[OK] ${tournament.key}`); + } catch (error) { + failed++; + console.error(`[FAIL] ${tournament.key}:`, error); + } + } + + console.log( + `Done. ${success} success, ${failed} failed (${tournaments.length} total).`, + ); +} diff --git a/src/lib/importTournamentMatches.ts b/src/lib/importTournamentMatches.ts new file mode 100644 index 00000000..4a5c1f03 --- /dev/null +++ b/src/lib/importTournamentMatches.ts @@ -0,0 +1,439 @@ +import axios, { AxiosResponse } from "axios"; +import prismaClient from "../prismaClient"; +import z from "zod"; +import { + eightTeamDoubleElimPlayoffMatchOrder, + fourTeamDoubleElimPlayoffMatchOrder, +} from "../handler/manager/managerConstants"; +import { DateTime } from "luxon"; +import { AllianceColor, MatchType } from "@prisma/client"; + +interface TBAEventResponse { + remap_teams: Record; + playoff_type: number; + key: string; + name: string; + event_code: string; + event_type: 0; + district: { + abbreviation: string; + display_name: string; + key: string; + year: number; + official_advancement_counts: { + dcmp: number; + cmp: number; + }; + }; + city: string; + state_prov: string; + country: string; + start_date: DateTime; + end_date: DateTime; + year: number; + short_name: string; + event_type_string: string; + week: number; + address: string; + postal_code: string; + gmaps_place_id: string; + gmaps_url: string; + lat: number; + lng: number; + location_name: string; + timezone: string; + website: string; + first_event_id: string; + first_event_code: string; + webcasts: [ + { + type: string; + channel: string; + date: string; + file: string; + status: string; + stream_title: string; + viewer_count: number; + }, + ]; + division_keys: [string]; + parent_event_key: string; + playoff_type_string: string; +} + +interface TBAAlliance { + score: number | null; + team_keys: string[]; + surrogate_team_keys: string[]; + dq_team_keys: string[]; +} + +interface TBAMatch { + key: string; + comp_level: "qm" | "em"; + set_number: number; + match_number: number; + alliances: { + red: TBAAlliance; + blue: TBAAlliance; + }; + winning_alliance: string; + event_key: string; + time: number; + actual_time: number; + predicted_time: number; + post_result_time: number; + score_breakdown: { + blue: {}; + red: {}; + }; + videos: [ + { + type: string; + key: string; + }, + ]; +} + +export const importTournamentMatches = async (tournamentKey: string) => { + if (!tournamentKey.startsWith("2026")) { + return; + } + + const tba = "https://www.thebluealliance.com/api/v3"; + + // Fetch event data + const event = await fetchFromTBA( + `${tba}/event/${tournamentKey}`, + ); + + // Create a list of remapped teams (8033B, 254C, 4414D, etc) + const remap_teams = + z + .object({ + remap_teams: z.record(z.string(), z.string()).nullish(), + }) + .passthrough() + .parse(event).remap_teams ?? {}; + + const fixRemappedTeam = async (team: string): Promise => { + const fakeTeamKey = team; // The one TBA sends you which is potentially "fake", like frc6418B + const mapEntry = Object.entries(remap_teams).find( + (v) => v[1] === fakeTeamKey, + ); + const realTeamKey = mapEntry ? mapEntry[0] : fakeTeamKey; + return Number(realTeamKey.substring(3)); + }; + + // Fetch all matches from the tournament + const matches = await fetchFromTBA( + `${tba}/event/${tournamentKey}/matches`, + ); + + if (event === undefined || matches === undefined) return; + + const playoffMatchOrder = + event.playoff_type === 10 + ? eightTeamDoubleElimPlayoffMatchOrder + : fourTeamDoubleElimPlayoffMatchOrder; + + // Sort matches by time + matches.sort( + (a: TBAMatch, b: TBAMatch) => + (a.actual_time ?? a.time ?? 0) - (b.actual_time ?? b.time ?? 0), + ); + + const quals = matches.filter((match) => match.comp_level === "qm"); + + for (const match of quals) { + const matchTeams = [ + ...match.alliances.red.team_keys, + ...match.alliances.blue.team_keys, + ]; + + for (let t = 0; t < 6; t++) { + const params = z + .object({ + matchNumber: z.number(), + tournamentKey: z.string(), + key: z.string(), + teamNumber: z.number(), + alliance: z.nativeEnum(AllianceColor), + station: z.number(), + }) + .safeParse({ + key: `${tournamentKey}_qm${match.match_number}`, + tournamentKey: tournamentKey, + matchNumber: match.match_number, + teamNumber: await fixRemappedTeam(matchTeams[t]), + alliance: t > 2 ? AllianceColor.BLUE : AllianceColor.RED, + station: (t + 1) % 3, + }); + + if (!params.success) { + throw params; + } + // TeamMatchData (old) + await prismaClient.teamMatchData.upsert({ + where: { + key: `${params.data.key}_${t}`, + }, + update: { + tournamentKey: params.data.tournamentKey, + matchNumber: params.data.matchNumber, + teamNumber: params.data.teamNumber, + matchType: MatchType.QUALIFICATION, + }, + create: { + key: `${params.data.key}_${t}`, + tournamentKey: params.data.tournamentKey, + matchNumber: params.data.matchNumber, + teamNumber: params.data.teamNumber, + matchType: MatchType.QUALIFICATION, + }, + }); + + // MatchParticipant + await prismaClient.matchParticipant.upsert({ + where: { + teamMatchKey: `${params.data.key}_${t}`, + }, + update: { + matchKey: params.data.key, + alliance: params.data.alliance, + teamNumber: params.data.teamNumber, + station: params.data.station, + }, + create: { + teamMatchKey: `${params.data.key}_${t}`, + matchKey: params.data.key, + alliance: params.data.alliance, + teamNumber: params.data.teamNumber, + station: params.data.station, + }, + }); + } + + // Match + await prismaClient.match.upsert({ + where: { + key: match.key, + }, + update: { + year: event.year, + compLevel: MatchType.QUALIFICATION, + setNumber: match.set_number, + matchNumber: match.match_number, + scheduledTime: new Date(match.time * 1000), + actualTime: new Date(match.actual_time * 1000), + redScore: match.alliances.red.score, + blueScore: match.alliances.blue.score, + rawTba: JSON.stringify(match), + updatedAt: new Date(), + }, + create: { + key: match.key, + tournamentKey: event.key, + year: event.year, + compLevel: MatchType.QUALIFICATION, + setNumber: match.set_number, + matchNumber: match.match_number, + scheduledTime: new Date(match.time * 1000), + actualTime: new Date(match.actual_time * 1000), + redScore: match.alliances.red.score, + blueScore: match.alliances.blue.score, + rawTba: JSON.stringify(match), + updatedAt: new Date(), + }, + }); + } + const elims = matches.filter((match) => match.comp_level === "em"); + + for (const match of elims) { + const matchTeams = [ + ...match.alliances.red.team_keys, + ...match.alliances.blue.team_keys, + ]; + + if (matchTeams.length !== 6) { + continue; + } + + const matchSuffix = match.key.split("_")[1] ?? ""; + + const matchNumber = playoffMatchOrder.get(matchSuffix); + + if (!matchNumber) { + continue; + } + + for (let t = 0; t < 6; t++) { + const params = z + .object({ + matchNumber: z.number(), + tournamentKey: z.string(), + key: z.string(), + teamNumber: z.number(), + alliance: z.nativeEnum(AllianceColor), + station: z.number(), + }) + .safeParse({ + key: `${tournamentKey}_em${matchNumber}`, + tournamentKey: tournamentKey, + matchNumber: matchNumber, + teamNumber: await fixRemappedTeam(matchTeams[t]), + alliance: t > 2 ? AllianceColor.BLUE : AllianceColor.RED, + station: (t + 1) % 3, + }); + + if (!params.success) { + throw params; + } + // TeamMatchData (old) + await prismaClient.teamMatchData.upsert({ + where: { + key: `${params.data.key}_${t}`, + }, + update: { + tournamentKey: params.data.tournamentKey, + matchNumber: params.data.matchNumber, + teamNumber: params.data.teamNumber, + matchType: MatchType.ELIMINATION, + }, + create: { + key: `${params.data.key}_${t}`, + tournamentKey: params.data.tournamentKey, + matchNumber: params.data.matchNumber, + teamNumber: params.data.teamNumber, + matchType: MatchType.ELIMINATION, + }, + }); + + // MatchParticipant + await prismaClient.matchParticipant.upsert({ + where: { + teamMatchKey: `${params.data.key}_${t}`, + }, + update: { + matchKey: params.data.key, + alliance: params.data.alliance, + teamNumber: params.data.teamNumber, + station: params.data.station, + }, + create: { + teamMatchKey: `${params.data.key}_${t}`, + matchKey: params.data.key, + alliance: params.data.alliance, + teamNumber: params.data.teamNumber, + station: params.data.station, + }, + }); + } + + // Match + await prismaClient.match.upsert({ + where: { + key: `${tournamentKey}_em${matchNumber}`, + }, + update: { + year: event.year, + compLevel: MatchType.ELIMINATION, + setNumber: match.set_number, + matchNumber: match.match_number, + scheduledTime: new Date(match.time * 1000), + actualTime: new Date(match.actual_time * 1000), + redScore: match.alliances.red.score, + blueScore: match.alliances.blue.score, + rawTba: JSON.stringify(match), + updatedAt: new Date(), + }, + create: { + key: `${tournamentKey}_em${matchNumber}`, + tournamentKey: event.key, + year: event.year, + compLevel: MatchType.ELIMINATION, + setNumber: match.set_number, + matchNumber: match.match_number, + scheduledTime: new Date(match.time * 1000), + actualTime: new Date(match.actual_time * 1000), + redScore: match.alliances.red.score, + blueScore: match.alliances.blue.score, + rawTba: JSON.stringify(match), + updatedAt: new Date(), + }, + }); + } +}; + +const fetchFromTBA = async (url: string): Promise => { + const fetchRow = await prismaClient.dataFetch.findUnique({ + where: { + key: url, + }, + }); + + let response: AxiosResponse; + + try { + const now = new Date(); + + response = await axios.get(url, { + headers: { + "X-TBA-Auth-Key": process.env.TBA_KEY, + "If-None-Match": fetchRow?.etag ?? undefined, + }, + validateStatus: (status) => + (status >= 200 && status < 300) || status === 304, + }); + + if (response.status !== 304) { + await prismaClient.dataFetch.upsert({ + where: { + key: url, + }, + create: { + key: url, + lastTried: new Date(), + etag: response.headers.etag, + data: JSON.stringify(response.data), + }, + update: { + lastTried: new Date(), + etag: response.headers.etag, + data: JSON.stringify(response.data), + }, + }); + + return response.data; + } else { + await prismaClient.dataFetch.update({ + where: { + key: url, + }, + data: { + lastFetched: now, + lastTried: now, + }, + }); + + return JSON.parse(fetchRow?.data ?? "null") as T; + } + } catch (error) { + console.error(error); + throw error; + } +}; + +const validateETag = async (url: string) => { + const now = new Date(); + + await prismaClient.dataFetch.update({ + where: { + key: url, + }, + data: { + lastFetched: now, + lastTried: now, + }, + }); +}; diff --git a/src/runImportAllTournaments.ts b/src/runImportAllTournaments.ts new file mode 100644 index 00000000..aba3de2d --- /dev/null +++ b/src/runImportAllTournaments.ts @@ -0,0 +1,14 @@ +import "dotenv/config"; +import importAllTournaments from "./lib/importAllTournaments.js"; + +const main = async () => { + try { + await importAllTournaments(); + process.exit(0); + } catch (error) { + console.error(error); + process.exit(1); + } +}; + +main(); From 975a7df9e052b28e804b9e499acefbf187d60918 Mon Sep 17 00:00:00 2001 From: JackAttack-365 <142643773+jackattack-4@users.noreply.github.com> Date: Sat, 29 Aug 2026 18:45:48 -0700 Subject: [PATCH 2/8] import matches on start (temporary) --- railway.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/railway.json b/railway.json index 36d1390c..bb58e996 100644 --- a/railway.json +++ b/railway.json @@ -8,7 +8,7 @@ "deploy": { "runtime": "V2", "numReplicas": 1, - "startCommand": "sleep 3 && export DATABASE_URL=$DATABASE_PRIVATE_URL && npm start", + "startCommand": "sleep 3 && export DATABASE_URL=$DATABASE_PRIVATE_URL && npm run import-all-matches && npm start", "preDeployCommand": [ "sleep 3 && DATABASE_URL=$DATABASE_PRIVATE_URL npx prisma db push && npx prisma generate" ], From 59e13183e317a2fe50836b2692c8b186a8712536 Mon Sep 17 00:00:00 2001 From: JackAttack-365 <142643773+jackattack-4@users.noreply.github.com> Date: Sun, 30 Aug 2026 13:58:14 -0700 Subject: [PATCH 3/8] fix upsert orders --- prisma/schema.prisma | 2 +- src/lib/importTournamentMatches.ts | 75 ++++++++++++++++-------------- 2 files changed, 41 insertions(+), 36 deletions(-) diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 921c6889..47022fea 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -37,7 +37,7 @@ model Match { actualTime DateTime? redScore Int? blueScore Int? - rawTba Json + rawTba String createdAt DateTime @default(now()) updatedAt DateTime @updatedAt diff --git a/src/lib/importTournamentMatches.ts b/src/lib/importTournamentMatches.ts index 4a5c1f03..6cf555fd 100644 --- a/src/lib/importTournamentMatches.ts +++ b/src/lib/importTournamentMatches.ts @@ -151,6 +151,39 @@ export const importTournamentMatches = async (tournamentKey: string) => { ...match.alliances.blue.team_keys, ]; + // Match + await prismaClient.match.upsert({ + where: { + key: match.key, + }, + update: { + year: event.year, + compLevel: MatchType.QUALIFICATION, + setNumber: match.set_number, + matchNumber: match.match_number, + scheduledTime: new Date(match.time * 1000), + actualTime: new Date(match.actual_time * 1000), + redScore: match.alliances.red.score, + blueScore: match.alliances.blue.score, + rawTba: JSON.stringify(match), + updatedAt: new Date(), + }, + create: { + key: match.key, + tournamentKey: event.key, + year: event.year, + compLevel: MatchType.QUALIFICATION, + setNumber: match.set_number, + matchNumber: match.match_number, + scheduledTime: new Date(match.time * 1000), + actualTime: new Date(match.actual_time * 1000), + redScore: match.alliances.red.score, + blueScore: match.alliances.blue.score, + rawTba: JSON.stringify(match), + updatedAt: new Date(), + }, + }); + for (let t = 0; t < 6; t++) { const params = z .object({ @@ -167,7 +200,7 @@ export const importTournamentMatches = async (tournamentKey: string) => { matchNumber: match.match_number, teamNumber: await fixRemappedTeam(matchTeams[t]), alliance: t > 2 ? AllianceColor.BLUE : AllianceColor.RED, - station: (t + 1) % 3, + station: t % 3, }); if (!params.success) { @@ -213,40 +246,9 @@ export const importTournamentMatches = async (tournamentKey: string) => { }, }); } - - // Match - await prismaClient.match.upsert({ - where: { - key: match.key, - }, - update: { - year: event.year, - compLevel: MatchType.QUALIFICATION, - setNumber: match.set_number, - matchNumber: match.match_number, - scheduledTime: new Date(match.time * 1000), - actualTime: new Date(match.actual_time * 1000), - redScore: match.alliances.red.score, - blueScore: match.alliances.blue.score, - rawTba: JSON.stringify(match), - updatedAt: new Date(), - }, - create: { - key: match.key, - tournamentKey: event.key, - year: event.year, - compLevel: MatchType.QUALIFICATION, - setNumber: match.set_number, - matchNumber: match.match_number, - scheduledTime: new Date(match.time * 1000), - actualTime: new Date(match.actual_time * 1000), - redScore: match.alliances.red.score, - blueScore: match.alliances.blue.score, - rawTba: JSON.stringify(match), - updatedAt: new Date(), - }, - }); + console.log(`Q${match.match_number} imported`); } + const elims = matches.filter((match) => match.comp_level === "em"); for (const match of elims) { @@ -283,7 +285,7 @@ export const importTournamentMatches = async (tournamentKey: string) => { matchNumber: matchNumber, teamNumber: await fixRemappedTeam(matchTeams[t]), alliance: t > 2 ? AllianceColor.BLUE : AllianceColor.RED, - station: (t + 1) % 3, + station: t % 3, }); if (!params.success) { @@ -437,3 +439,6 @@ const validateETag = async (url: string) => { }, }); }; + +await importTournamentMatches("2026casnf"); +process.exit(1); From 9d9305297b0baddc94b4ba5475a633644ecf1fce Mon Sep 17 00:00:00 2001 From: JackAttack-365 <142643773+jackattack-4@users.noreply.github.com> Date: Sun, 30 Aug 2026 15:11:12 -0700 Subject: [PATCH 4/8] link to endpoint --- railway.json | 2 +- src/app.ts | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/railway.json b/railway.json index bb58e996..36d1390c 100644 --- a/railway.json +++ b/railway.json @@ -8,7 +8,7 @@ "deploy": { "runtime": "V2", "numReplicas": 1, - "startCommand": "sleep 3 && export DATABASE_URL=$DATABASE_PRIVATE_URL && npm run import-all-matches && npm start", + "startCommand": "sleep 3 && export DATABASE_URL=$DATABASE_PRIVATE_URL && npm start", "preDeployCommand": [ "sleep 3 && DATABASE_URL=$DATABASE_PRIVATE_URL npx prisma db push && npx prisma generate" ], diff --git a/src/app.ts b/src/app.ts index 39a7d2fd..9e8160ba 100644 --- a/src/app.ts +++ b/src/app.ts @@ -11,6 +11,7 @@ import posthogReporter from "./lib/middleware/posthogMiddleware.js"; import routes from "./routes/index.js"; import path from "path"; +import importAllTournaments from "./lib/importAllTournaments.js"; export const app = express(); @@ -41,6 +42,7 @@ app.use(posthogReporter); // API entry point app.use("/v1", routes); //theo was here -app.get("/status", (req, res) => { +app.get("/status", async (req, res) => { + await importAllTournaments(); res.status(200).send("Server running"); }); From c8c5615c7e1b59fc3b69cbb52f5e48e81663a52f Mon Sep 17 00:00:00 2001 From: JackAttack-365 <142643773+jackattack-4@users.noreply.github.com> Date: Sun, 30 Aug 2026 15:13:58 -0700 Subject: [PATCH 5/8] add .js endings --- src/lib/importTournamentMatches.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/lib/importTournamentMatches.ts b/src/lib/importTournamentMatches.ts index 6cf555fd..4f33448f 100644 --- a/src/lib/importTournamentMatches.ts +++ b/src/lib/importTournamentMatches.ts @@ -1,10 +1,10 @@ import axios, { AxiosResponse } from "axios"; -import prismaClient from "../prismaClient"; +import prismaClient from "../prismaClient.js"; import z from "zod"; import { eightTeamDoubleElimPlayoffMatchOrder, fourTeamDoubleElimPlayoffMatchOrder, -} from "../handler/manager/managerConstants"; +} from "../handler/manager/managerConstants.js"; import { DateTime } from "luxon"; import { AllianceColor, MatchType } from "@prisma/client"; From 0e24cd589324fcfde851a2f453074b471a62042f Mon Sep 17 00:00:00 2001 From: JackAttack-365 <142643773+jackattack-4@users.noreply.github.com> Date: Sun, 30 Aug 2026 15:18:36 -0700 Subject: [PATCH 6/8] fix endpoints --- src/app.ts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/app.ts b/src/app.ts index 9e8160ba..b35a47f9 100644 --- a/src/app.ts +++ b/src/app.ts @@ -42,7 +42,10 @@ app.use(posthogReporter); // API entry point app.use("/v1", routes); //theo was here -app.get("/status", async (req, res) => { - await importAllTournaments(); +app.get("/status", (req, res) => { res.status(200).send("Server running"); }); + +app.get("/import", async (req, res) => { + await importAllTournaments(); +}); From f09531effccb53ccfe0e33543191de5ab21254e1 Mon Sep 17 00:00:00 2001 From: JackAttack-365 <142643773+jackattack-4@users.noreply.github.com> Date: Tue, 1 Sep 2026 09:09:04 -0700 Subject: [PATCH 7/8] hopefully fix run on startup --- railway.json | 2 +- src/lib/importTournamentMatches.ts | 7 +++---- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/railway.json b/railway.json index 36d1390c..56033c17 100644 --- a/railway.json +++ b/railway.json @@ -22,7 +22,7 @@ "restartPolicyType": "ON_FAILURE", "restartPolicyMaxRetries": 20, "healthcheckPath": "/status", - "healthcheckTimeout": 1000 + "healthcheckTimeout": 1000000000 }, "environments": { diff --git a/src/lib/importTournamentMatches.ts b/src/lib/importTournamentMatches.ts index 4f33448f..a263eafc 100644 --- a/src/lib/importTournamentMatches.ts +++ b/src/lib/importTournamentMatches.ts @@ -95,7 +95,9 @@ interface TBAMatch { ]; } -export const importTournamentMatches = async (tournamentKey: string) => { +export const importTournamentMatches = async ( + tournamentKey: string, +): Promise => { if (!tournamentKey.startsWith("2026")) { return; } @@ -439,6 +441,3 @@ const validateETag = async (url: string) => { }, }); }; - -await importTournamentMatches("2026casnf"); -process.exit(1); From c91b59df02e224cd3a4a2ae348582e91b75c95ce Mon Sep 17 00:00:00 2001 From: JackAttack-365 <142643773+jackattack-4@users.noreply.github.com> Date: Tue, 1 Sep 2026 21:23:43 -0700 Subject: [PATCH 8/8] mergeable now i think --- railway.json | 2 +- src/app.ts | 4 +- .../analysis/picklist/picklistShell.ts | 4 +- .../predictions/qualRankingPredictionLogic.ts | 4 +- src/handler/manager/addTournamentMatches.ts | 245 ------------------ src/handler/manager/checkMatchExists.ts | 4 +- src/handler/manager/getMatches.ts | 4 +- src/handler/manager/pitDisplay.ts | 2 +- .../manager/scouters/getScheduleForScouter.ts | 4 +- .../manager/scoutreports/addScoutReport.ts | 4 +- .../scoutreports/addScoutReportDashboard.ts | 4 +- src/lib/fetchMatches.ts | 4 +- src/lib/importTournamentMatches.ts | 39 ++- src/lib/scheduleJobs.ts | 2 +- 14 files changed, 40 insertions(+), 286 deletions(-) delete mode 100644 src/handler/manager/addTournamentMatches.ts diff --git a/railway.json b/railway.json index 56033c17..69092f4f 100644 --- a/railway.json +++ b/railway.json @@ -22,7 +22,7 @@ "restartPolicyType": "ON_FAILURE", "restartPolicyMaxRetries": 20, "healthcheckPath": "/status", - "healthcheckTimeout": 1000000000 + "healthcheckTimeout": 30000 }, "environments": { diff --git a/src/app.ts b/src/app.ts index b35a47f9..ba9fc6ef 100644 --- a/src/app.ts +++ b/src/app.ts @@ -12,6 +12,7 @@ import posthogReporter from "./lib/middleware/posthogMiddleware.js"; import routes from "./routes/index.js"; import path from "path"; import importAllTournaments from "./lib/importAllTournaments.js"; +import { requireAuth } from "./lib/middleware/requireAuth.js"; export const app = express(); @@ -46,6 +47,7 @@ app.get("/status", (req, res) => { res.status(200).send("Server running"); }); -app.get("/import", async (req, res) => { +app.get("/import", requireAuth, async (req, res) => { await importAllTournaments(); + res.status(200).send("Import complete"); }); diff --git a/src/handler/analysis/picklist/picklistShell.ts b/src/handler/analysis/picklist/picklistShell.ts index 7575527d..2b978f99 100644 --- a/src/handler/analysis/picklist/picklistShell.ts +++ b/src/handler/analysis/picklist/picklistShell.ts @@ -1,6 +1,6 @@ import prismaClient from "../../../prismaClient.js"; import z from "zod"; -import { addTournamentMatches } from "../../manager/addTournamentMatches.js"; +import { importTournamentMatches } from "../../../lib/importTournamentMatches.js"; import { Metric, metricsCategory, @@ -122,7 +122,7 @@ export const picklistShell = createAnalysisHandler({ }, }); if (!matches) { - await addTournamentMatches(query.tournamentKey); + await importTournamentMatches(query.tournamentKey); } // Teams to look at diff --git a/src/handler/analysis/predictions/qualRankingPredictionLogic.ts b/src/handler/analysis/predictions/qualRankingPredictionLogic.ts index 212e544a..bc5e90f0 100644 --- a/src/handler/analysis/predictions/qualRankingPredictionLogic.ts +++ b/src/handler/analysis/predictions/qualRankingPredictionLogic.ts @@ -5,7 +5,7 @@ import { User } from "@prisma/client"; import { alliancePage } from "./alliancePage.js"; import z from "zod"; import { runAnalysis } from "../analysisFunction.js"; -import { addTournamentMatches } from "../../manager/addTournamentMatches.js"; +import { importTournamentMatches } from "../../../lib/importTournamentMatches.js"; type TeamRanking = { teamNumber: number; @@ -46,7 +46,7 @@ const config = { let matchesResponse = null; let teamsResponse = null; - await addTournamentMatches(args.tournamentKey); + await importTournamentMatches(args.tournamentKey); const url = "https://www.thebluealliance.com/api/v3"; try { diff --git a/src/handler/manager/addTournamentMatches.ts b/src/handler/manager/addTournamentMatches.ts deleted file mode 100644 index 5033ee28..00000000 --- a/src/handler/manager/addTournamentMatches.ts +++ /dev/null @@ -1,245 +0,0 @@ -import prismaClient from "../../prismaClient.js"; -import z from "zod"; -import axios from "axios"; -import type { AxiosResponse } from "axios"; -import { - eightTeamDoubleElimPlayoffMatchOrder, - fourTeamDoubleElimPlayoffMatchOrder, -} from "./managerConstants.js"; - -interface TbaAlliance { - team_keys: string[]; -} - -interface TbaMatch { - actual_time: number | null; - time: number | null; - comp_level: string; - match_number: number; - key: string; - alliances: { - red: TbaAlliance; - blue: TbaAlliance; - }; -} - -type TbaMatchesResponse = TbaMatch[]; - -export const addTournamentMatches = async ( - tournamentKey: string, -): Promise => { - try { - if (tournamentKey === undefined) { - throw "tournament key is undefined"; - } - - if (!tournamentKey.startsWith("2026")) { - return; - } - - const url = "https://www.thebluealliance.com/api/v3"; - const tournamentRow = await prismaClient.tournament.findUnique({ - where: { - key: tournamentKey, - }, - }); - - if (tournamentRow === null) { - throw "tournament not found when trying to insert tournament matches"; - } - - const eventResponse = await fetch(`${url}/event/${tournamentKey}`, { - headers: { "X-TBA-Auth-Key": process.env.TBA_KEY ?? "" }, - }); - - const json: unknown = await eventResponse.json(); - - console.log(JSON.stringify(json, null, 2)); - - const event = z - .object({ - remap_teams: z.record(z.string(), z.string()).nullish(), - }) - .passthrough() - .parse(json); - - const remap_teams = event.remap_teams ?? {}; - let matchesResponse: AxiosResponse; - try { - matchesResponse = await axios.get( - `${url}/event/${tournamentKey}/matches`, - { - headers: { - "X-TBA-Auth-Key": process.env.TBA_KEY, - "If-None-Match": tournamentRow.latestFetchETag ?? "", - }, - }, - ); - } catch (error) { - if (axios.isAxiosError(error) && error.response?.status === 304) { - return; - } else { - throw error; - } - } - - await prismaClient.tournament.update({ - where: { - key: tournamentKey, - }, - data: { - latestFetchETag: matchesResponse.headers.etag, - }, - }); - - const playoffMatchOrder = - event.playoff_type === 10 - ? eightTeamDoubleElimPlayoffMatchOrder - : fourTeamDoubleElimPlayoffMatchOrder; - - // For each match in the tournament - matchesResponse.data.sort( - (a: TbaMatch, b: TbaMatch) => - (a.actual_time ?? a.time ?? 0) - (b.actual_time ?? b.time ?? 0), - ); - - for (const match of matchesResponse.data) { - if (match.comp_level == "qm") { - //all teams in the match - const teams = [ - ...match.alliances.red.team_keys, - ...match.alliances.blue.team_keys, - ]; - //make matches with trailing _0, _1, _2 etc - for (let k = 0; k < teams.length; k++) { - const currMatchKey = `${tournamentKey}_qm${match.match_number}_${k}`; - - const fakeTeamKey = teams[k]; // The one TBA sends you which is potentially "fake", like frc6418B - const mapEntry = Object.entries(remap_teams).find( - (v) => v[1] === fakeTeamKey, - ); - const realTeamKey = mapEntry ? mapEntry[0] : fakeTeamKey; - const currTeam = Number(realTeamKey.substring(3)); - - const params = z - .object({ - matchNumber: z.number(), - tournamentKey: z.string(), - key: z.string(), - teamNumber: z.number(), - }) - .safeParse({ - key: currMatchKey, - tournamentKey: tournamentKey, - matchNumber: match.match_number, - teamNumber: currTeam, - }); - - if (!params.success) { - throw params; - } - - //cant use currMatch key bc theres an issue with the enum - await prismaClient.teamMatchData.upsert({ - where: { - key: currMatchKey, - }, - update: { - tournamentKey: params.data.tournamentKey, - matchNumber: params.data.matchNumber, - teamNumber: params.data.teamNumber, - matchType: "QUALIFICATION", - }, - create: { - key: params.data.key, - tournamentKey: params.data.tournamentKey, - matchNumber: params.data.matchNumber, - teamNumber: params.data.teamNumber, - matchType: "QUALIFICATION", - }, - }); - } - } else { - const teams = [ - ...match.alliances.red.team_keys, - ...match.alliances.blue.team_keys, - ]; - - if (teams.length !== 6) { - continue; - } - - const mappedTeams: number[] = []; - let allTeamsKnown = true; - for (const teamKey of teams) { - const mapEntry = Object.entries(remap_teams).find( - (v) => v[1] === teamKey, - ); - const realTeamKey = mapEntry ? mapEntry[0] : teamKey; - const teamNumber = Number(realTeamKey.substring(3)); - if (!Number.isFinite(teamNumber) || teamNumber <= 0) { - allTeamsKnown = false; - break; - } - mappedTeams.push(teamNumber); - } - - if (!allTeamsKnown) { - continue; - } - - const matchSuffix = match.key.split("_")[1] ?? ""; - const matchNumber = playoffMatchOrder.get(matchSuffix); - if (!matchNumber) { - continue; - } - - for (let k = 0; k < 6; k++) { - const currTeam = mappedTeams[k]; - - const currMatchKey = `${tournamentKey}_em${matchNumber}_${k}`; - - const params = z - .object({ - matchNumber: z.number(), - tournamentKey: z.string(), - key: z.string(), - teamNumber: z.number(), - }) - .safeParse({ - key: currMatchKey, - tournamentKey: tournamentKey, - matchNumber: matchNumber, - teamNumber: currTeam, - }); - - if (!params.success) { - throw params; - } - - //cant use currMatch key bc theres an issue with the enum - await prismaClient.teamMatchData.upsert({ - where: { - key: currMatchKey, - }, - update: { - tournamentKey: params.data.tournamentKey, - matchNumber: params.data.matchNumber, - teamNumber: params.data.teamNumber, - matchType: "ELIMINATION", - }, - create: { - key: params.data.key, - tournamentKey: params.data.tournamentKey, - matchNumber: params.data.matchNumber, - teamNumber: params.data.teamNumber, - matchType: "ELIMINATION", - }, - }); - } - } - } - } catch (error) { - console.log(error); - } -}; diff --git a/src/handler/manager/checkMatchExists.ts b/src/handler/manager/checkMatchExists.ts index a8bd44c0..b66a5283 100644 --- a/src/handler/manager/checkMatchExists.ts +++ b/src/handler/manager/checkMatchExists.ts @@ -1,5 +1,5 @@ import z from "zod"; -import { addTournamentMatches } from "./addTournamentMatches.js"; +import { importTournamentMatches } from "../../lib/importTournamentMatches.js"; import { Request, Response } from "express"; import prismaClient from "../../prismaClient.js"; import { MatchType } from "@prisma/client"; @@ -27,7 +27,7 @@ export const checkMatchExists = async ( const params = parsed.data; - await addTournamentMatches(params.tournamentKey); + await importTournamentMatches(params.tournamentKey); const match = await prismaClient.teamMatchData.findFirst({ where: { diff --git a/src/handler/manager/getMatches.ts b/src/handler/manager/getMatches.ts index 2b9936c1..2e8f1ecf 100644 --- a/src/handler/manager/getMatches.ts +++ b/src/handler/manager/getMatches.ts @@ -2,7 +2,7 @@ import { Response } from "express"; import prismaClient from "../../prismaClient.js"; import z from "zod"; import { AuthenticatedRequest } from "../../lib/middleware/requireAuth.js"; -import { addTournamentMatches } from "./addTournamentMatches.js"; +import { importTournamentMatches } from "../../lib/importTournamentMatches.js"; import { ReverseMatchTypeMap } from "./managerConstants.js"; import { MatchType, Prisma } from "@prisma/client"; import { @@ -45,7 +45,7 @@ export const getMatches = async ( return; } - await addTournamentMatches(params.data.tournamentKey); + await importTournamentMatches(params.data.tournamentKey); // Assuming all elimination matches are not scouted, find the last scouted match (and pretend it is the last completed one) const last = await prismaClient.teamMatchData.findFirst({ diff --git a/src/handler/manager/pitDisplay.ts b/src/handler/manager/pitDisplay.ts index 59fec645..0a5e80ef 100644 --- a/src/handler/manager/pitDisplay.ts +++ b/src/handler/manager/pitDisplay.ts @@ -35,7 +35,7 @@ export const pitDisplay = async ( webcasts: null, rankingBlocks: null, }; - // await addTournamentMatches(params.data.tournamentKey) + // await importTournamentMatches(params.data.tournamentKey) const matchesWithTeam = await prismaClient.teamMatchData.findMany({ where: { tournamentKey: params.data.tournamentKey, diff --git a/src/handler/manager/scouters/getScheduleForScouter.ts b/src/handler/manager/scouters/getScheduleForScouter.ts index caed3b99..cca99ff2 100644 --- a/src/handler/manager/scouters/getScheduleForScouter.ts +++ b/src/handler/manager/scouters/getScheduleForScouter.ts @@ -3,7 +3,7 @@ import prismaClient from "../../../prismaClient.js"; import z from "zod"; import { MatchTypeMap, ScouterScheduleMap } from "../managerConstants.js"; import SHA256 from "crypto-js/sha256.js"; -import { addTournamentMatches } from "../addTournamentMatches.js"; +import { importTournamentMatches } from "../../../lib/importTournamentMatches.js"; export const getScheduleForScouter = async ( req: Request, @@ -63,7 +63,7 @@ export const getScheduleForScouter = async ( matchNumber: "desc", }, }); - await addTournamentMatches(params.data.tournamentKey); + await importTournamentMatches(params.data.tournamentKey); if (maxQualifierRow === null) { res.status(400).send({ error: "Matches are not available for this tournamnet", diff --git a/src/handler/manager/scoutreports/addScoutReport.ts b/src/handler/manager/scoutreports/addScoutReport.ts index cd4ece4e..53ade866 100644 --- a/src/handler/manager/scoutreports/addScoutReport.ts +++ b/src/handler/manager/scoutreports/addScoutReport.ts @@ -2,7 +2,7 @@ import { Request, Response } from "express"; import prismaClient from "../../../prismaClient.js"; import z from "zod"; import { PositionMap, EventActionMap } from "../managerConstants.js"; -import { addTournamentMatches } from "../addTournamentMatches.js"; +import { importTournamentMatches } from "../../../lib/importTournamentMatches.js"; import { AutoClimb, Beached, @@ -170,7 +170,7 @@ export const addScoutReport = async ( }); if (!matchRow) { - await addTournamentMatches(paramsScoutReport.tournamentKey); + await importTournamentMatches(paramsScoutReport.tournamentKey); matchRow = await prismaClient.teamMatchData.findFirst({ where: { diff --git a/src/handler/manager/scoutreports/addScoutReportDashboard.ts b/src/handler/manager/scoutreports/addScoutReportDashboard.ts index c4601870..9d8ad556 100644 --- a/src/handler/manager/scoutreports/addScoutReportDashboard.ts +++ b/src/handler/manager/scoutreports/addScoutReportDashboard.ts @@ -3,7 +3,7 @@ import prismaClient from "../../../prismaClient.js"; import z from "zod"; import { AuthenticatedRequest } from "../../../lib/middleware/requireAuth.js"; import { PositionMap, EventActionMap } from "../managerConstants.js"; -import { addTournamentMatches } from "../addTournamentMatches.js"; +import { importTournamentMatches } from "../../../lib/importTournamentMatches.js"; import { totalPointsScoutingLead } from "../../analysis/scoutingLead/totalPointsScoutingLead.js"; import { AutoClimb, @@ -106,7 +106,7 @@ export const addScoutReportDashboard = async ( }); if (!matchRow) { - await addTournamentMatches(paramsScoutReport.tournamentKey); + await importTournamentMatches(paramsScoutReport.tournamentKey); matchRow = await prismaClient.teamMatchData.findFirst({ where: { diff --git a/src/lib/fetchMatches.ts b/src/lib/fetchMatches.ts index c3261625..9eaac24a 100644 --- a/src/lib/fetchMatches.ts +++ b/src/lib/fetchMatches.ts @@ -1,5 +1,5 @@ import prisma from "../prismaClient.js"; -import { addTournamentMatches } from "../handler/manager/addTournamentMatches.js"; +import { importTournamentMatches } from "./importTournamentMatches.js"; export default async function fetchMatches(): Promise { // upsert current tournaments in the matches table @@ -25,6 +25,6 @@ export default async function fetchMatches(): Promise { }, }); for (const tournamentKeyRow of distinctTournamentKeys) { - await addTournamentMatches(tournamentKeyRow.tournamentKey); + await importTournamentMatches(tournamentKeyRow.tournamentKey); } } diff --git a/src/lib/importTournamentMatches.ts b/src/lib/importTournamentMatches.ts index a263eafc..0c0632af 100644 --- a/src/lib/importTournamentMatches.ts +++ b/src/lib/importTournamentMatches.ts @@ -5,7 +5,6 @@ import { eightTeamDoubleElimPlayoffMatchOrder, fourTeamDoubleElimPlayoffMatchOrder, } from "../handler/manager/managerConstants.js"; -import { DateTime } from "luxon"; import { AllianceColor, MatchType } from "@prisma/client"; interface TBAEventResponse { @@ -28,8 +27,8 @@ interface TBAEventResponse { city: string; state_prov: string; country: string; - start_date: DateTime; - end_date: DateTime; + start_date: string; + end_date: string; year: number; short_name: string; event_type_string: string; @@ -118,7 +117,7 @@ export const importTournamentMatches = async ( .passthrough() .parse(event).remap_teams ?? {}; - const fixRemappedTeam = async (team: string): Promise => { + const fixRemappedTeam = (team: string): number => { const fakeTeamKey = team; // The one TBA sends you which is potentially "fake", like frc6418B const mapEntry = Object.entries(remap_teams).find( (v) => v[1] === fakeTeamKey, @@ -200,7 +199,7 @@ export const importTournamentMatches = async ( key: `${tournamentKey}_qm${match.match_number}`, tournamentKey: tournamentKey, matchNumber: match.match_number, - teamNumber: await fixRemappedTeam(matchTeams[t]), + teamNumber: fixRemappedTeam(matchTeams[t]), alliance: t > 2 ? AllianceColor.BLUE : AllianceColor.RED, station: t % 3, }); @@ -248,7 +247,6 @@ export const importTournamentMatches = async ( }, }); } - console.log(`Q${match.match_number} imported`); } const elims = matches.filter((match) => match.comp_level === "em"); @@ -285,7 +283,7 @@ export const importTournamentMatches = async ( key: `${tournamentKey}_em${matchNumber}`, tournamentKey: tournamentKey, matchNumber: matchNumber, - teamNumber: await fixRemappedTeam(matchTeams[t]), + teamNumber: fixRemappedTeam(matchTeams[t]), alliance: t > 2 ? AllianceColor.BLUE : AllianceColor.RED, station: t % 3, }); @@ -423,21 +421,20 @@ const fetchFromTBA = async (url: string): Promise => { return JSON.parse(fetchRow?.data ?? "null") as T; } } catch (error) { + // Record the failed attempt + await prismaClient.dataFetch.upsert({ + where: { + key: url, + }, + create: { + key: url, + lastTried: new Date(), + }, + update: { + lastTried: new Date(), + }, + }); console.error(error); throw error; } }; - -const validateETag = async (url: string) => { - const now = new Date(); - - await prismaClient.dataFetch.update({ - where: { - key: url, - }, - data: { - lastFetched: now, - lastTried: now, - }, - }); -}; diff --git a/src/lib/scheduleJobs.ts b/src/lib/scheduleJobs.ts index 4da5b1a1..9edcae0c 100644 --- a/src/lib/scheduleJobs.ts +++ b/src/lib/scheduleJobs.ts @@ -5,7 +5,7 @@ import prisma from "../prismaClient.js"; import deleteOldRequests from "./deleteOldRequests.js"; export default async function scheduleJobs(): Promise { - const year = 2024; + const year = new Date().getFullYear(); // Prevent unnecessary fetching in dev mode which is frequently restarted if (