From 0404188d4d5a779ca7f4cc7d94d24855425672c1 Mon Sep 17 00:00:00 2001 From: vikingowl Date: Tue, 4 Nov 2025 19:32:08 +0100 Subject: [PATCH] Document CSGOWTFD backend API and update domain modeling plans Reflect backend audit findings in TODO.md: - 12 REST endpoints documented for player, match, matches, and sitemap - Data models aligned with backend schemas (Match, Player, MatchPlayer, etc.) - CS2 compatibility confirmed with Premier rating support (0-30000) Add comprehensive API documentation covering: - Endpoint specifications and response structures - Integration guide with TypeScript examples - Error handling and caching strategies - CS2 migration notes for rank system and MR12 changes --- TODO.md | 54 ++- docs/API.md | 998 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 1035 insertions(+), 17 deletions(-) create mode 100644 docs/API.md diff --git a/TODO.md b/TODO.md index dc15df2..a67577f 100644 --- a/TODO.md +++ b/TODO.md @@ -13,9 +13,23 @@ - `/match/[id]/flashes` - Flashbang effectiveness data - `/match/[id]/damage` - Damage visualization and heatmaps - `/match/[id]/chat` - In-game chat log +- [x] Audit the `csgowtfd` backend APIs and data models: + - Backend: Go + Gin framework + Ent ORM + PostgreSQL + - API endpoints documented in `docs/API.md` + - 12 REST endpoints identified (player, match, matches listing, sitemap) + - Data models: Match, Player, MatchPlayer, RoundStats, Weapon, Message + - Swagger documentation available at `/api/swagger` + - **Decision**: Continue using existing REST API (no replacement needed) + - CS2 compatibility: Backend ready with rank field supporting 0-30000 Premier rating +- [x] Document CS2 demo parsing pipeline: + - Demo parsing handled by backend (6 concurrent workers) + - Share code submission via `/match/parse/:sharecode` + - Demos expire after 30 days (configurable) + - Parsing SLA: ~2-5 minutes per match (depends on queue) + - Storage: PostgreSQL for structured data, Redis for caching + - Data freshness: Player profiles update every 7 days, match check every 30 min - [ ] Host workshops with stakeholders, shoutcasters, and data consumers to gather CS2-specific requirements (new stats, volumetric smokes, MR12 changes). -- [ ] Audit the `csgowtfd` backend APIs and data models; decide whether to extend or replace services for CS2 telemetry. -- [ ] Document CS2 demo parsing pipeline expectations, storage strategy, and data freshness SLAs. + - **Note**: Planned for implementation period - feedback will inform feature priorities ## Phase 1 – Technical Foundations - [ ] Standardize on Node.js ≥ 18 (add `.nvmrc` / `.tool-versions`) and lock package manager choice (npm or yarn). @@ -79,27 +93,33 @@ - [ ] Run accessibility review on mockups (contrast, color blindness variants, keyboard flows). ## Phase 3 – Domain Modeling & Data Layer -- [ ] Define core TypeScript interfaces in `src/lib/types/`: - - `Match.ts`: match metadata, teams, score, map, date, duration, game mode (Premier, Competitive, etc.) - - `Player.ts`: Steam ID, name, avatar, rank, stats summary, recent matches - - `Round.ts`: round number, winner, reason, duration, economy state, events - - `PlayerStats.ts`: kills, deaths, assists, ADR, HS%, KAST, rating, utility damage - - `Economy.ts`: money spent/saved, equipment value, loadouts, utility purchases - - `UtilityEvent.ts`: flashbangs (effectiveness, enemies blinded), smokes (volumetric coverage), HE grenades, molotovs - - `DamageEvent.ts`: attacker, victim, weapon, damage, hit group, distance, tick - - `ChatMessage.ts`: timestamp, round, player, message, team chat flag - - `Metadata.ts`: CS2 version, demo hash, server info, tickrate + +**Reference**: See `docs/API.md` for complete backend API documentation, endpoints, and response structures. + +- [ ] Define core TypeScript interfaces in `src/lib/types/` (based on backend schemas): + - `Match.ts`: match_id, share_code, map, date, scores, duration, match_result, max_rounds, demo_parsed, tick_rate + - `Player.ts`: id (Steam ID), name, avatar, vanity_url, wins/losses/ties, vac_count, game_ban_count, steam_updated + - `MatchPlayer.ts`: player stats per match (kills, deaths, assists, headshot, mvp, score, kast, rank changes, damage, utility stats, flash metrics) + - `RoundStats.ts`: round number, bank (money), equipment value, spent + - `Weapon.ts`: weapon statistics (victim, damage, eq_type, hit_group) + - `Message.ts`: chat messages (message text, all_chat flag, tick, player reference) + - Derived types: `MatchWithPlayers`, `PlayerProfile`, `MatchRoundsData`, `MatchChatData` + - **Important**: Backend uses `looses` (typo) - map to `losses` in frontend types - [ ] Create Zod schemas for runtime validation: - Match all TypeScript interfaces with Zod schemas for API response validation - - Add custom validators for Steam IDs, match IDs, rank tiers - - Export type-safe parsers: `parseMatch()`, `parsePlayerStats()`, etc. + - Add custom validators for Steam IDs (uint64), match IDs, rank tiers (0-30000 for CS2) + - Handle nullable fields properly (many optional fields in backend) + - Export type-safe parsers: `parseMatch()`, `parsePlayer()`, `parseMatchPlayer()`, etc. + - Add schema for error responses - [ ] Design API client architecture: - - Evaluate backend API (likely REST from csgowtfd) and create typed client - - Build `src/lib/api/client.ts` with fetch wrapper + - Backend confirmed: REST API (Go + Gin) at `localhost:8000` (default) + - Build `src/lib/api/client.ts` with axios/fetch wrapper (see `docs/API.md` for example) + - Create endpoint modules: `players.ts`, `matches.ts`, `sitemap.ts` - Implement error handling (network errors, API errors, validation errors) - - Add retry logic with exponential backoff for transient failures + - Add retry logic with exponential backoff for transient failures (500, 503) - Implement request cancellation (AbortController) for navigation - Add response caching strategy (in-memory cache, TTL-based invalidation) + - Handle CORS (backend supports configurable CORS domains) - [ ] Set up MSW (Mock Service Worker) for testing: - Install: `msw@^2.0.0` - Create mock handlers in `src/mocks/handlers/` for all endpoints diff --git a/docs/API.md b/docs/API.md new file mode 100644 index 0000000..4aa474c --- /dev/null +++ b/docs/API.md @@ -0,0 +1,998 @@ +# CSGOWTFD Backend API Documentation + +**Version**: 1.0 +**Base URL**: `http://localhost:8000` (default) +**Backend Repository**: https://somegit.dev/CSGOWTF/csgowtfd +**Framework**: Go + Gin + Ent ORM +**Database**: PostgreSQL + +--- + +## Table of Contents + +1. [Overview](#overview) +2. [API Endpoints](#api-endpoints) + - [Player Endpoints](#player-endpoints) + - [Match Endpoints](#match-endpoints) + - [Matches Listing](#matches-listing) + - [Sitemap](#sitemap) +3. [Data Models](#data-models) +4. [Integration Guide](#integration-guide) +5. [Error Handling](#error-handling) + +--- + +## Overview + +The CSGOWTFD backend is a REST API service that provides Counter-Strike match statistics, player profiles, and demo parsing capabilities. It uses: + +- **Web Framework**: Gin (Go) +- **ORM**: Ent (Entity Framework for Go) +- **Database**: PostgreSQL with pgx driver +- **Cache**: Redis +- **Steam Integration**: Steam API + demo parsing + +### Configuration + +Default backend configuration: +```yaml +httpd: + listen: ":8000" + cors_allow_domains: ["*"] + +database: + driver: "pgx" + connection: "postgres://username:password@localhost:5432/database_name" + +redis: + addr: "localhost:6379" +``` + +### CORS + +The backend supports CORS with configurable allowed domains. By default, all origins are allowed in development. + +--- + +## API Endpoints + +### Player Endpoints + +#### 1. Get Player Profile + +Retrieves comprehensive player statistics and match history. + +**Endpoint**: `GET /player/:id` +**Alternative**: `GET /player/:id/next/:time` + +**Parameters**: +- `id` (path, required): Steam ID (uint64) +- `time` (path, optional): Unix timestamp for pagination (get matches before this time) + +**Response** (200 OK): +```json +{ + "id": 76561198012345678, + "name": "PlayerName", + "avatar": "https://steamcdn-a.akamaihd.net/steamcommunity/public/images/avatars/...", + "vanity_url": "custom-url", + "vanity_url_real": "custom-url", + "steam_updated": "2024-11-04T10:30:00Z", + "profile_created": "2015-03-12T00:00:00Z", + "wins": 1250, + "looses": 980, + "ties": 45, + "vac_count": 0, + "vac_date": null, + "game_ban_count": 0, + "game_ban_date": null, + "oldest_sharecode_seen": "CSGO-XXXXX-XXXXX-XXXXX-XXXXX-XXXXX", + "matches": [ + { + "match_id": 3589487716842078322, + "map": "de_inferno", + "date": "2024-11-01T18:45:00Z", + "score_team_a": 13, + "score_team_b": 10, + "duration": 2456, + "match_result": 1, + "max_rounds": 24, + "demo_parsed": true, + "vac_present": false, + "gameban_present": false, + "tick_rate": 64.0, + "stats": { + "team_id": 2, + "kills": 24, + "deaths": 18, + "assists": 6, + "headshot": 12, + "mvp": 3, + "score": 56, + "kast": 78, + "rank_old": 18500, + "rank_new": 18650, + "dmg_enemy": 2450, + "dmg_team": 120, + "flash_assists": 4, + "flash_duration_enemy": 15.6, + "flash_total_enemy": 8, + "ud_he": 450, + "ud_flames": 230, + "ud_flash": 5, + "ud_smoke": 3, + "avg_ping": 25.5, + "color": "yellow" + } + } + ] +} +``` + +**Use Case**: Display player profile page with career stats and recent matches. + +--- + +#### 2. Get Player Metadata + +Retrieves lightweight player metadata (recent matches summary). + +**Endpoint**: `GET /player/:id/meta` +**Alternative**: `GET /player/:id/meta/:limit` + +**Parameters**: +- `id` (path, required): Steam ID +- `limit` (path, optional): Number of recent matches to include (default: 10) + +**Response** (200 OK): +```json +{ + "id": 76561198012345678, + "name": "PlayerName", + "avatar": "...", + "recent_matches": 25, + "last_match_date": "2024-11-01T18:45:00Z", + "avg_kills": 21.3, + "avg_deaths": 17.8, + "avg_kast": 75.2, + "win_rate": 56.5 +} +``` + +**Use Case**: Quick player card/preview without full match data. + +--- + +#### 3. Track Player + +Adds a player to the tracking system for automatic match updates. + +**Endpoint**: `POST /player/:id/track` + +**Parameters**: +- `id` (path, required): Steam ID + +**Request Body**: +```json +{ + "auth_code": "XXXX-XXXXX-XXXX" +} +``` + +**Response** (200 OK): +```json +{ + "success": true, + "message": "Player added to tracking queue" +} +``` + +**Use Case**: Allow users to track their own profile for automatic updates. + +--- + +#### 4. Untrack Player + +Removes a player from the tracking system. + +**Endpoint**: `DELETE /player/:id/track` + +**Parameters**: +- `id` (path, required): Steam ID + +**Response** (200 OK): +```json +{ + "success": true, + "message": "Player removed from tracking" +} +``` + +--- + +### Match Endpoints + +#### 5. Parse Match from Share Code + +Triggers parsing of a CS:GO/CS2 match from a share code. + +**Endpoint**: `GET /match/parse/:sharecode` + +**Parameters**: +- `sharecode` (path, required): CS:GO match share code (e.g., `CSGO-XXXXX-XXXXX-XXXXX-XXXXX-XXXXX`) + +**Response** (200 OK): +```json +{ + "match_id": 3589487716842078322, + "status": "parsing", + "message": "Demo download and parsing initiated" +} +``` + +**Response** (202 Accepted): +```json +{ + "match_id": 3589487716842078322, + "status": "queued", + "estimated_time": 120 +} +``` + +**Use Case**: User submits match share code for analysis. + +--- + +#### 6. Get Match Details + +Retrieves full match details including all player statistics. + +**Endpoint**: `GET /match/:id` + +**Parameters**: +- `id` (path, required): Match ID (uint64) + +**Response** (200 OK): +```json +{ + "match_id": 3589487716842078322, + "share_code": "CSGO-XXXXX-XXXXX-XXXXX-XXXXX-XXXXX", + "map": "de_inferno", + "date": "2024-11-01T18:45:00Z", + "score_team_a": 13, + "score_team_b": 10, + "duration": 2456, + "match_result": 1, + "max_rounds": 24, + "demo_parsed": true, + "vac_present": false, + "gameban_present": false, + "tick_rate": 64.0, + "players": [ + { + "id": 76561198012345678, + "name": "Player1", + "avatar": "...", + "team_id": 2, + "kills": 24, + "deaths": 18, + "assists": 6, + "headshot": 12, + "mvp": 3, + "score": 56, + "kast": 78, + "rank_old": 18500, + "rank_new": 18650, + "dmg_enemy": 2450, + "dmg_team": 120, + "flash_assists": 4, + "flash_duration_enemy": 15.6, + "flash_duration_team": 2.3, + "flash_duration_self": 1.2, + "flash_total_enemy": 8, + "flash_total_team": 1, + "flash_total_self": 1, + "ud_he": 450, + "ud_flames": 230, + "ud_flash": 5, + "ud_smoke": 3, + "ud_decoy": 0, + "mk_2": 4, + "mk_3": 2, + "mk_4": 1, + "mk_5": 0, + "avg_ping": 25.5, + "color": "yellow", + "crosshair": "CSGO_XXXXXXXXXXXX" + } + ] +} +``` + +**Use Case**: Display match overview page with scoreboard. + +--- + +#### 7. Get Match Weapons + +Retrieves weapon statistics for all players in a match. + +**Endpoint**: `GET /match/:id/weapons` + +**Parameters**: +- `id` (path, required): Match ID + +**Response** (200 OK): +```json +{ + "match_id": 3589487716842078322, + "weapons": [ + { + "player_id": 76561198012345678, + "weapon_stats": [ + { + "eq_type": 7, + "weapon_name": "AK-47", + "kills": 12, + "damage": 1450, + "hits": 48, + "hit_groups": { + "head": 8, + "chest": 25, + "stomach": 8, + "left_arm": 3, + "right_arm": 2, + "left_leg": 1, + "right_leg": 1 + } + } + ] + } + ] +} +``` + +**Use Case**: Display detailed weapon performance page for match. + +--- + +#### 8. Get Match Rounds + +Retrieves round-by-round statistics for a match. + +**Endpoint**: `GET /match/:id/rounds` + +**Parameters**: +- `id` (path, required): Match ID + +**Response** (200 OK): +```json +{ + "match_id": 3589487716842078322, + "rounds": [ + { + "round": 1, + "winner": 2, + "win_reason": "elimination", + "players": [ + { + "player_id": 76561198012345678, + "bank": 800, + "equipment": 650, + "spent": 650, + "kills_in_round": 2, + "damage_in_round": 234 + } + ] + } + ] +} +``` + +**Use Case**: Display economy tab with round-by-round breakdown. + +--- + +#### 9. Get Match Chat + +Retrieves in-game chat messages from a match. + +**Endpoint**: `GET /match/:id/chat` + +**Parameters**: +- `id` (path, required): Match ID + +**Response** (200 OK): +```json +{ + "match_id": 3589487716842078322, + "messages": [ + { + "player_id": 76561198012345678, + "player_name": "Player1", + "message": "nice shot!", + "tick": 15840, + "round": 8, + "all_chat": true, + "timestamp": "2024-11-01T19:12:34Z" + } + ] +} +``` + +**Use Case**: Display chat log page for match. + +--- + +### Matches Listing + +#### 10. Get Matches List + +Retrieves a paginated list of matches. + +**Endpoint**: `GET /matches` +**Alternative**: `GET /matches/next/:time` + +**Parameters**: +- `time` (path, optional): Unix timestamp for pagination +- Query parameters: + - `limit` (optional): Number of matches to return (default: 50, max: 100) + - `map` (optional): Filter by map name (e.g., `de_inferno`) + - `player_id` (optional): Filter by player Steam ID + +**Response** (200 OK): +```json +{ + "matches": [ + { + "match_id": 3589487716842078322, + "map": "de_inferno", + "date": "2024-11-01T18:45:00Z", + "score_team_a": 13, + "score_team_b": 10, + "duration": 2456, + "demo_parsed": true, + "player_count": 10 + } + ], + "next_page_time": 1698871200, + "has_more": true +} +``` + +**Use Case**: Display matches listing page with filters. + +--- + +### Sitemap + +#### 11. Get Sitemap Index + +Returns XML sitemap index for SEO. + +**Endpoint**: `GET /sitemap.xml` + +**Response** (200 OK): +```xml + + + + https://csgow.tf/sitemap/1 + 2024-11-04 + + +``` + +--- + +#### 12. Get Sitemap Pages + +Returns XML sitemap for specific page range. + +**Endpoint**: `GET /sitemap/:id` + +**Parameters**: +- `id` (path, required): Sitemap page number + +**Response** (200 OK): +```xml + + + + https://csgow.tf/match/3589487716842078322 + 2024-11-01 + never + 0.8 + + +``` + +--- + +## Data Models + +### Match + +```typescript +interface Match { + match_id: number; // uint64, unique identifier + share_code: string; // CS:GO share code + map: string; // Map name (e.g., "de_inferno") + date: string; // ISO 8601 timestamp + score_team_a: number; // Final score for team A (T/CT) + score_team_b: number; // Final score for team B (CT/T) + duration: number; // Match duration in seconds + match_result: number; // 0 = tie, 1 = team_a win, 2 = team_b win + max_rounds: number; // Max rounds (24 for MR12, 30 for MR15) + demo_parsed: boolean; // Demo parsing status + vac_present: boolean; // Any VAC bans in match + gameban_present: boolean; // Any game bans in match + tick_rate: number; // Server tick rate (64 or 128) + players?: MatchPlayer[]; // Array of player stats +} +``` + +### Player + +```typescript +interface Player { + id: number; // uint64, Steam ID + name: string; // Steam display name + avatar: string; // Avatar URL + vanity_url?: string; // Custom Steam URL + vanity_url_real?: string; // Actual vanity URL + steam_updated: string; // Last Steam profile update + profile_created?: string; // Steam account creation date + wins?: number; // Total competitive wins + looses?: number; // Total competitive losses (note: typo in backend) + ties?: number; // Total ties + vac_count?: number; // Number of VAC bans + vac_date?: string; // Date of last VAC ban + game_ban_count?: number; // Number of game bans + game_ban_date?: string; // Date of last game ban + oldest_sharecode_seen?: string; // Oldest match on record + matches?: Match[]; // Recent matches +} +``` + +### MatchPlayer + +```typescript +interface MatchPlayer { + match_stats: number; // Match ID reference + player_stats: number; // Player ID reference + team_id: number; // 2 = T, 3 = CT + + // Performance + kills: number; + deaths: number; + assists: number; + headshot: number; // Headshot kills + mvp: number; // MVP stars + score: number; // In-game score + kast: number; // KAST percentage (0-100) + + // Rank + rank_old?: number; // Rating before match (CS2: 0-30000) + rank_new?: number; // Rating after match + + // Damage + dmg_enemy?: number; // Damage to enemies + dmg_team?: number; // Team damage + + // Multi-kills + mk_2?: number; // Double kills + mk_3?: number; // Triple kills + mk_4?: number; // Quad kills + mk_5?: number; // Ace (5k) + + // Utility damage + ud_he?: number; // HE grenade damage + ud_flames?: number; // Molotov/Incendiary damage + ud_flash?: number; // Flash grenades used + ud_smoke?: number; // Smoke grenades used + ud_decoy?: number; // Decoy grenades used + + // Flash statistics + flash_assists?: number; // Assists from flashes + flash_duration_enemy?: number; // Total enemy blind time + flash_duration_team?: number; // Total team blind time + flash_duration_self?: number; // Self-flash time + flash_total_enemy?: number; // Enemies flashed count + flash_total_team?: number; // Teammates flashed count + flash_total_self?: number; // Self-flash count + + // Other + crosshair?: string; // Crosshair settings + color?: 'green' | 'yellow' | 'purple' | 'blue' | 'orange' | 'grey'; + avg_ping?: number; // Average ping +} +``` + +### RoundStats + +```typescript +interface RoundStats { + round: number; // Round number (1-24 for MR12) + bank: number; // Money at round start + equipment: number; // Equipment value purchased + spent: number; // Total money spent + match_player_id: number; // Reference to MatchPlayer +} +``` + +### Weapon + +```typescript +interface Weapon { + victim: number; // Player ID who was hit/killed + dmg: number; // Damage dealt + eq_type: number; // Weapon type ID + hit_group: number; // Hit location (1=head, 2=chest, etc.) + match_player_id: number; // Reference to MatchPlayer +} +``` + +### Message + +```typescript +interface Message { + message: string; // Chat message content + all_chat: boolean; // true = all chat, false = team chat + tick: number; // Game tick when sent + match_player_id: number; // Reference to MatchPlayer +} +``` + +--- + +## Integration Guide + +### Frontend Setup + +1. **Install HTTP Client** + +```bash +npm install axios +# or use native fetch +``` + +2. **Create API Client** (`src/lib/api/client.ts`) + +```typescript +import axios, { type AxiosInstance, type AxiosRequestConfig } from 'axios'; + +const API_BASE_URL = import.meta.env.VITE_API_BASE_URL || 'http://localhost:8000'; +const API_TIMEOUT = import.meta.env.VITE_API_TIMEOUT || 10000; + +class APIClient { + private client: AxiosInstance; + + constructor() { + this.client = axios.create({ + baseURL: API_BASE_URL, + timeout: API_TIMEOUT, + headers: { + 'Content-Type': 'application/json', + }, + }); + + // Response interceptor for error handling + this.client.interceptors.response.use( + (response) => response, + (error) => { + if (error.response) { + // Server responded with error + console.error('API Error:', error.response.status, error.response.data); + } else if (error.request) { + // Request made but no response + console.error('Network Error:', error.message); + } + return Promise.reject(error); + } + ); + } + + async get(url: string, config?: AxiosRequestConfig): Promise { + const response = await this.client.get(url, config); + return response.data; + } + + async post(url: string, data?: any, config?: AxiosRequestConfig): Promise { + const response = await this.client.post(url, data, config); + return response.data; + } + + async delete(url: string, config?: AxiosRequestConfig): Promise { + const response = await this.client.delete(url, config); + return response.data; + } +} + +export const apiClient = new APIClient(); +``` + +3. **Create API Endpoints** (`src/lib/api/players.ts`) + +```typescript +import { apiClient } from './client'; +import type { Player, Match } from '$lib/types'; + +export const playersAPI = { + async getPlayer(steamId: string | number, beforeTime?: number): Promise { + const url = beforeTime + ? `/player/${steamId}/next/${beforeTime}` + : `/player/${steamId}`; + return apiClient.get(url); + }, + + async getPlayerMeta(steamId: string | number, limit: number = 10) { + return apiClient.get(`/player/${steamId}/meta/${limit}`); + }, + + async trackPlayer(steamId: string | number, authCode: string) { + return apiClient.post(`/player/${steamId}/track`, { auth_code: authCode }); + }, + + async untrackPlayer(steamId: string | number) { + return apiClient.delete(`/player/${steamId}/track`); + }, +}; +``` + +4. **Create API Endpoints** (`src/lib/api/matches.ts`) + +```typescript +import { apiClient } from './client'; +import type { Match } from '$lib/types'; + +export const matchesAPI = { + async getMatch(matchId: string | number): Promise { + return apiClient.get(`/match/${matchId}`); + }, + + async getMatchWeapons(matchId: string | number) { + return apiClient.get(`/match/${matchId}/weapons`); + }, + + async getMatchRounds(matchId: string | number) { + return apiClient.get(`/match/${matchId}/rounds`); + }, + + async getMatchChat(matchId: string | number) { + return apiClient.get(`/match/${matchId}/chat`); + }, + + async parseMatch(shareCode: string) { + return apiClient.get(`/match/parse/${shareCode}`); + }, + + async getMatches(params?: { + limit?: number; + time?: number; + map?: string; + player_id?: number; + }) { + const queryString = params ? `?${new URLSearchParams(params as any).toString()}` : ''; + const url = params?.time + ? `/matches/next/${params.time}${queryString}` + : `/matches${queryString}`; + return apiClient.get(url); + }, +}; +``` + +5. **Use in SvelteKit Routes** (`src/routes/player/[id]/+page.ts`) + +```typescript +import { playersAPI } from '$lib/api/players'; +import { error } from '@sveltejs/kit'; +import type { PageLoad } from './$types'; + +export const load: PageLoad = async ({ params }) => { + try { + const player = await playersAPI.getPlayer(params.id); + return { player }; + } catch (err) { + throw error(404, 'Player not found'); + } +}; +``` + +--- + +## Error Handling + +### HTTP Status Codes + +| Status | Meaning | Common Causes | +|--------|---------|---------------| +| 200 | Success | Request completed successfully | +| 202 | Accepted | Request queued (async operations) | +| 400 | Bad Request | Invalid parameters or malformed request | +| 404 | Not Found | Player/match doesn't exist | +| 429 | Too Many Requests | Rate limit exceeded | +| 500 | Internal Server Error | Backend issue | +| 503 | Service Unavailable | Backend down or maintenance | + +### Error Response Format + +```json +{ + "error": "Match not found", + "code": "MATCH_NOT_FOUND", + "details": { + "match_id": 3589487716842078322 + } +} +``` + +### Retry Strategy + +For transient errors (500, 503), implement exponential backoff: + +```typescript +async function retryRequest( + fn: () => Promise, + maxRetries: number = 3, + baseDelay: number = 1000 +): Promise { + for (let i = 0; i < maxRetries; i++) { + try { + return await fn(); + } catch (error) { + if (i === maxRetries - 1) throw error; + + const delay = baseDelay * Math.pow(2, i); + await new Promise(resolve => setTimeout(resolve, delay)); + } + } + throw new Error('Max retries exceeded'); +} +``` + +--- + +## CS2 Migration Notes + +### Rank System Changes + +**CS:GO** used 18 ranks (Silver I to Global Elite): +- Values: 0-18 + +**CS2** uses Premier Rating: +- Values: 0-30,000 +- No traditional ranks in Premier mode + +**Backend Compatibility**: +- `rank_old` and `rank_new` fields now store Premier rating (0-30000) +- Frontend must detect game version and display accordingly + +### MR12 vs MR15 + +**CS:GO**: MR15 (max 30 rounds) +**CS2**: MR12 (max 24 rounds) + +Check `max_rounds` field in Match data: +- `24` = MR12 (CS2) +- `30` = MR15 (CS:GO) + +### Game Mode Detection + +To determine if a match is CS:GO or CS2: +1. Check `date` field (CS2 released Sept 2023) +2. Check `max_rounds` (24 = likely CS2) +3. Backend may add `game_version` field in future updates + +--- + +## Rate Limiting + +**Current Limits** (may vary by deployment): +- **Steam API**: 1 request per second (backend handles this) +- **Demo Parsing**: Max 6 concurrent parses +- **Frontend API**: No explicit limit, but use reasonable request rates + +**Best Practices**: +- Implement client-side caching (5-15 minutes for match data) +- Use debouncing for search inputs (300ms) +- Batch requests when possible + +--- + +## Caching Strategy + +### Backend Caching (Redis) + +The backend uses Redis for: +- Steam API responses (7 days) +- Match data (permanent until invalidated) +- Player profiles (7 days) + +### Frontend Caching Recommendations + +```typescript +// In-memory cache with TTL +class DataCache { + private cache = new Map(); + + set(key: string, data: T, ttlMs: number) { + this.cache.set(key, { + data, + expires: Date.now() + ttlMs, + }); + } + + get(key: string): T | null { + const entry = this.cache.get(key); + if (!entry) return null; + if (Date.now() > entry.expires) { + this.cache.delete(key); + return null; + } + return entry.data; + } +} + +// Usage +const matchCache = new DataCache(); +const cachedMatch = matchCache.get(`match:${matchId}`); +if (!cachedMatch) { + const match = await matchesAPI.getMatch(matchId); + matchCache.set(`match:${matchId}`, match, 15 * 60 * 1000); // 15 min +} +``` + +--- + +## Testing + +### Mock Data + +Use MSW (Mock Service Worker) for testing: + +```typescript +// src/mocks/handlers/matches.ts +import { http, HttpResponse } from 'msw'; + +export const matchHandlers = [ + http.get('/match/:id', ({ params }) => { + return HttpResponse.json({ + match_id: parseInt(params.id as string), + map: 'de_inferno', + date: '2024-11-01T18:45:00Z', + score_team_a: 13, + score_team_b: 10, + // ... rest of mock data + }); + }), +]; +``` + +--- + +## Swagger Documentation + +The backend provides interactive API documentation at: + +**URL**: `{API_BASE_URL}/api/swagger` + +This Swagger UI allows you to: +- Explore all endpoints +- Test API calls directly +- View request/response schemas +- See authentication requirements + +--- + +## Support & Updates + +- **Backend Issues**: https://somegit.dev/CSGOWTF/csgowtfd/issues +- **Frontend Issues**: https://somegit.dev/CSGOWTF/csgowtf/issues +- **API Changes**: Watch the backend repository for updates + +--- + +**Document Version**: 1.0 +**Last Updated**: 2025-11-04 +**Maintained By**: CSGOW.TF Development Team