feat(players): add Challonge temporary imported-player expiration

This commit is contained in:
Pandipipas
2026-02-17 18:59:54 +01:00
parent dd2d8b79ca
commit 77d4ec47b4
+69 -4
View File
@@ -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<string, TemporaryStartGGPlayerMeta>;
type TemporaryChallongePlayersMap = Record<string, TemporaryStartGGPlayerMeta>;
const playersStore = usePlayersStore();
const rows = computed<PlayerRow[]>(() => playersStore.rows);
@@ -122,6 +124,7 @@ const selectedStartGGPlayerIds = ref<string[]>([]);
const selectedTournamentSlug = ref('');
const tournamentInput = ref('');
const temporaryStartGGPlayers = ref<TemporaryStartGGPlayersMap>({});
const temporaryChallongePlayers = ref<TemporaryChallongePlayersMap>({});
let temporaryCleanupTimer: ReturnType<typeof setInterval> | 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<string, unknown>).forEach(([playerId, value]) => {
if (!playerId || typeof value !== 'object' || value === null) {
return;
}
const candidate = value as Record<string, unknown>;
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);