singleton(Logger::class, fn() => new Logger());\n$logger = $container->get(Logger::class);\n\n// --- 2. Robust Error Handling ---\nset_exception_handler(function (Throwable $e) use ($logger) {\n $code = ($e->getCode() >= 400 && $e->getCode() < 600) ? $e->getCode() : 500;\n\n http_response_code($code);\n $logger->error($e->getMessage(), [\n 'type' => get_class($e),\n 'file' => $e->getFile(),\n 'line' => $e->getLine(),\n 'trace' => $e->getTraceAsString()\n ]);\n\n if (!headers_sent()) {\n header('Content-Type: application/json; charset=utf-8');\n }\n\n echo json_encode([\n 'error' => true,\n 'message' => $code === 500 ? 'Internal Server Error' : $e->getMessage(),\n 'ref' => $_SERVER['HTTP_X_REQUEST_ID'] ?? null\n ]);\n exit;\n});\n\n// --- 3. Persistence Layer Configuration ---\n$container->singleton('core_pdo', function () {\n $host = getenv('DB_HOST') ?: 'localhost';\n $pass = trim(file_get_contents('/run/secrets/mysql_root_password'));\n\n return new PDO(\n "mysql:host={$host};dbname=core;charset=utf8mb4",\n 'root',\n $pass,\n [\n PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,\n PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,\n PDO::ATTR_EMULATE_PREPARES => false,\n ]\n );\n});\n\n$container->singleton(TenantResolver::class, function ($c) use ($logger) {\n return new TenantResolver($c->get('core_pdo'), $logger);\n});\n\n// --- 4. Request Lifecycle & Tenant Context ---\ntry {\n $resolver = $container->get(TenantResolver::class);\n $host = $_SERVER['HTTP_HOST'] ?? 'localhost';\n $correlationId = $_SERVER['HTTP_X_REQUEST_ID'] ?? uniqid('req_', true);\n\n // Resolve Tenant Metadata\n $tenant = $resolver->resolve($host);\n\n // Inject Context into Logger\n $logger->setContext($tenant['uuid'] ?? 'system', $correlationId);\n\n // Tenant-Specific DB Connection\n if (isset($tenant['db_name'])) {\n $container->singleton(PDO::class, function () use ($tenant) {\n $host = getenv('DB_HOST') ?: 'localhost';\n $pass = trim(file_get_contents('/run/secrets/mysql_root_password'));\n\n return new PDO(\n "mysql:host={$host};dbname={$tenant['db_name']};charset=utf8mb4",\n 'root',\n $pass,\n [\n PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,\n PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,\n ]\n );\n });\n }\n\n // --- 5. Routing Engine ---\n $router = new Router();\n\n /**\n * Yönetim paneli giriş ekranı (GET /login)\n * Sadece HTML form döner, kimlik doğrulama AuthController::login tarafından yapılır.\n */\n $router->get('/login', function () use ($tenant) {\n $tenantName = htmlspecialchars($tenant['name'] ?? 'Lemerco Sales', ENT_QUOTES, 'UTF-8');\n\n header('Content-Type: text/html; charset=utf-8');\n header('X-Frame-Options: SAMEORIGIN');\n header('X-Content-Type-Options: nosniff');\n header('Referrer-Policy: no-referrer');\n\n return <<\n\n\n \n {$tenantName} | Yönetim Girişi\n \n \n\n\n\n
\n
\n

{$tenantName}

\n

Yönetim paneline erişmek için hesabınızla giriş yapın.

