Commit 3e80ca64 authored by Administrator's avatar Administrator

Update 6 files via Son of Anton

parent ea9f177f
.git
.gitignore
.env.example
docker/
Dockerfile
captain-definition
*.md
.idea/
.vscode/
......
......@@ -51,7 +51,7 @@ RUN chown -R www-data:www-data /var/www/html/storage \
&& chown -R www-data:www-data /var/www/html/public \
&& chmod -R 755 /var/www/html/public
# ── Default environment (overridable via CapRover env vars) ──
# ── Environment (overridable via CapRover env vars) ──
ENV APP_URL=http://localhost
ENV APP_DEBUG=true
ENV APP_ENV=local
......
#!/bin/bash
set -e
echo "╔══════════════════════════════════════════════════╗"
echo "║ THE CLUB ERP — CapRover Deployment Starting ║"
echo "╚══════════════════════════════════════════════════╝"
echo "=========================================="
echo " THE CLUB ERP — Starting Deployment"
echo "=========================================="
# ── Resolve DB credentials from environment ──
# ── DB credentials from environment ──
DB_HOST="${DB_HOST:-srv-captain--mysql-db}"
DB_PORT="${DB_PORT:-3306}"
DB_NAME="${DB_NAME:-the_club_erp}"
DB_USER="${DB_USER:-root}"
DB_PASS="${DB_PASS:-Alarcade123#}"
echo ""
echo "📡 Database: ${DB_USER}@${DB_HOST}:${DB_PORT}/${DB_NAME}"
echo ""
echo "DB: ${DB_USER}@${DB_HOST}:${DB_PORT}/${DB_NAME}"
# ── Generate .env from environment variables (so PHP app can read it) ──
# ── Write .env file from Docker env vars ──
cat > /var/www/html/.env <<EOF
APP_URL=${APP_URL:-http://localhost}
APP_DEBUG=${APP_DEBUG:-true}
......@@ -33,179 +31,139 @@ SMS_API_KEY=${SMS_API_KEY:-}
SMS_SENDER_ID=${SMS_SENDER_ID:-}
EOF
echo "✅ .env generated from environment variables"
# ── Wait for MySQL to be reachable ──
echo ""
echo "⏳ Waiting for MySQL at ${DB_HOST}:${DB_PORT}..."
max_tries=60
count=0
while ! mysqladmin ping -h "${DB_HOST}" -P "${DB_PORT}" -u "${DB_USER}" -p"${DB_PASS}" --silent 2>/dev/null; do
count=$((count + 1))
if [ $count -ge $max_tries ]; then
echo "❌ FATAL: Could not connect to MySQL after ${max_tries} attempts (2 min)"
echo " Check that srv-captain--mysql-db is running in CapRover"
exit 1
chown www-data:www-data /var/www/html/.env
chmod 640 /var/www/html/.env
echo "✅ .env written"
# ── Wait for MySQL ──
echo "Waiting for MySQL..."
TRIES=0
MAX_TRIES=60
until mysqladmin ping -h "${DB_HOST}" -P "${DB_PORT}" -u "${DB_USER}" -p"${DB_PASS}" --silent 2>/dev/null; do
TRIES=$((TRIES + 1))
if [ $TRIES -ge $MAX_TRIES ]; then
echo "❌ MySQL not reachable after 2 minutes!"
echo "Starting Apache anyway — app will show DB error"
exec apache2-foreground
fi
echo " Attempt ${count}/${max_tries} — retrying in 2s..."
sleep 2
done
echo "✅ MySQL is alive!"
echo "✅ MySQL is alive"
# ── Create database if it doesn't exist ──
echo ""
echo "📦 Creating database '${DB_NAME}' if not exists..."
mysql -h "${DB_HOST}" -P "${DB_PORT}" -u "${DB_USER}" -p"${DB_PASS}" --default-character-set=utf8mb4 -e "
# ── Create database ──
echo "Creating database if needed..."
mysql -h "${DB_HOST}" -P "${DB_PORT}" -u "${DB_USER}" -p"${DB_PASS}" -e "
CREATE DATABASE IF NOT EXISTS \`${DB_NAME}\`
CHARACTER SET utf8mb4
COLLATE utf8mb4_unicode_ci;
"
echo "✅ Database '${DB_NAME}' ready"
# ── Create seeds tracking table (not in migrations but needed by SeederRunner) ──
echo ""
echo "📋 Ensuring seeds tracking table exists..."
mysql -h "${DB_HOST}" -P "${DB_PORT}" -u "${DB_USER}" -p"${DB_PASS}" "${DB_NAME}" --default-character-set=utf8mb4 -e "
CREATE TABLE IF NOT EXISTS \`seeds\` (
\`id\` INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
\`seed\` VARCHAR(255) NOT NULL,
\`executed_at\` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
UNIQUE KEY \`uq_seeds_name\` (\`seed\`)
CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
" 2>&1 || echo "⚠️ Could not create database (may already exist)"
# ── Create tracking tables (schema.sql has them but migrations don't) ──
echo "Creating infrastructure tables..."
mysql -h "${DB_HOST}" -P "${DB_PORT}" -u "${DB_USER}" -p"${DB_PASS}" "${DB_NAME}" -e "
CREATE TABLE IF NOT EXISTS seeds (
id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
seed VARCHAR(255) NOT NULL,
executed_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
UNIQUE KEY uq_seeds_name (seed)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
"
# ── Create async_event_queue table (in schema.sql but not in migrations) ──
mysql -h "${DB_HOST}" -P "${DB_PORT}" -u "${DB_USER}" -p"${DB_PASS}" "${DB_NAME}" --default-character-set=utf8mb4 -e "
CREATE TABLE IF NOT EXISTS \`async_event_queue\` (
\`id\` BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
\`event_name\` VARCHAR(200) NOT NULL,
\`event_data_json\` JSON NULL,
\`status\` VARCHAR(30) NOT NULL DEFAULT 'pending',
\`attempts\` INT UNSIGNED NOT NULL DEFAULT 0,
\`max_attempts\` INT UNSIGNED NOT NULL DEFAULT 3,
\`created_at\` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
\`processed_at\` TIMESTAMP NULL DEFAULT NULL,
\`failed_at\` TIMESTAMP NULL DEFAULT NULL,
\`error_message\` TEXT NULL,
INDEX \`idx_async_events_status\` (\`status\`),
INDEX \`idx_async_events_created\` (\`created_at\`)
CREATE TABLE IF NOT EXISTS async_event_queue (
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
event_name VARCHAR(200) NOT NULL,
event_data_json JSON NULL,
status VARCHAR(30) NOT NULL DEFAULT 'pending',
attempts INT UNSIGNED NOT NULL DEFAULT 0,
max_attempts INT UNSIGNED NOT NULL DEFAULT 3,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
processed_at TIMESTAMP NULL DEFAULT NULL,
failed_at TIMESTAMP NULL DEFAULT NULL,
error_message TEXT NULL,
INDEX idx_async_events_status (status),
INDEX idx_async_events_created (created_at)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
"
# ── Create cron_job_log table (in schema.sql but not in migrations) ──
mysql -h "${DB_HOST}" -P "${DB_PORT}" -u "${DB_USER}" -p"${DB_PASS}" "${DB_NAME}" --default-character-set=utf8mb4 -e "
CREATE TABLE IF NOT EXISTS \`cron_job_log\` (
\`id\` BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
\`job_name\` VARCHAR(200) NOT NULL,
\`started_at\` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
\`finished_at\` TIMESTAMP NULL DEFAULT NULL,
\`status\` VARCHAR(30) NOT NULL DEFAULT 'running',
\`records_processed\` INT UNSIGNED NOT NULL DEFAULT 0,
\`error_message\` TEXT NULL,
\`execution_time_ms\` INT UNSIGNED NULL,
INDEX \`idx_cron_job_name\` (\`job_name\`),
INDEX \`idx_cron_job_started\` (\`started_at\`)
CREATE TABLE IF NOT EXISTS cron_job_log (
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
job_name VARCHAR(200) NOT NULL,
started_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
finished_at TIMESTAMP NULL DEFAULT NULL,
status VARCHAR(30) NOT NULL DEFAULT 'running',
records_processed INT UNSIGNED NOT NULL DEFAULT 0,
error_message TEXT NULL,
execution_time_ms INT UNSIGNED NULL,
INDEX idx_cron_job_name (job_name),
INDEX idx_cron_job_started (started_at)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
"
" 2>&1 || echo "⚠️ Infrastructure tables may already exist"
echo "✅ Infrastructure tables ready"
# ══════════════════════════════════════════
# MIGRATIONS — All 15 Phases in sequence
# ══════════════════════════════════════════
echo ""
echo "╔══════════════════════════════════════════════════╗"
echo "║ RUNNING ALL MIGRATIONS ║"
echo "╚══════════════════════════════════════════════════╝"
echo ""
echo "Migration files to process:"
ls -1 /var/www/html/database/migrations/Phase_*.php 2>/dev/null | wc -l
echo " migration files found"
# ══════════════════════════════════════
# RUN MIGRATIONS
# ══════════════════════════════════════
echo ""
echo "=========================================="
echo " RUNNING MIGRATIONS"
echo "=========================================="
cd /var/www/html
php cli.php migrate 2>&1
MIGRATE_EXIT=$?
if [ $MIGRATE_EXIT -ne 0 ]; then
echo "⚠️ Migration exited with code ${MIGRATE_EXIT}"
echo " Checking if tables were created anyway..."
TABLE_COUNT=$(mysql -h "${DB_HOST}" -P "${DB_PORT}" -u "${DB_USER}" -p"${DB_PASS}" "${DB_NAME}" -N -e "SELECT COUNT(*) FROM information_schema.tables WHERE table_schema='${DB_NAME}';" 2>/dev/null)
echo " Tables in database: ${TABLE_COUNT}"
if [ "${TABLE_COUNT}" -lt 5 ]; then
echo "❌ FATAL: Migration failed and database is nearly empty"
exit 1
fi
fi
# Show migration status
echo ""
echo "📊 Migration status:"
php cli.php migrate:status 2>&1 || true
echo ""
# Count migration files
MIGRATION_COUNT=$(ls -1 database/migrations/Phase_*.php 2>/dev/null | wc -l)
echo "Found ${MIGRATION_COUNT} migration files"
# ══════════════════════════════════════════
# SEEDS — All 15 Phases in sequence
# ══════════════════════════════════════════
echo ""
echo "╔══════════════════════════════════════════════════╗"
echo "║ RUNNING ALL SEEDS ║"
echo "╚══════════════════════════════════════════════════╝"
echo ""
echo "Seed files to process:"
ls -1 /var/www/html/database/seeds/Phase_*.php 2>/dev/null | wc -l
echo " seed files found"
echo ""
php cli.php migrate 2>&1 || {
echo "⚠️ Migration command returned error"
echo "Checking table count..."
TC=$(mysql -h "${DB_HOST}" -P "${DB_PORT}" -u "${DB_USER}" -p"${DB_PASS}" "${DB_NAME}" -N -e "SELECT COUNT(*) FROM information_schema.tables WHERE table_schema='${DB_NAME}';" 2>/dev/null || echo "0")
echo "Tables: ${TC}"
}
php cli.php seed 2>&1
SEED_EXIT=$?
if [ $SEED_EXIT -ne 0 ]; then
echo "⚠️ Seeding exited with code ${SEED_EXIT} — some seeds may have already run"
fi
# ── Verify deployment ──
echo ""
echo "╔══════════════════════════════════════════════════╗"
echo "║ DEPLOYMENT VERIFICATION ║"
echo "╚══════════════════════════════════════════════════╝"
# ══════════════════════════════════════
# RUN SEEDS
# ══════════════════════════════════════
echo ""
echo "=========================================="
echo " RUNNING SEEDS"
echo "=========================================="
TABLE_COUNT=$(mysql -h "${DB_HOST}" -P "${DB_PORT}" -u "${DB_USER}" -p"${DB_PASS}" "${DB_NAME}" -N -e "SELECT COUNT(*) FROM information_schema.tables WHERE table_schema='${DB_NAME}';" 2>/dev/null)
echo "📊 Total tables in database: ${TABLE_COUNT}"
SEED_COUNT=$(ls -1 database/seeds/Phase_*.php 2>/dev/null | wc -l)
echo "Found ${SEED_COUNT} seed files"
EMPLOYEE_COUNT=$(mysql -h "${DB_HOST}" -P "${DB_PORT}" -u "${DB_USER}" -p"${DB_PASS}" "${DB_NAME}" -N -e "SELECT COUNT(*) FROM employees;" 2>/dev/null || echo "0")
echo "👤 Employees (incl. super admin): ${EMPLOYEE_COUNT}"
php cli.php seed 2>&1 || {
echo "⚠️ Seed command returned error (some may have already run)"
}
ROLE_COUNT=$(mysql -h "${DB_HOST}" -P "${DB_PORT}" -u "${DB_USER}" -p"${DB_PASS}" "${DB_NAME}" -N -e "SELECT COUNT(*) FROM roles;" 2>/dev/null || echo "0")
echo "🔐 Roles: ${ROLE_COUNT}"
BRANCH_COUNT=$(mysql -h "${DB_HOST}" -P "${DB_PORT}" -u "${DB_USER}" -p"${DB_PASS}" "${DB_NAME}" -N -e "SELECT COUNT(*) FROM branches;" 2>/dev/null || echo "0")
echo "🏢 Branches: ${BRANCH_COUNT}"
# ══════════════════════════════════════
# VERIFY
# ══════════════════════════════════════
echo ""
echo "=========================================="
echo " DEPLOYMENT VERIFICATION"
echo "=========================================="
RULE_COUNT=$(mysql -h "${DB_HOST}" -P "${DB_PORT}" -u "${DB_USER}" -p"${DB_PASS}" "${DB_NAME}" -N -e "SELECT COUNT(*) FROM business_rules;" 2>/dev/null || echo "0")
echo "📏 Business rules: ${RULE_COUNT}"
TC=$(mysql -h "${DB_HOST}" -P "${DB_PORT}" -u "${DB_USER}" -p"${DB_PASS}" "${DB_NAME}" -N -e "SELECT COUNT(*) FROM information_schema.tables WHERE table_schema='${DB_NAME}';" 2>/dev/null || echo "?")
echo "Tables: ${TC}"
FORM_COUNT=$(mysql -h "${DB_HOST}" -P "${DB_PORT}" -u "${DB_USER}" -p"${DB_PASS}" "${DB_NAME}" -N -e "SELECT COUNT(*) FROM form_schemas;" 2>/dev/null || echo "0")
echo "📝 Form schemas: ${FORM_COUNT}"
EC=$(mysql -h "${DB_HOST}" -P "${DB_PORT}" -u "${DB_USER}" -p"${DB_PASS}" "${DB_NAME}" -N -e "SELECT COUNT(*) FROM employees;" 2>/dev/null || echo "0")
echo "Employees: ${EC}"
WORKFLOW_COUNT=$(mysql -h "${DB_HOST}" -P "${DB_PORT}" -u "${DB_USER}" -p"${DB_PASS}" "${DB_NAME}" -N -e "SELECT COUNT(*) FROM workflow_definitions;" 2>/dev/null || echo "0")
echo "🔄 Workflows: ${WORKFLOW_COUNT}"
RC=$(mysql -h "${DB_HOST}" -P "${DB_PORT}" -u "${DB_USER}" -p"${DB_PASS}" "${DB_NAME}" -N -e "SELECT COUNT(*) FROM roles;" 2>/dev/null || echo "0")
echo "Roles: ${RC}"
GOV_COUNT=$(mysql -h "${DB_HOST}" -P "${DB_PORT}" -u "${DB_USER}" -p"${DB_PASS}" "${DB_NAME}" -N -e "SELECT COUNT(*) FROM governorates;" 2>/dev/null || echo "0")
echo "🗺️ Governorates: ${GOV_COUNT}"
BC=$(mysql -h "${DB_HOST}" -P "${DB_PORT}" -u "${DB_USER}" -p"${DB_PASS}" "${DB_NAME}" -N -e "SELECT COUNT(*) FROM branches;" 2>/dev/null || echo "0")
echo "Branches: ${BC}"
echo ""
echo "╔══════════════════════════════════════════════════╗"
echo "║ ✅ THE CLUB ERP — DEPLOYED SUCCESSFULLY ║"
echo "║ ║"
echo "║ 🔑 Login: admin / ChangeMe123! ║"
echo "║ ⚠️ You'll be forced to change password ║"
echo "╚══════════════════════════════════════════════════╝"
echo "=========================================="
echo " ✅ Login: admin / ChangeMe123!"
echo " ⚠️ Password change forced on first login"
echo "=========================================="
echo ""
# ── Fix final permissions ──
# ── Fix permissions one last time ──
chown -R www-data:www-data /var/www/html/storage 2>/dev/null || true
chmod -R 775 /var/www/html/storage 2>/dev/null || true
# ── Start Apache in foreground ──
echo "🚀 Starting Apache on port 80..."
# ── Start Apache ──
echo "Starting Apache..."
exec apache2-foreground
\ No newline at end of file
<?php
declare(strict_types=1);
require_once __DIR__ . '/../app/Core/Autoloader.php';
\App\Core\Autoloader::register();
/**
* THE CLUB ERP — Front Controller
* All requests route through here.
*/
// Load .env
$envFile = dirname(__DIR__) . '/.env';
// ── Show ALL errors during startup (before ExceptionHandler takes over) ──
error_reporting(E_ALL);
ini_set('display_errors', '1');
// ── Base path resolution ──
define('BASE_PATH', dirname(__DIR__));
// ── Load .env FIRST (before anything else) ──
$envFile = BASE_PATH . '/.env';
if (file_exists($envFile)) {
$lines = file($envFile, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
if ($lines !== false) {
foreach ($lines as $line) {
$line = trim($line);
if ($line === '' || str_starts_with($line, '#')) {
continue;
}
if (str_contains($line, '=')) {
[$key, $val] = explode('=', $line, 2);
$key = trim($key);
$val = trim($val);
$_ENV[$key] = $val;
$_SERVER[$key] = $val;
foreach ($lines as $line) {
$line = trim($line);
if ($line === '' || str_starts_with($line, '#')) {
continue;
}
if (str_contains($line, '=')) {
[$key, $value] = explode('=', $line, 2);
$key = trim($key);
$value = trim($value);
// Remove surrounding quotes
if ((str_starts_with($value, '"') && str_ends_with($value, '"')) ||
(str_starts_with($value, "'") && str_ends_with($value, "'"))) {
$value = substr($value, 1, -1);
}
$_ENV[$key] = $value;
$_SERVER[$key] = $value;
putenv("{$key}={$value}");
}
}
}
\App\Core\ExceptionHandler::register();
// ── Autoloader ──
require_once BASE_PATH . '/app/Core/Autoloader.php';
App\Core\Autoloader::register();
// ── Helpers (must be loaded before Config since config files call env()) ──
require_once BASE_PATH . '/app/Core/Helpers.php';
$app = \App\Core\App::getInstance();
$app->boot();
// ── Exception handler ──
$exHandler = new App\Core\ExceptionHandler();
$exHandler->register();
$request = \App\Core\Request::capture();
$response = $app->router()->dispatch($request);
$response->send();
\ No newline at end of file
// ── Boot application ──
try {
$app = App\Core\App::getInstance();
$app->boot();
// ── Dispatch request ──
$request = App\Core\Request::capture();
$response = $app->router()->dispatch($request);
$response->send();
} catch (\Throwable $e) {
// If we get here during boot, show the REAL error
$debug = ($_ENV['APP_DEBUG'] ?? 'false') === 'true'
|| (getenv('APP_DEBUG') ?: 'false') === 'true';
if ($debug) {
http_response_code(500);
header('Content-Type: text/html; charset=utf-8');
echo '<html dir="rtl"><head><meta charset="utf-8"><title>Boot Error</title>';
echo '<style>body{font-family:monospace;background:#1a1a2e;color:#e0e0e0;padding:40px;direction:ltr;text-align:left;}';
echo 'h1{color:#ff6b6b;}pre{background:#16213e;padding:20px;border-radius:8px;overflow-x:auto;white-space:pre-wrap;word-wrap:break-word;}';
echo '.info{color:#4ecdc4;margin:10px 0;}</style></head><body>';
echo '<h1>💀 BOOT FAILURE</h1>';
echo '<p class="info">Exception: <strong>' . htmlspecialchars(get_class($e)) . '</strong></p>';
echo '<p class="info">Message: <strong>' . htmlspecialchars($e->getMessage()) . '</strong></p>';
echo '<p class="info">File: ' . htmlspecialchars($e->getFile()) . ':' . $e->getLine() . '</p>';
echo '<h3>Stack Trace:</h3>';
echo '<pre>' . htmlspecialchars($e->getTraceAsString()) . '</pre>';
echo '<h3>Environment Check:</h3>';
echo '<pre>';
echo "DB_HOST: " . (getenv('DB_HOST') ?: ($_ENV['DB_HOST'] ?? 'NOT SET')) . "\n";
echo "DB_PORT: " . (getenv('DB_PORT') ?: ($_ENV['DB_PORT'] ?? 'NOT SET')) . "\n";
echo "DB_NAME: " . (getenv('DB_NAME') ?: ($_ENV['DB_NAME'] ?? 'NOT SET')) . "\n";
echo "DB_USER: " . (getenv('DB_USER') ?: ($_ENV['DB_USER'] ?? 'NOT SET')) . "\n";
echo "DB_PASS: " . (getenv('DB_PASS') ? '***SET***' : 'NOT SET') . "\n";
echo "APP_DEBUG: " . (getenv('APP_DEBUG') ?: ($_ENV['APP_DEBUG'] ?? 'NOT SET')) . "\n";
echo ".env exists: " . (file_exists(BASE_PATH . '/.env') ? 'YES' : 'NO') . "\n";
echo "PHP version: " . PHP_VERSION . "\n";
echo "Extensions: " . implode(', ', get_loaded_extensions()) . "\n";
echo '</pre>';
echo '</body></html>';
} else {
http_response_code(500);
header('Content-Type: text/html; charset=utf-8');
echo '<html dir="rtl"><body style="font-family:Cairo,sans-serif;text-align:center;padding:100px;background:#f9fafb;">';
echo '<h1 style="color:#dc2626;">حدث خطأ في النظام</h1>';
echo '<p style="color:#6b7280;">يرجى التواصل مع مسئول النظام</p>';
echo '</body></html>';
}
exit(1);
}
\ No newline at end of file
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