import PocketBase from "pocketbase"; import * as xlsx from "xlsx"; import { nanoid } from "nanoid"; import { createTeamsService } from "./src/lib/pocketbase/services/teams.ts"; import { createPlayersService } from "./src/lib/pocketbase/services/players.ts"; import { createMatchesService } from "./src/lib/pocketbase/services/matches.ts"; import { createTournamentsService } from "./src/lib/pocketbase/services/tournaments.ts"; const POCKETBASE_URL = "http://127.0.0.1:8090"; const EXCEL_FILE_PATH = "./Teams-2.xlsx"; const ADMIN_EMAIL = "kyle.yohler@gmail.com"; const ADMIN_PASSWORD = "xj44aqz9CWrNNM0o"; // --- Helpers --- async function createPlayerIfMissing(playersService, nameColumn, idColumn) { const playerId = idColumn?.trim(); if (playerId) return playerId; let firstName, lastName; if (!nameColumn || !nameColumn.trim()) { firstName = `Player_${nanoid(4)}`; lastName = "(Regional)"; } else { const parts = nameColumn.trim().split(" "); firstName = parts[0]; lastName = parts[1] || "(Regional)"; } const newPlayer = await playersService.createPlayer({ first_name: firstName, last_name: lastName }); return newPlayer.id; } async function handleTeamsSheet(rows, teamsService, playersService, pb, tournamentIdMap = {}) { console.log(`šŸ“„ Importing ${rows.length} teams...`); const teamIdMap = {}; // spreadsheet ID -> PocketBase ID for (const [i, row] of rows.entries()) { try { const spreadsheetTeamId = row["ID"]?.toString().trim(); if (!spreadsheetTeamId) { console.warn(`āš ļø [${i + 1}] Team row missing spreadsheet ID, skipping.`); continue; } const p1Id = await createPlayerIfMissing(playersService, row["P1 Name"], row["P1 ID"]); const p2Id = await createPlayerIfMissing(playersService, row["P2 Name"], row["P2 ID"]); let name = row["Name"]?.trim(); if (!name) { const p1First = row["P1 Name"]?.split(" ")[0] || "Player1"; const p2First = row["P2 Name"]?.split(" ")[0] || "Player2"; name = `${p1First} and ${p2First}`; console.warn(`āš ļø [${i + 1}] No team name found. Using generated name: ${name}`); } const existing = await pb.collection("teams").getFullList({ filter: `name = "${name}"`, fields: "id", }); if (existing.length > 0) { console.log(`ā„¹ļø [${i + 1}] Team "${name}" already exists, skipping.`); teamIdMap[spreadsheetTeamId] = existing[0].id; continue; } // If there's a tournament for this team, get its PB ID const tournamentSpreadsheetId = row["Tournament ID"]?.toString().trim(); const tournamentId = tournamentSpreadsheetId ? tournamentIdMap[tournamentSpreadsheetId] : undefined; const teamInput = { name, primary_color: row.primary_color || "", accent_color: row.accent_color || "", logo: row.logo || "", players: [p1Id, p2Id], tournament: tournamentId, // single tournament relation, private: true }; const team = await teamsService.createTeam(teamInput); teamIdMap[spreadsheetTeamId] = team.id; console.log(`āœ… [${i + 1}] Created team: ${team.name} with players: ${[p1Id, p2Id].join(", ")}`); // Add the team to the tournament's "teams" relation if (tournamentId) { await pb.collection("tournaments").update(tournamentId, { "teams+": [team.id], }); console.log(`āœ… Added team "${team.name}" to tournament ${tournamentId}`); } } catch (err) { console.error(`āŒ [${i + 1}] Failed to create team: ${err.message}`); } } return teamIdMap; } async function handleTournamentSheet(rows, tournamentsService, teamIdMap, pb) { console.log(`šŸ“„ Importing ${rows.length} tournaments...`); const tournamentIdMap = {}; const validFormats = ["double_elim", "single_elim", "groups", "swiss", "swiss_bracket"]; for (const [i, row] of rows.entries()) { try { const spreadsheetId = row["ID"]?.toString().trim(); if (!spreadsheetId) { console.warn(`āš ļø [${i + 1}] Tournament missing spreadsheet ID, skipping.`); continue; } if (!row["Name"]) { console.warn(`āš ļø [${i + 1}] Tournament name missing, skipping.`); continue; } const format = validFormats.includes(row["Format"]) ? row["Format"] : "double_elim"; // Convert start_time to ISO datetime string let startTime = null; if (row["Start Time"]) { try { startTime = new Date(row["Start Time"]).toISOString(); } catch (e) { console.warn(`āš ļø [${i + 1}] Invalid start time format, using null`); } } const tournamentInput = { name: row["Name"], start_time: startTime, format, regional: true, teams: Object.values(teamIdMap), // Add all created teams }; const tournament = await tournamentsService.createTournament(tournamentInput); tournamentIdMap[spreadsheetId] = tournament.id; console.log(`āœ… [${i + 1}] Created tournament: ${tournament.name} with ${Object.values(teamIdMap).length} teams`); } catch (err) { console.error(`āŒ [${i + 1}] Failed to create tournament: ${err.message}`); } } return tournamentIdMap; } async function handleMatchesSheet(rows, matchesService, teamIdMap, tournamentIdMap, pb) { console.log(`šŸ“„ Importing ${rows.length} matches...`); const tournamentMatchesMap = {}; for (const [i, row] of rows.entries()) { try { const homeId = teamIdMap[row["Home ID"]]; const awayId = teamIdMap[row["Away ID"]]; const tournamentId = tournamentIdMap[row["Tournament ID"]]; if (!homeId || !awayId || !tournamentId) { console.warn(`āš ļø [${i + 1}] Could not find mapping for Home, Away, or Tournament, skipping.`); continue; } // --- Ensure the teams are linked to the tournament --- for (const teamId of [homeId, awayId]) { const team = await pb.collection("teams").getOne(teamId, { fields: "tournaments" }); const tournaments = team.tournaments || []; if (!tournaments.includes(tournamentId)) { // Add tournament to team await pb.collection("teams").update(teamId, { "tournaments+": [tournamentId] }); // Add team to tournament await pb.collection("tournaments").update(tournamentId, { "teams+": [teamId] }); console.log(`āœ… Linked team ${team.name} to tournament ${tournamentId}`); } } // --- Create match --- const data = { tournament: tournamentId, home: homeId, away: awayId, home_cups: Number(row["Home cups"] || 0), away_cups: Number(row["Away cups"] || 0), status: "ended", lid: i+1 }; const match = await matchesService.createMatch(data); console.log(`āœ… [${i + 1}] Created match ID: ${match.id}`); if (!tournamentMatchesMap[tournamentId]) tournamentMatchesMap[tournamentId] = []; tournamentMatchesMap[tournamentId].push(match.id); } catch (err) { console.error(`āŒ [${i + 1}] Failed to create match: ${err.message}`); } } // Update each tournament with the created match IDs for (const [tournamentId, matchIds] of Object.entries(tournamentMatchesMap)) { try { await pb.collection("tournaments").update(tournamentId, { "matches+": matchIds }); console.log(`āœ… Updated tournament ${tournamentId} with ${matchIds.length} matches`); } catch (err) { console.error(`āŒ Failed to update tournament ${tournamentId} with matches: ${err.message}`); } } } // --- Main Import --- export async function importExcel() { const pb = new PocketBase(POCKETBASE_URL); await pb.admins.authWithPassword(ADMIN_EMAIL, ADMIN_PASSWORD); const teamsService = createTeamsService(pb); const playersService = createPlayersService(pb); const tournamentsService = createTournamentsService(pb); const matchesService = createMatchesService(pb); const workbook = xlsx.readFile(EXCEL_FILE_PATH); let teamIdMap = {}; let tournamentIdMap = {}; // Process sheets in correct order: Tournaments -> Teams -> Matches const sheetOrder = ["tournament", "tournaments", "teams", "matches"]; const processedSheets = new Set(); for (const sheetNamePattern of sheetOrder) { for (const sheetName of workbook.SheetNames) { if (processedSheets.has(sheetName)) continue; if (sheetName.toLowerCase() !== sheetNamePattern) continue; const worksheet = workbook.Sheets[sheetName]; const rows = xlsx.utils.sheet_to_json(worksheet); console.log(`\nšŸ“˜ Processing sheet: ${sheetName}`); switch (sheetName.toLowerCase()) { case "teams": teamIdMap = await handleTeamsSheet(rows, teamsService, playersService, pb, tournamentIdMap); break; case "tournament": case "tournaments": tournamentIdMap = await handleTournamentSheet(rows, tournamentsService, teamIdMap, pb); break; case "matches": await handleMatchesSheet(rows, matchesService, teamIdMap, tournamentIdMap, pb); break; default: console.log(`āš ļø No handler found for sheet '${sheetName}', skipping.`); } processedSheets.add(sheetName); } } console.log("\nšŸŽ‰ All sheets imported successfully!"); } // --- Run --- importExcel().catch(console.error);