🔧 Refactor Challonge client and tournament logic to use reusable hooks, reducing code duplication and improving maintainability
This commit is contained in:
@@ -250,6 +250,13 @@ import TournamentDetail from '../components/challonge/TournamentDetail.vue';
|
|||||||
import { ScopeType } from '../services/challonge.service.js';
|
import { ScopeType } from '../services/challonge.service.js';
|
||||||
|
|
||||||
const { getApiKey } = useChallongeApiKey();
|
const { getApiKey } = useChallongeApiKey();
|
||||||
|
const apiKey = computed(() => getApiKey());
|
||||||
|
const maskedApiKey = computed(() => {
|
||||||
|
if (!apiKey.value) return '';
|
||||||
|
return apiKey.value.slice(0, 4) + '•••••••' + apiKey.value.slice(-4);
|
||||||
|
});
|
||||||
|
|
||||||
|
// OAuth Management
|
||||||
const {
|
const {
|
||||||
isAuthenticated,
|
isAuthenticated,
|
||||||
accessToken,
|
accessToken,
|
||||||
@@ -257,36 +264,19 @@ const {
|
|||||||
logout: oauthLogout,
|
logout: oauthLogout,
|
||||||
loading: oauthLoading
|
loading: oauthLoading
|
||||||
} = useChallongeOAuth();
|
} = useChallongeOAuth();
|
||||||
|
|
||||||
|
// Client Credentials Management
|
||||||
const {
|
const {
|
||||||
isAuthenticated: isClientCredsAuthenticated,
|
isAuthenticated: isClientCredsAuthenticated,
|
||||||
accessToken: clientCredsToken
|
accessToken: clientCredsToken
|
||||||
} = useChallongeClientCredentials();
|
} = useChallongeClientCredentials();
|
||||||
|
|
||||||
// API Configuration
|
// API Configuration
|
||||||
const apiVersion = ref('v2.1'); // 'v1' or 'v2.1'
|
const apiVersion = ref('v2.1');
|
||||||
|
|
||||||
// State management with useAsyncState
|
|
||||||
const tournamentListState = useAsyncState();
|
|
||||||
const loadMoreState = useAsyncState();
|
|
||||||
const tournamentDetailsState = useAsyncState();
|
|
||||||
|
|
||||||
// Destructure for template usage
|
|
||||||
const { data: tournaments, isLoading: loading, error } = tournamentListState;
|
|
||||||
const { isLoading: loadingMore } = loadMoreState;
|
|
||||||
|
|
||||||
const searchQuery = ref('');
|
|
||||||
const expandedTournamentId = ref(null);
|
|
||||||
|
|
||||||
// Pagination
|
|
||||||
const currentPage = ref(1);
|
|
||||||
const perPage = ref(10);
|
|
||||||
const totalTournaments = ref(0);
|
|
||||||
const hasNextPage = ref(false);
|
|
||||||
|
|
||||||
// v2.1 Tournament Scope
|
|
||||||
const tournamentScope = ref(ScopeType.USER);
|
const tournamentScope = ref(ScopeType.USER);
|
||||||
|
const perPage = ref(100);
|
||||||
|
|
||||||
// Debug mode - enabled to see what's happening
|
// Debug mode
|
||||||
const debugMode = ref(true);
|
const debugMode = ref(true);
|
||||||
|
|
||||||
// Collapsible section states
|
// Collapsible section states
|
||||||
@@ -294,249 +284,56 @@ const apiKeyCollapsed = ref(false);
|
|||||||
const oauthCollapsed = ref(false);
|
const oauthCollapsed = ref(false);
|
||||||
const clientCredsCollapsed = ref(false);
|
const clientCredsCollapsed = ref(false);
|
||||||
|
|
||||||
// Make apiKey reactive
|
// Initialize Challonge Client (replaces ~100 lines of inline client creation)
|
||||||
const apiKey = computed(() => getApiKey());
|
const {
|
||||||
|
client,
|
||||||
|
apiVersion: clientApiVersion,
|
||||||
|
tournamentScope: clientTournamentScope,
|
||||||
|
maskedApiKey: clientMaskedApiKey,
|
||||||
|
authType
|
||||||
|
} = useChallongeClient(
|
||||||
|
apiKey,
|
||||||
|
apiVersion,
|
||||||
|
tournamentScope,
|
||||||
|
{
|
||||||
|
oauthToken: accessToken,
|
||||||
|
oauthAuthenticated: isAuthenticated,
|
||||||
|
clientCredsToken: clientCredsToken,
|
||||||
|
clientCredsAuthenticated: isClientCredsAuthenticated
|
||||||
|
},
|
||||||
|
debugMode
|
||||||
|
);
|
||||||
|
|
||||||
const maskedApiKey = computed(() => {
|
// Initialize Tournament Tests (replaces ~200 lines of tournament logic)
|
||||||
if (!apiKey.value) return '';
|
const {
|
||||||
return apiKey.value.slice(0, 4) + '•••••••' + apiKey.value.slice(-4);
|
tournaments,
|
||||||
|
loading,
|
||||||
|
loadingMore,
|
||||||
|
error,
|
||||||
|
searchQuery,
|
||||||
|
expandedTournamentId,
|
||||||
|
currentPage,
|
||||||
|
hasNextPage,
|
||||||
|
tournamentDetails,
|
||||||
|
paginationInfo,
|
||||||
|
filteredTournaments,
|
||||||
|
testListTournaments,
|
||||||
|
loadMoreTournaments,
|
||||||
|
changePerPage,
|
||||||
|
toggleTournamentDetails,
|
||||||
|
resetState,
|
||||||
|
tournamentDetailsState
|
||||||
|
} = useChallongeTests(client, apiVersion, tournamentScope);
|
||||||
|
|
||||||
|
// Update perPage when selector changes
|
||||||
|
watch(perPage, (newValue) => {
|
||||||
|
changePerPage(newValue);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Tournament details computed from async state
|
|
||||||
const tournamentDetails = computed(() => tournamentDetailsState.data.value);
|
|
||||||
|
|
||||||
// Create API client reactively based on version, key, and OAuth status
|
|
||||||
const client = computed(() => {
|
|
||||||
if (apiVersion.value === 'v1') {
|
|
||||||
// v1 only supports API key
|
|
||||||
if (!apiKey.value) return null;
|
|
||||||
return createChallongeV1Client(apiKey.value);
|
|
||||||
} else {
|
|
||||||
// v2.1 supports OAuth, client credentials, and API key
|
|
||||||
// Smart priority based on scope selection:
|
|
||||||
// - APPLICATION scope: prefer client credentials (they're designed for this)
|
|
||||||
// - USER scope: prefer OAuth user tokens or API key (client creds don't work well here)
|
|
||||||
|
|
||||||
if (tournamentScope.value === ScopeType.APPLICATION) {
|
|
||||||
// APPLICATION scope - prefer client credentials
|
|
||||||
if (isClientCredsAuthenticated.value && clientCredsToken.value) {
|
|
||||||
console.log('🔐 Using Client Credentials token for APPLICATION scope');
|
|
||||||
return createChallongeV2Client(
|
|
||||||
{ token: clientCredsToken.value, type: AuthType.OAUTH },
|
|
||||||
{ debug: debugMode.value }
|
|
||||||
);
|
|
||||||
} else if (isAuthenticated.value && accessToken.value) {
|
|
||||||
console.log('🔐 Using OAuth user token for APPLICATION scope');
|
|
||||||
return createChallongeV2Client(
|
|
||||||
{ token: accessToken.value, type: AuthType.OAUTH },
|
|
||||||
{ debug: debugMode.value }
|
|
||||||
);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// USER scope - prefer OAuth user tokens or API key (client creds may not work)
|
|
||||||
if (isAuthenticated.value && accessToken.value) {
|
|
||||||
console.log('🔐 Using OAuth user token for USER scope');
|
|
||||||
return createChallongeV2Client(
|
|
||||||
{ token: accessToken.value, type: AuthType.OAUTH },
|
|
||||||
{ debug: debugMode.value }
|
|
||||||
);
|
|
||||||
} else if (apiKey.value) {
|
|
||||||
console.log('🔑 Using API Key for USER scope');
|
|
||||||
return createChallongeV2Client(
|
|
||||||
{ token: apiKey.value, type: AuthType.API_KEY },
|
|
||||||
{ debug: debugMode.value }
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fallback: try API key
|
|
||||||
if (apiKey.value) {
|
|
||||||
console.log('🔑 Using API Key (fallback)');
|
|
||||||
return createChallongeV2Client(
|
|
||||||
{ token: apiKey.value, type: AuthType.API_KEY },
|
|
||||||
{ debug: debugMode.value }
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Pagination info
|
|
||||||
const paginationInfo = computed(() => {
|
|
||||||
if (!tournaments.value) return '';
|
|
||||||
const start = (currentPage.value - 1) * perPage.value + 1;
|
|
||||||
const end = Math.min(
|
|
||||||
start + tournaments.value.length - 1,
|
|
||||||
totalTournaments.value || tournaments.value.length
|
|
||||||
);
|
|
||||||
const total = totalTournaments.value || tournaments.value.length;
|
|
||||||
return `Showing ${start}-${end} of ${total}`;
|
|
||||||
});
|
|
||||||
|
|
||||||
// Filter tournaments (client-side for now, can be moved to server-side)
|
|
||||||
const filteredTournaments = computed(() => {
|
|
||||||
if (!tournaments.value) return null;
|
|
||||||
if (!searchQuery.value.trim()) return tournaments.value;
|
|
||||||
|
|
||||||
const query = searchQuery.value.toLowerCase();
|
|
||||||
return tournaments.value.filter(t => {
|
|
||||||
const name = getTournamentName(t).toLowerCase();
|
|
||||||
return name.includes(query);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// Helper to get tournament name (handles both v1 and v2.1 response structures)
|
|
||||||
function getTournamentName(tournament) {
|
|
||||||
return tournament.tournament?.name || tournament.name || '';
|
|
||||||
}
|
|
||||||
|
|
||||||
// Helper to get tournament ID
|
|
||||||
function getTournamentId(tournament) {
|
|
||||||
return tournament.tournament?.id || tournament.id;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Helper to get tournament property
|
|
||||||
function getTournamentProp(tournament, prop) {
|
|
||||||
return tournament.tournament?.[prop] || tournament[prop];
|
|
||||||
}
|
|
||||||
|
|
||||||
async function testListTournaments(resetPagination = true) {
|
|
||||||
if (resetPagination) {
|
|
||||||
currentPage.value = 1;
|
|
||||||
searchQuery.value = '';
|
|
||||||
expandedTournamentId.value = null;
|
|
||||||
tournamentDetailsState.reset();
|
|
||||||
}
|
|
||||||
|
|
||||||
await tournamentListState.execute(async () => {
|
|
||||||
if (apiVersion.value === 'v1') {
|
|
||||||
// v1 doesn't support pagination
|
|
||||||
const result = await client.value.tournaments.list();
|
|
||||||
totalTournaments.value = result.length;
|
|
||||||
hasNextPage.value = false;
|
|
||||||
return result;
|
|
||||||
} else {
|
|
||||||
// v2.1 - Query all tournament states (pending, in_progress, ended) in parallel
|
|
||||||
// USER scope returns tournaments you have access to:
|
|
||||||
// - Tournaments you created
|
|
||||||
// - Tournaments where you're added as an admin
|
|
||||||
const result = await queryAllTournaments(client.value, {
|
|
||||||
page: currentPage.value,
|
|
||||||
per_page: 100,
|
|
||||||
scopeType: tournamentScope.value
|
|
||||||
});
|
|
||||||
|
|
||||||
console.log('📊 Tournament API Response (All States):', {
|
|
||||||
page: currentPage.value,
|
|
||||||
perPage: 100,
|
|
||||||
scope: tournamentScope.value,
|
|
||||||
scopeRaw: tournamentScope.value,
|
|
||||||
ScopeType_USER: ScopeType.USER,
|
|
||||||
ScopeType_APPLICATION: ScopeType.APPLICATION,
|
|
||||||
states: [
|
|
||||||
'pending',
|
|
||||||
'checking_in',
|
|
||||||
'checked_in',
|
|
||||||
'accepting_predictions',
|
|
||||||
'group_stages_underway',
|
|
||||||
'group_stages_finalized',
|
|
||||||
'underway',
|
|
||||||
'awaiting_review',
|
|
||||||
'complete'
|
|
||||||
],
|
|
||||||
resultsCount: result.length,
|
|
||||||
isOAuthAuthenticated: isAuthenticated.value,
|
|
||||||
isClientCredsAuthenticated: isClientCredsAuthenticated.value,
|
|
||||||
authType: isClientCredsAuthenticated.value
|
|
||||||
? 'Client Credentials'
|
|
||||||
: isAuthenticated.value
|
|
||||||
? 'OAuth'
|
|
||||||
: 'API Key',
|
|
||||||
results: result
|
|
||||||
});
|
|
||||||
|
|
||||||
totalTournaments.value = result.length;
|
|
||||||
hasNextPage.value = result.length >= 100;
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
async function loadMoreTournaments() {
|
|
||||||
if (apiVersion.value === 'v1') return; // v1 doesn't support pagination
|
|
||||||
|
|
||||||
currentPage.value++;
|
|
||||||
|
|
||||||
const result = await loadMoreState.execute(async () => {
|
|
||||||
const newResults = await queryAllTournaments(client.value, {
|
|
||||||
page: currentPage.value,
|
|
||||||
per_page: 100,
|
|
||||||
scopeType: tournamentScope.value
|
|
||||||
});
|
|
||||||
|
|
||||||
hasNextPage.value = newResults.length === perPage.value;
|
|
||||||
return newResults;
|
|
||||||
});
|
|
||||||
|
|
||||||
if (result) {
|
|
||||||
// Append new results to existing tournaments
|
|
||||||
tournaments.value = [...tournaments.value, ...result];
|
|
||||||
} else {
|
|
||||||
// Revert page increment on error
|
|
||||||
currentPage.value--;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function changePerPage(newLimit) {
|
|
||||||
perPage.value = newLimit;
|
|
||||||
await testListTournaments(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
async function toggleTournamentDetails(tournamentId) {
|
|
||||||
if (expandedTournamentId.value === tournamentId) {
|
|
||||||
expandedTournamentId.value = null;
|
|
||||||
tournamentDetailsState.reset();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
expandedTournamentId.value = tournamentId;
|
|
||||||
|
|
||||||
await tournamentDetailsState.execute(async () => {
|
|
||||||
if (apiVersion.value === 'v1') {
|
|
||||||
return await client.value.tournaments.get(tournamentId, {
|
|
||||||
includeParticipants: true,
|
|
||||||
includeMatches: true
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
// v2.1 get tournament
|
|
||||||
return await client.value.tournaments.get(tournamentId);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Reset expanded state if there was an error
|
|
||||||
if (tournamentDetailsState.error.value) {
|
|
||||||
expandedTournamentId.value = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function switchApiVersion() {
|
|
||||||
// Clear state when switching versions
|
|
||||||
tournamentListState.reset();
|
|
||||||
loadMoreState.reset();
|
|
||||||
tournamentDetailsState.reset();
|
|
||||||
searchQuery.value = '';
|
|
||||||
expandedTournamentId.value = null;
|
|
||||||
currentPage.value = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
function formatDate(dateString) {
|
|
||||||
if (!dateString) return '';
|
|
||||||
return new Date(dateString).toLocaleString();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Watch for API version changes
|
// Watch for API version changes
|
||||||
watch(apiVersion, switchApiVersion);
|
watch(apiVersion, () => {
|
||||||
|
resetState();
|
||||||
|
});
|
||||||
|
|
||||||
// Check for debug mode on mount
|
// Check for debug mode on mount
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
|
|||||||
Reference in New Issue
Block a user