Commit b0303129 authored by Mahmoud Aglan's avatar Mahmoud Aglan

init

parents
.DS_Store
storage/uploads/*
!storage/uploads/.gitkeep
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"
Header set Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; font-src 'self'"
-----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)
================================================================================
This diff is collapsed.
This diff is collapsed.
FROM php:8.3-apache
RUN a2enmod rewrite headers
RUN docker-php-ext-install opcache
RUN sed -i 's/AllowOverride None/AllowOverride All/' /etc/apache2/apache2.conf
COPY . /var/www/html/
RUN chown -R www-data:www-data /var/www/html/storage
ENV APP_ENV=production
ENV APP_SECRET=dev-secret-change-in-production-64chars-minimum-required-here!!
ENV SUPABASE_URL=https://safe-supabase-kong.caprover.al-arcade.com
ENV SUPABASE_SERVICE_KEY=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb2xlIjoic2VydmljZV9yb2xlIiwiaXNzIjoic3VwYWJhc2UiLCJpYXQiOjE3MzU2ODk2MDAsImV4cCI6MTg5MzQ1NjAwMH0.wNfmuJNkX-bZwD7RbjxOChlRf_3Xm4I7bswEYTcDCg4
ENV SUPABASE_ANON_KEY=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb2xlIjoiYW5vbiIsImlzcyI6InN1cGFiYXNlIiwiaWF0IjoxNzM1Njg5NjAwLCJleHAiOjE4OTM0NTYwMDB9.31PF6PvP-pSrvRuQwLFptQoejR0W1A7o53lZhEbnz84
ENV STOCKFISH_API_URL=https://stockfishapi.caprover.al-arcade.com
ENV STOCKFISH_API_KEY=sk-alarc-stockfish-mgmt-2024
ENV SWISS_API_URL=https://swissapi.caprover.al-arcade.com/api/v1
ENV ADMIN_USERNAME=admin
EXPOSE 80
This diff is collapsed.
<?php
$supabase = ApiProxy::healthCheck(
SUPABASE_URL . '/rest/v1/',
['apikey: ' . SUPABASE_SERVICE_KEY]
);
$stockfish = ApiProxy::healthCheck(STOCKFISH_API_URL . '/health');
$swiss = ApiProxy::healthCheck(SWISS_API_URL . '/health');
Response::json([
'supabase' => $supabase,
'stockfish' => $stockfish,
'swiss' => $swiss,
]);
{
"schemaVersion": 2,
"dockerfilePath": "./Dockerfile"
}
<?php
define('APP_NAME', 'El3ab Management');
define('APP_VERSION', '1.0.0');
define('APP_ENV', getenv('APP_ENV') ?: 'production');
define('APP_SECRET', getenv('APP_SECRET') ?: 'dev-secret-change-in-production-64chars-minimum-required-here!!');
define('SUPABASE_URL', getenv('SUPABASE_URL') ?: 'https://safe-supabase-kong.caprover.al-arcade.com');
define('SUPABASE_SERVICE_KEY', getenv('SUPABASE_SERVICE_KEY') ?: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb2xlIjoic2VydmljZV9yb2xlIiwiaXNzIjoic3VwYWJhc2UiLCJpYXQiOjE3MzU2ODk2MDAsImV4cCI6MTg5MzQ1NjAwMH0.wNfmuJNkX-bZwD7RbjxOChlRf_3Xm4I7bswEYTcDCg4');
define('SUPABASE_ANON_KEY', getenv('SUPABASE_ANON_KEY') ?: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb2xlIjoiYW5vbiIsImlzcyI6InN1cGFiYXNlIiwiaWF0IjoxNzM1Njg5NjAwLCJleHAiOjE4OTM0NTYwMDB9.31PF6PvP-pSrvRuQwLFptQoejR0W1A7o53lZhEbnz84');
define('STOCKFISH_API_URL', getenv('STOCKFISH_API_URL') ?: 'https://stockfishapi.caprover.al-arcade.com');
define('STOCKFISH_API_KEY', getenv('STOCKFISH_API_KEY') ?: 'sk-alarc-stockfish-mgmt-2024');
define('SWISS_API_URL', getenv('SWISS_API_URL') ?: 'https://swissapi.caprover.al-arcade.com/api/v1');
define('ADMIN_USERNAME', getenv('ADMIN_USERNAME') ?: 'admin');
define('ADMIN_PASSWORD_HASH', '$2y$12$6HZ3kC4ogVWhgm1ZaU7.A.oM3xJC6aYHL9Iw.5eZ84tVrEjDQE9zO');
define('SESSION_TIMEOUT', 86400); // 24 hours
define('REMEMBER_ME_DAYS', 30);
define('PER_PAGE_DEFAULT', 25);
define('PER_PAGE_OPTIONS', [25, 50, 100]);
define('BASE_PATH', dirname(__DIR__));
define('MODULES_PATH', BASE_PATH . '/modules');
define('LAYOUTS_PATH', BASE_PATH . '/layouts');
define('PUBLIC_PATH', BASE_PATH . '/public');
define('STORAGE_PATH', BASE_PATH . '/storage');
<?php
return [
'superadmin' => ['*'],
'admin' => [
'dashboard', 'players', 'games', 'chess-bots', 'tournaments',
'organizations', 'economy', 'ads', 'moderation', 'feature-flags',
'notifications', 'analytics', 'audit-log'
],
'moderator' => [
'dashboard', 'players.list', 'players.show', 'players.ban', 'players.unban',
'moderation'
],
'viewer' => [
'dashboard', 'players.list', 'players.show', 'games.list',
'tournaments.list', 'tournaments.show', 'organizations.list',
'analytics', 'audit-log'
],
];
<?php
return [
'' => ['module' => 'dashboard', 'action' => 'index'],
'dashboard' => ['module' => 'dashboard', 'action' => 'index'],
'login' => ['module' => 'auth', 'action' => 'login'],
'logout' => ['module' => 'auth', 'action' => 'logout'],
'players' => ['module' => 'players', 'action' => 'list'],
'players/create' => ['module' => 'players', 'action' => 'create'],
'players/store' => ['module' => 'players', 'action' => 'store'],
'players/{id}' => ['module' => 'players', 'action' => 'show'],
'players/{id}/edit' => ['module' => 'players', 'action' => 'edit'],
'players/{id}/update' => ['module' => 'players', 'action' => 'update'],
'players/{id}/delete' => ['module' => 'players', 'action' => 'delete'],
'players/{id}/ban' => ['module' => 'players', 'action' => 'ban'],
'players/{id}/unban' => ['module' => 'players', 'action' => 'unban'],
'players/{id}/grant' => ['module' => 'players', 'action' => 'grant'],
'players/{id}/revoke' => ['module' => 'players', 'action' => 'revoke'],
'games' => ['module' => 'games', 'action' => 'list'],
'games/create' => ['module' => 'games', 'action' => 'create'],
'games/store' => ['module' => 'games', 'action' => 'store'],
'games/{id}/edit' => ['module' => 'games', 'action' => 'edit'],
'games/{id}/update' => ['module' => 'games', 'action' => 'update'],
'games/{id}/toggle' => ['module' => 'games', 'action' => 'toggle'],
'games/{id}/delete' => ['module' => 'games', 'action' => 'delete'],
'chess-bots' => ['module' => 'chess-bots', 'action' => 'list'],
'chess-bots/create' => ['module' => 'chess-bots', 'action' => 'create'],
'chess-bots/store' => ['module' => 'chess-bots', 'action' => 'store'],
'chess-bots/{id}' => ['module' => 'chess-bots', 'action' => 'show'],
'chess-bots/{id}/edit' => ['module' => 'chess-bots', 'action' => 'edit'],
'chess-bots/{id}/update' => ['module' => 'chess-bots', 'action' => 'update'],
'chess-bots/{id}/delete' => ['module' => 'chess-bots', 'action' => 'delete'],
'chess-bots/{id}/portrait' => ['module' => 'chess-bots', 'action' => 'portrait'],
'chess-bots/test-move' => ['module' => 'chess-bots', 'action' => 'testMove'],
'chess-bots/pool' => ['module' => 'chess-bots', 'action' => 'pool'],
'tournaments' => ['module' => 'tournaments', 'action' => 'list'],
'tournaments/create' => ['module' => 'tournaments', 'action' => 'create'],
'tournaments/store' => ['module' => 'tournaments', 'action' => 'store'],
'tournaments/{id}' => ['module' => 'tournaments', 'action' => 'show'],
'tournaments/{id}/edit' => ['module' => 'tournaments', 'action' => 'edit'],
'tournaments/{id}/update' => ['module' => 'tournaments', 'action' => 'update'],
'tournaments/{id}/start' => ['module' => 'tournaments', 'action' => 'start'],
'tournaments/{id}/complete' => ['module' => 'tournaments', 'action' => 'complete'],
'tournaments/{id}/cancel' => ['module' => 'tournaments', 'action' => 'cancel'],
'tournaments/{id}/rounds/generate' => ['module' => 'tournaments', 'action' => 'generateRound'],
'tournaments/{id}/rounds/{roundId}/results' => ['module' => 'tournaments', 'action' => 'submitResults'],
'tournaments/{id}/standings' => ['module' => 'tournaments', 'action' => 'standings'],
'organizations' => ['module' => 'organizations', 'action' => 'list'],
'organizations/create' => ['module' => 'organizations', 'action' => 'create'],
'organizations/store' => ['module' => 'organizations', 'action' => 'store'],
'organizations/{id}' => ['module' => 'organizations', 'action' => 'show'],
'organizations/{id}/edit' => ['module' => 'organizations', 'action' => 'edit'],
'organizations/{id}/update' => ['module' => 'organizations', 'action' => 'update'],
'organizations/{id}/verify' => ['module' => 'organizations', 'action' => 'verify'],
'organizations/{id}/delete' => ['module' => 'organizations', 'action' => 'delete'],
'organizations/{id}/members' => ['module' => 'organizations', 'action' => 'members'],
'organizations/{id}/members/add' => ['module' => 'organizations', 'action' => 'addMember'],
'organizations/{id}/members/{memberId}/remove' => ['module' => 'organizations', 'action' => 'removeMember'],
'economy' => ['module' => 'economy', 'action' => 'index'],
'economy/transactions' => ['module' => 'economy', 'action' => 'transactions'],
'economy/grant' => ['module' => 'economy', 'action' => 'grant'],
'economy/revoke' => ['module' => 'economy', 'action' => 'revoke'],
'economy/bulk-grant' => ['module' => 'economy', 'action' => 'bulkGrant'],
'ads' => ['module' => 'ads', 'action' => 'list'],
'ads/create' => ['module' => 'ads', 'action' => 'create'],
'ads/store' => ['module' => 'ads', 'action' => 'store'],
'ads/{id}' => ['module' => 'ads', 'action' => 'show'],
'ads/{id}/edit' => ['module' => 'ads', 'action' => 'edit'],
'ads/{id}/update' => ['module' => 'ads', 'action' => 'update'],
'ads/{id}/toggle' => ['module' => 'ads', 'action' => 'toggle'],
'ads/{id}/delete' => ['module' => 'ads', 'action' => 'delete'],
'moderation' => ['module' => 'moderation', 'action' => 'list'],
'moderation/{id}' => ['module' => 'moderation', 'action' => 'show'],
'moderation/{id}/resolve' => ['module' => 'moderation', 'action' => 'resolve'],
'moderation/{id}/dismiss' => ['module' => 'moderation', 'action' => 'dismiss'],
'moderation/bulk-dismiss' => ['module' => 'moderation', 'action' => 'bulkDismiss'],
'feature-flags' => ['module' => 'feature-flags', 'action' => 'list'],
'feature-flags/create' => ['module' => 'feature-flags', 'action' => 'create'],
'feature-flags/store' => ['module' => 'feature-flags', 'action' => 'store'],
'feature-flags/{id}/edit' => ['module' => 'feature-flags', 'action' => 'edit'],
'feature-flags/{id}/update' => ['module' => 'feature-flags', 'action' => 'update'],
'feature-flags/{id}/toggle' => ['module' => 'feature-flags', 'action' => 'toggle'],
'feature-flags/{id}/delete' => ['module' => 'feature-flags', 'action' => 'delete'],
'settings' => ['module' => 'settings', 'action' => 'index'],
'settings/update' => ['module' => 'settings', 'action' => 'update'],
'branding' => ['module' => 'branding', 'action' => 'index'],
'branding/colors' => ['module' => 'branding', 'action' => 'colors'],
'branding/assets' => ['module' => 'branding', 'action' => 'assets'],
'branding/update-color' => ['module' => 'branding', 'action' => 'updateColor'],
'branding/upload-asset' => ['module' => 'branding', 'action' => 'uploadAsset'],
'analytics' => ['module' => 'analytics', 'action' => 'index'],
'notifications' => ['module' => 'notifications', 'action' => 'list'],
'notifications/send' => ['module' => 'notifications', 'action' => 'send'],
'notifications/broadcast' => ['module' => 'notifications', 'action' => 'broadcast'],
'notifications/{id}/delete' => ['module' => 'notifications', 'action' => 'delete'],
'audit-log' => ['module' => 'audit-log', 'action' => 'index'],
'audit-log/export' => ['module' => 'audit-log', 'action' => 'export'],
'api/health' => ['module' => 'api', 'action' => 'health'],
];
<?php
class ApiProxy
{
public static function stockfish(string $method, string $path, ?array $body = null): array
{
return self::request($method, STOCKFISH_API_URL . $path, $body, [
'X-API-Key: ' . STOCKFISH_API_KEY,
'Content-Type: application/json',
]);
}
public static function swiss(string $method, string $path, ?array $body = null, ?string $token = null): array
{
$headers = ['Content-Type: application/json'];
if ($token) {
$headers[] = 'Authorization: Bearer ' . $token;
}
return self::request($method, SWISS_API_URL . $path, $body, $headers);
}
public static function request(string $method, string $url, ?array $body = null, array $headers = []): array
{
$ch = curl_init();
curl_setopt_array($ch, [
CURLOPT_URL => $url,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => $headers,
CURLOPT_TIMEOUT => 30,
CURLOPT_FOLLOWLOCATION => true,
]);
switch (strtoupper($method)) {
case 'POST':
curl_setopt($ch, CURLOPT_POST, true);
if ($body !== null) {
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($body));
}
break;
case 'PATCH':
case 'PUT':
case 'DELETE':
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, strtoupper($method));
if ($body !== null) {
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($body));
}
break;
}
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
$error = curl_error($ch);
$time = curl_getinfo($ch, CURLINFO_TOTAL_TIME);
curl_close($ch);
if ($error) {
return ['status' => 0, 'body' => null, 'error' => $error, 'time_ms' => round($time * 1000)];
}
return [
'status' => $status,
'body' => json_decode($response, true) ?? $response,
'error' => null,
'time_ms' => round($time * 1000),
];
}
public static function healthCheck(string $url, array $headers = []): array
{
$ch = curl_init();
curl_setopt_array($ch, [
CURLOPT_URL => $url,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => $headers,
CURLOPT_TIMEOUT => 5,
CURLOPT_CONNECTTIMEOUT => 3,
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
$time = curl_getinfo($ch, CURLINFO_TOTAL_TIME);
$error = curl_error($ch);
curl_close($ch);
return [
'online' => $status >= 200 && $status < 500 && !$error,
'status' => $status,
'latency_ms' => round($time * 1000),
'error' => $error ?: null,
];
}
}
<?php
class AuditLog
{
public static function log(string $action, string $entityType, ?string $entityId = null, ?array $oldValue = null, ?array $newValue = null): void
{
$db = Database::getInstance();
$db->insert('audit_log', [
'actor' => Auth::user()['username'] ?? 'system',
'action' => $action,
'entity_type' => $entityType,
'entity_id' => $entityId,
'old_value' => $oldValue ? json_encode($oldValue) : null,
'new_value' => $newValue ? json_encode($newValue) : null,
'ip_address' => $_SERVER['REMOTE_ADDR'] ?? null,
]);
}
}
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
<?php
session_start();
require_once __DIR__ . '/config/app.php';
require_once __DIR__ . '/core/Database.php';
require_once __DIR__ . '/core/Auth.php';
require_once __DIR__ . '/core/Router.php';
require_once __DIR__ . '/core/View.php';
require_once __DIR__ . '/core/Validator.php';
require_once __DIR__ . '/core/ApiProxy.php';
require_once __DIR__ . '/core/AuditLog.php';
require_once __DIR__ . '/core/Pagination.php';
require_once __DIR__ . '/core/Response.php';
$route = trim($_GET['route'] ?? '', '/');
$method = $_SERVER['REQUEST_METHOD'];
if (str_starts_with($route, 'public/')) {
return false;
}
$router = new Router();
$router->dispatch($route, $method);
This diff is collapsed.
This diff is collapsed.
<!DOCTYPE html>
<html lang="ar" dir="rtl">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>خطأ — El3ab</title>
<link href="https://fonts.googleapis.com/css2?family=IBM+Plex+Sans+Arabic:wght@400;600;700&display=swap" rel="stylesheet">
<link rel="stylesheet" href="/public/css/variables.css">
<link rel="stylesheet" href="/public/css/reset.css">
<link rel="stylesheet" href="/public/css/components.css">
<link rel="stylesheet" href="/public/css/utilities.css">
<style>
.error-page {
min-height: 100vh;
display: flex;
align-items: center;
justify-content: center;
text-align: center;
padding: var(--space-6);
}
</style>
</head>
<body>
<div class="error-page">
<?= $content ?>
</div>
</body>
</html>
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
/* Ads module styles */
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
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