Commit cc2ec1eb authored by Mahmoud Aglan's avatar Mahmoud Aglan

Pre-reset backup: connections docs, system description

parents
-----BEGIN RSA PRIVATE KEY-----
MIIEowIBAAKCAQEAwNRFitb/O8fS9TaUoCZ/VJZUEehvdyb1EgCjPrQbTeT6TlZh
rvkzHYcGIKsgarI6wuP9aK4+rLW8SL9VP6Ey3G4CgY/Hx9ZxhoSN5N2ffZkJE1Ji
hvgkDXzSN+l4P3e422ICxuVQqozba8/o8pZo46EzgRry760i9RcR8Q8JsXysjeQ1
Q68F8JhUYt1GNQlc5/A1EHEHyv1XIMDkYQ0eart1iUf9uvU/tp6pFTNUq/UtL/BT
RaJdnShbstS7bsfZwkyRtzXUlu15z/xdCsoXbbz+GC4oV7thzZQ+eRS8sZBGTsHF
6AaNqvd3QQnbFEpUSDzK3xupVEvLw3BbwYFvxwIDAQABAoIBAB4Gr9F/yvynD/1p
A1mwxPEJ+4tSU1ENeunTuZfA+eN2PVfHcayKV2BIrzaVDxYuLKI+WC5du5qvLeNy
D7c5xa63XqKIHgbLKKBWsbWqoPQwyU397SOxLgP/pMhaDYRsgxd+Oop4GMiF6IDw
PgjQTQLtDhUTejLCFghuEDgmLE87oi4oV3m8y36Yl1gHSHLzHivk8tiJoFdd9Jw3
FdM9wPS2FcafGaT7CDhbmo8XtHgynxbjCAX6D8tOpsbhVuClseRLXMfhkai0UuO4
JhgJ4tvDoxW3G/3qZkSvL/jUr5gybUCjVAcBfE7HIvfcYKQxU+iX9R8Q7cWzFO/d
RjooSWECgYEA9DDSYxBXuOkVHq9KDWnRBWUslLk2i9nvPEL+JVPqzR6Q+uRnZzl/
j54XBd25lAcCkTDmjZTHKFroX5uvgBzHGfGZFGtoZuObfVkzK9eicupPqTe7rcN6
fTJWeJnNJsYbkGzv0jrqvBJOG+/9zGVsn7UQZvWHiS9lgKYrDz2Vx5cCgYEAyieS
3xFK+lytLgJ6pqNx6RuvEAKgouZi3sgYIxwyMSA9Yap/5po6Osj8w9X18Bvm6YYF
gok+Zx63pEB7296RrmGDxkOw5Hl/gH07Yx2hvM4et3RyvK3udOXCdcXgWN0ue/Uf
H75UZ4CLAmNALEUa9lOcB2uydVHOhXCmgPveH1ECgYAtShzLKM3MStaS8VnfsP+G
a6RgFRXrzEjVuWsfizfiQUgMcG5JM93Xyi9k9CGmNcKhIRuxqKVjc7DjgqGDNlMr
GacVpXIgmxhMoE2gVQcZHyIVNXQGn1nJfJuTFJt7FIUqPTohmLHOneqEvfcpgKor
2M4o+mLf6718pdUYp4hvEwKBgBSJhLBIz3cz5xwfgFphjHcEKvrTaYJjKXQ8m8cl
XCwFfHbpnWjODlBejt9OY1frXcAnr3Odgct0IW/8ZRjnOaGfooWH5vavKTbigiAF
qKLHxfMZT3a/rNQPa3wPiEU+4zQQqQLOkUCanIS3lJNqydxwjg9q74xfrT19Pk0o
SV6hAoGBAKfidUGqWGH3FugbgG2cm7rK54nh978brZLKglekR1RlRWKG7QpRP33v
D13y3BD1rRM3vguD2aABhwqbYVt1hjHA+mv+yDzJps08FtZIasiTRpm2mFanOD84
yKf+0/HMD2G45HzoMYdG6BdZ5HP1y4WFNfRoxjwCTnwyrDMNJhOl
-----END RSA PRIVATE KEY-----
================================================================================
AL-ARCADE SELF-HOSTED SUPABASE — CONNECTION & REFERENCE
================================================================================
SERVER ACCESS
=============
IP: 3.68.63.185
User: ubuntu
SSH Key: NewServer.pem
SSH Command: ssh -i NewServer.pem ubuntu@3.68.63.185
All docker commands require sudo.
SUPABASE API URL
================
https://safe-supabase-kong.caprover.al-arcade.com
SUPABASE STUDIO (Dashboard)
============================
URL: https://safe-supabase-studio.caprover.al-arcade.com
Auth: HTTP Basic Auth
Username: admin
Password: Alarcade123#
API KEYS
========
Anon Key (public, client-side safe):
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb2xlIjoiYW5vbiIsImlzcyI6InN1cGFiYXNlIiwiaWF0IjoxNzM1Njg5NjAwLCJleHAiOjE4OTM0NTYwMDB9.31PF6PvP-pSrvRuQwLFptQoejR0W1A7o53lZhEbnz84
Service Role Key (secret, server-side only, bypasses RLS):
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb2xlIjoic2VydmljZV9yb2xlIiwiaXNzIjoic3VwYWJhc2UiLCJpYXQiOjE3MzU2ODk2MDAsImV4cCI6MTg5MzQ1NjAwMH0.wNfmuJNkX-bZwD7RbjxOChlRf_3Xm4I7bswEYTcDCg4
JWT Secret:
902343981eb82f43ff7a3757f3fcf25f14a2b9c729454eae5029ee3d1f189eb7
DATABASE DIRECT CONNECTION
==========================
Host: safe-supabase-db (internal) or localhost from server
Port: 5432
Database: postgres
Admin User: supabase_admin
Password: 28ac17bf9d4f7a3d1bad045408102cf5
Connection String (from server):
postgresql://supabase_admin:28ac17bf9d4f7a3d1bad045408102cf5@localhost:5432/postgres
Connection Pooler (Supavisor):
Port 6543 (transaction mode)
API ENDPOINTS
=============
All endpoints are relative to the API URL above.
All require header: apikey: <anon_key or service_role_key>
REST API: /rest/v1/
Auth: /auth/v1/
Storage: /storage/v1/
Realtime: /realtime/v1/
Edge Functions: /functions/v1/<function_name>
GraphQL: /graphql/v1
Postgres Meta: /pg/
CLIENT SDK SETUP
================
JavaScript/TypeScript:
----------------------
import { createClient } from '@supabase/supabase-js'
const supabase = createClient(
'https://safe-supabase-kong.caprover.al-arcade.com',
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb2xlIjoiYW5vbiIsImlzcyI6InN1cGFiYXNlIiwiaWF0IjoxNzM1Njg5NjAwLCJleHAiOjE4OTM0NTYwMDB9.31PF6PvP-pSrvRuQwLFptQoejR0W1A7o53lZhEbnz84'
)
Unity C#:
---------
var url = "https://safe-supabase-kong.caprover.al-arcade.com";
var key = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb2xlIjoiYW5vbiIsImlzcyI6InN1cGFiYXNlIiwiaWF0IjoxNzM1Njg5NjAwLCJleHAiOjE4OTM0NTYwMDB9.31PF6PvP-pSrvRuQwLFptQoejR0W1A7o53lZhEbnz84";
var client = new Supabase.Client(url, key);
Flutter/Dart:
-------------
final supabase = Supabase.initialize(
url: 'https://safe-supabase-kong.caprover.al-arcade.com',
anonKey: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb2xlIjoiYW5vbiIsImlzcyI6InN1cGFiYXNlIiwiaWF0IjoxNzM1Njg5NjAwLCJleHAiOjE4OTM0NTYwMDB9.31PF6PvP-pSrvRuQwLFptQoejR0W1A7o53lZhEbnz84',
);
Python:
-------
from supabase import create_client
supabase = create_client(
"https://safe-supabase-kong.caprover.al-arcade.com",
"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb2xlIjoiYW5vbiIsImlzcyI6InN1cGFiYXNlIiwiaWF0IjoxNzM1Njg5NjAwLCJleHAiOjE4OTM0NTYwMDB9.31PF6PvP-pSrvRuQwLFptQoejR0W1A7o53lZhEbnz84"
)
AVAILABLE FEATURES
==================
1. DATABASE (PostgreSQL 15)
- Create tables, schemas, views, functions, triggers
- Row Level Security (RLS) policies
- Extensions: pgvector, pg_graphql, pgjwt, uuid-ossp, pgcrypto
- Full SQL access via psql or REST API
2. AUTHENTICATION (GoTrue v2.186.0)
- Email/password sign-up and login
- Anonymous users
- JWT-based sessions
- Admin user management API
- Auto-confirm enabled (no SMTP configured yet)
3. STORAGE (v1.22.12)
- Create buckets (public or private)
- Upload/download files up to 50MB
- Image transformations via ImgProxy
- RLS policies on buckets/objects
4. REALTIME (v2.34.47)
- Postgres Changes (subscribe to INSERT/UPDATE/DELETE)
- Broadcast (send messages between clients)
- Presence (track online users)
- Enable per table: ALTER PUBLICATION supabase_realtime ADD TABLE <table_name>;
5. EDGE FUNCTIONS (Deno runtime v1.71.2)
- Deploy at /captain/data/safe-supabase/functions/
- Each function is a folder with index.ts
- Accessible at /functions/v1/<function_name>
6. REST API (PostgREST v12.2.8)
- Auto-generated REST endpoints for all tables
- Filtering, pagination, ordering, embedding (joins)
- Respects RLS policies based on JWT role
7. GRAPHQL (pg_graphql)
- Auto-generated GraphQL schema from tables
- Endpoint: /graphql/v1
8. CONNECTION POOLING (Supavisor 2.7.4)
- Transaction mode on port 6543
- Max 100 client connections, pool size 20
9. IMAGE TRANSFORMATION (ImgProxy v3.30.1)
- Resize, crop, format conversion
- WebP auto-detection
10. ANALYTICS (Logflare 1.36.1)
- PostgreSQL backend (not BigQuery)
- Log collection via Vector
MANAGING VIA SSH (for AI agents)
================================
Run SQL:
sudo docker exec safe-supabase-db psql -U supabase_admin -d postgres -c "YOUR SQL HERE"
Create a table:
sudo docker exec safe-supabase-db psql -U supabase_admin -d postgres -c "
CREATE TABLE public.my_table (
id uuid DEFAULT gen_random_uuid() PRIMARY KEY,
created_at timestamptz DEFAULT now(),
name text NOT NULL
);
ALTER TABLE public.my_table ENABLE ROW LEVEL SECURITY;
"
Enable Realtime on a table:
sudo docker exec safe-supabase-db psql -U supabase_admin -d postgres -c "
ALTER PUBLICATION supabase_realtime ADD TABLE public.my_table;
"
Create a storage bucket:
curl -X POST http://localhost:8787/storage/v1/bucket \
-H 'apikey: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb2xlIjoic2VydmljZV9yb2xlIiwiaXNzIjoic3VwYWJhc2UiLCJpYXQiOjE3MzU2ODk2MDAsImV4cCI6MTg5MzQ1NjAwMH0.wNfmuJNkX-bZwD7RbjxOChlRf_3Xm4I7bswEYTcDCg4' \
-H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb2xlIjoic2VydmljZV9yb2xlIiwiaXNzIjoic3VwYWJhc2UiLCJpYXQiOjE3MzU2ODk2MDAsImV4cCI6MTg5MzQ1NjAwMH0.wNfmuJNkX-bZwD7RbjxOChlRf_3Xm4I7bswEYTcDCg4' \
-H 'Content-Type: application/json' \
-d '{"id":"my-bucket","name":"my-bucket","public":true}'
List auth users:
curl http://localhost:8787/auth/v1/admin/users \
-H 'apikey: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb2xlIjoic2VydmljZV9yb2xlIiwiaXNzIjoic3VwYWJhc2UiLCJpYXQiOjE3MzU2ODk2MDAsImV4cCI6MTg5MzQ1NjAwMH0.wNfmuJNkX-bZwD7RbjxOChlRf_3Xm4I7bswEYTcDCg4' \
-H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb2xlIjoic2VydmljZV9yb2xlIiwiaXNzIjoic3VwYWJhc2UiLCJpYXQiOjE3MzU2ODk2MDAsImV4cCI6MTg5MzQ1NjAwMH0.wNfmuJNkX-bZwD7RbjxOChlRf_3Xm4I7bswEYTcDCg4'
Deploy an edge function:
# Create function directory on server
sudo mkdir -p /captain/data/safe-supabase/functions/my-function
# Write index.ts to the function directory
# Function becomes available at /functions/v1/my-function
Restart a service:
sudo docker restart safe-supabase-<service>
# Services: db, kong, auth, rest, realtime, storage, functions, meta, analytics, supavisor, imgproxy, vector, studio
View service logs:
sudo docker logs safe-supabase-<service> --tail 50
DOCKER COMPOSE LOCATION
========================
/captain/data/safe-supabase/compose/docker-compose.yml
PERSISTENT DATA
===============
/captain/data/safe-supabase/db/data — PostgreSQL data
/captain/data/safe-supabase/storage — File uploads
/captain/data/safe-supabase/functions — Edge functions code
/captain/data/safe-supabase/kong — Kong config
IMPORTANT NOTES
===============
- This is a SINGLE PROJECT deployment (not multi-tenant like supabase.com)
- All apps/games share the same database — use schemas or table prefixes to organize
- The anon key is safe to embed in client apps (RLS protects data)
- The service role key must NEVER be in client code (it bypasses all security)
- Always enable RLS on tables and write policies before exposing to clients
- Database is NOT exposed to the internet — only accessible via Kong API or SSH
- Expires: JWT keys expire in ~5 years (2030-01-01)
================================================================================
# Swiss System Tournament API — Complete Documentation
**Base URL:** `https://swissapi.caprover.al-arcade.com`
**API Prefix:** `/api/v1`
**Version:** 1.0.0
---
## Table of Contents
1. [Overview](#overview)
2. [Authentication](#authentication)
3. [Error Handling](#error-handling)
4. [Rate Limiting](#rate-limiting)
5. [Endpoints](#endpoints)
- [Health & Root](#health--root)
- [Auth](#auth)
- [Organizations](#organizations)
- [Events](#events)
- [Tournaments](#tournaments)
- [Players](#players)
- [Rounds](#rounds)
- [Pairings](#pairings)
- [Standings](#standings)
- [Categories](#categories)
- [Export](#export)
6. [Enums & Types](#enums--types)
7. [FIDE Dutch Pairing Engine](#fide-dutch-pairing-engine)
8. [Tiebreak Systems](#tiebreak-systems)
9. [Realtime WebSocket](#realtime-websocket)
10. [Tournament Lifecycle](#tournament-lifecycle)
11. [Multi-Tenancy & RBAC](#multi-tenancy--rbac)
---
## Overview
A full SaaS multi-tenant REST API for managing FIDE-compliant Swiss System chess tournaments. Features:
- Full FIDE Dutch System (C.04) pairing engine
- 11 tiebreak calculators
- Elo rating calculations with K-factor
- Multi-organization support with role-based access
- Realtime updates via Supabase WebSocket
- FIDE TRF export format
- Crosstable generation
- Bulk player import
- Automatic standings recalculation
---
## Authentication
All authenticated endpoints require a JWT Bearer token in the `Authorization` header:
```
Authorization: Bearer <access_token>
```
Tokens are obtained via `/api/v1/auth/login` or `/api/v1/auth/signup`. Refresh expired tokens via `/api/v1/auth/refresh`.
The JWT contains:
- `sub` — User UUID
- `role` — Supabase role
- `exp` — Expiration timestamp
---
## Error Handling
All errors follow this format:
```json
{
"statusCode": 400,
"message": "Human-readable error description",
"code": "MACHINE_READABLE_CODE"
}
```
### Error Codes
| Code | HTTP Status | Description |
|------|-------------|-------------|
| `VALIDATION_ERROR` | 400 | Request body/params failed validation |
| `UNAUTHORIZED` | 401 | Missing or invalid JWT token |
| `FORBIDDEN` | 403 | Insufficient role/permissions |
| `NOT_FOUND` | 404 | Resource does not exist |
| `CONFLICT` | 409 | Duplicate resource or state conflict |
| `BUSINESS_RULE_VIOLATION` | 422 | Operation violates tournament rules |
| `INTERNAL_ERROR` | 500 | Unexpected server error |
---
## Rate Limiting
- **Default:** 100 requests per 60 seconds per IP
- Headers returned: `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset`
- Exceeding returns `429 Too Many Requests`
---
## Endpoints
---
### Health & Root
#### `GET /`
Returns API metadata. No auth required.
**Response `200`:**
```json
{
"name": "Swiss System Tournament API",
"version": "1.0.0",
"docs": "/api/v1"
}
```
#### `GET /health`
Health check endpoint. No auth required.
**Response `200`:**
```json
{
"status": "ok",
"timestamp": "2026-05-24T18:04:39.000Z"
}
```
---
### Auth
Base path: `/api/v1/auth`
---
#### `POST /api/v1/auth/signup`
Create a new user account. Optionally creates an organization.
**Auth required:** No
**Request Body:**
| Field | Type | Required | Constraints |
|-------|------|----------|-------------|
| `email` | string | Yes | Valid email address |
| `password` | string | Yes | 8-128 characters |
| `fullName` | string | Yes | 1-200 characters |
| `organizationName` | string | No | 1-200 characters. If provided, creates an org and assigns user as `org_admin` |
**Example Request:**
```json
{
"email": "arbiter@chess-club.org",
"password": "securePassword123",
"fullName": "Magnus Carlsen",
"organizationName": "Oslo Chess Club"
}
```
**Response `201`:**
```json
{
"user": {
"id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"email": "arbiter@chess-club.org",
"fullName": "Magnus Carlsen"
},
"accessToken": "eyJhbGciOiJIUzI1NiIs...",
"refreshToken": "v1.MjAyNi0wNS0yNFQx..."
}
```
---
#### `POST /api/v1/auth/login`
Authenticate an existing user.
**Auth required:** No
**Request Body:**
| Field | Type | Required | Constraints |
|-------|------|----------|-------------|
| `email` | string | Yes | Valid email |
| `password` | string | Yes | Min 1 character |
**Example Request:**
```json
{
"email": "arbiter@chess-club.org",
"password": "securePassword123"
}
```
**Response `200`:**
```json
{
"user": {
"id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"email": "arbiter@chess-club.org",
"fullName": "Magnus Carlsen"
},
"accessToken": "eyJhbGciOiJIUzI1NiIs...",
"refreshToken": "v1.MjAyNi0wNS0yNFQx...",
"expiresAt": 1716580800
}
```
---
#### `POST /api/v1/auth/refresh`
Exchange a refresh token for new access/refresh tokens.
**Auth required:** No
**Request Body:**
| Field | Type | Required | Constraints |
|-------|------|----------|-------------|
| `refreshToken` | string | Yes | Valid refresh token |
**Response `200`:**
```json
{
"accessToken": "eyJhbGciOiJIUzI1NiIs...",
"refreshToken": "v1.MjAyNi0wNS0yNFQx...",
"expiresAt": 1716580800
}
```
---
#### `GET /api/v1/auth/me`
Get the authenticated user's profile.
**Auth required:** Yes
**Response `200`:**
```json
{
"id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"fullName": "Magnus Carlsen",
"displayName": "Magnus",
"avatarUrl": null,
"fideId": "1503014",
"fideRatingStandard": 2830,
"fideRatingRapid": 2830,
"fideRatingBlitz": 2886,
"nationalId": null,
"nationalRating": null,
"birthDate": "1990-11-30",
"countryCode": "NOR",
"title": "GM",
"createdAt": "2026-05-24T18:00:00.000Z",
"updatedAt": "2026-05-24T18:00:00.000Z"
}
```
---
#### `PATCH /api/v1/auth/me`
Update the authenticated user's profile.
**Auth required:** Yes
**Request Body (all fields optional):**
| Field | Type | Constraints |
|-------|------|-------------|
| `fullName` | string | 1-200 characters |
| `displayName` | string | Max 100 characters |
| `fideId` | string | FIDE ID number |
| `nationalId` | string | National federation ID |
| `birthDate` | string | Date format |
| `countryCode` | string | Exactly 3 characters (ISO 3166-1 alpha-3) |
| `title` | string | e.g. "GM", "IM", "FM", "CM", "WGM", "WIM" |
**Example Request:**
```json
{
"fideId": "1503014",
"fideRatingStandard": 2830,
"title": "GM",
"countryCode": "NOR"
}
```
**Response `200`:** Updated profile (same shape as `GET /me`).
---
### Organizations
Base path: `/api/v1/organizations`
All organization endpoints require authentication.
---
#### `POST /api/v1/organizations`
Create a new organization. The creating user becomes `org_admin`.
**Auth required:** Yes
**Role required:** None (any authenticated user)
**Request Body:**
| Field | Type | Required | Constraints |
|-------|------|----------|-------------|
| `name` | string | Yes | 1-200 characters |
| `contactEmail` | string | Yes | Valid email |
| `website` | string | No | Valid URL |
| `countryCode` | string | No | Exactly 3 characters |
| `fideFederationId` | string | No | FIDE federation code |
**Example Request:**
```json
{
"name": "Egyptian Chess Federation",
"contactEmail": "info@ecf-chess.org",
"website": "https://ecf-chess.org",
"countryCode": "EGY",
"fideFederationId": "EGY"
}
```
**Response `201`:**
```json
{
"id": "f47ac10b-58cc-4372-a567-0e02b2c3d479",
"name": "Egyptian Chess Federation",
"slug": "egyptian-chess-federation",
"logoUrl": null,
"website": "https://ecf-chess.org",
"countryCode": "EGY",
"fideFederationId": "EGY",
"contactEmail": "info@ecf-chess.org",
"settings": {},
"subscriptionTier": "free",
"maxTournaments": 5,
"maxPlayersPerTournament": 200,
"createdAt": "2026-05-24T18:00:00.000Z",
"updatedAt": "2026-05-24T18:00:00.000Z"
}
```
---
#### `GET /api/v1/organizations`
List all organizations the authenticated user is a member of.
**Auth required:** Yes
**Response `200`:**
```json
{
"data": [
{
"id": "f47ac10b-58cc-4372-a567-0e02b2c3d479",
"name": "Egyptian Chess Federation",
"slug": "egyptian-chess-federation",
"contactEmail": "info@ecf-chess.org",
"role": "org_admin",
"...": "..."
}
]
}
```
---
#### `GET /api/v1/organizations/:orgId`
Get a single organization's details.
**Auth required:** Yes
**Role required:** Any org member
**Path Parameters:**
| Param | Type | Description |
|-------|------|-------------|
| `orgId` | UUID | Organization ID |
**Response `200`:** Full organization object.
---
#### `PATCH /api/v1/organizations/:orgId`
Update an organization.
**Auth required:** Yes
**Role required:** `org_admin` or higher
**Request Body:** Same fields as create, all optional.
**Response `200`:** Updated organization object.
---
#### `DELETE /api/v1/organizations/:orgId`
Delete an organization and all its data (events, tournaments, etc).
**Auth required:** Yes
**Role required:** `org_admin`
**Response:** `204 No Content`
---
#### `GET /api/v1/organizations/:orgId/members`
List all members of an organization with their profiles.
**Auth required:** Yes
**Role required:** Any org member
**Response `200`:**
```json
{
"data": [
{
"id": "membership-uuid",
"userId": "user-uuid",
"organizationId": "org-uuid",
"role": "org_admin",
"status": "active",
"invitedBy": null,
"joinedAt": "2026-05-24T18:00:00.000Z",
"createdAt": "2026-05-24T18:00:00.000Z",
"updatedAt": "2026-05-24T18:00:00.000Z",
"profile": {
"fullName": "Magnus Carlsen",
"displayName": "Magnus",
"fideId": "1503014"
}
}
]
}
```
---
#### `POST /api/v1/organizations/:orgId/members`
Invite a user to the organization by email.
**Auth required:** Yes
**Role required:** `org_admin`
**Request Body:**
| Field | Type | Required | Constraints |
|-------|------|----------|-------------|
| `email` | string | Yes | Valid email of existing user |
| `role` | string | Yes | `org_admin`, `arbiter`, `player`, or `spectator` |
**Example Request:**
```json
{
"email": "arbiter@chess-club.org",
"role": "arbiter"
}
```
**Response `201`:** Membership record.
---
#### `PATCH /api/v1/organizations/:orgId/members/:memberId`
Change a member's role.
**Auth required:** Yes
**Role required:** `org_admin`
**Request Body:**
| Field | Type | Required | Constraints |
|-------|------|----------|-------------|
| `role` | string | Yes | `org_admin`, `arbiter`, `player`, or `spectator` |
**Response `200`:** Updated membership record.
---
#### `DELETE /api/v1/organizations/:orgId/members/:memberId`
Remove a member from the organization.
**Auth required:** Yes
**Role required:** `org_admin`
**Response:** `204 No Content`
---
### Events
Events are multi-tournament containers (e.g., "Cairo Open 2026" with sections A, B, C).
---
#### `POST /api/v1/organizations/:orgId/events`
Create a new event within an organization.
**Auth required:** Yes
**Role required:** `org_admin` or `arbiter`
**Org membership:** Required
**Request Body:**
| Field | Type | Required | Constraints |
|-------|------|----------|-------------|
| `name` | string | Yes | 1-200 characters |
| `description` | string | No | Free text |
| `venue` | string | No | Venue name |
| `city` | string | No | City name |
| `countryCode` | string | No | 3 characters (ISO) |
| `dateStart` | string | Yes | Date (YYYY-MM-DD) |
| `dateEnd` | string | Yes | Date (YYYY-MM-DD) |
| `timeControlDescription` | string | No | e.g., "90min + 30sec/move" |
| `timeControlType` | string | No | `standard`, `rapid`, `blitz`, `bullet`. Default: `standard` |
| `chiefArbiterId` | UUID | No | User ID of chief arbiter |
| `isFideRated` | boolean | No | Default: `false` |
**Example Request:**
```json
{
"name": "Cairo International Open 2026",
"description": "9-round Swiss system tournament",
"venue": "Cairo Convention Center",
"city": "Cairo",
"countryCode": "EGY",
"dateStart": "2026-07-01",
"dateEnd": "2026-07-09",
"timeControlDescription": "90 minutes for 40 moves + 30 minutes + 30 seconds increment from move 1",
"timeControlType": "standard",
"isFideRated": true
}
```
**Response `201`:**
```json
{
"id": "event-uuid",
"organizationId": "org-uuid",
"name": "Cairo International Open 2026",
"description": "9-round Swiss system tournament",
"venue": "Cairo Convention Center",
"city": "Cairo",
"countryCode": "EGY",
"dateStart": "2026-07-01",
"dateEnd": "2026-07-09",
"timeControlDescription": "90 minutes for 40 moves + 30 minutes + 30 seconds increment from move 1",
"timeControlType": "standard",
"chiefArbiterId": null,
"deputyArbiterIds": [],
"status": "draft",
"isFideRated": true,
"fideEventId": null,
"metadata": "{}",
"createdAt": "2026-05-24T18:00:00.000Z",
"updatedAt": "2026-05-24T18:00:00.000Z"
}
```
---
#### `GET /api/v1/organizations/:orgId/events`
List all events in an organization, ordered by `dateStart` descending.
**Auth required:** Yes
**Role required:** Any org member
**Response `200`:**
```json
{
"data": [ ...array of event objects... ]
}
```
---
#### `GET /api/v1/events/:eventId`
Get a single event by ID.
**Auth required:** Yes
**Response `200`:** Full event object.
---
#### `PATCH /api/v1/events/:eventId`
Update an event.
**Auth required:** Yes
**Request Body:** Same fields as create, all optional.
**Response `200`:** Updated event object.
---
#### `DELETE /api/v1/events/:eventId`
Delete an event and all its tournaments.
**Auth required:** Yes
**Response:** `204 No Content`
---
### Tournaments
Tournaments are individual sections within an event (e.g., "Open A", "Under 1800", "Women's Section").
---
#### `POST /api/v1/events/:eventId/tournaments`
Create a tournament within an event.
**Auth required:** Yes
**Request Body:**
| Field | Type | Required | Constraints |
|-------|------|----------|-------------|
| `name` | string | Yes | 1-200 characters |
| `tournamentType` | string | No | `swiss` (default), `round_robin`, `double_round_robin` |
| `roundsNumber` | integer | Yes | 1-30 |
| `tableStartNumber` | integer | No | Default: 1 |
| `pairingSystem` | string | No | Default: `dutch` |
| `accelerationMethod` | string/null | No | e.g., "baku" |
| `accelerationRounds` | integer | No | Default: 0 |
| `initialOrdering` | string[] | No | Default: `["fide_rating","name"]` |
| `tiebreakRules` | string[] | No | Default: `["buchholz_cut_1","buchholz","sonneborn_berger"]`. See [Tiebreak Systems](#tiebreak-systems) for valid values |
| `maxPlayers` | integer | No | Default: 200 |
| `byeValue` | string | No | Default: `"1.00"`. Points awarded for a bye |
| `maxByesPerPlayer` | integer | No | Default: 1 |
| `registrationOpensAt` | datetime | No | ISO 8601 |
| `registrationClosesAt` | datetime | No | ISO 8601 |
**Example Request:**
```json
{
"name": "Open Section A",
"tournamentType": "swiss",
"roundsNumber": 9,
"tiebreakRules": ["buchholz_cut_1", "buchholz", "sonneborn_berger", "progressive_score"],
"maxPlayers": 150,
"byeValue": "1.00",
"maxByesPerPlayer": 1,
"accelerationMethod": "baku",
"accelerationRounds": 3
}
```
**Response `201`:**
```json
{
"id": "tournament-uuid",
"eventId": "event-uuid",
"organizationId": "org-uuid",
"name": "Open Section A",
"tournamentType": "swiss",
"status": "draft",
"roundsNumber": 9,
"currentRound": 0,
"tableStartNumber": 1,
"pairingSystem": "dutch",
"accelerationMethod": "baku",
"accelerationRounds": 3,
"colorAllocationRule": "equalise_alternate",
"initialOrdering": ["fide_rating", "name"],
"tiebreakRules": ["buchholz_cut_1", "buchholz", "sonneborn_berger", "progressive_score"],
"ratedMinimum": null,
"ratedMaximum": null,
"kFactorOverride": null,
"maxPlayers": 150,
"byeValue": "1.00",
"maxByesPerPlayer": 1,
"registrationOpensAt": null,
"registrationClosesAt": null,
"startedAt": null,
"completedAt": null,
"createdAt": "2026-05-24T18:00:00.000Z",
"updatedAt": "2026-05-24T18:00:00.000Z"
}
```
---
#### `GET /api/v1/events/:eventId/tournaments`
List all tournaments in an event.
**Auth required:** Yes
**Response `200`:**
```json
{
"data": [ ...array of tournament objects... ]
}
```
---
#### `GET /api/v1/tournaments/:tournamentId`
Get a single tournament.
**Auth required:** Yes
**Response `200`:** Full tournament object.
---
#### `PATCH /api/v1/tournaments/:tournamentId`
Update a tournament's settings.
**Auth required:** Yes
**Request Body:** Same fields as create, all optional.
**Response `200`:** Updated tournament object.
---
#### `POST /api/v1/tournaments/:tournamentId/start`
Transition tournament from `draft`/`registration` to `in_progress`.
**Auth required:** Yes
**Business Rules:**
- Status must be `draft` or `registration`
- At least 2 registered players required
**Response `200`:** Tournament with `status: "in_progress"` and `startedAt` set.
---
#### `POST /api/v1/tournaments/:tournamentId/complete`
Mark a tournament as completed.
**Auth required:** Yes
**Business Rules:**
- Status must be `in_progress`
**Response `200`:** Tournament with `status: "completed"` and `completedAt` set.
---
#### `DELETE /api/v1/tournaments/:tournamentId`
Delete a tournament and all associated data.
**Auth required:** Yes
**Response:** `204 No Content`
---
### Players
Tournament player registration and management.
---
#### `POST /api/v1/tournaments/:tournamentId/players`
Register a single player in a tournament.
**Auth required:** Yes
**Request Body:**
| Field | Type | Required | Constraints |
|-------|------|----------|-------------|
| `name` | string | Yes | 1-200 characters |
| `startNumber` | integer | No | Auto-assigned if omitted |
| `birthDate` | string | No | Date (YYYY-MM-DD) |
| `countryCode` | string | No | 3 characters |
| `city` | string | No | Player's city |
| `club` | string | No | Club name |
| `title` | string | No | FIDE title (GM, IM, FM, etc.) |
| `fideId` | string | No | FIDE player ID |
| `fideRatingStandard` | integer | No | FIDE standard rating |
| `fideRatingRapid` | integer | No | FIDE rapid rating |
| `fideRatingBlitz` | integer | No | FIDE blitz rating |
| `nationalId` | string | No | National federation ID |
| `nationalRating` | integer | No | National rating |
| `categoryId` | UUID | No | Category/section UUID |
**Example Request:**
```json
{
"name": "Anand, Viswanathan",
"fideId": "5000017",
"fideRatingStandard": 2751,
"countryCode": "IND",
"title": "GM",
"club": "Petroleum Sports Promotion Board"
}
```
**Response `201`:**
```json
{
"id": "player-uuid",
"tournamentId": "tournament-uuid",
"organizationId": "org-uuid",
"categoryId": null,
"userId": null,
"startNumber": 1,
"name": "Anand, Viswanathan",
"birthDate": null,
"countryCode": "IND",
"city": null,
"club": "Petroleum Sports Promotion Board",
"title": "GM",
"fideId": "5000017",
"fideRatingStandard": 2751,
"fideRatingRapid": null,
"fideRatingBlitz": null,
"nationalId": null,
"nationalRating": null,
"isActive": true,
"withdrawnAfterRound": null,
"roundsExcluded": [],
"receivedByeInRounds": [],
"totalPoints": "0",
"floatHistory": [],
"colorHistory": [],
"createdAt": "2026-05-24T18:00:00.000Z",
"updatedAt": "2026-05-24T18:00:00.000Z"
}
```
---
#### `GET /api/v1/tournaments/:tournamentId/players`
List all players in a tournament, ordered by `startNumber` ascending.
**Auth required:** Yes
**Response `200`:**
```json
{
"data": [ ...array of player objects... ]
}
```
---
#### `GET /api/v1/tournaments/:tournamentId/players/:playerId`
Get a single player's details.
**Auth required:** Yes
**Response `200`:** Full player object.
---
#### `PATCH /api/v1/tournaments/:tournamentId/players/:playerId`
Update a player's information.
**Auth required:** Yes
**Request Body:** Same fields as create, all optional.
**Response `200`:** Updated player object.
---
#### `DELETE /api/v1/tournaments/:tournamentId/players/:playerId`
Remove or withdraw a player.
**Auth required:** Yes
**Behavior:**
- If tournament is `in_progress`: Player is **withdrawn** (`isActive=false`, `withdrawnAfterRound` set). Returns `200` with updated player.
- If tournament is `draft`/`registration`: Player is **hard-deleted**. Returns `204 No Content`.
---
#### `POST /api/v1/tournaments/:tournamentId/players/import`
Bulk import multiple players at once.
**Auth required:** Yes
**Request Body:**
| Field | Type | Required | Constraints |
|-------|------|----------|-------------|
| `players` | array | Yes | Array of player objects (same fields as single create) |
**Example Request:**
```json
{
"players": [
{ "name": "Carlsen, Magnus", "fideId": "1503014", "fideRatingStandard": 2830, "countryCode": "NOR", "title": "GM" },
{ "name": "Nakamura, Hikaru", "fideId": "2016192", "fideRatingStandard": 2794, "countryCode": "USA", "title": "GM" },
{ "name": "Ding, Liren", "fideId": "8603677", "fideRatingStandard": 2780, "countryCode": "CHN", "title": "GM" }
]
}
```
**Response `201`:**
```json
{
"data": [ ...array of created player objects... ],
"count": 3
}
```
---
### Rounds
Round generation and management. The pairing engine runs here.
---
#### `GET /api/v1/tournaments/:tournamentId/rounds`
List all rounds in a tournament, ordered by `roundNumber` ascending.
**Auth required:** Yes
**Response `200`:**
```json
{
"data": [
{
"id": "round-uuid",
"tournamentId": "tournament-uuid",
"organizationId": "org-uuid",
"roundNumber": 1,
"status": "completed",
"scheduledAt": null,
"startedAt": "2026-07-01T09:00:00.000Z",
"completedAt": "2026-07-01T14:00:00.000Z",
"pairedAt": "2026-07-01T08:45:00.000Z",
"pairedBy": "user-uuid",
"createdAt": "2026-05-24T18:00:00.000Z",
"updatedAt": "2026-05-24T18:00:00.000Z"
}
]
}
```
---
#### `GET /api/v1/tournaments/:tournamentId/rounds/:roundNum`
Get a specific round by number.
**Auth required:** Yes
**Path Parameters:**
| Param | Type | Description |
|-------|------|-------------|
| `tournamentId` | UUID | Tournament ID |
| `roundNum` | integer | Round number (1-indexed) |
**Response `200`:** Single round object.
---
#### `POST /api/v1/tournaments/:tournamentId/rounds/generate`
**Run the FIDE Dutch Pairing Engine** to generate the next round's pairings.
**Auth required:** Yes
**Business Rules:**
- Tournament must have status `in_progress`
- If a previous round exists, it must have status `completed`
- `currentRound + 1` must not exceed `roundsNumber`
- At least 2 active players required
**Response `201`:**
```json
{
"round": {
"id": "round-uuid",
"tournamentId": "tournament-uuid",
"roundNumber": 1,
"status": "paired",
"pairedAt": "2026-07-01T08:45:00.000Z",
"pairedBy": "user-uuid"
},
"pairingsCount": 75,
"bye": 142,
"logs": [
"Round 1: 150 active players",
"Bye allocated to player #142 (lowest in lowest score group)",
"Score group 0.0: 149 players",
"Bracket pairing: S1=75, S2=74",
"All 75 pairings generated successfully"
]
}
```
The `bye` field contains the `startNumber` of the player who received the bye, or `null` if even number of players.
The `logs` array provides a human-readable audit trail of every pairing decision made by the engine.
---
#### `DELETE /api/v1/tournaments/:tournamentId/rounds/:roundNum`
Unpair the last round (delete all its pairings and the round itself).
**Auth required:** Yes
**Business Rules:**
- Can only unpair the **last** round (where `currentRound === roundNum`)
- Decrements `tournament.currentRound`
**Response:** `204 No Content`
---
### Pairings
Individual game pairings within rounds.
---
#### `GET /api/v1/rounds/:roundId/pairings`
List all pairings in a round, ordered by `boardNumber` ascending.
**Auth required:** Yes
**Response `200`:**
```json
{
"data": [
{
"id": "pairing-uuid",
"roundId": "round-uuid",
"tournamentId": "tournament-uuid",
"organizationId": "org-uuid",
"boardNumber": 1,
"whitePlayerId": "player-uuid-1",
"blackPlayerId": "player-uuid-2",
"result": "not_played",
"whitePoints": "0.00",
"blackPoints": "0.00",
"isForfeit": false,
"isBye": false,
"resultEnteredBy": null,
"resultEnteredAt": null,
"resultConfirmed": false,
"createdAt": "2026-05-24T18:00:00.000Z",
"updatedAt": "2026-05-24T18:00:00.000Z"
}
]
}
```
---
#### `GET /api/v1/pairings/:pairingId`
Get a single pairing.
**Auth required:** Yes
**Response `200`:** Full pairing object.
---
#### `PATCH /api/v1/pairings/:pairingId/result`
Enter or update a game result.
**Auth required:** Yes
**Request Body:**
| Field | Type | Required | Constraints |
|-------|------|----------|-------------|
| `result` | string | Yes | See [Game Results](#game-results) enum |
**Example Request:**
```json
{
"result": "white_wins"
}
```
**Response `200`:** Updated pairing with computed points:
```json
{
"id": "pairing-uuid",
"boardNumber": 1,
"whitePlayerId": "player-uuid-1",
"blackPlayerId": "player-uuid-2",
"result": "white_wins",
"whitePoints": "1.00",
"blackPoints": "0.00",
"isForfeit": false,
"resultEnteredBy": "user-uuid",
"resultEnteredAt": "2026-07-01T14:30:00.000Z",
"...": "..."
}
```
**Point Calculation:**
| Result | White Points | Black Points |
|--------|:-----------:|:------------:|
| `white_wins` | 1.00 | 0.00 |
| `black_wins` | 0.00 | 1.00 |
| `draw` | 0.50 | 0.50 |
| `white_forfeit` | 0.00 | 1.00 |
| `black_forfeit` | 1.00 | 0.00 |
| `double_forfeit` | 0.00 | 0.00 |
| `bye_full` | 1.00 | 0.00 |
| `bye_half` | 0.50 | 0.00 |
| `bye_zero` | 0.00 | 0.00 |
**Side Effects:** Recalculates `totalPoints` for all affected players.
---
#### `POST /api/v1/rounds/:roundId/pairings/results`
Enter multiple results at once (batch).
**Auth required:** Yes
**Request Body:**
| Field | Type | Required | Constraints |
|-------|------|----------|-------------|
| `results` | array | Yes | Array of `{ pairingId: UUID, result: GameResult }` |
**Example Request:**
```json
{
"results": [
{ "pairingId": "uuid-1", "result": "white_wins" },
{ "pairingId": "uuid-2", "result": "draw" },
{ "pairingId": "uuid-3", "result": "black_wins" }
]
}
```
**Response `200`:**
```json
{
"data": [ ...updated pairing objects... ],
"allComplete": true
}
```
**Side Effects:**
- If `allComplete` is `true`: Round status is automatically updated to `completed`.
- Recalculates all player points.
---
#### `POST /api/v1/rounds/:roundId/pairings/manual`
Manually create a pairing (arbiter override).
**Auth required:** Yes
**Request Body:**
| Field | Type | Required | Constraints |
|-------|------|----------|-------------|
| `boardNumber` | integer | Yes | Min 1 |
| `whitePlayerId` | UUID | Yes | Must be a player in the tournament |
| `blackPlayerId` | UUID/null | No | `null` for bye |
| `isBye` | boolean | No | Default: `false`. If `true`, auto-sets result to `bye_full` |
**Example Request:**
```json
{
"boardNumber": 76,
"whitePlayerId": "player-uuid",
"blackPlayerId": null,
"isBye": true
}
```
**Response `201`:** Created pairing object.
---
#### `DELETE /api/v1/pairings/:pairingId`
Delete a single pairing.
**Auth required:** Yes
**Response:** `204 No Content`
---
### Standings
Tournament standings with tiebreak calculations.
---
#### `GET /api/v1/tournaments/:tournamentId/standings`
Get current standings for a tournament.
**Auth required:** Yes
**Query Parameters:**
| Param | Type | Required | Description |
|-------|------|----------|-------------|
| `round` | integer | No | Round number. Defaults to current round |
| `category` | UUID | No | Filter by category ID |
**Example:** `GET /api/v1/tournaments/abc123/standings?round=5&category=cat-uuid`
**Response `200`:**
```json
{
"data": [
{
"rank": 1,
"rankCategory": 1,
"playerId": "player-uuid",
"name": "Carlsen, Magnus",
"startNumber": 1,
"points": 4.5,
"tiebreakValues": [18.5, 22.0, 15.75, 10.0],
"fideRating": 2830,
"club": "Offerspill Chess Club",
"gamesPlayed": 5,
"wins": 4,
"draws": 1,
"losses": 0
},
{
"rank": 2,
"rankCategory": 2,
"playerId": "player-uuid-2",
"name": "Nakamura, Hikaru",
"startNumber": 2,
"points": 4.0,
"tiebreakValues": [19.0, 23.5, 14.00, 10.0],
"fideRating": 2794,
"club": null,
"gamesPlayed": 5,
"wins": 3,
"draws": 2,
"losses": 0
}
],
"roundNumber": 5
}
```
The `tiebreakValues` array corresponds to the tournament's `tiebreakRules` array in order. For example, if `tiebreakRules: ["buchholz_cut_1", "buchholz", "sonneborn_berger", "progressive_score"]`, then `tiebreakValues[0]` is Buchholz Cut 1, `[1]` is full Buchholz, etc.
---
#### `POST /api/v1/tournaments/:tournamentId/standings/recalculate`
Force recalculation of all standings and tiebreaks.
**Auth required:** Yes
**Business Rules:** At least one round must have been played.
**Response `200`:**
```json
{
"data": [ ...recalculated standings... ],
"roundNumber": 5
}
```
---
### Categories
Sub-sections within a tournament (e.g., "Under 2000", "Women", "Seniors 65+").
---
#### `POST /api/v1/tournaments/:tournamentId/categories`
Create a category.
**Auth required:** Yes
**Request Body:**
| Field | Type | Required | Constraints |
|-------|------|----------|-------------|
| `name` | string | Yes | 1-100 characters |
| `abbreviation` | string | No | Max 10 characters |
| `description` | string | No | Free text |
| `minRating` | integer | No | Minimum rating for eligibility |
| `maxRating` | integer | No | Maximum rating for eligibility |
| `minAge` | integer | No | Minimum age |
| `maxAge` | integer | No | Maximum age |
| `gender` | string/null | No | `"M"`, `"F"`, or `null` |
| `sortOrder` | integer | No | Display order. Default: 0 |
**Example Request:**
```json
{
"name": "Under 2000",
"abbreviation": "U2000",
"maxRating": 1999,
"sortOrder": 1
}
```
**Response `201`:**
```json
{
"id": "category-uuid",
"tournamentId": "tournament-uuid",
"organizationId": "org-uuid",
"name": "Under 2000",
"abbreviation": "U2000",
"description": null,
"minRating": null,
"maxRating": 1999,
"minAge": null,
"maxAge": null,
"gender": null,
"sortOrder": 1,
"createdAt": "2026-05-24T18:00:00.000Z",
"updatedAt": "2026-05-24T18:00:00.000Z"
}
```
---
#### `GET /api/v1/tournaments/:tournamentId/categories`
List all categories for a tournament, ordered by `sortOrder`.
**Auth required:** Yes
**Response `200`:**
```json
{
"data": [ ...array of category objects... ]
}
```
---
#### `PATCH /api/v1/categories/:categoryId`
Update a category.
**Auth required:** Yes
**Request Body:** Same fields as create, all optional.
**Response `200`:** Updated category object.
---
#### `DELETE /api/v1/categories/:categoryId`
Delete a category. Players in this category will have their `categoryId` set to `null`.
**Auth required:** Yes
**Response:** `204 No Content`
---
### Export
Export tournament data in various formats.
---
#### `GET /api/v1/tournaments/:tournamentId/export/trf`
Export in **FIDE Tournament Report File** format. This is the official format required for FIDE-rated tournament submissions.
**Auth required:** Yes
**Response:** Plain text file download.
- `Content-Type: text/plain`
- `Content-Disposition: attachment; filename="Tournament_Name.trf"`
**TRF Format Example:**
```
012 Cairo International Open 2026
022 Cairo
032 EGY
042 2026-07-01
052 Chief Arbiter Name
062 150
072 9
082 1234567890
092 Standard
102 IA Chief Arbiter
132 90/40+30+30
001 1 Carlsen g NOR 1503014 2830 9.0 1 w 1 2 b 1 3 w = ...
001 2 Nakamura g USA 2016192 2794 8.5 1 b 0 2 w 1 3 b 1 ...
```
---
#### `GET /api/v1/tournaments/:tournamentId/export/json`
Export complete tournament data as JSON.
**Auth required:** Yes
**Response `200`:**
```json
{
"tournament": { ...full tournament object... },
"event": { ...full event object... },
"players": [ ...all player objects... ],
"rounds": [ ...all round objects... ],
"pairings": [ ...all pairing objects... ]
}
```
---
#### `GET /api/v1/tournaments/:tournamentId/export/crosstable`
Generate a crosstable showing all head-to-head results.
**Auth required:** Yes
**Response `200`:**
```json
{
"data": [
{
"startNumber": 1,
"name": "Carlsen, Magnus",
"rating": 2830,
"points": 7.5,
"results": {
"2": "1w",
"5": "0.5b",
"8": "1w",
"3": "0.5w",
"12": "1b",
"4": "1w",
"6": "0.5b",
"9": "1w",
"7": "1b"
}
}
]
}
```
The `results` object maps opponent `startNumber` to a result string:
- `"1w"` = Win with white
- `"1b"` = Win with black
- `"0.5w"` = Draw with white
- `"0.5b"` = Draw with black
- `"0w"` = Loss with white
- `"0b"` = Loss with black
---
## Enums & Types
### Tournament Type
| Value | Description |
|-------|-------------|
| `swiss` | Swiss system (paired by engine) |
| `round_robin` | Everyone plays everyone |
| `double_round_robin` | Everyone plays everyone twice |
### Tournament Status
| Value | Description |
|-------|-------------|
| `draft` | Initial state, setup phase |
| `registration` | Open for player registration |
| `in_progress` | Tournament has started, rounds being played |
| `completed` | All rounds finished |
| `cancelled` | Tournament was cancelled |
### Round Status
| Value | Description |
|-------|-------------|
| `pending` | Round exists but not yet paired |
| `paired` | Pairings generated, games not started |
| `in_progress` | Games being played |
| `completed` | All results entered |
### Game Results
| Value | White Points | Black Points | Notes |
|-------|:---:|:---:|-------|
| `white_wins` | 1 | 0 | Normal white victory |
| `black_wins` | 0 | 1 | Normal black victory |
| `draw` | 0.5 | 0.5 | Drawn game |
| `white_forfeit` | 0 | 1 | White forfeited |
| `black_forfeit` | 1 | 0 | Black forfeited |
| `double_forfeit` | 0 | 0 | Both forfeited |
| `bye_full` | 1 | - | Full-point bye |
| `bye_half` | 0.5 | - | Half-point bye |
| `bye_zero` | 0 | - | Zero-point bye |
| `not_played` | 0 | 0 | Default, no result yet |
### User Roles (hierarchy)
| Role | Level | Capabilities |
|------|:-----:|-------------|
| `super_admin` | 5 | Everything across all orgs |
| `org_admin` | 4 | Full control of own org |
| `arbiter` | 3 | Manage events/tournaments they're assigned to |
| `player` | 2 | View data, participate |
| `spectator` | 1 | Read-only access |
### Time Control Type
| Value | Description |
|-------|-------------|
| `standard` | Classical (≥60 min) |
| `rapid` | Rapid (15-60 min) |
| `blitz` | Blitz (3-15 min) |
| `bullet` | Bullet (<3 min) |
### FIDE Titles
| Value | Description |
|-------|-------------|
| `GM` | Grandmaster |
| `IM` | International Master |
| `FM` | FIDE Master |
| `CM` | Candidate Master |
| `WGM` | Woman Grandmaster |
| `WIM` | Woman International Master |
| `WFM` | Woman FIDE Master |
| `WCM` | Woman Candidate Master |
---
## FIDE Dutch Pairing Engine
The API implements the **FIDE Dutch System (C.04)** pairing algorithm. This is the official system used in FIDE-rated Swiss tournaments worldwide.
### How It Works
When you call `POST /tournaments/:id/rounds/generate`, the engine:
1. **Filters active players** — excludes withdrawn and excluded players
2. **Allocates bye** — lowest-ranked player in lowest score group who hasn't had a bye
3. **Applies acceleration** (if configured) — artificially inflates scores in early rounds to create more decisive games at the top
4. **Forms score groups** — players with the same score are grouped together
5. **For each bracket** (highest score group down):
- Splits into S1 (top half by rating) and S2 (bottom half)
- Checks compatibility constraints:
- No player pair that already played each other
- Color allocation constraints (absolute/strong/mild)
- Attempts pairing with systematic transposition and exchange
- Applies float rules for players moving between groups
6. **Assigns board numbers** — top-rated pair on board 1
7. **Allocates colors** according to FIDE C.04 rules
### Color Allocation Rules (FIDE C.04.A7-A9)
| Priority | Rule | Description |
|----------|------|-------------|
| **Absolute** | Must | Had same color last 2 games → MUST get other color |
| **Strong** | Should | More total games with one color → SHOULD get other color |
| **Mild** | Prefer | Alternate from last game → preference only |
### Acceleration
When `accelerationMethod` is set (e.g., "baku"), players' virtual scores are boosted in the first N rounds (`accelerationRounds`) to create stronger pairings at the top tables early in the tournament.
### Float Management
Players who cannot be paired within their score group "float" up or down:
- **Float up**: Paired against a higher-scoring opponent
- **Float down**: Paired against a lower-scoring opponent
- The engine tracks float history to avoid repeated floating
---
## Tiebreak Systems
The following tiebreak calculators are available. Set them in `tournament.tiebreakRules`:
| Value | Name | Description |
|-------|------|-------------|
| `buchholz` | Buchholz | Sum of all opponents' scores |
| `buchholz_cut_1` | Buchholz Cut 1 | Buchholz minus lowest opponent score |
| `buchholz_median` | Buchholz Median | Buchholz minus highest and lowest |
| `sonneborn_berger` | Sonneborn-Berger | Sum of defeated opponents' scores + half of drawn opponents' scores |
| `direct_encounter` | Direct Encounter | Result between tied players |
| `number_of_wins` | Number of Wins | Total games won (excludes forfeits) |
| `number_of_blacks` | Number of Blacks | Games played with black pieces |
| `koya` | Koya System | Points scored against opponents with ≥50% |
| `progressive_score` | Progressive Score | Cumulative sum of round-by-round scores |
| `average_rating_opponents` | Average Rating of Opponents | Mean rating of all opponents faced |
| `performance_rating` | Performance Rating | FIDE performance rating |
### FIDE Recommended Tiebreak Order
For Swiss tournaments, FIDE recommends:
```json
["buchholz_cut_1", "buchholz", "sonneborn_berger"]
```
For Round Robin:
```json
["direct_encounter", "number_of_wins", "sonneborn_berger"]
```
---
## Realtime WebSocket
The API broadcasts live updates via Supabase Realtime. Connect to receive instant notifications when:
- **Pairings are generated** — new round appears
- **Results are entered** — game results update live
- **Standings change** — rankings recalculated
### Connecting
```javascript
import { createClient } from '@supabase/supabase-js'
const supabase = createClient(
'https://safe-supabase-kong.caprover.al-arcade.com',
'YOUR_ANON_KEY'
)
// Subscribe to pairings changes for a tournament
supabase
.channel('tournament-updates')
.on('postgres_changes', {
event: '*',
schema: 'public',
table: 'pairings',
filter: `tournament_id=eq.YOUR_TOURNAMENT_ID`
}, (payload) => {
console.log('Pairing update:', payload)
})
.on('postgres_changes', {
event: '*',
schema: 'public',
table: 'standings',
filter: `tournament_id=eq.YOUR_TOURNAMENT_ID`
}, (payload) => {
console.log('Standing update:', payload)
})
.on('postgres_changes', {
event: '*',
schema: 'public',
table: 'rounds',
filter: `tournament_id=eq.YOUR_TOURNAMENT_ID`
}, (payload) => {
console.log('Round update:', payload)
})
.subscribe()
```
### Published Tables
| Table | Events | Use Case |
|-------|--------|----------|
| `pairings` | INSERT, UPDATE | New pairings, result entry |
| `standings` | INSERT, UPDATE | Ranking changes |
| `rounds` | INSERT, UPDATE | New rounds, status changes |
| `tournament_players` | UPDATE | Player withdrawal, score updates |
---
## Tournament Lifecycle
```
┌──────────┐ start() ┌─────────────┐ complete() ┌───────────┐
│ draft │ ───────────────> │ in_progress │ ──────────────> │ completed │
└──────────┘ └─────────────┘ └───────────┘
│ │
│ (optional) │ generate() / result / generate() ...
v │
┌──────────────┐ │
│ registration │ ───────────────────┘
└──────────────┘ start()
┌───────────┐
│ cancelled │ (can set from any state)
└───────────┘
```
### Typical Tournament Flow
1. **Create event**`POST /organizations/:orgId/events`
2. **Create tournament**`POST /events/:eventId/tournaments`
3. **Register players**`POST /tournaments/:id/players` (or `/import` for bulk)
4. **Start tournament**`POST /tournaments/:id/start`
5. **Generate round 1**`POST /tournaments/:id/rounds/generate`
6. **Enter results**`PATCH /pairings/:id/result` (or batch via `POST /rounds/:id/pairings/results`)
7. **Repeat steps 5-6** for each round
8. **View standings**`GET /tournaments/:id/standings`
9. **Complete tournament**`POST /tournaments/:id/complete`
10. **Export**`GET /tournaments/:id/export/trf`
---
## Multi-Tenancy & RBAC
### Organization Isolation
Every resource belongs to an organization via `organization_id`. PostgreSQL Row Level Security (RLS) policies enforce that:
- Users can only see data from organizations they belong to
- Admin operations require `org_admin` role in that specific org
- The API layer enforces this at the application level too (defense-in-depth)
### Role-Based Access Control Matrix
| Action | Super Admin | Org Admin | Arbiter | Player | Spectator |
|--------|:-:|:-:|:-:|:-:|:-:|
| Create organizations | Yes | Yes | Yes | Yes | Yes |
| Manage own org settings | Yes | Yes | - | - | - |
| Delete org | Yes | Yes | - | - | - |
| Invite/remove members | Yes | Yes | - | - | - |
| Create events | Yes | Yes | Yes | - | - |
| Manage tournaments | Yes | Yes | Yes | - | - |
| Generate pairings | Yes | Yes | Yes | - | - |
| Enter results | Yes | Yes | Yes | - | - |
| View standings/pairings | Yes | Yes | Yes | Yes | Yes |
| Export TRF | Yes | Yes | Yes | - | - |
| View audit logs | Yes | Yes | - | - | - |
### Organization Header
For routes that require org context, include:
```
X-Organization-Id: <org-uuid>
```
Or use the `:orgId` path parameter where available.
---
## Full cURL Examples
### Complete Tournament Setup
```bash
# 1. Sign up
curl -X POST https://swissapi.caprover.al-arcade.com/api/v1/auth/signup \
-H "Content-Type: application/json" \
-d '{
"email": "admin@chess-club.org",
"password": "mySecurePass123",
"fullName": "Tournament Director",
"organizationName": "Local Chess Club"
}'
# Save the token
TOKEN="eyJhbGciOiJIUzI1NiIs..."
# 2. Create event
curl -X POST https://swissapi.caprover.al-arcade.com/api/v1/organizations/$ORG_ID/events \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "Spring Open 2026",
"dateStart": "2026-06-01",
"dateEnd": "2026-06-03",
"venue": "Community Center",
"timeControlType": "rapid",
"timeControlDescription": "15+10"
}'
# 3. Create tournament
curl -X POST https://swissapi.caprover.al-arcade.com/api/v1/events/$EVENT_ID/tournaments \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "Open Section",
"roundsNumber": 7,
"tiebreakRules": ["buchholz_cut_1", "buchholz", "sonneborn_berger"]
}'
# 4. Bulk import players
curl -X POST https://swissapi.caprover.al-arcade.com/api/v1/tournaments/$TOURNAMENT_ID/players/import \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"players": [
{"name": "Smith, John", "fideRatingStandard": 2100, "countryCode": "USA"},
{"name": "Mueller, Hans", "fideRatingStandard": 1950, "countryCode": "GER"},
{"name": "Petrov, Ivan", "fideRatingStandard": 2200, "countryCode": "RUS"}
]
}'
# 5. Start tournament
curl -X POST https://swissapi.caprover.al-arcade.com/api/v1/tournaments/$TOURNAMENT_ID/start \
-H "Authorization: Bearer $TOKEN"
# 6. Generate round 1
curl -X POST https://swissapi.caprover.al-arcade.com/api/v1/tournaments/$TOURNAMENT_ID/rounds/generate \
-H "Authorization: Bearer $TOKEN"
# 7. Enter results (batch)
curl -X POST https://swissapi.caprover.al-arcade.com/api/v1/rounds/$ROUND_ID/pairings/results \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"results": [
{"pairingId": "uuid-1", "result": "white_wins"},
{"pairingId": "uuid-2", "result": "draw"},
{"pairingId": "uuid-3", "result": "black_wins"}
]
}'
# 8. Get standings
curl https://swissapi.caprover.al-arcade.com/api/v1/tournaments/$TOURNAMENT_ID/standings \
-H "Authorization: Bearer $TOKEN"
# 9. Export TRF
curl https://swissapi.caprover.al-arcade.com/api/v1/tournaments/$TOURNAMENT_ID/export/trf \
-H "Authorization: Bearer $TOKEN" \
-o tournament.trf
```
---
## SDK Integration Example (TypeScript)
```typescript
const API_BASE = 'https://swissapi.caprover.al-arcade.com/api/v1';
class SwissSystemClient {
private token: string;
constructor(token: string) {
this.token = token;
}
private async request(method: string, path: string, body?: unknown) {
const res = await fetch(`${API_BASE}${path}`, {
method,
headers: {
'Authorization': `Bearer ${this.token}`,
'Content-Type': 'application/json',
},
body: body ? JSON.stringify(body) : undefined,
});
if (!res.ok) throw new Error(`${res.status}: ${await res.text()}`);
if (res.status === 204) return null;
return res.json();
}
// Auth
static async login(email: string, password: string) {
const res = await fetch(`${API_BASE}/auth/login`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ email, password }),
});
const data = await res.json();
return new SwissSystemClient(data.accessToken);
}
// Tournaments
createTournament(eventId: string, data: unknown) {
return this.request('POST', `/events/${eventId}/tournaments`, data);
}
startTournament(id: string) {
return this.request('POST', `/tournaments/${id}/start`);
}
// Players
importPlayers(tournamentId: string, players: unknown[]) {
return this.request('POST', `/tournaments/${tournamentId}/players/import`, { players });
}
// Rounds
generateRound(tournamentId: string) {
return this.request('POST', `/tournaments/${tournamentId}/rounds/generate`);
}
// Results
enterResults(roundId: string, results: Array<{pairingId: string, result: string}>) {
return this.request('POST', `/rounds/${roundId}/pairings/results`, { results });
}
// Standings
getStandings(tournamentId: string, round?: number) {
const query = round ? `?round=${round}` : '';
return this.request('GET', `/tournaments/${tournamentId}/standings${query}`);
}
// Export
async exportTRF(tournamentId: string): Promise<string> {
const res = await fetch(`${API_BASE}/tournaments/${tournamentId}/export/trf`, {
headers: { 'Authorization': `Bearer ${this.token}` },
});
return res.text();
}
}
```
---
## Database Schema (ERD Summary)
```
organizations
└── org_memberships ── user_profiles (auth.users)
└── events
└── tournaments
├── categories
├── tournament_players
├── rounds
│ └── pairings
└── standings
└── audit_logs
```
All tables have `organization_id` for multi-tenancy isolation via RLS.
---
## Deployment Info
| Property | Value |
|----------|-------|
| Public URL | `https://swissapi.caprover.al-arcade.com` |
| Internal URL | `http://srv-captain--swissapi` |
| Health Check | `GET /health` |
| Runtime | Node.js 20 (Alpine) |
| Database | PostgreSQL 15 (Supabase) |
| Auth Provider | Supabase GoTrue |
| Realtime | Supabase Realtime (WebSocket) |
================================================================================
STOCKFISH CHESS BOT API - COMPLETE REFERENCE
================================================================================
BASE URL: https://stockfishapi.caprover.al-arcade.com
================================================================================
ENDPOINTS
================================================================================
--------------------------------------------------------------------------------
1. GET MOVE FROM BOT
--------------------------------------------------------------------------------
POST /api/chess/move
Description:
Get a chess move from a specific bot personality. The bot will play according
to its configured skill level, depth, contempt, and blunder probability.
Response includes simulated human-like think time.
Headers:
Content-Type: application/json
Request Body:
{
"fen": "string (required) - FEN notation of the current board position",
"bot_id": "string (required) - ID of the bot to play against",
"time_limit_ms": 0 (optional, int) - time limit in ms. If 0 or omitted, uses depth-based search
}
Response (200 OK):
{
"best_move": "e2e4", // UCI move notation (from-square + to-square, e.g. e2e4, g1f3, e7e8q for promotion)
"evaluation": 0.35, // centipawn evaluation / 100. Positive = white advantage. 999.0 = white mates, -999.0 = black mates
"depth": 10, // search depth reached
"nodes": 125000, // nodes searched
"think_time_ms": 1500, // total time including artificial delay (simulates human thinking)
"pv": "e2e4 e7e5 g1f3" // principal variation (best line), space-separated UCI moves
}
Errors:
400: {"error": "invalid request body"}
400: {"error": "fen is required"}
400: {"error": "bot_id is required"}
404: {"error": "bot not found"}
500: {"error": "engine error: ..."}
Example:
curl -X POST https://stockfishapi.caprover.al-arcade.com/api/chess/move \
-H "Content-Type: application/json" \
-d '{"fen": "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1", "bot_id": "nour"}'
--------------------------------------------------------------------------------
2. ANALYZE POSITION
--------------------------------------------------------------------------------
POST /api/chess/analyze
Description:
Deep multi-line analysis of a position at full Stockfish strength (skill 20).
Returns multiple candidate moves ranked by evaluation.
Headers:
Content-Type: application/json
Request Body:
{
"fen": "string (required) - FEN notation of the position to analyze",
"depth": 18, (optional, int 1-30, default 18) - search depth
"lines": 3 (optional, int 1-5, default 3) - number of candidate moves to return
}
Response (200 OK):
{
"fen": "rnbqkbnr/pppppppp/8/8/4P3/8/PPPP1PPP/RNBQKBNR b KQkq - 0 1",
"depth": 18,
"lines": [
{
"rank": 1,
"move": "e7e5", // best move in UCI notation
"evaluation": -0.25, // eval from engine perspective (positive = side to move is better)
"depth": 18,
"pv": "e7e5 g1f3 b8c6 ..." // full principal variation
},
{
"rank": 2,
"move": "c7c5",
"evaluation": -0.15,
"depth": 18,
"pv": "c7c5 g1f3 d7d6 ..."
}
]
}
Errors:
400: {"error": "invalid request body"}
400: {"error": "fen is required"}
500: {"error": "engine error: ..."}
Timeout: 30 seconds max per analysis request.
Example:
curl -X POST https://stockfishapi.caprover.al-arcade.com/api/chess/analyze \
-H "Content-Type: application/json" \
-d '{"fen": "rnbqkbnr/pppppppp/8/8/4P3/8/PPPP1PPP/RNBQKBNR b KQkq - 0 1", "depth": 20, "lines": 3}'
--------------------------------------------------------------------------------
3. LIST BOTS
--------------------------------------------------------------------------------
GET /api/chess/bots
Description:
Returns all available bot personalities sorted by difficulty (easiest first).
Response (200 OK):
{
"bots": [
{
"id": "amina",
"name": "Amina",
"name_ar": "أمينة المبتدئة",
"style": "beginner",
"style_ar": "مبتدئة",
"bio": "Just learning chess! Makes mistakes but tries her best.",
"bio_ar": "لسه بتتعلم شطرنج! بتغلط كتير بس بتحاول.",
"elo_min": 400,
"elo_max": 600,
"skill_level": 1,
"depth": 3,
"contempt": 0,
"blunder_chance": 0.30,
"think_time_min_ms": 500,
"think_time_max_ms": 2000,
"opening_book": [],
"avatar_id": "bot-amina",
"portrait_url": "/portraits/amina.png"
},
...
]
}
Example:
curl https://stockfishapi.caprover.al-arcade.com/api/chess/bots
--------------------------------------------------------------------------------
4. POOL STATS
--------------------------------------------------------------------------------
GET /api/chess/stats
Description:
Returns the current Stockfish process pool status.
Response (200 OK):
{
"pool_alive": 6, // number of Stockfish processes currently running
"pool_idle": 4 // number of those processes currently idle (available)
}
Example:
curl https://stockfishapi.caprover.al-arcade.com/api/chess/stats
--------------------------------------------------------------------------------
5. HEALTH CHECK
--------------------------------------------------------------------------------
GET /health
Description:
Verifies the engine is operational by running a depth-1 search on the
starting position. Used by CapRover/Docker health checks.
Response (200 OK):
{
"status": "healthy",
"engine": "stockfish-18",
"pool_alive": 6,
"pool_idle": 4
}
Response (503 Service Unavailable):
{
"status": "unhealthy",
"error": "acquire process: context deadline exceeded"
}
Example:
curl https://stockfishapi.caprover.al-arcade.com/health
================================================================================
AVAILABLE BOTS (sorted by difficulty)
================================================================================
ID | Name | Arabic Name | Style | Style (AR) | ELO | Skill | Depth | Blunder% | Think Time (ms) | Portrait
--------------|-----------------|------------------|--------------|-------------|-----------|-------|-------|----------|-----------------|------------------
amina | Amina | أمينة المبتدئة | beginner | مبتدئة | 400-600 | 1 | 3 | 30% | 500-2000 | /portraits/amina.png
tarek | Tarek | طارق المتحفظ | defensive | دفاعي | 800-1000 | 5 | 6 | 15% | 1000-3000 | /portraits/tarek.png
nour | Nour | نور المهاجمة | aggressive | هجومية | 1000-1200 | 8 | 10 | 8% | 800-3000 | /portraits/nour.png
omar | Omar | عمر الاستراتيجي | positional | استراتيجي | 1200-1400 | 11 | 12 | 4% | 1500-4000 | /portraits/omar.png
layla | Layla | ليلى المبدعة | creative | إبداعية | 1400-1600 | 14 | 14 | 2% | 1000-5000 | /portraits/layla.png
ziad | Ziad | زياد الصلب | solid | صلب | 1600-1800 | 17 | 16 | 1% | 2000-6000 | /portraits/ziad.png
grandmaster | Grandmaster Bot | الجراند ماستر | near_perfect | شبه مثالي | 2000-2200 | 20 | 20 | 0% | 3000-8000 | /portraits/grandmaster.png
================================================================================
BOT BEHAVIOR DETAILS
================================================================================
BLUNDER MECHANISM:
Each move request, the bot rolls against its blunder_chance probability.
If it "blunders", the engine searches at depth=1 with skill_level=0,
producing a weak/random move. Otherwise it plays at its configured strength.
THINK TIME SIMULATION:
After the engine returns a move, the API adds artificial delay to simulate
human-like thinking. The delay is random between think_time_min and think_time_max.
Total response time = engine_time + artificial_delay.
If the client disconnects (context cancelled), the delay is aborted.
CONTEMPT:
Positive contempt = bot plays more aggressively, avoids draws.
Negative contempt = bot is happy to draw, plays defensively.
Range: -100 to 100.
SKILL LEVEL:
Stockfish's internal skill parameter (0-20).
0 = weakest, introduces random errors.
20 = full strength, no artificial weakening.
OPENING BOOKS (metadata only, not enforced by engine):
Listed per bot for frontend display. The engine does not use opening books;
it calculates from the given FEN position directly.
AVATAR IDs:
Each bot has an avatar_id field for frontend use (e.g. "bot-amina", "bot-nour").
Map these to your avatar image assets.
PORTRAITS:
Each bot has a portrait_url field pointing to a 512x512 pixel image.
Portraits are served publicly at: https://stockfishapi.caprover.al-arcade.com/portraits/{bot_id}.png
Supported formats: PNG, JPG, WebP.
Upload via admin panel: /admin/bots/edit/{id} -> Portrait upload form.
Recommended dimensions: 512x512 pixels (square).
These are character portraits/avatars for display in the chess UI.
STYLE LABELS (Arabic):
Each bot has a "style_ar" field with the Arabic translation of its play style.
Use this for bilingual (EN/AR) UI display.
Examples: "مبتدئة" (beginner), "دفاعي" (defensive), "هجومية" (aggressive),
"استراتيجي" (positional), "إبداعية" (creative), "صلب" (solid),
"شبه مثالي" (near_perfect).
================================================================================
MOVE NOTATION
================================================================================
All moves use UCI (Universal Chess Interface) long algebraic notation:
- Normal move: source_square + destination_square (e.g. "e2e4", "g1f3")
- Pawn promotion: source + destination + piece_letter (e.g. "e7e8q" for queen promotion)
- Castling: king's start + king's end (e.g. "e1g1" for white kingside, "e1c1" for queenside)
- En passant: normal pawn capture notation (e.g. "e5d6")
Piece letters for promotion: q=queen, r=rook, b=bishop, n=knight
================================================================================
FEN (Forsyth-Edwards Notation) FORMAT
================================================================================
A FEN string describes a complete board position in a single line:
"rnbqkbnr/pppppppp/8/8/4P3/8/PPPP1PPP/RNBQKBNR b KQkq e3 0 1"
| | | | | |
| | | | | +-- fullmove number
| | | | +----- halfmove clock (50-move rule)
| | | +---------- en passant target square ("-" if none)
| | +----------------- castling availability (KQkq or "-")
| +--------------------------- active color: "w" or "b"
+------------------------------------- piece placement (rank 8 to rank 1, "/" separated)
Piece letters: K=king, Q=queen, R=rook, B=bishop, N=knight, P=pawn
Uppercase = white, lowercase = black
Numbers = consecutive empty squares
Starting position FEN:
rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1
================================================================================
EVALUATION VALUES
================================================================================
The "evaluation" field in responses:
- Measured in pawns (centipawns / 100)
- Positive = white advantage
- Negative = black advantage
- +0.50 means white is half a pawn ahead
- +999.0 = white has forced checkmate
- -999.0 = black has forced checkmate
- Values near 0.0 = roughly equal position
================================================================================
RATE LIMITING
================================================================================
- 60 requests per minute per IP address
- Applies to all endpoints
- X-Forwarded-For header is respected (for reverse proxy setups)
- When exceeded: 429 Too Many Requests {"error": "rate limit exceeded"}
================================================================================
CORS
================================================================================
- Access-Control-Allow-Origin: * (all origins allowed)
- Allowed Methods: GET, POST, OPTIONS
- Allowed Headers: Content-Type, Authorization, apikey
- Preflight cache: 86400 seconds (24 hours)
================================================================================
MANAGEMENT API (Full Control)
================================================================================
Base: https://stockfishapi.caprover.al-arcade.com/api/manage
Authentication:
All /api/manage/* endpoints require an API key via header:
X-API-Key: sk-alarc-stockfish-mgmt-2024
OR:
Authorization: Bearer sk-alarc-stockfish-mgmt-2024
Rate Limiting: None on management endpoints (auth-gated).
--------------------------------------------------------------------------------
SYSTEM INFO
--------------------------------------------------------------------------------
GET /api/manage/info
Returns full system overview + all available endpoint list.
Response:
{
"engine": "stockfish-18",
"version": "1.0.0",
"bot_count": 7,
"pool": { "alive": 6, "idle": 4, "max_size": 12, "idle_timeout": 300 },
"settings": { "port": "80", "stockfish_path": "/usr/local/bin/stockfish" },
"endpoints": { ... all endpoints listed ... }
}
--------------------------------------------------------------------------------
BOT CRUD
--------------------------------------------------------------------------------
GET /api/manage/bots
List all bots with count.
GET /api/manage/bots/{id}
Get a single bot by ID.
POST /api/manage/bots
Create a new bot. Send full bot JSON in body.
Required: id, name, style
Returns 201 on success, 409 if ID already exists.
Body:
{
"id": "yasmin",
"name": "Yasmin",
"name_ar": "ياسمين",
"style": "tricky",
"style_ar": "ماكرة",
"bio": "Sets traps and waits for you to fall in.",
"bio_ar": "بتحط فخاخ وبتستنى تقع فيها.",
"elo_min": 1100,
"elo_max": 1300,
"skill_level": 9,
"depth": 11,
"contempt": 20,
"blunder_chance": 0.06,
"think_time_min_ms": 1000,
"think_time_max_ms": 3500,
"opening_book": ["sicilian", "french"],
"avatar_id": "bot-yasmin",
"portrait_url": "/portraits/yasmin.png"
}
PATCH /api/manage/bots/{id}
Partial update. Only send fields you want to change.
Body: { "skill_level": 12, "blunder_chance": 0.05, "style_ar": "ذكية" }
PUT /api/manage/bots/{id}
Full replace. Overwrites entire bot with new data.
Body: same as POST but ID comes from URL.
DELETE /api/manage/bots/{id}
Delete a bot and its portrait files.
--------------------------------------------------------------------------------
BULK OPERATIONS
--------------------------------------------------------------------------------
POST /api/manage/bots/bulk
Create multiple bots at once.
Body: [ {bot1}, {bot2}, ... ]
Response: { "created": ["id1","id2"], "skipped": ["id3 (already exists)"] }
DELETE /api/manage/bots/bulk
Delete multiple bots at once.
Body: { "ids": ["amina", "tarek"] }
Response: { "deleted": ["amina","tarek"], "not_found": [] }
GET /api/manage/bots/export
Download all bots as JSON file (Content-Disposition: attachment).
POST /api/manage/bots/import?overwrite=true
Import bots from JSON array. With overwrite=true, existing bots are replaced.
Body: [ {bot1}, {bot2}, ... ]
Response: { "imported": ["id1"], "skipped": ["id2 (exists)"], "total": 8 }
--------------------------------------------------------------------------------
PORTRAIT MANAGEMENT
--------------------------------------------------------------------------------
POST /api/manage/bots/{id}/portrait
Upload a portrait image (512x512 px).
Content-Type: multipart/form-data
Field name: "portrait"
Accepted: .png, .jpg, .jpeg, .webp (max 10MB)
curl example:
curl -X POST https://stockfishapi.caprover.al-arcade.com/api/manage/bots/amina/portrait \
-H "X-API-Key: sk-alarc-stockfish-mgmt-2024" \
-F "portrait=@amina_512x512.png"
Response: { "status": "uploaded", "bot_id": "amina", "portrait_url": "/portraits/amina.png", "size_bytes": 245000 }
DELETE /api/manage/bots/{id}/portrait
Delete all portrait files for a bot.
Response: { "status": "deleted", "bot_id": "amina" }
Portraits are served publicly (no auth):
GET https://stockfishapi.caprover.al-arcade.com/portraits/{bot_id}.png
--------------------------------------------------------------------------------
ENGINE / POOL
--------------------------------------------------------------------------------
GET /api/manage/pool
Full pool stats with utilization percentage.
Response:
{
"pool_alive": 6,
"pool_idle": 4,
"pool_max_size": 12,
"pool_utilization": "16.7%",
"idle_timeout_sec": 300
}
POST /api/manage/test-move
Test the engine directly. Supports raw mode (custom params) or bot mode.
Bot mode:
{ "fen": "...", "bot_id": "nour" }
Raw mode (bypass bot config, use custom engine params):
{
"fen": "rnbqkbnr/pppppppp/8/8/4P3/8/PPPP1PPP/RNBQKBNR b KQkq - 0 1",
"raw_mode": true,
"depth": 25,
"skill_level": 20,
"contempt": 0,
"time_limit_ms": 5000,
"multi_pv": 3
}
Response:
{
"best_move": "e7e5",
"evaluation": -0.12,
"depth": 25,
"nodes": 5000000,
"pv": "e7e5 g1f3 b8c6",
"engine_time_ms": 3200,
"request": { "fen": "...", "depth": 25, "skill_level": 20, "contempt": 0, "multi_pv": 3 }
}
POST /api/manage/analyze
Deep analysis (up to depth 40, up to 10 lines).
{ "fen": "...", "depth": 30, "lines": 5 }
Response: { "fen": "...", "depth": 30, "lines": [...], "engine_time_ms": 12000 }
--------------------------------------------------------------------------------
SETTINGS
--------------------------------------------------------------------------------
GET /api/manage/settings
{ "port": "80", "pool_size": 12, "idle_timeout_sec": 300, "stockfish_path": "/usr/local/bin/stockfish" }
PATCH /api/manage/settings
Update settings (partial). Some require restart.
Body: { "pool_size": 16, "idle_timeout_sec": 600 }
Response: { "status": "updated", "settings": {...}, "note": "some changes require restart" }
--------------------------------------------------------------------------------
LOGS
--------------------------------------------------------------------------------
GET /api/manage/logs?limit=50
Get recent request logs (max 100).
Response:
{
"logs": [
{ "Timestamp": "2024-01-15 14:30:22", "Method": "POST", "Path": "/api/chess/move", "Status": 200, "Duration": "45ms", "IP": "1.2.3.4" },
...
],
"count": 50
}
================================================================================
MANAGEMENT API - QUICK REFERENCE (curl examples)
================================================================================
# Auth header (use in all requests)
AUTH="-H 'X-API-Key: sk-alarc-stockfish-mgmt-2024'"
# System info
curl -s https://stockfishapi.caprover.al-arcade.com/api/manage/info \
-H "X-API-Key: sk-alarc-stockfish-mgmt-2024" | jq .
# List bots
curl -s https://stockfishapi.caprover.al-arcade.com/api/manage/bots \
-H "X-API-Key: sk-alarc-stockfish-mgmt-2024" | jq .
# Get single bot
curl -s https://stockfishapi.caprover.al-arcade.com/api/manage/bots/nour \
-H "X-API-Key: sk-alarc-stockfish-mgmt-2024" | jq .
# Create bot
curl -s -X POST https://stockfishapi.caprover.al-arcade.com/api/manage/bots \
-H "X-API-Key: sk-alarc-stockfish-mgmt-2024" \
-H "Content-Type: application/json" \
-d '{"id":"yasmin","name":"Yasmin","name_ar":"ياسمين","style":"tricky","style_ar":"ماكرة","skill_level":9,"depth":11,"contempt":20,"blunder_chance":0.06,"think_time_min_ms":1000,"think_time_max_ms":3500,"elo_min":1100,"elo_max":1300}' | jq .
# Update bot (partial)
curl -s -X PATCH https://stockfishapi.caprover.al-arcade.com/api/manage/bots/amina \
-H "X-API-Key: sk-alarc-stockfish-mgmt-2024" \
-H "Content-Type: application/json" \
-d '{"skill_level":2,"blunder_chance":0.25}' | jq .
# Replace bot (full)
curl -s -X PUT https://stockfishapi.caprover.al-arcade.com/api/manage/bots/amina \
-H "X-API-Key: sk-alarc-stockfish-mgmt-2024" \
-H "Content-Type: application/json" \
-d '{"name":"Amina","name_ar":"أمينة","style":"beginner","style_ar":"مبتدئة","skill_level":1,"depth":3}' | jq .
# Delete bot
curl -s -X DELETE https://stockfishapi.caprover.al-arcade.com/api/manage/bots/yasmin \
-H "X-API-Key: sk-alarc-stockfish-mgmt-2024" | jq .
# Bulk create
curl -s -X POST https://stockfishapi.caprover.al-arcade.com/api/manage/bots/bulk \
-H "X-API-Key: sk-alarc-stockfish-mgmt-2024" \
-H "Content-Type: application/json" \
-d '[{"id":"bot1","name":"Bot One","style":"test","skill_level":5,"depth":5},{"id":"bot2","name":"Bot Two","style":"test","skill_level":10,"depth":10}]' | jq .
# Bulk delete
curl -s -X DELETE https://stockfishapi.caprover.al-arcade.com/api/manage/bots/bulk \
-H "X-API-Key: sk-alarc-stockfish-mgmt-2024" \
-H "Content-Type: application/json" \
-d '{"ids":["bot1","bot2"]}' | jq .
# Export all bots (download)
curl -s https://stockfishapi.caprover.al-arcade.com/api/manage/bots/export \
-H "X-API-Key: sk-alarc-stockfish-mgmt-2024" -o bots_backup.json
# Import bots (with overwrite)
curl -s -X POST "https://stockfishapi.caprover.al-arcade.com/api/manage/bots/import?overwrite=true" \
-H "X-API-Key: sk-alarc-stockfish-mgmt-2024" \
-H "Content-Type: application/json" \
-d @bots_backup.json | jq .
# Upload portrait
curl -s -X POST https://stockfishapi.caprover.al-arcade.com/api/manage/bots/amina/portrait \
-H "X-API-Key: sk-alarc-stockfish-mgmt-2024" \
-F "portrait=@amina.png" | jq .
# Delete portrait
curl -s -X DELETE https://stockfishapi.caprover.al-arcade.com/api/manage/bots/amina/portrait \
-H "X-API-Key: sk-alarc-stockfish-mgmt-2024" | jq .
# Pool stats
curl -s https://stockfishapi.caprover.al-arcade.com/api/manage/pool \
-H "X-API-Key: sk-alarc-stockfish-mgmt-2024" | jq .
# Test move (bot mode)
curl -s -X POST https://stockfishapi.caprover.al-arcade.com/api/manage/test-move \
-H "X-API-Key: sk-alarc-stockfish-mgmt-2024" \
-H "Content-Type: application/json" \
-d '{"fen":"rnbqkbnr/pppppppp/8/8/4P3/8/PPPP1PPP/RNBQKBNR b KQkq - 0 1","bot_id":"grandmaster"}' | jq .
# Test move (raw mode - custom engine params)
curl -s -X POST https://stockfishapi.caprover.al-arcade.com/api/manage/test-move \
-H "X-API-Key: sk-alarc-stockfish-mgmt-2024" \
-H "Content-Type: application/json" \
-d '{"fen":"rnbqkbnr/pppppppp/8/8/4P3/8/PPPP1PPP/RNBQKBNR b KQkq - 0 1","raw_mode":true,"depth":30,"skill_level":20,"contempt":0}' | jq .
# Deep analysis
curl -s -X POST https://stockfishapi.caprover.al-arcade.com/api/manage/analyze \
-H "X-API-Key: sk-alarc-stockfish-mgmt-2024" \
-H "Content-Type: application/json" \
-d '{"fen":"rnbqkbnr/pppppppp/8/8/4P3/8/PPPP1PPP/RNBQKBNR b KQkq - 0 1","depth":30,"lines":5}' | jq .
# Get settings
curl -s https://stockfishapi.caprover.al-arcade.com/api/manage/settings \
-H "X-API-Key: sk-alarc-stockfish-mgmt-2024" | jq .
# Update settings
curl -s -X PATCH https://stockfishapi.caprover.al-arcade.com/api/manage/settings \
-H "X-API-Key: sk-alarc-stockfish-mgmt-2024" \
-H "Content-Type: application/json" \
-d '{"pool_size":16,"idle_timeout_sec":600}' | jq .
# View logs
curl -s "https://stockfishapi.caprover.al-arcade.com/api/manage/logs?limit=20" \
-H "X-API-Key: sk-alarc-stockfish-mgmt-2024" | jq .
================================================================================
ADMIN PANEL
================================================================================
URL: https://stockfishapi.caprover.al-arcade.com/admin
Login: username "admin", password "Alarcade123#"
Features:
- Dashboard: live pool stats, bot count, request metrics
- Bot management: create, edit, delete bots at runtime
- Pool monitoring: alive/idle process counts
- Test Move: test any bot with a custom FEN position
- Request Logs: last 100 requests with method, path, status, duration, IP
- Settings: view/modify port, pool size, idle timeout
Note: Runtime changes (bot edits, new bots) persist only until container restart.
Core bot definitions are compiled into the binary.
================================================================================
INFRASTRUCTURE
================================================================================
- Engine: Stockfish 18 (compiled from source, NNUE enabled)
- Runtime: Go 1.22, single static binary
- Process Pool: 12 Stockfish processes (configurable via POOL_SIZE env)
- Pre-warmed: half the pool at startup for instant first requests
- Idle Reaper: kills processes unused for 300s (configurable via IDLE_TIMEOUT_SEC)
- Platform: Docker on CapRover (Ubuntu 22.04 base)
- Architecture: x86-64-sse41-popcnt (broad server CPU compatibility)
- Health Check: every 30s, 10s timeout, 3 retries, 15s start period
- Port: 80 inside container (CapRover handles HTTPS/reverse proxy)
================================================================================
ENVIRONMENT VARIABLES
================================================================================
PORT=80 # HTTP listen port
STOCKFISH_PATH=/usr/local/bin/stockfish # path to Stockfish binary
POOL_SIZE=12 # max concurrent Stockfish processes
IDLE_TIMEOUT_SEC=300 # kill idle processes after this many seconds
================================================================================
INTEGRATION EXAMPLES
================================================================================
--- JavaScript/Fetch ---
// Get a move from "nour" bot
const response = await fetch('https://stockfishapi.caprover.al-arcade.com/api/chess/move', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
fen: 'rnbqkbnr/pppppppp/8/8/4P3/8/PPPP1PPP/RNBQKBNR b KQkq e3 0 1',
bot_id: 'nour'
})
});
const data = await response.json();
console.log(data.best_move); // e.g. "e7e5"
// Analyze a position
const analysis = await fetch('https://stockfishapi.caprover.al-arcade.com/api/chess/analyze', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
fen: 'rnbqkbnr/pppppppp/8/8/4P3/8/PPPP1PPP/RNBQKBNR b KQkq e3 0 1',
depth: 20,
lines: 3
})
});
const result = await analysis.json();
result.lines.forEach(line => {
console.log(`#${line.rank}: ${line.move} (eval: ${line.evaluation})`);
});
// List all bots
const botsResp = await fetch('https://stockfishapi.caprover.al-arcade.com/api/chess/bots');
const botsData = await botsResp.json();
botsData.bots.forEach(bot => {
console.log(`${bot.name} (${bot.elo_min}-${bot.elo_max} ELO) - ${bot.style}`);
});
--- Python ---
import requests
# Get move
resp = requests.post('https://stockfishapi.caprover.al-arcade.com/api/chess/move', json={
'fen': 'rnbqkbnr/pppppppp/8/8/4P3/8/PPPP1PPP/RNBQKBNR b KQkq e3 0 1',
'bot_id': 'omar'
})
move = resp.json()
print(f"Best move: {move['best_move']}, Eval: {move['evaluation']}")
# Analyze
resp = requests.post('https://stockfishapi.caprover.al-arcade.com/api/chess/analyze', json={
'fen': 'r1bqkbnr/pppppppp/2n5/8/4P3/8/PPPP1PPP/RNBQKBNR w KQkq - 1 2',
'depth': 22,
'lines': 5
})
for line in resp.json()['lines']:
print(f"#{line['rank']}: {line['move']} (eval {line['evaluation']:.2f})")
--- cURL ---
# Quick move
curl -s -X POST https://stockfishapi.caprover.al-arcade.com/api/chess/move \
-H "Content-Type: application/json" \
-d '{"fen":"rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1","bot_id":"amina"}' | jq .
# Health check
curl -s https://stockfishapi.caprover.al-arcade.com/health | jq .
================================================================================
TYPICAL GAME FLOW (for client integration)
================================================================================
1. GET /api/chess/bots -> show bot selection to user
2. User picks a bot (e.g. "nour") and starts a game
3. Client tracks FEN locally (or uses a chess library)
4. When it's the bot's turn:
POST /api/chess/move with current FEN and bot_id
5. Apply the returned best_move to the board
6. Repeat from step 4 until checkmate/stalemate/draw
7. Optionally: POST /api/chess/analyze for post-game analysis
Important notes for client implementation:
- Always send the FULL FEN including castling rights, en passant, move counters
- The API does NOT validate if the FEN is a legal position
- The API does NOT track game state - it's stateless (one move per request)
- Handle the artificial think_time_ms for UX (show "thinking..." animation)
- Handle 429 rate limit gracefully (retry after 1 second)
- The response time can be up to 8000ms+ for grandmaster bot (due to simulated think time)
================================================================================
ERROR HANDLING
================================================================================
All errors return JSON with an "error" field:
400 Bad Request - malformed JSON, missing required fields
404 Not Found - invalid bot_id
429 Too Many Requests - rate limit exceeded (60/min/IP)
500 Internal Server Error - Stockfish process crash, pool exhaustion, timeout
503 Service Unavailable - health check failed (engine not responding)
Recommended retry strategy:
- 429: wait 1-2 seconds, retry
- 500: wait 2-5 seconds, retry up to 3 times
- 503: service is down, alert user
================================================================================
RESPONSE TIME EXPECTATIONS
================================================================================
Bot | Typical Response Time (includes simulated think time)
----------------|------------------------------------------------------
amina | 500ms - 2000ms
tarek | 1000ms - 3000ms
nour | 800ms - 3000ms
omar | 1500ms - 4000ms
layla | 1000ms - 5000ms
ziad | 2000ms - 6000ms
grandmaster | 3000ms - 8000ms
Analysis endpoint: 1-30 seconds depending on depth (no artificial delay).
Health check: < 5 seconds.
Set client timeouts accordingly (recommend 15s for moves, 35s for analysis).
# EL3AB MANAGEMENT SYSTEM
> Pure PHP + HTML + CSS + JS management panel for the El3ab competitive gaming platform.
> No frameworks. No npm. No build tools. Raw muscle, modular, sexy UI.
> Deploy: GitLab repo → CapRover (PHP container, auto-deploy on push to main).
---
# PHILOSOPHY
- **PHP** — Server logic, API proxy, session management, DB operations
- **HTML** — Semantic, accessible, RTL-native
- **CSS** — Custom properties, animations, responsive grid, zero libraries
- **JS** — Vanilla ES6+, fetch API, DOM manipulation, no jQuery/React/Vue
- **Arabic-first** — All UI text in Arabic, numbers are standard (0123456789), RTL layout
- **Modular** — Each feature is a self-contained folder with its own PHP/JS/CSS
- **Full CRUD** — Every entity: Create, Read (list + detail), Update, Delete with confirmation
- **Edge cases** — Empty states, loading, errors, validation, pagination, search, bulk actions, soft delete
---
# TABLE OF CONTENTS
1. [Infrastructure & Connections](#1-infrastructure--connections)
2. [Project Structure](#2-project-structure)
3. [Authentication & Authorization](#3-authentication--authorization)
4. [Database Schema](#4-database-schema)
5. [UI/UX Design System](#5-uiux-design-system)
6. [Module: Dashboard](#6-module-dashboard)
7. [Module: Players](#7-module-players)
8. [Module: Games](#8-module-games)
9. [Module: Chess Bots (Stockfish)](#9-module-chess-bots-stockfish)
10. [Module: Tournaments (Swiss API)](#10-module-tournaments-swiss-api)
11. [Module: Organizations](#11-module-organizations)
12. [Module: Economy & Virtual Currency](#12-module-economy--virtual-currency)
13. [Module: Advertisements](#13-module-advertisements)
14. [Module: Moderation & Reports](#14-module-moderation--reports)
15. [Module: Feature Flags](#15-module-feature-flags)
16. [Module: System Settings](#16-module-system-settings)
17. [Module: Branding & Theming](#17-module-branding--theming)
18. [Module: Analytics](#18-module-analytics)
19. [Module: Notifications](#19-module-notifications)
20. [Module: Audit Log](#20-module-audit-log)
21. [CRUD Patterns & Edge Cases](#21-crud-patterns--edge-cases)
22. [API Proxy Layer](#22-api-proxy-layer)
23. [Deployment & CapRover](#23-deployment--caprover)
24. [Execution Plan](#24-execution-plan)
---
# 1. INFRASTRUCTURE & CONNECTIONS
## Server
| Key | Value |
|-----|-------|
| IP | 3.68.63.185 |
| User | ubuntu |
| SSH | `ssh -i NewServer.pem ubuntu@3.68.63.185` |
## Supabase (Self-Hosted)
| Key | Value |
|-----|-------|
| API URL | `https://safe-supabase-kong.caprover.al-arcade.com` |
| Studio | `https://safe-supabase-studio.caprover.al-arcade.com` |
| Anon Key | `eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb2xlIjoiYW5vbiIsImlzcyI6InN1cGFiYXNlIiwiaWF0IjoxNzM1Njg5NjAwLCJleHAiOjE4OTM0NTYwMDB9.31PF6PvP-pSrvRuQwLFptQoejR0W1A7o53lZhEbnz84` |
| Service Role Key | `eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb2xlIjoic2VydmljZV9yb2xlIiwiaXNzIjoic3VwYWJhc2UiLCJpYXQiOjE3MzU2ODk2MDAsImV4cCI6MTg5MzQ1NjAwMH0.wNfmuJNkX-bZwD7RbjxOChlRf_3Xm4I7bswEYTcDCg4` |
| DB Direct | `postgresql://supabase_admin:28ac17bf9d4f7a3d1bad045408102cf5@localhost:5432/postgres` |
| REST | `/rest/v1/` |
| Auth | `/auth/v1/` |
| Storage | `/storage/v1/` |
## Stockfish Bot API
| Key | Value |
|-----|-------|
| Base URL | `https://stockfishapi.caprover.al-arcade.com` |
| Management Key | `sk-alarc-stockfish-mgmt-2024` |
| Admin Panel | `/admin` (admin/Alarcade123#) |
| Get Move | `POST /api/chess/move` |
| Analyze | `POST /api/chess/analyze` |
| List Bots | `GET /api/chess/bots` |
| CRUD Bots | `/api/manage/bots` (GET/POST/PATCH/PUT/DELETE) |
| Portraits | `POST /api/manage/bots/{id}/portrait` |
| Pool Stats | `GET /api/manage/pool` |
## Swiss System Tournament API
| Key | Value |
|-----|-------|
| Base URL | `https://swissapi.caprover.al-arcade.com/api/v1` |
| Health | `GET /health` |
| Auth | `POST /api/v1/auth/login` + `POST /api/v1/auth/signup` |
| Organizations | Full CRUD at `/api/v1/organizations` |
| Events | Full CRUD at `/api/v1/organizations/:orgId/events` |
| Tournaments | Full CRUD + start/complete at `/api/v1/events/:eventId/tournaments` |
| Players | Full CRUD + bulk import at `/api/v1/tournaments/:id/players` |
| Rounds | Generate + delete at `/api/v1/tournaments/:id/rounds` |
| Pairings | Results + manual at `/api/v1/rounds/:id/pairings` |
| Standings | `GET /api/v1/tournaments/:id/standings` |
| Export | TRF, JSON, Crosstable |
---
# 2. PROJECT STRUCTURE
```
el3ab-management/
├── captain-definition # CapRover deploy config
├── Dockerfile # PHP 8.3 + Apache
├── .htaccess # URL rewriting
├── index.php # Router / entry point
├── config/
│ ├── app.php # Constants, API keys, DB config
│ ├── routes.php # Route definitions
│ └── permissions.php # Role → permission matrix
├── core/
│ ├── Database.php # Supabase REST client (service_role)
│ ├── Auth.php # Session management, login/logout
│ ├── Router.php # URL → controller dispatcher
│ ├── View.php # Template renderer (include-based)
│ ├── Validator.php # Input validation helpers
│ ├── ApiProxy.php # HTTP client for external APIs
│ ├── AuditLog.php # Log every write action
│ ├── Pagination.php # Offset/limit pagination
│ └── Response.php # JSON/HTML response helpers
├── modules/
│ ├── dashboard/
│ │ ├── controller.php
│ │ ├── views/
│ │ │ └── index.php
│ │ └── assets/
│ │ ├── dashboard.css
│ │ └── dashboard.js
│ ├── players/
│ │ ├── controller.php # list, show, create, update, delete, ban, unban
│ │ ├── views/
│ │ │ ├── list.php
│ │ │ ├── show.php
│ │ │ ├── form.php # create + edit (same form)
│ │ │ └── _table_row.php # partial for AJAX reload
│ │ └── assets/
│ │ ├── players.css
│ │ └── players.js
│ ├── games/
│ ├── chess-bots/
│ ├── tournaments/
│ ├── organizations/
│ ├── economy/
│ ├── ads/
│ ├── moderation/
│ ├── feature-flags/
│ ├── settings/
│ ├── branding/
│ ├── analytics/
│ ├── notifications/
│ └── audit-log/
├── layouts/
│ ├── app.php # Main layout (sidebar + topbar + content)
│ ├── auth.php # Login layout (centered card)
│ ├── partials/
│ │ ├── sidebar.php
│ │ ├── topbar.php
│ │ ├── toast.php # Toast notification system
│ │ ├── modal.php # Reusable modal shell
│ │ ├── confirm-dialog.php # Delete confirmation
│ │ ├── empty-state.php # No data illustration
│ │ ├── loading-skeleton.php # Skeleton loaders
│ │ └── pagination.php # Pagination component
│ └── components/
│ ├── data-table.php # Sortable, searchable table
│ ├── stat-card.php # Dashboard stat card
│ ├── form-field.php # Input wrapper with validation
│ ├── toggle-switch.php # Boolean toggle
│ ├── color-picker.php # Theme color picker
│ ├── file-upload.php # Drag & drop upload
│ ├── badge.php # Status badges
│ └── dropdown.php # Action dropdown menu
├── public/
│ ├── css/
│ │ ├── variables.css # CSS custom properties (colors, spacing, motion)
│ │ ├── reset.css # Modern CSS reset
│ │ ├── layout.css # Grid, sidebar, responsive
│ │ ├── components.css # Buttons, cards, forms, tables
│ │ ├── animations.css # Keyframes, transitions
│ │ └── utilities.css # Helpers (flex, text, spacing)
│ ├── js/
│ │ ├── app.js # Global: toast, modal, fetch wrapper
│ │ ├── sidebar.js # Sidebar toggle, active state
│ │ ├── data-table.js # Sort, search, pagination, bulk select
│ │ └── form-validation.js # Client-side validation
│ ├── fonts/
│ │ └── ibm-plex-arabic/ # IBM Plex Arabic (variable weight)
│ └── img/
│ ├── logo.svg
│ ├── empty-states/ # SVG illustrations for empty states
│ └── icons/ # Custom SVG icons (if needed beyond Lucide CDN)
└── storage/
└── uploads/ # Temp upload dir (move to Supabase Storage)
```
---
# 3. AUTHENTICATION & AUTHORIZATION
## Login
- **URL:** `/login`
- **Credentials:** `admin` / `Alarcade123#` (superadmin — hardcoded check in Phase 1)
- **Session:** PHP `$_SESSION` with CSRF token
- **Timeout:** 24 hours inactivity → force re-login
- **Remember me:** Optional persistent cookie (30 days)
## Roles
| Role | Level | Access |
|------|:-----:|--------|
| `superadmin` | 100 | Everything. Full CRUD on all entities. System settings. |
| `admin` | 80 | All modules except system settings and branding |
| `moderator` | 50 | Players (view/ban), Reports, Moderation only |
| `viewer` | 10 | Read-only access to all modules |
## Phase 1 (NOW)
Single superadmin user. Login form validates against hardcoded credentials:
- Email/username: `admin`
- Password: `Alarcade123#`
Password stored as `password_hash()` in a PHP constant. Session created on success.
## Phase 2 (Later)
Multi-user from `platform_admins` table in Supabase with proper password hashing and role assignment.
## Permission Check Pattern
```php
// In every controller action:
Auth::requireRole('admin'); // Redirects to /login if not authenticated or insufficient role
```
## CSRF Protection
Every form includes `<input type="hidden" name="_csrf" value="<?= Auth::csrfToken() ?>">`.
Every POST/PUT/DELETE validates the token server-side before processing.
---
# 4. DATABASE SCHEMA
All tables live in Supabase PostgreSQL. The PHP app talks to them via the REST API using the **service_role key** (bypasses RLS — this is an admin panel, RLS is for player-facing apps).
## Core Tables
```sql
-- Platform admin users (Phase 2)
CREATE TABLE platform_admins (
id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
username TEXT UNIQUE NOT NULL,
email TEXT UNIQUE NOT NULL,
password_hash TEXT NOT NULL,
display_name TEXT NOT NULL,
role TEXT NOT NULL DEFAULT 'viewer' CHECK (role IN ('superadmin','admin','moderator','viewer')),
avatar_url TEXT,
is_active BOOLEAN DEFAULT true,
last_login_at TIMESTAMPTZ,
created_at TIMESTAMPTZ DEFAULT now(),
updated_at TIMESTAMPTZ DEFAULT now()
);
-- Player profiles (main player table)
CREATE TABLE profiles (
id UUID PRIMARY KEY REFERENCES auth.users(id),
username TEXT UNIQUE NOT NULL,
display_name TEXT,
display_name_ar TEXT,
email TEXT,
avatar_url TEXT,
country_code TEXT,
city TEXT,
bio TEXT,
-- Ratings
elo_blitz INT DEFAULT 1200,
elo_rapid INT DEFAULT 1200,
elo_classical INT DEFAULT 1200,
elo_backgammon INT DEFAULT 1200,
elo_dominoes INT DEFAULT 1200,
elo_ludo INT DEFAULT 1200,
elo_trivia INT DEFAULT 1200,
-- Status
level INT DEFAULT 1,
xp INT DEFAULT 0,
is_online BOOLEAN DEFAULT false,
is_banned BOOLEAN DEFAULT false,
ban_reason TEXT,
banned_at TIMESTAMPTZ,
banned_by UUID,
-- Economy
coins INT DEFAULT 0,
gems INT DEFAULT 0,
-- Stats
total_matches INT DEFAULT 0,
total_wins INT DEFAULT 0,
total_losses INT DEFAULT 0,
total_draws INT DEFAULT 0,
win_streak INT DEFAULT 0,
best_win_streak INT DEFAULT 0,
-- Timestamps
last_active_at TIMESTAMPTZ,
created_at TIMESTAMPTZ DEFAULT now(),
updated_at TIMESTAMPTZ DEFAULT now()
);
-- Game plugins registry
CREATE TABLE game_plugins (
game_key TEXT PRIMARY KEY,
name TEXT NOT NULL,
name_ar TEXT NOT NULL,
description TEXT,
description_ar TEXT,
icon TEXT,
is_enabled BOOLEAN DEFAULT true,
min_players INT DEFAULT 2,
max_players INT DEFAULT 2,
supports_ranked BOOLEAN DEFAULT true,
supports_tournament BOOLEAN DEFAULT true,
matchmaking_config JSONB DEFAULT '{}',
sort_order INT DEFAULT 0,
created_at TIMESTAMPTZ DEFAULT now(),
updated_at TIMESTAMPTZ DEFAULT now()
);
-- Matches (all games)
CREATE TABLE matches (
id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
game_key TEXT NOT NULL REFERENCES game_plugins(game_key),
status TEXT NOT NULL DEFAULT 'waiting' CHECK (status IN ('waiting','in_progress','completed','cancelled','abandoned')),
mode TEXT DEFAULT 'ranked' CHECK (mode IN ('ranked','casual','tournament','bot')),
-- Players (JSONB array of player objects)
players JSONB NOT NULL DEFAULT '[]',
-- Results
winner_id UUID,
result TEXT CHECK (result IN ('win','draw','forfeit','timeout','disconnect')),
-- Timing
time_control TEXT,
started_at TIMESTAMPTZ,
ended_at TIMESTAMPTZ,
duration_seconds INT,
-- Game-specific state
game_state JSONB DEFAULT '{}',
move_history JSONB DEFAULT '[]',
-- Tournament link
tournament_id UUID,
round_number INT,
-- Meta
created_at TIMESTAMPTZ DEFAULT now(),
updated_at TIMESTAMPTZ DEFAULT now()
);
-- Organizations
CREATE TABLE organizations (
id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
name TEXT NOT NULL,
name_ar TEXT,
slug TEXT UNIQUE,
logo_url TEXT,
description TEXT,
description_ar TEXT,
contact_email TEXT,
website TEXT,
country_code TEXT,
city TEXT,
is_verified BOOLEAN DEFAULT false,
verified_at TIMESTAMPTZ,
verification_docs JSONB DEFAULT '[]',
-- Swiss API link
swiss_api_org_id UUID,
swiss_api_token TEXT,
-- Settings
settings JSONB DEFAULT '{}',
is_active BOOLEAN DEFAULT true,
created_at TIMESTAMPTZ DEFAULT now(),
updated_at TIMESTAMPTZ DEFAULT now()
);
-- Organization members
CREATE TABLE org_members (
id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
org_id UUID NOT NULL REFERENCES organizations(id) ON DELETE CASCADE,
user_id UUID NOT NULL REFERENCES profiles(id),
role TEXT NOT NULL DEFAULT 'member' CHECK (role IN ('owner','admin','arbiter','member')),
status TEXT DEFAULT 'active' CHECK (status IN ('active','invited','suspended')),
joined_at TIMESTAMPTZ DEFAULT now(),
UNIQUE(org_id, user_id)
);
-- Tournaments (managed through Swiss API but tracked locally)
CREATE TABLE el3ab_tournaments (
id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
org_id UUID REFERENCES organizations(id),
game_key TEXT NOT NULL REFERENCES game_plugins(game_key),
name TEXT NOT NULL,
name_ar TEXT,
description TEXT,
-- Swiss API references
swiss_api_event_id UUID,
swiss_api_tournament_id UUID,
-- Config
format TEXT DEFAULT 'swiss' CHECK (format IN ('swiss','round_robin','bracket','arena')),
status TEXT DEFAULT 'draft' CHECK (status IN ('draft','registration','in_progress','completed','cancelled')),
max_players INT DEFAULT 64,
rounds_number INT,
time_control TEXT,
-- Economy
entry_fee_coins INT DEFAULT 0,
prize_pool JSONB DEFAULT '[]',
-- Dates
registration_opens_at TIMESTAMPTZ,
registration_closes_at TIMESTAMPTZ,
starts_at TIMESTAMPTZ,
ends_at TIMESTAMPTZ,
-- Meta
banner_url TEXT,
rules TEXT,
created_at TIMESTAMPTZ DEFAULT now(),
updated_at TIMESTAMPTZ DEFAULT now()
);
-- Feature flags
CREATE TABLE feature_flags (
id TEXT PRIMARY KEY,
label TEXT NOT NULL,
label_ar TEXT NOT NULL,
description TEXT,
is_enabled BOOLEAN DEFAULT false,
target TEXT DEFAULT 'all' CHECK (target IN ('all','percentage','user_list','org_list')),
target_value JSONB DEFAULT '{}',
category TEXT DEFAULT 'general',
created_at TIMESTAMPTZ DEFAULT now(),
updated_at TIMESTAMPTZ DEFAULT now()
);
-- System configuration (key-value)
CREATE TABLE system_config (
key TEXT PRIMARY KEY,
value TEXT NOT NULL,
value_type TEXT DEFAULT 'string' CHECK (value_type IN ('string','number','boolean','json')),
label TEXT,
label_ar TEXT,
description TEXT,
category TEXT DEFAULT 'general',
is_editable BOOLEAN DEFAULT true,
updated_at TIMESTAMPTZ DEFAULT now()
);
-- Platform theme tokens
CREATE TABLE platform_theme (
id TEXT PRIMARY KEY,
category TEXT NOT NULL,
label TEXT NOT NULL,
label_ar TEXT,
value TEXT NOT NULL,
value_type TEXT DEFAULT 'color' CHECK (value_type IN ('color','size','font','gradient','shadow')),
sort_order INT DEFAULT 0,
updated_at TIMESTAMPTZ DEFAULT now()
);
-- Platform assets (branding)
CREATE TABLE platform_assets (
id TEXT PRIMARY KEY,
category TEXT NOT NULL,
label TEXT NOT NULL,
label_ar TEXT,
asset_url TEXT,
fallback_type TEXT DEFAULT 'color' CHECK (fallback_type IN ('color','gradient','icon','text','none')),
fallback_value TEXT,
dimensions JSONB,
sort_order INT DEFAULT 0,
updated_at TIMESTAMPTZ DEFAULT now()
);
-- Advertisements
CREATE TABLE ad_campaigns (
id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
name TEXT NOT NULL,
advertiser TEXT NOT NULL,
status TEXT DEFAULT 'draft' CHECK (status IN ('draft','active','paused','completed','expired')),
-- Content
image_url TEXT,
click_url TEXT,
title TEXT,
title_ar TEXT,
body TEXT,
body_ar TEXT,
-- Targeting
placement TEXT NOT NULL CHECK (placement IN ('banner_top','banner_bottom','interstitial','sidebar','in_game','reward_video')),
target_countries JSONB DEFAULT '[]',
target_games JSONB DEFAULT '[]',
-- Schedule
starts_at TIMESTAMPTZ,
ends_at TIMESTAMPTZ,
-- Budget
budget_total INT DEFAULT 0,
budget_spent INT DEFAULT 0,
cpm NUMERIC(10,2) DEFAULT 0,
-- Stats
impressions INT DEFAULT 0,
clicks INT DEFAULT 0,
-- Meta
created_at TIMESTAMPTZ DEFAULT now(),
updated_at TIMESTAMPTZ DEFAULT now()
);
-- Moderation reports
CREATE TABLE cheat_reports (
id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
reporter_id UUID REFERENCES profiles(id),
reported_id UUID NOT NULL REFERENCES profiles(id),
match_id UUID REFERENCES matches(id),
reason TEXT NOT NULL CHECK (reason IN ('cheating','harassment','inappropriate_name','spam','other')),
description TEXT,
evidence_urls JSONB DEFAULT '[]',
status TEXT DEFAULT 'pending' CHECK (status IN ('pending','reviewing','resolved','dismissed')),
resolution TEXT,
resolved_by UUID,
resolved_at TIMESTAMPTZ,
created_at TIMESTAMPTZ DEFAULT now()
);
-- Economy transactions
CREATE TABLE transactions (
id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
user_id UUID NOT NULL REFERENCES profiles(id),
type TEXT NOT NULL CHECK (type IN ('earn','spend','refund','admin_grant','admin_revoke','tournament_prize','tournament_entry')),
currency TEXT NOT NULL CHECK (currency IN ('coins','gems')),
amount INT NOT NULL,
balance_after INT NOT NULL,
description TEXT,
reference_type TEXT,
reference_id UUID,
created_by UUID,
created_at TIMESTAMPTZ DEFAULT now()
);
-- Notifications
CREATE TABLE notifications (
id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
user_id UUID REFERENCES profiles(id),
type TEXT NOT NULL,
title TEXT NOT NULL,
title_ar TEXT,
body TEXT,
body_ar TEXT,
data JSONB DEFAULT '{}',
is_read BOOLEAN DEFAULT false,
is_broadcast BOOLEAN DEFAULT false,
created_at TIMESTAMPTZ DEFAULT now()
);
-- Audit log
CREATE TABLE audit_log (
id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
actor TEXT NOT NULL,
action TEXT NOT NULL,
entity_type TEXT NOT NULL,
entity_id TEXT,
old_value JSONB,
new_value JSONB,
ip_address TEXT,
created_at TIMESTAMPTZ DEFAULT now()
);
```
---
# 5. UI/UX DESIGN SYSTEM
## Brand Colors
| Token | Hex | Usage |
|-------|-----|-------|
| `--brand-blue` | `#2082F0` | Primary actions, links, active states |
| `--brand-orange` | `#E84D1E` | Destructive, alerts, live indicators |
| `--brand-gold` | `#E4AC38` | Premium, trophies, success |
| `--brand-sand` | `#FFCC66` | Soft highlights, secondary |
| `--brand-navy` | `#152132` | Background, depth |
| `--brand-cyan` | `#00FFFF` | Hover glows, accents |
| `--brand-purple` | `#6834BE` | Rare, elite, legendary |
## Surface Colors (Dark Theme)
| Token | Value |
|-------|-------|
| `--bg-primary` | `#0a0e1a` |
| `--bg-secondary` | `#111827` |
| `--bg-elevated` | `#1a2235` |
| `--bg-hover` | `#1f2937` |
| `--border` | `rgba(255,255,255,0.06)` |
| `--border-hover` | `rgba(255,255,255,0.12)` |
| `--text-primary` | `#ffffff` |
| `--text-secondary` | `rgba(255,255,255,0.6)` |
| `--text-muted` | `rgba(255,255,255,0.3)` |
## Typography
- **Font:** IBM Plex Arabic (self-hosted, variable weight)
- **Direction:** RTL (dir="rtl", lang="ar")
- **Numbers:** Always `0123456789` (font-feature-settings: "tnum" for tabular)
- **Sizes:** 12px (caption), 14px (body), 16px (subtitle), 20px (title), 28px (heading), 36px (hero)
## Spacing Scale
`4px, 8px, 12px, 16px, 20px, 24px, 32px, 40px, 48px, 64px`
## Border Radius
| Usage | Value |
|-------|-------|
| Buttons/Inputs | 8px |
| Cards | 12px |
| Modals | 16px |
| Avatars | 50% |
| Badges | 999px (pill) |
## Animations & Motion
```css
:root {
--ease-out: cubic-bezier(0.16, 1, 0.3, 1);
--ease-in: cubic-bezier(0.7, 0, 0.84, 0);
--ease-spring: cubic-bezier(0.34, 1.56, 0.64, 1);
--duration-fast: 150ms;
--duration-normal: 250ms;
--duration-slow: 400ms;
}
```
**Animation Rules:**
- Every page element enters with `fadeInUp` (staggered 50ms)
- Cards lift on hover (`translateY(-2px)` + shadow increase)
- Buttons scale on press (`scale(0.97)`) and glow on hover
- Modals slide up from bottom with backdrop fade
- Toasts slide in from top-right with spring physics
- Data tables: rows fade in staggered on load
- Stat numbers: count-up animation on dashboard
- Toggle switches: smooth slide with color transition
- Skeleton loaders: gradient shimmer using brand colors
- Delete: row shrinks height + fades out before removal
- Page transitions: content fades out/in (no full reload for AJAX actions)
**Microinteractions:**
- Sidebar nav items: icon rotates 5deg on hover, text slides right
- Active sidebar item: glowing left border (brand-blue)
- Form inputs: label floats up on focus, border transitions to brand-blue
- Checkboxes: satisfying checkmark draw animation
- Dropdowns: scale from origin point with opacity
- Status badges: subtle pulse animation for "live" states
## Responsive Breakpoints
| Name | Width | Behavior |
|------|-------|----------|
| Desktop | > 1200px | Full sidebar + content |
| Tablet | 768-1200px | Collapsible sidebar (icon-only) |
| Mobile | < 768px | Hidden sidebar (hamburger toggle) |
## RTL Considerations
- `dir="rtl"` on `<html>`
- Sidebar on the **right** side
- All `margin-left``margin-inline-start`
- All `text-align: left``text-align: start`
- Icons that indicate direction (arrows, chevrons) flip horizontally
- Number inputs remain LTR for readability (`dir="ltr"` on inputs)
---
# 6. MODULE: DASHBOARD
**Route:** `/` or `/dashboard`
## Layout
4 stat cards (top) + service health (middle) + recent activity (bottom)
## Stat Cards
| Card | Source | Icon |
|------|--------|------|
| اللاعبون المتصلون / اجمالي اللاعبين | `profiles` count where is_online=true / total | Users |
| المباريات النشطة / اجمالي المباريات | `matches` where status=in_progress / total | Gamepad |
| البطولات الجارية | `el3ab_tournaments` where status=in_progress | Trophy |
| البلاغات المعلقة | `cheat_reports` where status=pending | AlertTriangle |
## Service Health Panel
PHP cURL to each service, return status + latency:
- Supabase REST: `GET /rest/v1/` with apikey header
- Stockfish API: `GET /health`
- Swiss API: `GET /health`
Show green/red dot + latency in ms.
## Recent Activity
Last 10 entries from `audit_log` table, showing who did what when.
## Quick Actions
4 shortcut buttons: انشاء بطولة, مراجعة بلاغات, ادارة الالعاب, التحليلات
---
# 7. MODULE: PLAYERS
**Route:** `/players`
## Features
| Action | Method | Endpoint |
|--------|--------|----------|
| List all | GET | `profiles?select=*&order=created_at.desc` |
| Search | GET | `profiles?or=(username.ilike.*{q}*,display_name.ilike.*{q}*)` |
| View details | GET | `profiles?id=eq.{id}` |
| Edit profile | PATCH | `profiles?id=eq.{id}` |
| Ban player | PATCH | Set `is_banned=true, ban_reason, banned_at, banned_by` |
| Unban player | PATCH | Set `is_banned=false, ban_reason=null` |
| Grant currency | POST | Insert into `transactions` + update `profiles.coins/gems` |
| Revoke currency | POST | Insert into `transactions` + update `profiles.coins/gems` |
| Reset rating | PATCH | Set elo back to 1200 for selected game |
| View match history | GET | `matches?players->contains({id})&order=created_at.desc` |
| View transactions | GET | `transactions?user_id=eq.{id}&order=created_at.desc` |
| Delete player | DELETE | Soft-delete: set `is_active=false` (never hard delete) |
## List View
Data table with columns:
- الصورة (avatar)
- اسم المستخدم (username)
- الاسم المعروض (display_name)
- المستوى (level)
- العملات (coins)
- الحالة (online/offline/banned badge)
- آخر نشاط (last_active_at)
- الاجراءات (dropdown: عرض, تعديل, حظر, حذف)
Features: Search, sort by any column, pagination (25/50/100 per page), bulk select + bulk ban.
## Detail View
Full player card with:
- All ratings per game (editable inline)
- Match history tab
- Transaction history tab
- Reports tab (reports against this player)
- Activity timeline
## Form (Create/Edit)
All fields with proper validation. Country dropdown. Avatar upload (to Supabase Storage).
## Edge Cases
- Ban: Confirm dialog "هل تريد حظر هذا اللاعب؟" with reason textarea (required)
- Unban: Confirm dialog
- Grant/Revoke: Amount field with positive validation, auto-calculate balance_after
- If player has active matches when banned: those matches auto-forfeit
- Empty search: Show "لا توجد نتائج" with illustration
- Pagination: Show "عرض 1-25 من 1,423 لاعب"
---
# 8. MODULE: GAMES
**Route:** `/games`
## CRUD on `game_plugins`
| Action | Description |
|--------|-------------|
| List | All games with enable/disable toggle |
| Create | Add new game (game_key, name, name_ar, settings) |
| Edit | Modify game settings (matchmaking config, player counts) |
| Toggle | Enable/disable with immediate effect |
| Delete | Only if no matches exist for this game (else soft-disable) |
## Game Card Layout (not a table — visual grid)
Each game as a large card showing:
- Icon/image
- Arabic name + English name
- Player count range
- Supports ranked? Supports tournament?
- Total matches played
- Enable/disable toggle
- Edit button
## Matchmaking Config Editor
JSON editor for per-game matchmaking settings:
```json
{
"elo_range_initial": 100,
"elo_range_expansion_rate": 50,
"elo_range_max": 500,
"queue_timeout_seconds": 60,
"min_level_ranked": 5
}
```
---
# 9. MODULE: CHESS BOTS (STOCKFISH)
**Route:** `/chess-bots`
## API Proxy
All requests go through PHP proxy at `/api/proxy/stockfish.php` which adds the `X-API-Key: sk-alarc-stockfish-mgmt-2024` header.
## Features
| Action | Stockfish API Endpoint |
|--------|------------------------|
| List bots | `GET /api/manage/bots` |
| View bot | `GET /api/manage/bots/{id}` |
| Create bot | `POST /api/manage/bots` |
| Edit bot | `PATCH /api/manage/bots/{id}` |
| Delete bot | `DELETE /api/manage/bots/{id}` |
| Upload portrait | `POST /api/manage/bots/{id}/portrait` |
| Test bot move | `POST /api/manage/test-move` |
| Pool stats | `GET /api/manage/pool` |
| Export all | `GET /api/manage/bots/export` |
| Import bots | `POST /api/manage/bots/import` |
## Bot Card Layout
Each bot displayed as a character card:
- Portrait image (512x512)
- Name (Arabic + English)
- Style badge (Arabic)
- ELO range bar (visual)
- Difficulty stars (skill_level / 20 * 5)
- Blunder % indicator
- Think time range
- Edit / Delete / Test buttons
## Bot Form
| Field | Type | Validation |
|-------|------|------------|
| id | text | Required, lowercase, no spaces, unique |
| name | text | Required |
| name_ar | text | Required |
| style | select | beginner/defensive/aggressive/positional/creative/solid/near_perfect |
| style_ar | text | Required |
| bio | textarea | Optional |
| bio_ar | textarea | Optional |
| elo_min | number | Required, 0-3000 |
| elo_max | number | Required, > elo_min |
| skill_level | range slider | 0-20 |
| depth | number | 1-30 |
| contempt | range slider | -100 to 100 |
| blunder_chance | range slider | 0.00-1.00 (show as %) |
| think_time_min_ms | number | > 0 |
| think_time_max_ms | number | > think_time_min_ms |
| portrait | file upload | PNG/JPG/WebP, max 10MB |
## Test Panel
- FEN input (with "وضع البداية" button to reset to starting position)
- Bot selector dropdown
- "اختبر النقلة" button
- Result display: best_move, evaluation bar, depth, PV line
- Mini chessboard visualization (optional — CSS grid with unicode pieces)
## Pool Monitor
Live display of:
- Processes alive / max
- Processes idle
- Utilization % (progress bar)
- Auto-refresh every 10 seconds
---
# 10. MODULE: TOURNAMENTS (SWISS API)
**Route:** `/tournaments`
## Architecture
The management panel creates/manages tournaments via the Swiss System API. Local `el3ab_tournaments` table stores the El3ab-specific metadata (entry fees, prizes, banner). The Swiss API handles the actual pairing engine.
## Workflow
1. Admin creates tournament in El3ab panel → saves to `el3ab_tournaments`
2. PHP backend creates corresponding org/event/tournament in Swiss API
3. Players register via player app → PHP adds them to Swiss API tournament
4. Admin generates rounds → PHP calls Swiss API pairing engine
5. Results come in → PHP updates Swiss API
6. Standings are read from Swiss API and displayed
## Features
| Action | Description |
|--------|-------------|
| List | All tournaments with status filter (draft/registration/in_progress/completed) |
| Create | Wizard: game → format → settings → economy → schedule |
| Edit | Modify settings (only if draft/registration) |
| Start | Transition to in_progress (calls Swiss API start) |
| Generate Round | Call Swiss API pairing engine |
| Enter Results | Per-pairing result entry |
| View Standings | Live standings from Swiss API |
| Complete | Mark as completed, distribute prizes |
| Cancel | Cancel with optional refund |
| Export | TRF file, JSON, Crosstable |
## Tournament Create Wizard (Multi-Step Form)
**Step 1 — اساسيات:**
- Name / Name (Arabic)
- Game (dropdown from game_plugins)
- Organization (optional, dropdown)
- Description
**Step 2 — النظام:**
- Format: Swiss / Round Robin / Bracket / Arena
- Rounds number
- Time control
- Tiebreak rules (multi-select)
- Max players
**Step 3 — الاقتصاد:**
- Entry fee (coins)
- Prize distribution (1st, 2nd, 3rd + custom JSON)
- Charity percentage (optional)
**Step 4 — الجدول:**
- Registration opens/closes (datetime pickers)
- Tournament starts/ends
- Banner image upload
**Step 5 — مراجعة:**
- Summary of all settings
- Confirm & Create button
## Round Management View
- List of rounds with status
- "توليد الجولة التالية" button (generates via Swiss API)
- Per-round: list pairings with board number, white/black players, result
- Bulk result entry (dropdown per pairing: white wins / draw / black wins / forfeit)
- "ادخال جميع النتائج" button
## Standings View
Table pulled from Swiss API `GET /tournaments/:id/standings`:
- Rank
- Player name
- Points
- Tiebreak values
- Games played / W / D / L
---
# 11. MODULE: ORGANIZATIONS
**Route:** `/organizations`
## CRUD
| Action | Description |
|--------|-------------|
| List | All orgs with search, filter (verified/unverified), sort |
| Create | Name, slug, contact, country, logo upload |
| Edit | All fields, link to Swiss API org |
| Verify | Mark as verified (set is_verified=true, verified_at) |
| Manage Members | List members, add/remove, change roles |
| Delete | Soft-delete (set is_active=false) |
## Organization Detail View
Tabs:
- **معلومات عامة** — All fields, edit in-place
- **الاعضاء** — Members table with role badges, add/remove/change role
- **البطولات** — Tournaments owned by this org
- **التحقق** — Verification documents, approve/reject
## Member Management
- Add member by email/username search
- Role dropdown: owner / admin / arbiter / member
- Remove with confirmation
- Show join date
---
# 12. MODULE: ECONOMY & VIRTUAL CURRENCY
**Route:** `/economy`
## Currencies
| Currency | Arabic | Icon | Use |
|----------|--------|------|-----|
| Coins | عملات | 🪙 | Earned through play, spent on entry fees |
| Gems | جواهر | 💎 | Premium currency, purchased or rewards |
## Admin Actions
| Action | Description |
|--------|-------------|
| Grant coins/gems | Select player(s), amount, reason → insert transaction + update profile |
| Revoke coins/gems | Same but negative |
| View all transactions | Filterable table: user, type, currency, date range |
| Bulk grant | CSV upload or manual multi-select |
| Economy stats | Total coins in circulation, total gems, daily earn/spend rates |
| Price config | Edit costs of items (tournament entry, cosmetics) in system_config |
## Transaction Table
Columns: اللاعب, النوع, العملة, المبلغ, الرصيد بعد, الوصف, التاريخ
Filters: Currency, Type (earn/spend/refund/admin_grant/tournament), Date range, Player search
## Edge Cases
- Granting more than 1,000,000 at once: require confirmation "مبلغ كبير - هل انت متأكد؟"
- Revoke more than player's balance: reject with error "الرصيد غير كاف"
- Transaction always logs the admin who performed it (created_by)
---
# 13. MODULE: ADVERTISEMENTS
**Route:** `/ads`
## CRUD
| Action | Description |
|--------|-------------|
| List | All campaigns with status badges, impressions/clicks stats |
| Create | Name, advertiser, placement, content, targeting, schedule, budget |
| Edit | All fields |
| Activate/Pause | Toggle status |
| View stats | Impressions, clicks, CTR, budget spent |
| Delete | With confirmation |
## Campaign Form
| Field | Type |
|-------|------|
| name | text |
| advertiser | text |
| placement | select (banner_top, banner_bottom, interstitial, sidebar, in_game, reward_video) |
| image_url | file upload (to Supabase Storage) |
| click_url | URL |
| title / title_ar | text |
| body / body_ar | textarea |
| target_countries | multi-select |
| target_games | multi-select (from game_plugins) |
| starts_at / ends_at | datetime |
| budget_total | number |
| cpm | number |
## Stats Dashboard
Per campaign:
- Impressions (number + sparkline chart)
- Clicks (number + sparkline)
- CTR % (calculated)
- Budget: spent / total (progress bar)
- Status timeline
---
# 14. MODULE: MODERATION & REPORTS
**Route:** `/moderation`
## Report Queue
Table of all `cheat_reports` ordered by created_at desc, filterable by status.
Columns: المبلغ, المبلغ عنه, السبب, المباراة, الحالة, التاريخ, الاجراءات
## Actions per Report
| Action | Description |
|--------|-------------|
| View | Full details, evidence links, match replay link |
| Resolve | Mark resolved with action text (what was done) |
| Dismiss | Mark as dismissed with reason |
| Ban reported player | Quick-ban from report view |
| View reporter | Link to reporter's profile |
| View reported | Link to reported player's profile |
| View match | Link to match details |
## Status Flow
```
pending → reviewing → resolved
→ dismissed
```
## Bulk Actions
- Select multiple pending reports
- Bulk dismiss (with shared reason)
- Bulk resolve
---
# 15. MODULE: FEATURE FLAGS
**Route:** `/feature-flags`
## CRUD
| Action | Description |
|--------|-------------|
| List | All flags with toggle switches |
| Create | id, label, label_ar, description, target, category |
| Edit | All fields |
| Toggle | Enable/disable with immediate effect |
| Delete | With confirmation |
## Flag Card Layout
Each flag as a card:
- Toggle switch (prominent)
- Label (Arabic)
- Category badge
- Target indicator (all / percentage / specific users)
- Description (expandable)
## Target Types
| Target | Config |
|--------|--------|
| `all` | Flag applies to everyone |
| `percentage` | Slider: 0-100% of users |
| `user_list` | Search and add specific user IDs |
| `org_list` | Search and add specific org IDs |
---
# 16. MODULE: SYSTEM SETTINGS
**Route:** `/settings`
## Key-Value Editor
Grouped by category. Each setting shows:
- Label (Arabic)
- Current value
- Edit (inline or modal depending on type)
- Value type indicator
## Categories
| Category | Example Settings |
|----------|-----------------|
| `matchmaking` | elo_range_initial, queue_timeout, min_level_ranked |
| `economy` | daily_login_coins, win_reward_coins, tournament_rake_percent |
| `moderation` | auto_ban_threshold, report_cooldown_minutes |
| `platform` | maintenance_mode, platform_name, platform_version |
| `limits` | max_username_length, max_avatar_size_kb, max_orgs_per_user |
## Inline Editing
- String: text input
- Number: number input with +/- buttons
- Boolean: toggle switch
- JSON: code editor (textarea with monospace font)
## Protection
- `is_editable=false` settings are shown but greyed out
- Every change logs to audit_log
- Confirmation for critical settings (maintenance_mode, etc.)
---
# 17. MODULE: BRANDING & THEMING
**Route:** `/branding`
## Tabs
### Tab 1: الالوان (Colors)
Grid of color tokens from `platform_theme` where category='color':
- Color swatch preview
- Label (Arabic)
- Hex value
- Color picker input
- Save button per token
### Tab 2: الاصول المرئية (Assets)
Grid of all `platform_assets`:
- Current image preview (or fallback visualization)
- Label (Arabic)
- Upload button (drag & drop)
- Reset button (clear asset_url → show fallback)
- Fallback type indicator
### Tab 3: معاينة حية (Live Preview)
Miniature preview of how the current theme looks applied to sample UI elements (buttons, cards, text, backgrounds).
## Edge Cases
- Invalid color: validate hex format before saving
- Asset upload: validate file type (PNG/JPG/SVG/WebP), max 5MB
- Show color contrast ratio for text colors (accessibility)
- "اعادة الى الافتراضي" button to reset all tokens to defaults
---
# 18. MODULE: ANALYTICS
**Route:** `/analytics`
## Data Sources
All data pulled from Supabase tables with SQL aggregation:
- Player growth (registrations per day/week/month)
- Match volume per game
- Revenue (virtual currency flow)
- Active users (DAU/WAU/MAU)
- Tournament participation rates
- Top games by match count
## Charts (Pure CSS/JS — no chart libraries)
- **Bar charts:** CSS grid + div heights based on %
- **Sparklines:** SVG polyline
- **Progress bars:** CSS gradient fills
- **Numbers:** Count-up animation
## Date Range Filter
Dropdown: اليوم / هذا الاسبوع / هذا الشهر / آخر 3 اشهر / مخصص
## Stat Cards (Top Row)
| Stat | Source |
|------|--------|
| اللاعبون الجدد اليوم | profiles WHERE created_at > today |
| المباريات اليوم | matches WHERE created_at > today |
| الايرادات (عملات) | SUM(transactions.amount) WHERE type='spend' AND today |
| معدل الاحتفاظ | (active_7d / total) * 100 |
## Game Distribution Chart
Horizontal bars showing match count per game, sorted descending.
---
# 19. MODULE: NOTIFICATIONS
**Route:** `/notifications`
## Features
| Action | Description |
|--------|-------------|
| Send to user | Select user, type, title, body |
| Broadcast | Send to all users (is_broadcast=true) |
| View history | All sent notifications with read/unread stats |
| Templates | Pre-defined notification templates (maintenance, update, event) |
| Delete old | Bulk delete notifications older than X days |
## Notification Types
| Type | Usage |
|------|-------|
| `system` | Platform updates, maintenance |
| `tournament` | Registration open, round generated, results |
| `economy` | Currency granted, prize awarded |
| `moderation` | Account warning, ban notification |
| `social` | Friend request, org invite |
## Broadcast Form
- Title (Arabic)
- Body (Arabic)
- Type selector
- Optional: target specific game players only
- Preview before send
- Confirm: "سيتم ارسال هذا الاشعار الى {count} لاعب. متأكد؟"
---
# 20. MODULE: AUDIT LOG
**Route:** `/audit-log`
## Features
- Read-only table of all admin actions
- Filters: actor, action, entity_type, date range
- Search by entity_id
- Export as CSV
## Table Columns
| Column | Description |
|--------|-------------|
| التاريخ | Timestamp |
| المستخدم | Admin who performed action |
| الاجراء | create / update / delete / ban / toggle / grant |
| النوع | Entity type (player, tournament, setting, etc.) |
| المعرف | Entity ID (clickable link to entity) |
| التفاصيل | Expandable JSON diff (old_value → new_value) |
| IP | IP address of admin |
## Auto-Logging
Every write operation (create/update/delete) in any module automatically inserts an audit_log entry via `AuditLog::log()` helper.
---
# 21. CRUD PATTERNS & EDGE CASES
## Standard CRUD Flow (every module follows this)
### List View
- Skeleton loader while fetching
- Empty state with illustration if no data
- Search with debounce (300ms)
- Column sort (ASC/DESC toggle)
- Pagination with page size selector
- Bulk select checkbox + bulk actions dropdown
- Status filter pills/tabs
- "اضافة جديد" button (prominent, top-right for RTL)
### Create/Edit Form
- Client-side validation (required fields, formats, ranges)
- Server-side validation (duplicate checks, foreign key existence)
- CSRF token on every form
- Loading state on submit button (spinner, disable)
- Success → toast notification + redirect to list
- Error → inline error messages per field + toast
- Unsaved changes warning on navigation away
### Delete
- Always requires confirmation dialog
- Dialog shows entity name/identifier
- "حذف" button is red, requires typing entity name for critical entities
- Soft delete preferred (set is_active=false or status=deleted)
- Hard delete only for truly ephemeral data (notifications, old logs)
### Detail/Show View
- Breadcrumb navigation
- Edit button (top-right)
- Delete button (bottom, with warning color)
- Related entities in tabs
- Activity timeline (from audit_log)
## Edge Cases Handled Everywhere
| Edge Case | Handling |
|-----------|----------|
| Network timeout | Retry button + "فشل الاتصال" toast |
| 401 Unauthorized | Redirect to login immediately |
| 403 Forbidden | "ليس لديك صلاحية" page |
| 404 Not Found | "العنصر غير موجود" with back button |
| 409 Conflict | "يوجد عنصر بنفس البيانات" with suggestion |
| 422 Validation | Highlight invalid fields, scroll to first error |
| 429 Rate Limit | "كثرة الطلبات, انتظر قليلا" with countdown |
| 500 Server Error | "خطأ في الخادم" with support contact |
| Empty state | Custom illustration + helpful action button |
| Long loading | Skeleton loader (never blank screen) |
| Large dataset | Pagination + "يتم التحميل..." indicator |
| Concurrent edit | Last-write-wins (show "تم تعديل هذا العنصر بواسطة آخر" if updated_at changed) |
| Special characters | All user input sanitized (htmlspecialchars) |
| XSS | Output encoding everywhere, CSP headers |
| SQL Injection | N/A (using Supabase REST, not raw SQL) but parameterize everything |
| File upload | Validate type, size, dimensions on both client and server |
| Session expiry | AJAX calls detect 401 → show "انتهت الجلسة" modal → login |
---
# 22. API PROXY LAYER
PHP proxies all external API calls (browser never talks directly to Stockfish/Swiss/Supabase).
## Why Proxy
1. Hides API keys from browser
2. Avoids CORS entirely
3. Adds audit logging
4. Rate limiting/throttling
5. Response transformation
## Proxy Files
```
api/
├── supabase.php # All Supabase REST calls
├── stockfish.php # All Stockfish management calls
├── swiss.php # All Swiss API calls
└── health.php # Health check aggregator
```
## Supabase Proxy Pattern
```php
// api/supabase.php
class SupabaseProxy {
private string $baseUrl = 'https://safe-supabase-kong.caprover.al-arcade.com/rest/v1';
private string $serviceKey = '...service_role_key...';
public function select(string $table, array $params = []): array {
$query = http_build_query($params);
$response = $this->request('GET', "/{$table}?{$query}");
return json_decode($response, true);
}
public function insert(string $table, array $data): array { ... }
public function update(string $table, array $match, array $data): array { ... }
public function delete(string $table, array $match): bool { ... }
private function request(string $method, string $path, ?array $body = null): string {
// cURL with Authorization: Bearer $serviceKey
// + apikey header
// + Prefer: return=representation for INSERT/UPDATE
}
}
```
---
# 23. DEPLOYMENT & CAPROVER
## Dockerfile
```dockerfile
FROM php:8.3-apache
# Enable Apache modules
RUN a2enmod rewrite headers
# Install extensions
RUN docker-php-ext-install opcache
# Configure Apache for .htaccess
RUN sed -i 's/AllowOverride None/AllowOverride All/' /etc/apache2/apache2.conf
# Copy source
COPY . /var/www/html/
# Set permissions
RUN chown -R www-data:www-data /var/www/html/storage
EXPOSE 80
```
## captain-definition
```json
{
"schemaVersion": 2,
"dockerfilePath": "./Dockerfile"
}
```
## .htaccess (URL Rewriting)
```apache
RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^(.*)$ index.php?route=$1 [QSA,L]
# Security headers
Header set X-Content-Type-Options "nosniff"
Header set X-Frame-Options "DENY"
Header set X-XSS-Protection "1; mode=block"
Header set Referrer-Policy "strict-origin-when-cross-origin"
```
## CapRover Config
| Setting | Value |
|---------|-------|
| App Name | `el3ab-management` |
| Container HTTP Port | 80 |
| Force HTTPS | Yes |
| Websocket Support | No |
| Persistent Data | No (stateless PHP, all data in Supabase) |
## Environment Variables (in CapRover)
```
SUPABASE_URL=https://safe-supabase-kong.caprover.al-arcade.com
SUPABASE_SERVICE_KEY=eyJhbGci...service_role...
SUPABASE_ANON_KEY=eyJhbGci...anon...
STOCKFISH_API_URL=https://stockfishapi.caprover.al-arcade.com
STOCKFISH_API_KEY=sk-alarc-stockfish-mgmt-2024
SWISS_API_URL=https://swissapi.caprover.al-arcade.com/api/v1
ADMIN_USERNAME=admin
ADMIN_PASSWORD_HASH=$2y$12$... (bcrypt hash of Alarcade123#)
APP_SECRET=random-64-char-string-for-csrf-and-sessions
```
---
# 24. EXECUTION PLAN
## Phase 1: Foundation (Week 1)
| Task | Priority |
|------|----------|
| Project scaffold (folders, Dockerfile, .htaccess, index.php) | P0 |
| Core classes: Router, Auth, View, Database (Supabase REST client) | P0 |
| Login page (hardcoded admin/Alarcade123#) | P0 |
| Main layout: sidebar + topbar + content area | P0 |
| CSS design system: variables, reset, layout, components | P0 |
| Dashboard with real data (player count, match count, service health) | P0 |
## Phase 2: Core Modules (Week 2)
| Task | Priority |
|------|----------|
| Players module: full CRUD + ban/unban + currency grant | P0 |
| Games module: CRUD on game_plugins | P0 |
| Database migration script (create all tables via SSH) | P0 |
| Toast notifications, confirm dialogs, loading states | P0 |
| Pagination component, data table with sort/search | P0 |
## Phase 3: External Integrations (Week 3)
| Task | Priority |
|------|----------|
| Chess Bots module: full CRUD via Stockfish API proxy | P1 |
| Tournaments module: create wizard + Swiss API integration | P1 |
| Organizations module: CRUD + member management | P1 |
| API proxy layer (supabase.php, stockfish.php, swiss.php) | P0 |
## Phase 4: Management Features (Week 4)
| Task | Priority |
|------|----------|
| Economy module: transactions, grant/revoke, stats | P1 |
| Moderation module: report queue, resolve/dismiss | P1 |
| Advertisements module: campaign CRUD | P2 |
| Feature flags module: toggle switches, targeting | P1 |
| Notifications module: send/broadcast | P2 |
## Phase 5: Polish & Extras (Week 5)
| Task | Priority |
|------|----------|
| System settings module: key-value editor | P1 |
| Branding module: theme colors + asset upload | P2 |
| Analytics module: stats, charts, date ranges | P2 |
| Audit log module: read-only history | P1 |
| Animation polish: all microinteractions, page transitions | P1 |
| Mobile responsiveness pass | P1 |
| Security audit: CSRF, XSS, session, headers | P0 |
| Deploy to CapRover, test end-to-end | P0 |
---
# SUMMARY
| Metric | Value |
|--------|-------|
| Technology | PHP 8.3 + HTML + CSS + JS (zero frameworks) |
| Modules | 15 |
| Database Tables | 14 |
| External APIs | 3 (Supabase, Stockfish, Swiss) |
| Language | Arabic (RTL) with standard numbers (0-9) |
| Auth | Single superadmin (admin / Alarcade123#) |
| Deploy | CapRover (Dockerfile → auto-deploy on push) |
| CRUD Coverage | Every entity has Create, Read, Update, Delete |
| Edge Cases | Network errors, empty states, validation, concurrent edits, XSS, CSRF |
What is El3ab
El3ab is destined to be the Board Games hub and official tournaments Hub for the MENA regjion
El3ab's Visual Identity is "El3ab’s visual identity is a bold fusion of **Arab competitive culture**, **modern esports energy**, and **luxury board-game aesthetics**.
The entire platform is unapologetically Arabic at its core — not as a translated afterthought, but as a native-first competitive ecosystem built for the Arab world.
This is not “an app localized into Arabic.”
This is:
> **An Arabic-born digital tournament ecosystem designed to elevate traditional board games into a modern esports experience.**
The identity blends:
* competitive prestige
* Arab cultural familiarity
* modern UI minimalism
* premium tournament aesthetics
* social gaming energy
* strategic intelligence
into one cohesive visual language.
---
# CORE VISUAL DIRECTION
The platform feels like:
* a luxury Arab chess club
* a modern esports tournament arena
* a premium mobile gaming ecosystem
* a culturally authentic competitive platform
The design language communicates:
* victory
* ranking
* mastery
* strategy
* social competition
* prestige
* modern Arab digital culture
without becoming:
* childish
* casino-like
* medieval
* over-futuristic
* westernized generic esports branding
The entire identity should visually say:
> “Traditional Arab social gaming evolved into a world-class competitive platform.”
---
# THE ARAB IDENTITY
The Arabic identity is fundamental to the brand.
Not decorative.
Not secondary.
Fundamental.
The platform embraces:
* Arabic typography
* Arab social gaming culture
* traditional gathering games
* competitive local tournaments
* regional identity
* bilingual coexistence
The inclusion of:
* “EL3AB”
* “العب”
side-by-side represents:
* digital globalization
* Arab ownership
* regional pride
* accessibility for all Arab audiences
The Arabic typography should feel:
* modern
* clean
* technologically advanced
* youthful
* esports-ready
NOT:
* traditional calligraphy
* ornamental Islamic styling
* heritage branding
The goal is:
> “A modern Arabic digital-native gaming identity.”
---
# LOGO SYMBOLISM
The logo is a crown formed from board-game pawns.
This symbol communicates:
* victory
* rankings
* competition
* multiplayer hierarchy
* tournament progression
The center pawn represents:
* the champion
* first place
* mastery
The side pawns represent:
* second and third place
* competitive hierarchy
* tournament positioning
Together:
they create a modern geometric crown silhouette that feels:
* premium
* iconic
* mobile-native
* scalable
* tournament-ready
The logo must remain recognizable:
* at 15x15px
* in monochrome
* in esports overlays
* on tournament medals
* on livestreams
* on app icons
* on physical merchandise
---
# PRIMARY COLOR SYSTEM
The El3ab identity revolves around a highly recognizable competitive palette that combines:
* luxury golds
* electric esports blues
* energetic warm tones
* deep strategic darks
---
## PRIMARY COLORS
### Royal Competitive Blue
`#2082F0`
The core digital color of the platform.
Represents:
* technology
* modernity
* esports
* matchmaking systems
* online competition
* digital progression
Used for:
* UI highlights
* active states
* ranking systems
* buttons
* tournament visuals
* glow accents
This blue is the platform’s digital heartbeat.
---
### Competitive Orange
`#E84D1E`
Represents:
* intensity
* excitement
* action
* live competition
* urgency
* tournament energy
Used sparingly for:
* alerts
* high-energy moments
* CTA emphasis
* competitive highlights
Adds heat to the ecosystem.
---
### Victory Gold
`#E4AC38`
The primary prestige color.
Represents:
* trophies
* crowns
* champions
* first place
* elite status
* mastery
This gold is essential to the identity.
It transforms the platform from:
“gaming app”
into:
“competitive arena.”
---
### Premium Sand Gold
`#FFCC66`
A softer luxury accent inspired by:
* desert sunlight
* Arab luxury aesthetics
* warm prestige
* traditional elegance
Used for:
* secondary highlights
* subtle gradients
* elegant UI accents
* premium memberships
* luxury tournament branding
This color introduces warmth and regional character.
---
### Strategic Midnight Navy
`#152132`
The foundation color of the ecosystem.
Represents:
* focus
* strategy
* intelligence
* prestige
* night tournaments
* premium dashboards
This navy replaces pure black intentionally.
It creates:
* maturity
* readability
* sophistication
* premium contrast
while allowing brighter colors to glow naturally.
---
### Electric Cyan
`#00FFFF`
Represents:
* futuristic energy
* digital interactivity
* live systems
* modern gaming UI
* responsive interfaces
Used carefully for:
* hover states
* glow accents
* interactive moments
* dynamic gradients
Adds esports energy without overwhelming the design.
---
### Royal Purple
`#6834BE`
Represents:
* rarity
* elite status
* premium progression
* legendary rankings
* high-tier rewards
This color evokes:
* tournament prestige
* advanced ranks
* rare cosmetics
* high-level competition
Used mostly in:
* gradients
* reward systems
* elite visual moments
---
# VISUAL STYLE
The UI and branding should feel:
* clean
* symmetrical
* geometric
* responsive
* tournament-oriented
The design language uses:
* rounded rectangles
* strong hierarchy
* centered compositions
* bold typography
* glowing highlights
* subtle gradients
* premium spacing
The interface should resemble:
* esports dashboards
* modern tournament systems
* luxury fintech apps
* premium gaming ecosystems
---
# TYPOGRAPHY STYLE
Typography should feel:
* bold
* condensed
* modern
* digitally optimized
* multilingual
The typography system combines:
* esports clarity
* Arab modernism
* mobile readability
* premium confidence
The text hierarchy should feel:
* commanding
* competitive
* clean
* scalable
Large titles should resemble:
* esports event graphics
* tournament announcements
* streaming overlays
Body text should remain:
* highly readable
* approachable
* app-friendly
---
# EMOTIONAL EXPERIENCE
The El3ab identity should make users feel:
* competitive
* proud
* included
* ambitious
* socially connected
* regionally represented
It should emotionally communicate:
> “The Arab world deserves its own world-class competitive gaming ecosystem.”
And visually:
> “Board games are no longer casual pastimes — they are modern competitive digital sports.”
---
# FINAL BRAND ESSENCE
El3ab is:
> A premium Arabic-first competitive platform transforming traditional board games into a modern esports ecosystem through luxury visual design, strategic symbolism, and culturally authentic digital identity.
"
I expanded the documentation into a much more complete platform architecture document including:
* Full actor system
* Superadmin structure
* Organizations
* Sponsors / advertisers
* Charity system with tournament fee percentages
* Content managers
* Match moderators
* Tournament organizers
* Internal game services
* Analytics/audit services
And I fully documented the Chess package including:
* chess.js integration
* Stockfish API integration
* Swiss tournaments
* Bracket tournaments
* Hybrid Swiss → Top 8 bracket flow
* Two-round bracket matches
* Elo system
* Anti-abuse systems
* Cosmetics
* Match lifecycle
* Realtime architecture
* Supabase table ideas
* Future expansion systems
The document now reads much closer to an actual production-grade platform specification instead of just a template.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment