From 77d4ec47b4d2cfc72a7227d205b4a61a373ba78f Mon Sep 17 00:00:00 2001 From: Pandipipas <62224708+Pandipipas@users.noreply.github.com> Date: Tue, 17 Feb 2026 18:59:54 +0100 Subject: [PATCH] feat(players): add Challonge temporary imported-player expiration --- src/dashboard/scoreko-dev/views/Players.vue | 73 +++++++++++++++++++-- 1 file changed, 69 insertions(+), 4 deletions(-) diff --git a/src/dashboard/scoreko-dev/views/Players.vue b/src/dashboard/scoreko-dev/views/Players.vue index 9b82b53..e34f784 100644 --- a/src/dashboard/scoreko-dev/views/Players.vue +++ b/src/dashboard/scoreko-dev/views/Players.vue @@ -45,6 +45,7 @@ interface ChallongeImportedPlayer extends Player { const STARTGG_TOKEN_STORAGE_KEY = 'scoreko-dev.startgg-token'; const CHALLONGE_TOKEN_STORAGE_KEY = 'scoreko-dev.challonge-token'; const STARTGG_TEMP_PLAYERS_STORAGE_KEY = 'scoreko-dev.startgg-temp-players'; +const CHALLONGE_TEMP_PLAYERS_STORAGE_KEY = 'scoreko-dev.challonge-temp-players'; const STARTGG_TEMP_FALLBACK_DURATION_SECONDS = 12 * 60 * 60; interface TemporaryStartGGPlayerMeta { @@ -53,6 +54,7 @@ interface TemporaryStartGGPlayerMeta { } type TemporaryStartGGPlayersMap = Record; +type TemporaryChallongePlayersMap = Record; const playersStore = usePlayersStore(); const rows = computed(() => playersStore.rows); @@ -122,6 +124,7 @@ const selectedStartGGPlayerIds = ref([]); const selectedTournamentSlug = ref(''); const tournamentInput = ref(''); const temporaryStartGGPlayers = ref({}); +const temporaryChallongePlayers = ref({}); let temporaryCleanupTimer: ReturnType | null = null; const oauthLoading = ref(false); @@ -171,6 +174,10 @@ const persistTemporaryStartGGPlayers = () => { localStorage.setItem(STARTGG_TEMP_PLAYERS_STORAGE_KEY, JSON.stringify(temporaryStartGGPlayers.value)); }; +const persistTemporaryChallongePlayers = () => { + localStorage.setItem(CHALLONGE_TEMP_PLAYERS_STORAGE_KEY, JSON.stringify(temporaryChallongePlayers.value)); +}; + const loadTemporaryStartGGPlayers = (): TemporaryStartGGPlayersMap => { try { const raw = localStorage.getItem(STARTGG_TEMP_PLAYERS_STORAGE_KEY); @@ -205,6 +212,40 @@ const loadTemporaryStartGGPlayers = (): TemporaryStartGGPlayersMap => { } }; +const loadTemporaryChallongePlayers = (): TemporaryChallongePlayersMap => { + try { + const raw = localStorage.getItem(CHALLONGE_TEMP_PLAYERS_STORAGE_KEY); + if (!raw) { + return {}; + } + const parsed = JSON.parse(raw) as unknown; + if (typeof parsed !== 'object' || parsed === null) { + return {}; + } + + const result: TemporaryChallongePlayersMap = {}; + Object.entries(parsed as Record).forEach(([playerId, value]) => { + if (!playerId || typeof value !== 'object' || value === null) { + return; + } + const candidate = value as Record; + const expiresAt = Number(candidate.expiresAt); + const tournamentSlug = String(candidate.tournamentSlug || '').trim(); + if (!Number.isFinite(expiresAt) || expiresAt <= 0 || !tournamentSlug) { + return; + } + result[playerId] = { + expiresAt, + tournamentSlug, + }; + }); + + return result; + } catch { + return {}; + } +}; + const tournamentOptions = computed(() => recentTournaments.value.map((tournament) => ({ label: tournament.name, @@ -276,22 +317,31 @@ const clearTemporaryCleanupTimer = () => { const cleanupExpiredTemporaryPlayers = () => { const now = Math.floor(Date.now() / 1000); - const expiredIds = Object.entries(temporaryStartGGPlayers.value) + const expiredStartGGIds = Object.entries(temporaryStartGGPlayers.value) .filter(([, meta]) => meta.expiresAt <= now) .map(([playerId]) => playerId); + const expiredChallongeIds = Object.entries(temporaryChallongePlayers.value) + .filter(([, meta]) => meta.expiresAt <= now) + .map(([playerId]) => playerId); + + const expiredIds = Array.from(new Set([...expiredStartGGIds, ...expiredChallongeIds])); if (!expiredIds.length) { return; } - const nextMeta = { ...temporaryStartGGPlayers.value }; + const nextStartGGMeta = { ...temporaryStartGGPlayers.value }; + const nextChallongeMeta = { ...temporaryChallongePlayers.value }; expiredIds.forEach((playerId) => { playersStore.removePlayer(playerId); - delete nextMeta[playerId]; + delete nextStartGGMeta[playerId]; + delete nextChallongeMeta[playerId]; }); - temporaryStartGGPlayers.value = nextMeta; + temporaryStartGGPlayers.value = nextStartGGMeta; + temporaryChallongePlayers.value = nextChallongeMeta; persistTemporaryStartGGPlayers(); + persistTemporaryChallongePlayers(); }; const checkOAuthStatus = async () => { @@ -569,6 +619,11 @@ const importSelectedChallongePlayers = () => { selectedChallongePlayerIds.value.includes(player.id), ); + const nextMeta = { ...temporaryChallongePlayers.value }; + const tournament = importingChallongeTournament.value; + const fallbackEndAt = (tournament?.startAt ?? Math.floor(Date.now() / 1000)) + STARTGG_TEMP_FALLBACK_DURATION_SECONDS; + const expiresAt = tournament?.endAt ?? fallbackEndAt; + selectedPlayers.forEach((player) => { playersStore.upsertPlayer(player.id, { gamertag: player.gamertag, @@ -577,8 +632,17 @@ const importSelectedChallongePlayers = () => { country: player.country, twitter: player.twitter, }); + + if (tournament) { + nextMeta[player.id] = { + expiresAt, + tournamentSlug: tournament.slug, + }; + } }); + temporaryChallongePlayers.value = nextMeta; + persistTemporaryChallongePlayers(); challongeImportDialogOpen.value = false; }; @@ -718,6 +782,7 @@ const handleImport = async (event: Event) => { onMounted(() => { temporaryStartGGPlayers.value = loadTemporaryStartGGPlayers(); + temporaryChallongePlayers.value = loadTemporaryChallongePlayers(); cleanupExpiredTemporaryPlayers(); temporaryCleanupTimer = setInterval(cleanupExpiredTemporaryPlayers, 60 * 1000);