\n
\n
\n \n \n
\n
\n \n \n
\n \n
\n \n
\n
\n\n\nHTML;\n });\n\n /**\n * Login işlemini mevcut AuthController::login metoduna yönlendirir.\n * AuthController içinde zaten IP, rate-limit, oturum vs. nasıl tasarlandıysa öyle çalışır.\n */\n $router->post('/auth/login', [AuthController::class, 'login']);\n\n // Health Check (DevOps/Monitoring)\n $router->get('/health', function () {\n header('Content-Type: application/json');\n return json_encode(['status' => 'healthy', 'timestamp' => time()]);\n });\n\n // API Identity Status\n $router->get('/api/status', function () use ($tenant) {\n header('Content-Type: application/json');\n return json_encode([\n 'status' => 'active',\n 'identity' => [\n 'name' => $tenant['name'] ?? 'System',\n 'subdomain' => $tenant['subdomain'] ?? 'core',\n 'db_scope' => $tenant['db_name'] ?? 'shared'\n ]\n ]);\n });\n\n // Elegant Landing Page (Root /)\n $router->get('/', function () use ($tenant) {\n $data = [\n 'name' => htmlspecialchars($tenant['name'] ?? 'Lemerco Sales', ENT_QUOTES),\n 'subdomain' => htmlspecialchars($tenant['subdomain'] ?? 'portal', ENT_QUOTES),\n 'db' => htmlspecialchars($tenant['db_name'] ?? 'tenant_default', ENT_QUOTES),\n 'time' => date('Y-m-d H:i:s')\n ];\n\n // Security Headers\n header('Content-Type: text/html; charset=utf-8');\n header('X-Frame-Options: DENY');\n header('X-Content-Type-Options: nosniff');\n header("Content-Security-Policy: default-src 'self'; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; font-src https://fonts.gstatic.com;");\n\n return <<\n\n\n \n \n {$data['name']} | Business Suite\n \n\n\n\n
\n
\n
\n
\n
\n Sistem Çevrimiçi\n
\n \n

{$data['name']}

\n

\n Kurumsal kaynak planlama ve satış otomasyonu modülü aktif. \n Tüm verileriniz {$data['db']} üzerinden güvenle senkronize ediliyor.\n

