- Add Alembic migration setup with async configuration - Create 16 user data models (users, decks, cards, replays, etc.) - Implement comprehensive API endpoints with JWT auth - Add replay, card collection, group, network, preferences, and activity log routers - Include API documentation and migration test plan - Update Dockerfile to run migrations on startup
853 lines
15 KiB
Markdown
853 lines
15 KiB
Markdown
# User Data API Endpoints - Complete Documentation
|
|
|
|
## Overview
|
|
|
|
The User Data API provides comprehensive CRUD operations for:
|
|
- **Session Management** - User authentication sessions
|
|
- **Deck Versions** - Deck version history and rollback
|
|
- **Game Replays** - Game recording and playback
|
|
- **Game Outcomes** - Win/loss tracking with ratings
|
|
- **User Statistics** - Denormalized stats (games, wins, streaks)
|
|
- **Card Collection** - User-owned cards with condition/language
|
|
- **Wishlist** - Cards users want to acquire
|
|
- **Groups** - User groups with roles and chat
|
|
- **Networks** - Extended social connections
|
|
- **Preferences** - User settings and preferences
|
|
- **Activity Log** - Audit trail with JSONB metadata
|
|
|
|
## Base URL
|
|
|
|
```
|
|
/api/v1/user-data
|
|
```
|
|
|
|
## Authentication
|
|
|
|
All endpoints require a valid JWT token in the `Authorization` header:
|
|
|
|
```
|
|
Authorization: Bearer <your-jwt-token>
|
|
```
|
|
|
|
---
|
|
|
|
## 1. Session Management
|
|
|
|
### Get Active Sessions
|
|
```http
|
|
GET /sessions/me
|
|
```
|
|
|
|
**Response:**
|
|
```json
|
|
[
|
|
{
|
|
"cleaned_count": 2,
|
|
"message": "Found 2 active sessions"
|
|
}
|
|
]
|
|
```
|
|
|
|
### Cleanup Expired Sessions
|
|
```http
|
|
DELETE /sessions/cleanup
|
|
```
|
|
|
|
**Response:**
|
|
```json
|
|
{
|
|
"message": "Cleaned 5 expired sessions"
|
|
}
|
|
```
|
|
|
|
### Logout Current Session
|
|
```http
|
|
POST /sessions/logout
|
|
```
|
|
|
|
**Response:**
|
|
```json
|
|
{
|
|
"message": "Logged out successfully"
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## 2. Deck Versions
|
|
|
|
### Create Deck Version
|
|
```http
|
|
POST /decks/{deck_id}/versions
|
|
Content-Type: application/json
|
|
|
|
{
|
|
"content": "4x Thoughtseize, 4x Lightning Bolt, ...",
|
|
"status": "DRAFT",
|
|
"comment": "Updated for meta change"
|
|
}
|
|
```
|
|
|
|
**Response:**
|
|
```json
|
|
{
|
|
"id": 1,
|
|
"deck_id": 42,
|
|
"version_number": 3,
|
|
"content": "4x Thoughtseize, ...",
|
|
"status": "DRAFT",
|
|
"comment": "Updated for meta change",
|
|
"created_at": "2026-01-01T12:00:00Z"
|
|
}
|
|
```
|
|
|
|
### Get Deck Versions
|
|
```http
|
|
GET /decks/{deck_id}/versions?page=1&page_size=50
|
|
```
|
|
|
|
**Response:**
|
|
```json
|
|
{
|
|
"versions": [...],
|
|
"total": 10,
|
|
"page": 1,
|
|
"page_size": 50,
|
|
"total_pages": 1
|
|
}
|
|
```
|
|
|
|
### Update Deck Version
|
|
```http
|
|
PATCH /decks/{deck_id}/versions/{version_id}
|
|
Content-Type: application/json
|
|
|
|
{
|
|
"status": "FINAL",
|
|
"comment": "Ready for tournament"
|
|
}
|
|
```
|
|
|
|
### Delete Deck Version
|
|
```http
|
|
DELETE /decks/{deck_id}/versions/{version_id}
|
|
```
|
|
|
|
---
|
|
|
|
## 3. Game Replays
|
|
|
|
### Create Game Replay
|
|
```http
|
|
POST /replays
|
|
Content-Type: application/json
|
|
|
|
{
|
|
"game_uuid": "550e8400-e29b-41d4-a716-446655440000",
|
|
"room_id": 1,
|
|
"game_type": "Draft",
|
|
"format": "Standard",
|
|
"duration_seconds": 1800,
|
|
"start_time": "2026-01-01T12:00:00Z",
|
|
"end_time": "2026-01-01T12:30:00Z",
|
|
"status": "COMPLETED",
|
|
"replay_data": {
|
|
"turns": [...],
|
|
"deck": {...}
|
|
}
|
|
}
|
|
```
|
|
|
|
**Response:**
|
|
```json
|
|
{
|
|
"id": 1,
|
|
"game_uuid": "550e8400-...",
|
|
"room_id": 1,
|
|
"game_type": "Draft",
|
|
"format": "Standard",
|
|
"duration_seconds": 1800,
|
|
"start_time": "2026-01-01T12:00:00Z",
|
|
"end_time": "2026-01-01T12:30:00Z",
|
|
"status": "COMPLETED",
|
|
"replay_data": {...},
|
|
"created_at": "2026-01-01T12:30:00Z",
|
|
"updated_at": "2026-01-01T12:30:00Z",
|
|
"players": [...]
|
|
}
|
|
```
|
|
|
|
### Get Game Replays
|
|
```http
|
|
GET /replays?page=1&page_size=50&user_id=42&status_filter=COMPLETED
|
|
```
|
|
|
|
**Response:**
|
|
```json
|
|
{
|
|
"replays": [...],
|
|
"total": 100,
|
|
"page": 1,
|
|
"page_size": 50,
|
|
"total_pages": 2
|
|
}
|
|
```
|
|
|
|
### Get Replay Players
|
|
```http
|
|
GET /replays/{replay_id}/players
|
|
```
|
|
|
|
**Response:**
|
|
```json
|
|
[
|
|
{
|
|
"id": 1,
|
|
"user_id": 42,
|
|
"deck_id": 10,
|
|
"position": 1,
|
|
"won": true,
|
|
"lost": false,
|
|
"concession": false,
|
|
"turn_one": false
|
|
}
|
|
]
|
|
```
|
|
|
|
---
|
|
|
|
## 4. Game Outcomes
|
|
|
|
### Create Game Outcome
|
|
```http
|
|
POST /outcomes
|
|
Content-Type: application/json
|
|
|
|
{
|
|
"game_uuid": "550e8400-e29b-41d4-a716-446655440000",
|
|
"outcome": "WIN",
|
|
"opponent_id": 99,
|
|
"format": "Standard",
|
|
"rating_before": 1500,
|
|
"rating_after": 1525,
|
|
"rating_change": 25
|
|
}
|
|
```
|
|
|
|
**Response:**
|
|
```json
|
|
{
|
|
"id": 1,
|
|
"user_id": 42,
|
|
"game_uuid": "550e8400-...",
|
|
"outcome": "WIN",
|
|
"opponent_id": 99,
|
|
"format": "Standard",
|
|
"rating_before": 1500,
|
|
"rating_after": 1525,
|
|
"rating_change": 25,
|
|
"created_at": "2026-01-01T12:30:00Z"
|
|
}
|
|
```
|
|
|
|
### Get Game Outcomes
|
|
```http
|
|
GET /outcomes?page=1&page_size=50&user_id=42
|
|
```
|
|
|
|
---
|
|
|
|
## 5. User Statistics
|
|
|
|
### Get User Statistics
|
|
```http
|
|
GET /statistics/{user_id}
|
|
```
|
|
|
|
**Response:**
|
|
```json
|
|
{
|
|
"user_id": 42,
|
|
"total_games": 150,
|
|
"total_wins": 90,
|
|
"total_losses": 55,
|
|
"total_concessions": 5,
|
|
"win_rate": 60.0,
|
|
"current_streak": 3,
|
|
"best_streak": 8,
|
|
"average_rating": 1450.5,
|
|
"last_game_date": "2026-01-01T12:00:00Z",
|
|
"updated_at": "2026-01-01T12:30:00Z"
|
|
}
|
|
```
|
|
|
|
### Update User Statistics
|
|
```http
|
|
POST /statistics/update
|
|
Content-Type: application/json
|
|
|
|
{
|
|
"user_id": 42,
|
|
"outcome": "WIN"
|
|
}
|
|
```
|
|
|
|
**Response:**
|
|
```json
|
|
{
|
|
"user_id": 42,
|
|
"total_games": 151,
|
|
"total_wins": 91,
|
|
"total_losses": 55,
|
|
"win_rate": 60.26,
|
|
"current_streak": 4,
|
|
"updated_at": "2026-01-01T12:35:00Z"
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## 6. Card Collection
|
|
|
|
### Add Card to Collection
|
|
```http
|
|
POST /collection
|
|
Content-Type: application/json
|
|
|
|
{
|
|
"card_id": 12345,
|
|
"quantity": 4,
|
|
"condition": "NEAR_MINT",
|
|
"language": "EN",
|
|
"is_foil": true,
|
|
"is_alt_art": false,
|
|
"acquired_date": "2026-01-01T00:00:00Z",
|
|
"acquisition_method": "Bought",
|
|
"notes": "From card shop"
|
|
}
|
|
```
|
|
|
|
**Response:**
|
|
```json
|
|
{
|
|
"id": 1,
|
|
"user_id": 42,
|
|
"card_id": 12345,
|
|
"quantity": 4,
|
|
"condition": "NEAR_MINT",
|
|
"language": "EN",
|
|
"is_foil": true,
|
|
"is_alt_art": false,
|
|
"acquired_date": "2026-01-01T00:00:00Z",
|
|
"acquisition_method": "Bought",
|
|
"notes": "From card shop",
|
|
"created_at": "2026-01-01T12:00:00Z",
|
|
"updated_at": "2026-01-01T12:00:00Z"
|
|
}
|
|
```
|
|
|
|
### Get Card Collection
|
|
```http
|
|
GET /collection?page=1&page_size=50&is_foil=true&is_alt_art=false
|
|
```
|
|
|
|
**Response:**
|
|
```json
|
|
{
|
|
"cards": [...],
|
|
"total": 500,
|
|
"page": 1,
|
|
"page_size": 50,
|
|
"total_pages": 10
|
|
}
|
|
```
|
|
|
|
### Update Card in Collection
|
|
```http
|
|
PATCH /collection/{card_id}
|
|
Content-Type: application/json
|
|
|
|
{
|
|
"quantity": 3,
|
|
"condition": "EX",
|
|
"notes": "Slightly worn"
|
|
}
|
|
```
|
|
|
|
### Remove Card from Collection
|
|
```http
|
|
DELETE /collection/{card_id}
|
|
```
|
|
|
|
---
|
|
|
|
## 7. Wishlist
|
|
|
|
### Add to Wishlist
|
|
```http
|
|
POST /wishlist
|
|
Content-Type: application/json
|
|
|
|
{
|
|
"card_id": 12345,
|
|
"max_price": 50.00,
|
|
"notes": "Looking for foil version"
|
|
}
|
|
```
|
|
|
|
**Response:**
|
|
```json
|
|
{
|
|
"id": 1,
|
|
"user_id": 42,
|
|
"card_id": 12345,
|
|
"max_price": 50.00,
|
|
"notes": "Looking for foil version",
|
|
"created_at": "2026-01-01T12:00:00Z"
|
|
}
|
|
```
|
|
|
|
### Get Wishlist
|
|
```http
|
|
GET /wishlist?page=1&page_size=50
|
|
```
|
|
|
|
### Update Wishlist Item
|
|
```http
|
|
PATCH /wishlist/{item_id}
|
|
Content-Type: application/json
|
|
|
|
{
|
|
"max_price": 75.00,
|
|
"notes": "Willing to pay more"
|
|
}
|
|
```
|
|
|
|
### Remove from Wishlist
|
|
```http
|
|
DELETE /wishlist/{item_id}
|
|
```
|
|
|
|
---
|
|
|
|
## 8. Groups
|
|
|
|
### Create Group
|
|
```http
|
|
POST /groups
|
|
Content-Type: application/json
|
|
|
|
{
|
|
"name": "Standard Players",
|
|
"description": "Casual Standard players",
|
|
"is_public": true,
|
|
"max_members": 50
|
|
}
|
|
```
|
|
|
|
**Response:**
|
|
```json
|
|
{
|
|
"id": 1,
|
|
"name": "Standard Players",
|
|
"description": "Casual Standard players",
|
|
"owner_id": 42,
|
|
"is_public": true,
|
|
"max_members": 50,
|
|
"created_at": "2026-01-01T12:00:00Z",
|
|
"updated_at": "2026-01-01T12:00:00Z",
|
|
"member_count": 1,
|
|
"is_member": true
|
|
}
|
|
```
|
|
|
|
### Get User Groups
|
|
```http
|
|
GET /groups?page=1&page_size=50&is_public=true
|
|
```
|
|
|
|
**Response:**
|
|
```json
|
|
{
|
|
"groups": [...],
|
|
"total": 5,
|
|
"page": 1,
|
|
"page_size": 50,
|
|
"total_pages": 1
|
|
}
|
|
```
|
|
|
|
### Update Group
|
|
```http
|
|
PATCH /groups/{group_id}
|
|
Content-Type: application/json
|
|
|
|
{
|
|
"description": "Updated description",
|
|
"max_members": 100
|
|
}
|
|
```
|
|
|
|
### Delete Group
|
|
```http
|
|
DELETE /groups/{group_id}
|
|
```
|
|
|
|
---
|
|
|
|
## 9. Group Members
|
|
|
|
### Add Group Member
|
|
```http
|
|
POST /groups/{group_id}/members
|
|
Content-Type: application/json
|
|
|
|
{
|
|
"user_id": 99,
|
|
"role": "MEMBER"
|
|
}
|
|
```
|
|
|
|
**Response:**
|
|
```json
|
|
{
|
|
"message": "Member added",
|
|
"member_id": 5
|
|
}
|
|
```
|
|
|
|
### Update Group Member Role
|
|
```http
|
|
PATCH /groups/{group_id}/members/{member_id}
|
|
Content-Type: application/json
|
|
|
|
{
|
|
"role": "ADMIN"
|
|
}
|
|
```
|
|
|
|
**Response:**
|
|
```json
|
|
{
|
|
"message": "Member role updated"
|
|
}
|
|
```
|
|
|
|
### Remove Group Member
|
|
```http
|
|
DELETE /groups/{group_id}/members/{member_id}
|
|
```
|
|
|
|
---
|
|
|
|
## 10. Group Chat Messages
|
|
|
|
### Send Group Message
|
|
```http
|
|
POST /groups/{group_id}/messages
|
|
Content-Type: application/json
|
|
|
|
{
|
|
"message": "Hey everyone! Ready for a game?"
|
|
}
|
|
```
|
|
|
|
**Response:**
|
|
```json
|
|
{
|
|
"id": 1,
|
|
"group_id": 1,
|
|
"sender_id": 42,
|
|
"sender_username": "player42",
|
|
"message": "Hey everyone! Ready for a game?",
|
|
"created_at": "2026-01-01T12:00:00Z"
|
|
}
|
|
```
|
|
|
|
### Get Group Messages
|
|
```http
|
|
GET /groups/{group_id}/messages?page=1&page_size=50
|
|
```
|
|
|
|
**Response:**
|
|
```json
|
|
{
|
|
"messages": [...],
|
|
"total": 25,
|
|
"page": 1,
|
|
"page_size": 50,
|
|
"total_pages": 1
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## 11. Networks
|
|
|
|
### Create Network
|
|
```http
|
|
POST /networks
|
|
Content-Type: application/json
|
|
|
|
{
|
|
"name": "MTG Enthusiasts",
|
|
"description": "Friends who play Magic",
|
|
"is_public": true
|
|
}
|
|
```
|
|
|
|
**Response:**
|
|
```json
|
|
{
|
|
"id": 1,
|
|
"name": "MTG Enthusiasts",
|
|
"description": "Friends who play Magic",
|
|
"creator_id": 42,
|
|
"is_public": true,
|
|
"created_at": "2026-01-01T12:00:00Z",
|
|
"member_count": 1,
|
|
"is_member": true
|
|
}
|
|
```
|
|
|
|
### Get User Networks
|
|
```http
|
|
GET /networks?page=1&page_size=50
|
|
```
|
|
|
|
### Update Network
|
|
```http
|
|
PATCH /networks/{network_id}
|
|
Content-Type: application/json
|
|
|
|
{
|
|
"description": "Updated network description"
|
|
}
|
|
```
|
|
|
|
### Delete Network
|
|
```http
|
|
DELETE /networks/{network_id}
|
|
```
|
|
|
|
---
|
|
|
|
## 12. Network Members
|
|
|
|
### Add Network Member
|
|
```http
|
|
POST /networks/{network_id}/members
|
|
Content-Type: application/json
|
|
|
|
{
|
|
"user_id": 99,
|
|
"role": "MEMBER"
|
|
}
|
|
```
|
|
|
|
**Response:**
|
|
```json
|
|
{
|
|
"message": "Member added",
|
|
"member_id": 3
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## 13. User Preferences
|
|
|
|
### Get User Preferences
|
|
```http
|
|
GET /preferences
|
|
```
|
|
|
|
**Response:**
|
|
```json
|
|
{
|
|
"user_id": 42,
|
|
"theme": "light",
|
|
"notifications_enabled": true,
|
|
"email_notifications": true,
|
|
"auto_save_decks": true,
|
|
"default_format": "standard",
|
|
"language": "EN",
|
|
"updated_at": "2026-01-01T12:00:00Z"
|
|
}
|
|
```
|
|
|
|
### Update User Preferences
|
|
```http
|
|
PATCH /preferences
|
|
Content-Type: application/json
|
|
|
|
{
|
|
"theme": "dark",
|
|
"notifications_enabled": false,
|
|
"default_format": "modern"
|
|
}
|
|
```
|
|
|
|
**Response:**
|
|
```json
|
|
{
|
|
"user_id": 42,
|
|
"theme": "dark",
|
|
"notifications_enabled": false,
|
|
"email_notifications": true,
|
|
"auto_save_decks": true,
|
|
"default_format": "modern",
|
|
"language": "EN",
|
|
"updated_at": "2026-01-01T12:05:00Z"
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## 14. Activity Log
|
|
|
|
### Get Activity Log
|
|
```http
|
|
GET /activity?page=1&page_size=50&activity_type=LOGIN
|
|
```
|
|
|
|
**Response:**
|
|
```json
|
|
{
|
|
"entries": [
|
|
{
|
|
"id": 1,
|
|
"user_id": 42,
|
|
"activity_type": "LOGIN",
|
|
"activity_data": {"ip": "192.168.1.1"},
|
|
"ip_address": "192.168.1.1",
|
|
"created_at": "2026-01-01T12:00:00Z"
|
|
}
|
|
],
|
|
"total": 100,
|
|
"page": 1,
|
|
"page_size": 50,
|
|
"total_pages": 2
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## Error Responses
|
|
|
|
All endpoints return consistent error responses:
|
|
|
|
```json
|
|
{
|
|
"detail": "Error message"
|
|
}
|
|
```
|
|
|
|
### Common Error Codes
|
|
|
|
| Status Code | Description |
|
|
|------------|-------------|
|
|
| 400 | Bad Request - Invalid input |
|
|
| 401 | Unauthorized - Missing or invalid token |
|
|
| 403 | Forbidden - Insufficient permissions |
|
|
| 404 | Not Found - Resource doesn't exist |
|
|
| 409 | Conflict - Resource already exists |
|
|
| 500 | Internal Server Error |
|
|
|
|
---
|
|
|
|
## Testing with cURL
|
|
|
|
### Example: Create a Deck Version
|
|
```bash
|
|
curl -X POST "http://localhost:8000/api/v1/user-data/decks/42/versions" \
|
|
-H "Authorization: Bearer YOUR_JWT_TOKEN" \
|
|
-H "Content-Type: application/json" \
|
|
-d '{
|
|
"content": "4x Thoughtseize, 4x Lightning Bolt, ...",
|
|
"status": "DRAFT",
|
|
"comment": "Updated for meta"
|
|
}'
|
|
```
|
|
|
|
### Example: Get Card Collection
|
|
```bash
|
|
curl "http://localhost:8000/api/v1/user-data/collection?page=1&page_size=10" \
|
|
-H "Authorization: Bearer YOUR_JWT_TOKEN"
|
|
```
|
|
|
|
### Example: Add to Wishlist
|
|
```bash
|
|
curl -X POST "http://localhost:8000/api/v1/user-data/wishlist" \
|
|
-H "Authorization: Bearer YOUR_JWT_TOKEN" \
|
|
-H "Content-Type: application/json" \
|
|
-d '{
|
|
"card_id": 12345,
|
|
"max_price": 50.00,
|
|
"notes": "Looking for foil"
|
|
}'
|
|
```
|
|
|
|
---
|
|
|
|
## Available Endpoints Summary
|
|
|
|
| Method | Endpoint | Description |
|
|
|--------|----------|-------------|
|
|
| GET | `/sessions/me` | Get active sessions |
|
|
| DELETE | `/sessions/cleanup` | Cleanup expired sessions |
|
|
| POST | `/sessions/logout` | Logout current session |
|
|
| POST | `/decks/{id}/versions` | Create deck version |
|
|
| GET | `/decks/{id}/versions` | Get deck versions |
|
|
| PATCH | `/decks/{id}/versions/{vid}` | Update deck version |
|
|
| DELETE | `/decks/{id}/versions/{vid}` | Delete deck version |
|
|
| POST | `/replays` | Create game replay |
|
|
| GET | `/replays` | Get game replays |
|
|
| GET | `/replays/{id}` | Get specific replay |
|
|
| PATCH | `/replays/{id}` | Update replay |
|
|
| DELETE | `/replays/{id}` | Delete replay |
|
|
| POST | `/replays/{id}/players` | Add player to replay |
|
|
| GET | `/replays/{id}/players` | Get replay players |
|
|
| POST | `/outcomes` | Create game outcome |
|
|
| GET | `/outcomes` | Get game outcomes |
|
|
| GET | `/statistics/{id}` | Get user statistics |
|
|
| POST | `/statistics/update` | Update user statistics |
|
|
| POST | `/collection` | Add card to collection |
|
|
| GET | `/collection` | Get card collection |
|
|
| PATCH | `/collection/{id}` | Update card |
|
|
| DELETE | `/collection/{id}` | Remove card |
|
|
| POST | `/wishlist` | Add to wishlist |
|
|
| GET | `/wishlist` | Get wishlist |
|
|
| PATCH | `/wishlist/{id}` | Update wishlist item |
|
|
| DELETE | `/wishlist/{id}` | Remove from wishlist |
|
|
| POST | `/groups` | Create group |
|
|
| GET | `/groups` | Get user groups |
|
|
| GET | `/groups/{id}` | Get specific group |
|
|
| PATCH | `/groups/{id}` | Update group |
|
|
| DELETE | `/groups/{id}` | Delete group |
|
|
| POST | `/groups/{id}/members` | Add group member |
|
|
| PATCH | `/groups/{id}/members/{mid}` | Update member role |
|
|
| DELETE | `/groups/{id}/members/{mid}` | Remove member |
|
|
| POST | `/groups/{id}/messages` | Send group message |
|
|
| GET | `/groups/{id}/messages` | Get group messages |
|
|
| POST | `/networks` | Create network |
|
|
| GET | `/networks` | Get user networks |
|
|
| GET | `/networks/{id}` | Get specific network |
|
|
| PATCH | `/networks/{id}` | Update network |
|
|
| DELETE | `/networks/{id}` | Delete network |
|
|
| POST | `/networks/{id}/members` | Add network member |
|
|
| GET | `/preferences` | Get user preferences |
|
|
| PATCH | `/preferences` | Update preferences |
|
|
| GET | `/activity` | Get activity log |
|
|
|
|
---
|
|
|
|
## Next Steps
|
|
|
|
1. **Test endpoints** with curl or Postman
|
|
2. **Create integration tests** for each endpoint
|
|
3. **Add rate limiting** for production
|
|
4. **Implement pagination optimization** for large datasets
|
|
5. **Add search functionality** for cards and decks
|
|
6. **Create webhook endpoints** for real-time notifications
|