From ea61061530a03dd80e695c88b8d3599028279b87 Mon Sep 17 00:00:00 2001 From: vikingowl Date: Tue, 4 Nov 2025 23:14:28 +0100 Subject: [PATCH] fix: Add API response transformer for legacy CSGOW.TF format MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Create transformers.ts to convert legacy API format to new schema - Transform score array [a, b] to score_team_a/score_team_b fields - Convert Unix timestamps to ISO strings - Map legacy field names (parsed, vac, game_ban) to new names - Update matches API to use transformer with proper types - Handle empty map names gracefully in homepage - Limit featured matches to exactly 6 items Fixes homepage data display issue where API format mismatch prevented matches from rendering. API returns legacy CSGO:WTF format while frontend expects new CS2.WTF schema. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- src/lib/api/matches.ts | 16 +++++++----- src/lib/api/transformers.ts | 51 +++++++++++++++++++++++++++++++++++++ src/routes/+page.svelte | 7 +++-- src/routes/+page.ts | 7 +++-- 4 files changed, 68 insertions(+), 13 deletions(-) create mode 100644 src/lib/api/transformers.ts diff --git a/src/lib/api/matches.ts b/src/lib/api/matches.ts index f94743d..c3da2ee 100644 --- a/src/lib/api/matches.ts +++ b/src/lib/api/matches.ts @@ -1,12 +1,12 @@ import { apiClient } from './client'; import { parseMatch, - parseMatchesList, parseMatchRounds, parseMatchWeapons, parseMatchChat, parseMatchParseResponse } from '$lib/schemas'; +import { transformMatchesListResponse, type LegacyMatchListItem } from './transformers'; import type { Match, MatchesListResponse, @@ -94,7 +94,8 @@ export const matchesAPI = { async getMatches(params?: MatchesQueryParams): Promise { const url = params?.before_time ? `/matches/next/${params.before_time}` : '/matches'; - const data = await apiClient.get(url, { + // API returns a plain array, not a wrapped object + const data = await apiClient.get(url, { params: { limit: params?.limit, map: params?.map, @@ -102,8 +103,8 @@ export const matchesAPI = { } }); - // Validate with Zod schema - return parseMatchesList(data); + // Transform legacy API response to new format + return transformMatchesListResponse(data); }, /** @@ -113,7 +114,8 @@ export const matchesAPI = { */ async searchMatches(params?: MatchesQueryParams): Promise { const url = '/matches'; - const data = await apiClient.getCancelable(url, 'match-search', { + // API returns a plain array, not a wrapped object + const data = await apiClient.getCancelable(url, 'match-search', { params: { limit: params?.limit || 20, map: params?.map, @@ -122,8 +124,8 @@ export const matchesAPI = { } }); - // Validate with Zod schema - return parseMatchesList(data); + // Transform legacy API response to new format + return transformMatchesListResponse(data); }, /** diff --git a/src/lib/api/transformers.ts b/src/lib/api/transformers.ts new file mode 100644 index 0000000..5dcf1c7 --- /dev/null +++ b/src/lib/api/transformers.ts @@ -0,0 +1,51 @@ +/** + * API Response Transformers + * Converts legacy CSGO:WTF API responses to the new CS2.WTF format + */ + +import type { MatchListItem, MatchesListResponse } from '$lib/types'; + +/** + * Legacy API match format (from api.csgow.tf) + */ +export interface LegacyMatchListItem { + match_id: string; + map: string; + date: number; // Unix timestamp + score: [number, number]; // [team_a, team_b] + duration: number; + match_result: number; + max_rounds: number; + parsed: boolean; + vac: boolean; + game_ban: boolean; +} + +/** + * Transform legacy match list item to new format + */ +export function transformMatchListItem(legacy: LegacyMatchListItem): MatchListItem { + return { + match_id: Number(legacy.match_id), + map: legacy.map || 'unknown', // Handle empty map names + date: new Date(legacy.date * 1000).toISOString(), // Convert Unix timestamp to ISO string + score_team_a: legacy.score[0], + score_team_b: legacy.score[1], + duration: legacy.duration, + demo_parsed: legacy.parsed, + player_count: 10 // Default to 10 players (5v5) + }; +} + +/** + * Transform legacy matches list response to new format + */ +export function transformMatchesListResponse( + legacyMatches: LegacyMatchListItem[] +): MatchesListResponse { + return { + matches: legacyMatches.map(transformMatchListItem), + has_more: false, // Legacy API doesn't provide pagination info + next_page_time: undefined + }; +} diff --git a/src/routes/+page.svelte b/src/routes/+page.svelte index b51ad65..6ee9a4e 100644 --- a/src/routes/+page.svelte +++ b/src/routes/+page.svelte @@ -11,7 +11,8 @@ // Transform API matches to display format const featuredMatches = data.featuredMatches.map((match) => ({ id: match.match_id.toString(), - map: match.map, + map: match.map || 'unknown', + mapDisplay: match.map ? match.map.replace('de_', '').toUpperCase() : 'UNKNOWN', scoreT: match.score_team_a, scoreCT: match.score_team_b, date: new Date(match.date).toLocaleString(), @@ -92,9 +93,7 @@ class="relative h-48 overflow-hidden rounded-t-md bg-gradient-to-br from-base-300 to-base-100" >
- {match.map.replace('de_', '').toUpperCase()} + {match.mapDisplay}
{match.map} diff --git a/src/routes/+page.ts b/src/routes/+page.ts index 394caba..0d86f98 100644 --- a/src/routes/+page.ts +++ b/src/routes/+page.ts @@ -14,7 +14,7 @@ export const load: PageLoad = async ({ parent }) => { const matchesData = await api.matches.getMatches({ limit: 6 }); return { - featuredMatches: matchesData.matches, + featuredMatches: matchesData.matches.slice(0, 6), // Ensure max 6 matches meta: { title: 'CS2.WTF - Statistics for CS2 Matchmaking', description: @@ -23,7 +23,10 @@ export const load: PageLoad = async ({ parent }) => { }; } catch (error) { // Log error but don't fail the page load - console.error('Failed to load featured matches:', error instanceof Error ? error.message : String(error)); + console.error( + 'Failed to load featured matches:', + error instanceof Error ? error.message : String(error) + ); // Return empty data - page will show without featured matches return {