269 lines
9.3 KiB
JavaScript
269 lines
9.3 KiB
JavaScript
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); |