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
This commit is contained in:
54
TODO.md
54
TODO.md
@@ -13,9 +13,23 @@
|
|||||||
- `/match/[id]/flashes` - Flashbang effectiveness data
|
- `/match/[id]/flashes` - Flashbang effectiveness data
|
||||||
- `/match/[id]/damage` - Damage visualization and heatmaps
|
- `/match/[id]/damage` - Damage visualization and heatmaps
|
||||||
- `/match/[id]/chat` - In-game chat log
|
- `/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).
|
- [ ] 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.
|
- **Note**: Planned for implementation period - feedback will inform feature priorities
|
||||||
- [ ] Document CS2 demo parsing pipeline expectations, storage strategy, and data freshness SLAs.
|
|
||||||
|
|
||||||
## Phase 1 – Technical Foundations
|
## Phase 1 – Technical Foundations
|
||||||
- [ ] Standardize on Node.js ≥ 18 (add `.nvmrc` / `.tool-versions`) and lock package manager choice (npm or yarn).
|
- [ ] 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).
|
- [ ] Run accessibility review on mockups (contrast, color blindness variants, keyboard flows).
|
||||||
|
|
||||||
## Phase 3 – Domain Modeling & Data Layer
|
## 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.)
|
**Reference**: See `docs/API.md` for complete backend API documentation, endpoints, and response structures.
|
||||||
- `Player.ts`: Steam ID, name, avatar, rank, stats summary, recent matches
|
|
||||||
- `Round.ts`: round number, winner, reason, duration, economy state, events
|
- [ ] Define core TypeScript interfaces in `src/lib/types/` (based on backend schemas):
|
||||||
- `PlayerStats.ts`: kills, deaths, assists, ADR, HS%, KAST, rating, utility damage
|
- `Match.ts`: match_id, share_code, map, date, scores, duration, match_result, max_rounds, demo_parsed, tick_rate
|
||||||
- `Economy.ts`: money spent/saved, equipment value, loadouts, utility purchases
|
- `Player.ts`: id (Steam ID), name, avatar, vanity_url, wins/losses/ties, vac_count, game_ban_count, steam_updated
|
||||||
- `UtilityEvent.ts`: flashbangs (effectiveness, enemies blinded), smokes (volumetric coverage), HE grenades, molotovs
|
- `MatchPlayer.ts`: player stats per match (kills, deaths, assists, headshot, mvp, score, kast, rank changes, damage, utility stats, flash metrics)
|
||||||
- `DamageEvent.ts`: attacker, victim, weapon, damage, hit group, distance, tick
|
- `RoundStats.ts`: round number, bank (money), equipment value, spent
|
||||||
- `ChatMessage.ts`: timestamp, round, player, message, team chat flag
|
- `Weapon.ts`: weapon statistics (victim, damage, eq_type, hit_group)
|
||||||
- `Metadata.ts`: CS2 version, demo hash, server info, tickrate
|
- `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:
|
- [ ] Create Zod schemas for runtime validation:
|
||||||
- Match all TypeScript interfaces with Zod schemas for API response validation
|
- Match all TypeScript interfaces with Zod schemas for API response validation
|
||||||
- Add custom validators for Steam IDs, match IDs, rank tiers
|
- Add custom validators for Steam IDs (uint64), match IDs, rank tiers (0-30000 for CS2)
|
||||||
- Export type-safe parsers: `parseMatch()`, `parsePlayerStats()`, etc.
|
- 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:
|
- [ ] Design API client architecture:
|
||||||
- Evaluate backend API (likely REST from csgowtfd) and create typed client
|
- Backend confirmed: REST API (Go + Gin) at `localhost:8000` (default)
|
||||||
- Build `src/lib/api/client.ts` with fetch wrapper
|
- 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)
|
- 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
|
- Implement request cancellation (AbortController) for navigation
|
||||||
- Add response caching strategy (in-memory cache, TTL-based invalidation)
|
- 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:
|
- [ ] Set up MSW (Mock Service Worker) for testing:
|
||||||
- Install: `msw@^2.0.0`
|
- Install: `msw@^2.0.0`
|
||||||
- Create mock handlers in `src/mocks/handlers/` for all endpoints
|
- Create mock handlers in `src/mocks/handlers/` for all endpoints
|
||||||
|
|||||||
998
docs/API.md
Normal file
998
docs/API.md
Normal file
@@ -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
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<sitemapindex xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
|
||||||
|
<sitemap>
|
||||||
|
<loc>https://csgow.tf/sitemap/1</loc>
|
||||||
|
<lastmod>2024-11-04</lastmod>
|
||||||
|
</sitemap>
|
||||||
|
</sitemapindex>
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
#### 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
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
|
||||||
|
<url>
|
||||||
|
<loc>https://csgow.tf/match/3589487716842078322</loc>
|
||||||
|
<lastmod>2024-11-01</lastmod>
|
||||||
|
<changefreq>never</changefreq>
|
||||||
|
<priority>0.8</priority>
|
||||||
|
</url>
|
||||||
|
</urlset>
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 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<T>(url: string, config?: AxiosRequestConfig): Promise<T> {
|
||||||
|
const response = await this.client.get<T>(url, config);
|
||||||
|
return response.data;
|
||||||
|
}
|
||||||
|
|
||||||
|
async post<T>(url: string, data?: any, config?: AxiosRequestConfig): Promise<T> {
|
||||||
|
const response = await this.client.post<T>(url, data, config);
|
||||||
|
return response.data;
|
||||||
|
}
|
||||||
|
|
||||||
|
async delete<T>(url: string, config?: AxiosRequestConfig): Promise<T> {
|
||||||
|
const response = await this.client.delete<T>(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<Player> {
|
||||||
|
const url = beforeTime
|
||||||
|
? `/player/${steamId}/next/${beforeTime}`
|
||||||
|
: `/player/${steamId}`;
|
||||||
|
return apiClient.get<Player>(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<Match> {
|
||||||
|
return apiClient.get<Match>(`/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<T>(
|
||||||
|
fn: () => Promise<T>,
|
||||||
|
maxRetries: number = 3,
|
||||||
|
baseDelay: number = 1000
|
||||||
|
): Promise<T> {
|
||||||
|
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<T> {
|
||||||
|
private cache = new Map<string, { data: T; expires: number }>();
|
||||||
|
|
||||||
|
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<Match>();
|
||||||
|
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
|
||||||
Reference in New Issue
Block a user