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