\n\n
\n
\n Erişim Alanı\n {$data['subdomain']}.lemerco.com\n
\n
\n Sunucu Durumu\n Stabil\n
\n
\n\n
\n GET /api/status
\n Host: {$data['subdomain']}.lemerco.com\n
\n\n \n
\n
\n
\n\n\nHTML;\n });\n\n // Execute Request\n\n\n\n /**\n * Admin key guard\n */\n $adminGuard = function (): string {\n $keyFile = __DIR__ . '/../secrets/admin_panel_key.txt';\n $expected = is_file($keyFile) ? trim(file_get_contents($keyFile)) : '';\n $given = $_GET['key'] ?? $_POST['key'] ?? '';\n\n if ($expected === '' || !hash_equals($expected, $given)) {\n http_response_code(403);\n return '';\n }\n\n return $given;\n };\n\n /**\n * POST /admin/action - Controlled admin writes\n */\n $router->post('/admin/action', function () use ($container, $adminGuard) {\n $key = $adminGuard();\n if ($key === '') return 'Forbidden';\n\n if (session_status() !== PHP_SESSION_ACTIVE) {\n session_start();\n }\n $postedCsrf = $_POST['csrf_token'] ?? '';\n $sessionCsrf = $_SESSION['csrf_token'] ?? '';\n if ($sessionCsrf === '' || !hash_equals($sessionCsrf, $postedCsrf)) {\n http_response_code(403);\n return 'CSRF Forbidden';\n }\n\n $pdo = $container->get(PDO::class);\n $action = $_POST['action'] ?? '';\n $msg = 'ok';\n\n try {\n if ($action === 'create_user') {\n $stmt = $pdo->prepare("INSERT INTO users (email,password_hash,full_name,is_active) VALUES (:email,:hash,:name,1)");\n $stmt->execute([\n 'email' => trim($_POST['email'] ?? ''),\n 'hash' => password_hash((string)($_POST['password'] ?? ''), PASSWORD_ARGON2ID),\n 'name' => trim($_POST['full_name'] ?? '')\n ]);\n $msg = 'user_created';\n }\n\n if ($action === 'toggle_user') {\n $stmt = $pdo->prepare("UPDATE users SET is_active = IF(is_active=1,0,1) WHERE id=:id");\n $stmt->execute(['id' => (int)($_POST['id'] ?? 0)]);\n $msg = 'user_toggled';\n }\n\n if ($action === 'create_exam') {\n $stmt = $pdo->prepare("INSERT INTO exams (title,planned_date,status,created_by) VALUES (:t,:d,'draft',0)");\n $stmt->execute([\n 't' => trim($_POST['title'] ?? ''),\n 'd' => trim($_POST['planned_date'] ?? date('Y-m-d'))\n ]);\n $msg = 'exam_created';\n }\n\n if ($action === 'create_question') {\n $content = [\n 'question' => trim($_POST['question_text'] ?? ''),\n 'answer' => trim($_POST['answer'] ?? '')\n ];\n $stmt = $pdo->prepare("INSERT INTO questions (content_json,branch,topic,difficulty,status,source_type,created_by) VALUES (:json,:b,:t,:d,'draft','manual',0)");\n $stmt->execute([\n 'json' => json_encode($content, JSON_UNESCAPED_UNICODE),\n 'b' => trim($_POST['branch'] ?? ''),\n 't' => trim($_POST['topic'] ?? ''),\n 'd' => in_array(($_POST['difficulty'] ?? 'medium'), ['easy','medium','hard'], true) ? $_POST['difficulty'] : 'medium'\n ]);\n $msg = 'question_created';\n }\n\n if ($action === 'create_booklet') {\n $stmt = $pdo->prepare("INSERT INTO booklet_drafts (user_id,title,description,status) VALUES (0,:t,:d,'draft')");\n $stmt->execute([\n 't' => trim($_POST['title'] ?? ''),\n 'd' => trim($_POST['description'] ?? '')\n ]);\n $msg = 'booklet_created';\n }\n\n \n if ($action === 'toggle_user') {\n $stmt = $pdo->prepare("UPDATE users SET is_active = IF(is_active=1,0,1) WHERE id=:id");\n $stmt->execute(['id' => (int)($_POST['id'] ?? 0)]);\n $msg = 'user_status_updated';\n }\n\n if ($action === 'delete_user') {\n $stmt = $pdo->prepare("DELETE FROM users WHERE id=:id");\n $stmt->execute(['id' => (int)($_POST['id'] ?? 0)]);\n $msg = 'user_deleted';\n }\n\n if ($action === 'question_status') {\n $allowed = ['draft','pending_review','approved','rejected'];\n $status = in_array(($_POST['status'] ?? 'draft'), $allowed, true) ? $_POST['status'] : 'draft';\n $stmt = $pdo->prepare("UPDATE questions SET status=:s WHERE id=:id");\n $stmt->execute(['s' => $status, 'id' => (int)($_POST['id'] ?? 0)]);\n $msg = 'question_status_updated';\n }\n\n if ($action === 'delete_question') {\n $stmt = $pdo->prepare("DELETE FROM questions WHERE id=:id");\n $stmt->execute(['id' => (int)($_POST['id'] ?? 0)]);\n $msg = 'question_deleted';\n }\n\n if ($action === 'booklet_status') {\n $allowed = ['draft','pending_review','approved','rejected'];\n $status = in_array(($_POST['status'] ?? 'draft'), $allowed, true) ? $_POST['status'] : 'draft';\n $stmt = $pdo->prepare("UPDATE booklet_drafts SET status=:s WHERE id=:id");\n $stmt->execute(['s' => $status, 'id' => (int)($_POST['id'] ?? 0)]);\n $msg = 'booklet_status_updated';\n }\n\n if ($action === 'delete_booklet') {\n $stmt = $pdo->prepare("DELETE FROM booklet_drafts WHERE id=:id");\n $stmt->execute(['id' => (int)($_POST['id'] ?? 0)]);\n $msg = 'booklet_deleted';\n }\n\n if ($action === 'exam_status') {\n $allowed = ['draft','scheduled','active','completed','cancelled'];\n $status = in_array(($_POST['status'] ?? 'draft'), $allowed, true) ? $_POST['status'] : 'draft';\n $stmt = $pdo->prepare("UPDATE exams SET status=:s WHERE id=:id");\n $stmt->execute(['s' => $status, 'id' => (int)($_POST['id'] ?? 0)]);\n $msg = 'exam_status_updated';\n }\n\n if ($action === 'delete_exam') {\n $stmt = $pdo->prepare("DELETE FROM exams WHERE id=:id");\n $stmt->execute(['id' => (int)($_POST['id'] ?? 0)]);\n $msg = 'exam_deleted';\n }\n\n \n if ($action === 'edit_user') {\n $stmt = $pdo->prepare("UPDATE users SET email=:email, full_name=:name WHERE id=:id");\n $stmt->execute([\n 'email' => trim($_POST['email'] ?? ''),\n 'name' => trim($_POST['full_name'] ?? ''),\n 'id' => (int)($_POST['id'] ?? 0)\n ]);\n $msg = 'user_updated';\n }\n\n if ($action === 'edit_question') {\n $allowed = ['easy','medium','hard'];\n $difficulty = in_array(($_POST['difficulty'] ?? 'medium'), $allowed, true) ? $_POST['difficulty'] : 'medium';\n $content = [\n 'question' => trim($_POST['question_text'] ?? ''),\n 'answer' => trim($_POST['answer'] ?? '')\n ];\n $stmt = $pdo->prepare("UPDATE questions SET content_json=:json, branch=:branch, topic=:topic, difficulty=:difficulty WHERE id=:id");\n $stmt->execute([\n 'json' => json_encode($content, JSON_UNESCAPED_UNICODE),\n 'branch' => trim($_POST['branch'] ?? ''),\n 'topic' => trim($_POST['topic'] ?? ''),\n 'difficulty' => $difficulty,\n 'id' => (int)($_POST['id'] ?? 0)\n ]);\n $msg = 'question_updated';\n }\n\n if ($action === 'edit_booklet') {\n $stmt = $pdo->prepare("UPDATE booklet_drafts SET title=:title, description=:description WHERE id=:id");\n $stmt->execute([\n 'title' => trim($_POST['title'] ?? ''),\n 'description' => trim($_POST['description'] ?? ''),\n 'id' => (int)($_POST['id'] ?? 0)\n ]);\n $msg = 'booklet_updated';\n }\n\n if ($action === 'edit_exam') {\n $stmt = $pdo->prepare("UPDATE exams SET title=:title, planned_date=:planned_date WHERE id=:id");\n $stmt->execute([\n 'title' => trim($_POST['title'] ?? ''),\n 'planned_date' => trim($_POST['planned_date'] ?? date('Y-m-d')),\n 'id' => (int)($_POST['id'] ?? 0)\n ]);\n $msg = 'exam_updated';\n }\n\n if ($action === 'create_notification') {\n $stmt = $pdo->prepare("INSERT INTO notifications (user_id,title,body,type,reference_id) VALUES (:u,:t,:b,:type,:r)");\n $stmt->execute([\n 'u' => (int)($_POST['user_id'] ?? 0),\n 't' => trim($_POST['title'] ?? ''),\n 'b' => trim($_POST['body'] ?? ''),\n 'type' => trim($_POST['type'] ?? 'admin'),\n 'r' => ($_POST['reference_id'] ?? '') === '' ? null : (int)$_POST['reference_id']\n ]);\n $msg = 'notification_created';\n }\n } catch (Throwable $e) {\n $msg = 'error_' . substr(preg_replace('/[^a-zA-Z0-9_]+/', '_', $e->getMessage()), 0, 80);\n }\n\n header('Location: /admin?key=' . urlencode($key) . '&msg=' . urlencode($msg));\n return '';\n });\n\n /**\n * GET /admin - Advanced management dashboard\n */\n $router->get('/admin', function () use ($container, $tenant, $adminGuard) {\n $key = $adminGuard();\n if ($key === '') return 'Forbidden';\n\n if (session_status() !== PHP_SESSION_ACTIVE) {\n session_start();\n }\n if (empty($_SESSION['csrf_token'])) {\n $_SESSION['csrf_token'] = bin2hex(random_bytes(32));\n }\n $csrf = $_SESSION['csrf_token'];\n\n $core = $container->get('core_pdo');\n $tenantPdo = $container->get(PDO::class);\n $h = fn($v) => htmlspecialchars((string)($v ?? ''), ENT_QUOTES, 'UTF-8');\n\n $count = function (PDO $db, string $table): int {\n try { return (int)$db->query("SELECT COUNT(*) FROM {$table}")->fetchColumn(); }\n catch (Throwable $e) { return 0; }\n };\n\n $rows = function (PDO $db, string $sql): array {\n try { return $db->query($sql)->fetchAll(PDO::FETCH_ASSOC); }\n catch (Throwable $e) { return [['error' => $e->getMessage()]]; }\n };\n\n $table = function (array $rows) use ($h): string {\n if (!$rows) return '

