'PHP Error', 'message' => $errstr, 'file' => $errfile, 'line' => $errline ], 500); }); set_exception_handler(function ($e) { jsonResponse([ 'error_type' => 'PHP Exception', 'message' => $e->getMessage(), 'file' => $e->getFile(), 'line' => $e->getLine() ], 500); }); if ($_SERVER['REQUEST_METHOD'] == 'OPTIONS') { exit(0); } // Parse Request $method = $_SERVER['REQUEST_METHOD']; $request_uri = $_SERVER['REQUEST_URI']; $script_name = $_SERVER['SCRIPT_NAME']; // Basic Routing Logic // We need to extract everything AFTER /api/ // Example: /channels/api/auth/request-otp -> auth/request-otp // Get the path relative to the executed script (index.php) // When using .htaccess rewrite, SCRIPT_NAME remains /path/to/api/index.php // But REQUEST_URI is /path/to/api/auth/... $scriptDir = dirname($script_name); // Ensure strict slash handling if ($scriptDir == '/') $scriptDir = ''; if ($scriptDir == '\\') $scriptDir = ''; // Windows fix if needed // Remove the directory of the script from the URI // e.g. URI: /channels/api/auth/request-otp // scriptDir: /channels/api // result: /auth/request-otp $path = str_replace($scriptDir, '', $request_uri); // Also remove /index.php if it's explicitly there $path = str_replace('/index.php', '', $path); $path = explode('?', $path)[0]; // Remove query string $path = trim($path, '/'); $parts = explode('/', $path); // Support versioned routes: /v1/... $is_versioned = false; if (isset($parts[0]) && $parts[0] === 'v1') { $is_versioned = true; $parts = array_slice($parts, 1); } // DEBUGGING BLOCK REMOVED $resource = isset($parts[0]) ? $parts[0] : ''; $subresource = isset($parts[1]) ? $parts[1] : ''; // Helper for inputs $json_input = json_decode(file_get_contents('php://input'), true) ?? []; $input = array_merge($_POST, $json_input); // 1. Global Authentication Check // This will terminate the script with 401 if a token is provided but the account is inactive. $current_user_id = validateAuth(); // 2. Strict Access Control // Allow ONLY auth and countries as public. // Everything else (ads, categories, channels, user, AI, etc.) now REQUIRES a valid, active user token. $is_public = in_array($resource, ['auth', 'countries', 'phone']); if (!$is_public && !$current_user_id) { jsonResponse([ 'error' => 'Unauthorized: Authentication required', 'message' => 'Please provide a valid Bearer token for an active account' ], 401); } // Dispatcher try { switch ($resource) { case 'auth': require_once 'controllers/auth.php'; handleAuth($method, $subresource, $input); break; case 'categories': require_once 'controllers/categories.php'; handleCategories($method, $subresource, $parts, $input); break; case 'filters': require_once 'controllers/filters.php'; handleFilters($method, $subresource, $parts, $input); break; case 'countries': require_once 'controllers/countries.php'; handleCountries($method, $subresource, $input); break; case 'phone': require_once 'controllers/countries.php'; handleCountries($method, $subresource, $input); break; case 'locations': require_once 'controllers/locations.php'; handleLocations($method, $subresource, $parts, $input); break; case 'ads': require_once 'controllers/ads.php'; handleAds($method, $subresource, $parts, $input); break; case 'channels': require_once 'controllers/channels.php'; handleChannels($method, $subresource, $parts, $input); break; case 'notifications': require_once 'controllers/notifications.php'; if ($is_versioned && function_exists('handleNotificationsV1')) { handleNotificationsV1($method, $parts, $input); } else { handleNotifications($method, $parts, $input); } break; case 'announcements': require_once 'controllers/announcements.php'; if ($is_versioned && function_exists('handleAnnouncementsV1')) { handleAnnouncementsV1($method, $parts, $input); } else { jsonResponse(['error' => 'Not Found', 'path' => $path], 404); } break; case 'device': require_once 'controllers/auth.php'; // Device registration often grouped with auth handleDevice($method, $subresource, $input); break; case 'user': require_once 'controllers/auth.php'; // User preferences grouped with auth handleUser($method, $subresource, $input); break; case 'saved-ads': require_once 'controllers/saved_ads.php'; handleSavedAds($method, $subresource, $parts, $input); break; case 'reports': require_once 'controllers/reports.php'; handleReports($method, $input); break; case 'feedback': require_once 'controllers/reports.php'; // Grouped with reports handleFeedback($method, $input); break; case 'ai': require_once 'controllers/ai.php'; handleAI($method, $subresource, $input); break; default: jsonResponse(['error' => 'Not Found', 'path' => $path], 404); } } catch (Throwable $e) { jsonResponse([ 'error' => $e->getMessage(), 'file' => $e->getFile(), 'line' => $e->getLine() ], 500); }