# Chess Platform — Flutter Developer Guide

This document is the integration reference for building the mobile chess app against the backend hosted at **https://chess.sayedkhattab.com**.

---

## Table of Contents

1. [Overview](#1-overview)
2. [Environment & Base URL](#2-environment--base-url)
3. [Recommended Flutter Project Setup](#3-recommended-flutter-project-setup)
4. [Authentication](#4-authentication)
5. [User Profile & Discovery](#5-user-profile--discovery)
6. [Game Lifecycle](#6-game-lifecycle)
7. [Making Moves & Board Logic](#7-making-moves--board-logic)
8. [Friendships](#8-friendships)
9. [Game Invitations & Challenges](#9-game-invitations--challenges)
10. [Polling vs Real-Time (Important)](#10-polling-vs-real-time-important)
11. [Error Handling](#11-error-handling)
12. [Suggested App Screens & Flows](#12-suggested-app-screens--flows)
13. [Data Models (Dart Reference)](#13-data-models-dart-reference)
14. [API Quick Reference](#14-api-quick-reference)
15. [Testing Checklist](#15-testing-checklist)
16. [Roadmap — Upcoming Backend Features](#16-roadmap--upcoming-backend-features)

---

## 1. Overview

The chess platform consists of:

| Component | URL | Purpose |
|-----------|-----|---------|
| **Player API** | `https://chess.sayedkhattab.com/api` | Flutter app (auth, users, games) |
| **Admin Panel** | `https://chess.sayedkhattab.com/admin` | Web dashboard (not for the mobile app) |
| **Health Check** | `GET /api/health` | Server & database status |

### Architecture notes

- REST API over HTTPS (JSON)
- JWT Bearer authentication for all protected player routes
- Move validation is performed **on the client** (Flutter). The server stores moves and game state but does not run a chess engine yet.
- Real-time WebSocket is **not available yet**. Use polling (see [Section 10](#10-polling-vs-real-time-important)).
- Default starting ELO rating: **1200**

---

## 2. Environment & Base URL

### Production

```
BASE_URL = https://chess.sayedkhattab.com/api
```

### Headers (all requests)

```http
Content-Type: application/json
Accept: application/json
```

### Headers (authenticated requests)

```http
Authorization: Bearer <JWT_TOKEN>
```

### Health check

```http
GET /api/health
```

**Response 200:**

```json
{
  "status": "ok",
  "service": "chess-api",
  "database": "connected",
  "version": "1.0.0"
}
```

---

## 3. Recommended Flutter Project Setup

### 3.1 Packages

| Package | Purpose |
|---------|---------|
| `http` or `dio` | HTTP client |
| `flutter_secure_storage` | Persist JWT securely |
| `provider`, `riverpod`, or `bloc` | State management |
| `chess` or `dart_chess` | Board logic, FEN, legal moves |
| `json_annotation` + `build_runner` | JSON models (optional) |

### 3.2 Project structure (suggested)

```
lib/
├── core/
│   ├── api/
│   │   ├── api_client.dart       # Dio/http wrapper + interceptors
│   │   ├── auth_interceptor.dart # Attach Bearer token
│   │   └── api_exception.dart
│   ├── constants/
│   │   └── api_constants.dart    # BASE_URL
│   └── storage/
│       └── token_storage.dart
├── features/
│   ├── auth/
│   ├── home/
│   ├── profile/
│   ├── friends/
│   ├── games/
│   └── board/
├── models/
│   ├── user.dart
│   ├── game.dart
│   └── move.dart
└── main.dart
```

### 3.3 API client pattern

```dart
class ApiClient {
  static const baseUrl = 'https://chess.sayedkhattab.com/api';

  final Dio _dio;

  ApiClient(TokenStorage storage) : _dio = Dio(BaseOptions(baseUrl: baseUrl)) {
    _dio.interceptors.add(InterceptorsWrapper(
      onRequest: (options, handler) async {
        final token = await storage.readToken();
        if (token != null) {
          options.headers['Authorization'] = 'Bearer $token';
        }
        handler.next(options);
      },
      onError: (error, handler) {
        if (error.response?.statusCode == 401) {
          // Clear token → navigate to login
        }
        handler.next(error);
      },
    ));
  }
}
```

### 3.4 App bootstrap flow

```
App Start
   │
   ├─ Token exists? ──No──► Login / Register screen
   │
   └─ Yes ──► GET /api/auth/me
                │
                ├─ 200 ──► Home screen
                └─ 401 ──► Clear token → Login screen
```

---

## 4. Authentication

Player auth uses **phone + password**. Phone must start with `0` and contain digits only (10–15 digits).

JWT payload includes:

```json
{
  "id": 1,
  "phone": "0501234567",
  "aud": "app",
  "iat": 1234567890,
  "exp": 1237159890
}
```

Token lifetime: **30 days**.

---

### 4.1 Register

Creates a player account and returns a token immediately (auto-login).

```http
POST /api/auth/register
```

**Request body:**

```json
{
  "name": "Ahmed Ali",
  "phone": "0501234567",
  "password": "secret123"
}
```

| Field | Required | Rules |
|-------|----------|-------|
| `name` | Yes | Player display name |
| `phone` | Yes | Unique, starts with `0`, digits only, 10–15 chars |
| `password` | Yes | Minimum 6 characters |

**Response 201:**

```json
{
  "message": "تم إنشاء الحساب بنجاح",
  "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
  "user": {
    "id": 1,
    "name": "Ahmed Ali",
    "phone": "0501234567",
    "rating": 1200,
    "avatarUrl": null,
    "createdAt": "2026-06-02T22:36:56.000Z"
  }
}
```

**Errors:**

| Status | Meaning |
|--------|---------|
| 400 | Missing fields, invalid phone format, or password too short |
| 409 | Phone number already registered |

---

### 4.2 Login

```http
POST /api/auth/login
```

**Request body:**

```json
{
  "phone": "0501234567",
  "password": "secret123"
}
```

**Response 200:**

```json
{
  "message": "تم تسجيل الدخول",
  "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
  "user": {
    "id": 1,
    "name": "Ahmed Ali",
    "phone": "0501234567",
    "rating": 1200,
    "avatarUrl": null,
    "createdAt": "2026-06-02T22:36:56.000Z"
  }
}
```

**Errors:**

| Status | Meaning |
|--------|---------|
| 401 | Invalid phone or password |

---

### 4.3 Get current user

```http
GET /api/auth/me
Authorization: Bearer <token>
```

**Response 200:** User object (same shape as `user` in login response).

---

## 5. User Profile & Discovery

All routes below require authentication unless noted.

---

### 5.1 Update profile

```http
PATCH /api/users/me
```

**Request body (all fields optional):**

```json
{
  "name": "New Name",
  "avatarUrl": "https://example.com/avatar.png"
}
```

**Response 200:**

```json
{
  "message": "تم تحديث الملف الشخصي",
  "user": { /* User object */ }
}
```

---

### 5.2 Search players

Use this to find opponents or add friends.

```http
GET /api/users/search?q=ali
```

| Query | Required | Notes |
|-------|----------|-------|
| `q` | Yes | Minimum 2 characters |

**Response 200:**

```json
[
  {
    "id": 2,
    "name": "Ali",
    "phone": "0509876543",
    "rating": 1250,
    "avatarUrl": null
  }
]
```

Current user is excluded from results.

---

### 5.3 Get player by ID

```http
GET /api/users/{id}
```

**Response 200:** Full user profile (includes phone).

---

### 5.4 Leaderboard (public — no auth required)

```http
GET /api/users/leaderboard?limit=50
```

| Query | Default | Max |
|-------|---------|-----|
| `limit` | 50 | 100 |

**Response 200:**

```json
[
  {
    "id": 5,
    "name": "GM Player",
    "phone": "0501111111",
    "rating": 1850,
    "avatarUrl": null
  }
]
```

Sorted by rating descending.

---

## 6. Game Lifecycle

All game routes require authentication.

### Game statuses

| Status | Description |
|--------|-------------|
| `waiting` | Open game — waiting for a second player |
| `active` | Both players joined, game in progress |
| `finished` | Game ended (checkmate, resign, draw, etc.) |
| `abandoned` | Reserved for future use |

### Game results

| Result | Meaning |
|--------|---------|
| `white` | White wins |
| `black` | Black wins |
| `draw` | Draw |

### Initial board (FEN)

```
rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1
```

---

### 6.1 List games

Returns games where the user is a participant **or** open waiting games.

```http
GET /api/games?status=active&limit=30
```

| Query | Optional | Values |
|-------|----------|--------|
| `status` | Yes | `waiting`, `active`, `finished`, `abandoned` |
| `limit` | Yes | Default 30 |

**Response 200:**

```json
[
  {
    "id": 10,
    "whitePlayerId": 1,
    "blackPlayerId": 2,
    "whitePlayer": {
      "id": 1,
      "name": "Player One",
      "rating": 1200
    },
    "blackPlayer": {
      "id": 2,
      "name": "Player Two",
      "rating": 1220
    },
    "fen": "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1",
    "pgn": null,
    "status": "active",
    "result": null,
    "timeControlMs": 600000,
    "whiteTimeMs": 600000,
    "blackTimeMs": 600000,
    "currentTurn": "white",
    "moveCount": 0,
    "createdAt": "2026-06-02T23:00:00.000Z",
    "updatedAt": "2026-06-02T23:00:00.000Z",
    "finishedAt": null
  }
]
```

---

### 6.2 Create a game

Three modes depending on the request body:

#### A) Open game (matchmaking / random opponent)

Any player can join via `POST /api/games/:id/join`.

```json
{
  "color": "white",
  "timeControlMs": 600000
}
```

- Creator is assigned to `white` or `black` based on `color`
- Status starts as **`waiting`**
- `opponentId` is omitted

#### B) Direct challenge (specific opponent)

Game starts immediately as **`active`**.

```json
{
  "opponentId": 2,
  "color": "white",
  "timeControlMs": 300000
}
```

| Field | Default | Description |
|-------|---------|-------------|
| `opponentId` | — | Target player user ID |
| `color` | `"white"` | Creator's color: `"white"` or `"black"` |
| `timeControlMs` | `600000` (10 min) | Total time per player in milliseconds |

#### C) Challenge as black

Creator plays black; opponent is white:

```json
{
  "opponentId": 2,
  "color": "black",
  "timeControlMs": 600000
}
```

**Response 201:**

```json
{
  "message": "تم إنشاء المباراة",
  "game": { /* Game object */ }
}
```

---

### 6.3 Join an open game

```http
POST /api/games/{gameId}/join
```

- Only works when `status === "waiting"`
- Joining player becomes **black** (white is already assigned)
- Status changes to **`active`**

**Response 200:**

```json
{
  "message": "تم الانضمام للمباراة",
  "game": { /* Game object */ }
}
```

---

### 6.4 Get game details

```http
GET /api/games/{gameId}
```

Accessible if you are a participant or the game is `waiting`.

---

### 6.5 Get move history

```http
GET /api/games/{gameId}/moves
```

**Response 200:**

```json
[
  {
    "id": 1,
    "moveNumber": 1,
    "color": "white",
    "fromSquare": "e2",
    "toSquare": "e4",
    "san": "e4",
    "fenAfter": "rnbqkbnr/pppppppp/8/8/4P3/8/PPPP1PPP/RNBQKBNR b KQkq e3 0 1",
    "createdAt": "2026-06-02T23:01:00.000Z"
  }
]
```

Use this to reconstruct the board when opening an in-progress game.

---

### 6.6 Resign

```http
POST /api/games/{gameId}/resign
```

**Response 200:**

```json
{
  "message": "تم الاستسلام",
  "game": {
    "status": "finished",
    "result": "black"
  }
}
```

If white resigns → `result: "black"`. If black resigns → `result: "white"`.

---

## 7. Making Moves & Board Logic

### 7.1 Submit a move

```http
POST /api/games/{gameId}/move
```

**Request body:**

```json
{
  "fromSquare": "e2",
  "toSquare": "e4",
  "san": "e4",
  "fenAfter": "rnbqkbnr/pppppppp/8/8/4P3/8/PPPP1PPP/RNBQKBNR b KQkq e3 0 1",
  "result": null
}
```

| Field | Required | Description |
|-------|----------|-------------|
| `fromSquare` | Yes | Algebraic square, e.g. `"e2"` |
| `toSquare` | Yes | Algebraic square, e.g. `"e4"` |
| `san` | No | Standard algebraic notation |
| `fenAfter` | Yes | FEN after the move |
| `result` | No | Set on game-ending move: `"white"`, `"black"`, or `"draw"` |

### 7.2 Client responsibilities

The Flutter app **must**:

1. Validate moves locally using a chess library before sending to the API
2. Compute `fenAfter` after each move
3. Detect checkmate, stalemate, and draw conditions
4. Send `result` on the final move to mark the game as finished
5. Only allow moves when `game.currentTurn` matches the player's color
6. Handle `"ليس دورك"` (not your turn) error from the server

### 7.3 Recommended move flow

```
User taps piece → highlight legal moves (local engine)
       │
User selects destination
       │
Validate move locally
       │
POST /api/games/{id}/move
       │
├─ 200 → Update board from response.game.fen
└─ 4xx → Show error, revert UI
```

### 7.4 Suggested chess package usage

```dart
import 'package:chess/chess.dart';

final chess = Chess(); // load from game.fen
if (chess.move({'from': 'e2', 'to': 'e4'})) {
  final fenAfter = chess.fen;
  // send to API
}
```

---

## 8. Friendships

### Current state (Phase 1)

Dedicated friendship endpoints (`/api/friends/*`) are **planned but not yet deployed**. For the first release, implement friendships using a **hybrid approach**:

| Feature | Phase 1 approach |
|---------|------------------|
| Find players | `GET /api/users/search?q=...` |
| View profile | `GET /api/users/{id}` |
| Store friends list | **Local storage** on device (list of user IDs) |
| Challenge a friend | `POST /api/games` with `opponentId` |

### Recommended local friends model

```dart
class FriendEntry {
  final int userId;
  final String name;
  final String phone;
  final int rating;
  final String? avatarUrl;
  final DateTime addedAt;
}
```

Persist with `shared_preferences`, `hive`, or `isar`.

### Friends screen flow (Phase 1)

```
Friends Screen
   │
   ├─ Load friends from local storage
   │
   ├─ "Add Friend" button
   │     └─ Search screen → GET /api/users/search
   │           └─ Tap player → save to local friends list
   │
   └─ Tap friend row
         ├─ View profile
         └─ "Challenge" → POST /api/games { opponentId, color, timeControlMs }
```

### Phase 2 — Planned friendship API

When deployed, replace local storage with these endpoints:

| Method | Endpoint | Description |
|--------|----------|-------------|
| `GET` | `/api/friends` | List accepted friends |
| `GET` | `/api/friends/requests` | Incoming pending requests |
| `POST` | `/api/friends/request` | Send friend request `{ "userId": 2 }` |
| `POST` | `/api/friends/requests/{id}/accept` | Accept request |
| `POST` | `/api/friends/requests/{id}/reject` | Reject request |
| `DELETE` | `/api/friends/{userId}` | Remove friend |

**Design for Phase 2 now:** keep friend logic in a dedicated `FriendsRepository` so you can swap the data source from local storage to API without changing UI code.

---

## 9. Game Invitations & Challenges

### 9.1 Direct challenge (available now)

When Player A challenges Player B directly:

```http
POST /api/games
{
  "opponentId": 2,
  "color": "white",
  "timeControlMs": 600000
}
```

- Game is created with status **`active`** immediately
- Player B should poll `GET /api/games?status=active` or receive a push notification (future)
- Both players navigate to the board screen

**Flutter UX suggestion:**

1. Player A taps "Challenge" on friend's profile
2. Show time control picker (1+0, 3+0, 5+0, 10+0, etc.)
3. Show color picker (White / Black / Random)
4. Call `POST /api/games`
5. Navigate to board

### 9.2 Open game invitation (available now)

Player A creates an open game:

```http
POST /api/games
{ "color": "white", "timeControlMs": 600000 }
```

Player B finds and joins:

```http
GET /api/games?status=waiting     ← browse open games
POST /api/games/{id}/join         ← join as black
```

**Flutter UX:**

- **"Play Online"** screen lists waiting games
- Show creator name, time control, creator rating
- "Join" button calls join endpoint

### 9.3 Pending invitation model (Phase 1 client-side)

Until a dedicated invitations API exists, track outgoing challenges locally:

```dart
class PendingChallenge {
  final int gameId;
  final int opponentId;
  final DateTime sentAt;
  final String status; // 'waiting_opponent', 'active', 'declined'
}
```

Poll `GET /api/games/{gameId}` to detect when opponent has joined or game started.

### 9.4 Phase 2 — Planned invitation API

| Method | Endpoint | Description |
|--------|----------|-------------|
| `GET` | `/api/invitations` | Incoming & outgoing game invites |
| `POST` | `/api/invitations` | Send invite `{ "opponentId", "color", "timeControlMs" }` |
| `POST` | `/api/invitations/{id}/accept` | Accept → creates/joins game |
| `POST` | `/api/invitations/{id}/decline` | Decline invite |

Until Phase 2, use **direct challenge** (`POST /api/games` with `opponentId`) as the invitation mechanism.

---

## 10. Polling vs Real-Time (Important)

WebSocket / Socket.IO is **not implemented yet**.

### Recommended polling strategy

| Screen | Poll interval | Endpoint |
|--------|---------------|----------|
| Active game board | 2–3 seconds | `GET /api/games/{id}` + `/moves` |
| Game list / home | 5–10 seconds | `GET /api/games?status=active` |
| Waiting for opponent | 3 seconds | `GET /api/games/{id}` until status ≠ `waiting` |
| Invitations (Phase 2) | 5 seconds | `GET /api/invitations` |

### Detect opponent's move

```
Poll GET /api/games/{id}/moves
   │
Compare move count with local state
   │
If new moves → apply to local board from fenAfter
```

### Stop polling when

- Game `status === "finished"`
- User leaves the board screen
- App goes to background (resume on foreground)

---

## 11. Error Handling

All errors return JSON:

```json
{
  "message": "Arabic error message"
}
```

### Common HTTP status codes

| Status | Action in Flutter |
|--------|-------------------|
| 400 | Show validation message to user |
| 401 | Clear token, redirect to login |
| 403 | Show "permission denied" |
| 404 | Resource not found |
| 409 | Phone number already registered |
| 503 | Server/database down — show retry |

### Example error messages (Arabic)

| Message | Meaning |
|---------|---------|
| `يجب تسجيل الدخول` | Not authenticated |
| `جلسة غير صالحة` | Invalid/expired token |
| `بيانات الدخول غير صحيحة` | Wrong login credentials |
| `ليس دورك` | Not your turn |
| `المباراة ليست نشطة` | Game is not active |
| `رقم الهاتف مسجل مسبقاً` | Phone already registered |
| `رقم الهاتف يجب أن يبدأ بصفر ويحتوي على أرقام فقط` | Invalid phone format |

Display `message` to the user (Arabic). You may add English translations in the app if needed.

---

## 12. Suggested App Screens & Flows

### Screen map

```
Splash
  └─ Auth (Login / Register)
       └─ Home
            ├─ Play Online      → Open games list → Join → Board
            ├─ Challenge Friend → Friends → Profile → Challenge → Board
            ├─ My Games         → Active / Finished tabs
            ├─ Leaderboard      → GET /api/users/leaderboard
            ├─ Friends          → Local list + Search to add
            └─ Profile          → Edit name, avatarUrl
```

### Registration flow

```
Register Screen
  • name, phone, password
  • Validate locally (password ≥ 6 chars)
  • POST /api/auth/register
  • Save token → Home
```

### First game flow (two devices / two accounts)

```
Device A (player1):
  POST /api/games { "color": "white" }
  → game.id = 5, status = "waiting"
  → Board screen (waiting overlay)

Device B (player2):
  GET /api/games?status=waiting
  → sees game 5
  POST /api/games/5/join
  → status = "active"

Both devices:
  Poll moves → play → POST /api/games/5/move
  On checkmate → send result in last move
  Or POST /api/games/5/resign
```

---

## 13. Data Models (Dart Reference)

```dart
class User {
  final int id;
  final String name;
  final String phone;
  final int rating;
  final String? avatarUrl;
  final DateTime createdAt;

  factory User.fromJson(Map<String, dynamic> json) => User(
    id: json['id'],
    name: json['name'],
    phone: json['phone'],
    rating: json['rating'],
    avatarUrl: json['avatarUrl'],
    createdAt: DateTime.parse(json['createdAt']),
  );
}

class Game {
  final int id;
  final int? whitePlayerId;
  final int? blackPlayerId;
  final PlayerSummary? whitePlayer;
  final PlayerSummary? blackPlayer;
  final String fen;
  final String status;
  final String? result;
  final int timeControlMs;
  final int whiteTimeMs;
  final int blackTimeMs;
  final String currentTurn;
  final int moveCount;
  final DateTime createdAt;
  final DateTime updatedAt;
  final DateTime? finishedAt;
}

class PlayerSummary {
  final int id;
  final String name;
  final String phone;
  final int rating;
}

class GameMove {
  final int id;
  final int moveNumber;
  final String color;
  final String fromSquare;
  final String toSquare;
  final String? san;
  final String fenAfter;
  final DateTime createdAt;
}
```

---

## 14. API Quick Reference

### Auth

| Method | Path | Auth | Description |
|--------|------|------|-------------|
| POST | `/auth/register` | No | Register player |
| POST | `/auth/login` | No | Login |
| GET | `/auth/me` | Yes | Current user |

### Users

| Method | Path | Auth | Description |
|--------|------|------|-------------|
| GET | `/users/leaderboard` | No | Top players |
| GET | `/users/search?q=` | Yes | Search players |
| GET | `/users/{id}` | Yes | Player profile |
| PATCH | `/users/me` | Yes | Update profile |

### Games

| Method | Path | Auth | Description |
|--------|------|------|-------------|
| GET | `/games` | Yes | List games |
| POST | `/games` | Yes | Create game / challenge |
| GET | `/games/{id}` | Yes | Game details |
| GET | `/games/{id}/moves` | Yes | Move history |
| POST | `/games/{id}/join` | Yes | Join open game |
| POST | `/games/{id}/move` | Yes | Submit move |
| POST | `/games/{id}/resign` | Yes | Resign |

### System

| Method | Path | Auth | Description |
|--------|------|------|-------------|
| GET | `/health` | No | Server health |

---

## 15. Testing Checklist

Use two test accounts on two devices or emulators.

- [ ] Register new player
- [ ] Login with phone + password
- [ ] Token persists after app restart
- [ ] `GET /auth/me` restores session
- [ ] Search for another player
- [ ] Update display name and avatar URL
- [ ] View leaderboard
- [ ] Create open game → second player joins
- [ ] Create direct challenge with `opponentId`
- [ ] Play full game: moves sync via polling
- [ ] Resign ends game with correct result
- [ ] Finished games appear in history (`status=finished`)
- [ ] 401 on expired token redirects to login
- [ ] Handle Arabic error messages gracefully

### Test accounts

Create via register endpoint or ask the backend team for sandbox accounts.

---

## 16. Roadmap — Upcoming Backend Features

Plan your architecture to support these without major rewrites:

| Feature | Status | Notes |
|---------|--------|-------|
| Friend requests API | Planned | Replace local friends storage |
| Game invitations API | Planned | Accept/decline before game starts |
| WebSocket real-time | Planned | Replace polling for moves & invites |
| Server-side move validation | Planned | Chess engine on server |
| ELO rating updates | Planned | Auto-update after finished games |
| Push notifications (FCM) | Planned | Invites, friend requests, your turn |
| Draw offer / accept | Planned | `POST /api/games/{id}/draw` |
| Clock sync | Planned | Server-authoritative timers |

---

## Support & Contact

- **API Base URL:** https://chess.sayedkhattab.com/api
- **Admin Panel:** https://chess.sayedkhattab.com/admin (web only)
- **Health Check:** https://chess.sayedkhattab.com/api/health

For backend changes or new endpoints (friendships, invitations, WebSocket), coordinate with the backend team before implementing dependent UI.

---

*Last updated: June 2026 — API version 1.0.0*