Kayıt yok.

';\n $cols = array_keys($rows[0]);\n $out = '
';\n foreach ($cols as $c) $out .= '';\n $out .= '';\n foreach ($rows as $r) {\n $out .= '';\n foreach ($cols as $c) {\n $v = $r[$c] ?? '';\n if ($c !== 'actions' && is_string($v) && strlen($v) > 160) $v = substr($v, 0, 160).'...';\n if ($c === 'actions') { $out .= ''; } elseif ($c === 'status' || $c === 'is_active') { $sv=$h((string)$v); $cls='status-'.preg_replace('/[^a-z0-9_\-]/','_',strtolower((string)$v)); $out .= ''; } else { $out .= ''; }\n }\n $out .= '';\n }\n return $out.'
'.$h($c).'
'.$v.''.$sv.''.$h($v).'
';\n };\n\n $cards = [\n 'Tenants' => $count($core, 'tenants'),\n 'Core Admins' => $count($core, 'super_admin_users'),\n 'Users' => $count($tenantPdo, 'users'),\n 'Questions' => $count($tenantPdo, 'questions'),\n 'Booklets' => $count($tenantPdo, 'booklet_drafts'),\n 'Exams' => $count($tenantPdo, 'exams'),\n 'AI Jobs' => $count($tenantPdo, 'ai_jobs'),\n 'Imports' => $count($tenantPdo, 'imports'),\n 'PDF Files' => $count($tenantPdo, 'pdf_files'),\n 'Notifications' => $count($tenantPdo, 'notifications'),\n ];\n\n $cardHtml = '';\n foreach ($cards as $label => $value) {\n $cardHtml .= '
'.$h($label).''.$h($value).'
';\n }\n\n $msg = $h($_GET['msg'] ?? '');\n $tenantName = $h($tenant['name'] ?? 'Tenant');\n $dbName = $h($tenant['db_name'] ?? '-');\n\n $k = $h($key);\n $action = '/admin/action?key='.$k;\n\n $sections = '';\n $sections .= '

Yeni Tenant Kullanıcısı

';\n $sections .= '

Yeni Soru

';\n $sections .= '

Yeni Sınav

';\n $sections .= '

Yeni Booklet Draft

';\n $sections .= '

Bildirim Oluştur

';\n\n $sections .= '

Tenants

'.$table($rows($core, "SELECT id,name,subdomain,db_name,status,created_at FROM tenants ORDER BY id DESC LIMIT 50")).'
';\n $sections .= '

Core Admins

'.$table($rows($core, "SELECT id,email,full_name,created_at FROM super_admin_users ORDER BY id DESC LIMIT 50")).'
';\n $sections .= '

Users

'.$table($rows($tenantPdo, "SELECT id,email,full_name,is_active,created_at,\nCONCAT('
') AS actions\nFROM users ORDER BY id DESC LIMIT 50")).'
';\n $sections .= '

Questions

'.$table($rows($tenantPdo, "SELECT id,branch,topic,difficulty,status,source_type,created_by,created_at,\nCONCAT('
') AS actions\nFROM questions ORDER BY id DESC LIMIT 50")).'
';\n $sections .= '

Booklets

'.$table($rows($tenantPdo, "SELECT id,user_id,title,status,rejection_reason,created_at,\nCONCAT('
') AS actions\nFROM booklet_drafts ORDER BY id DESC LIMIT 50")).'
';\n $sections .= '

Exams

'.$table($rows($tenantPdo, "SELECT id,title,planned_date,status,created_by,created_at,\nCONCAT('
') AS actions\nFROM exams ORDER BY id DESC LIMIT 50")).'
';\n $sections .= '

AI Jobs

'.$table($rows($tenantPdo, "SELECT id,user_id,type,input_ref,status,tokens_used,created_at FROM ai_jobs ORDER BY id DESC LIMIT 50")).'
';\n $sections .= '

Imports

'.$table($rows($tenantPdo, "SELECT id,user_id,file_name,status,total_rows,processed_rows,success_count,error_count,created_at FROM imports ORDER BY id DESC LIMIT 50")).'
';\n $sections .= '

PDF Files

'.$table($rows($tenantPdo, "SELECT id,booklet_version_id,user_id,file_path,file_size_bytes,page_count_estimated,created_at FROM pdf_files ORDER BY id DESC LIMIT 50")).'
';\n $sections .= '

Notifications

'.$table($rows($tenantPdo, "SELECT id,user_id,title,type,reference_id,read_at,created_at FROM notifications ORDER BY id DESC LIMIT 50")).'
';\n\n header('Content-Type: text/html; charset=utf-8');\n\n return <<\n\n\n\nLemerco Management Admin\n\n\n\n\n\n
\n

Lemerco Management Admin

Tenant: {$tenantName} / DB: {$dbName}
Live Admin
{$msg}
\n
\n

Dashboard

\n
{$cardHtml}
\n\n{$sections}\n\n
\n
\n\n\nHTML;\n });\n\n\n $response = $router->dispatch($_SERVER['REQUEST_METHOD'], $_SERVER['REQUEST_URI'], $container);\n\n if ($response !== null) {\n echo $response;\n }\n\n} catch (Throwable $e) {\n // Top-level catch to ensure even resolution errors are caught by our handler\n throw $e;\n}\n