Files
api/services/nginx/app/index.php
T

216 lines
7.7 KiB
PHP

<?php global /** @var response $response */
$DEBUG, $CONFIG_DB, $ENCRYPTION_KEY, $CORS, $WD, $db, $response, $request, $router, $redis;
ini_set('output_buffering', 'off');
ini_set('zlib.output_compression', false);
/**
* This is the main entry point to the Truck Wash API.
*/
const WD = __DIR__;
/** CORS */
header("Access-Control-Allow-Origin: *");
header("Access-Control-Allow-Headers: Content-Type, Authorization, X-Customer-Number");
header("Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS");
// OPTIONS requests are preflight requests for CORS, we can just return a 200 OK response
if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') {
header('Access-Control-Allow-Origin: *');
header('Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS');
header('Access-Control-Allow-Headers: *');
header('Content-Type: application/json');
http_response_code(200);
exit;
}
require_once 'config.php';
/** Debug */
if ($DEBUG) {
ini_set('display_errors', 1);
ini_set('display_startup_errors', 1);
error_reporting(E_ALL);
} else {
ini_set('display_errors', 0);
ini_set('display_startup_errors', 0);
error_reporting(0);
}
/** Autoload */
require_once __DIR__ . '/vendor/autoload.php';
// Initialize Redis for autoload caching
require_once WD . '/interfaces/redis_i.php';
require_once WD . '/traits/redis_t.php';
require_once WD . '/classes/redis.php';
try {
$redis_instance = (new classes\redis())->connect();
define("redis", $redis_instance);
} catch (Exception $e) {
// Redis might not be available, autoloader will fall back to manual search
}
/**
* Lightweight project autoloader: load classes/interfaces/traits/modules on demand.
* Maps top-level namespaces to folders under the app root (WD).
* - classes\* -> WD/classes/*
* - interfaces\* -> WD/interfaces/*
* - traits\* -> WD/traits/*
* - objects\* -> WD/objects/*
* - statistics\* -> WD/statistics/*
* - modules\...\* -> WD/modules/...
* - <module>\...\* -> WD/modules/<module>/... (e.g., bird\bird_c)
*/
spl_autoload_register(function (string $class): void {
$class = ltrim($class, '\\');
$wdReal = rtrim((string) realpath(WD), DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR;
$modulesRoot = $wdReal . 'modules' . DIRECTORY_SEPARATOR;
$isPathInside = static function (string $path, string $root): bool {
$resolved = realpath($path);
if ($resolved === false) {
return false;
}
$resolved = rtrim($resolved, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR;
return str_starts_with($resolved, $root);
};
// Check Redis cache first
if (defined('redis')) {
try {
$cached = redis->get('autoload:' . $class);
if ($cached && is_file($cached) && $isPathInside($cached, $wdReal)) {
require_once $cached;
return;
}
} catch (\Throwable $e) {
// Fall back to manual search on Redis error
}
}
$parts = explode('\\', $class);
$top = strtolower($parts[0] ?? '');
$relative = implode(DIRECTORY_SEPARATOR, array_slice($parts, 1));
$base = WD . DIRECTORY_SEPARATOR;
$candidates = [];
// 1. Core folders: classes, interfaces, traits, objects, statistics
$core_folders = ['classes', 'interfaces', 'traits', 'objects', 'statistics'];
if (in_array($top, $core_folders)) {
$candidates[] = $base . $top . DIRECTORY_SEPARATOR . $relative;
}
// 2. Modules folder: explicitly starting with 'modules'
elseif ($top === 'modules') {
$candidates[] = $base . 'modules' . DIRECTORY_SEPARATOR . $relative;
}
// 3. Fallback for module-specific classes without 'modules' prefix or nested in modules
else {
// Try directly under modules (e.g. bird\bird_c -> modules/bird/bird_c.php)
$candidates[] = $base . 'modules' . DIRECTORY_SEPARATOR . str_replace('\\', DIRECTORY_SEPARATOR, $class);
// Try inside each module subfolder (e.g. customers\economicCustomers -> modules/economic/customers/economicCustomers.php)
static $module_dirs = null;
if ($module_dirs === null) {
if (defined('redis')) {
try {
$module_dirs = redis->get_array('autoload:module_dirs');
} catch (\Throwable $e) {}
}
if (is_array($module_dirs)) {
$module_dirs = array_values(array_filter($module_dirs, static function ($item) use ($base, $modulesRoot, $isPathInside): bool {
if (!is_string($item) || $item === '' || str_contains($item, DIRECTORY_SEPARATOR) || str_contains($item, '..')) {
return false;
}
$candidate = $base . 'modules' . DIRECTORY_SEPARATOR . $item;
return is_dir($candidate) && $isPathInside($candidate, $modulesRoot);
}));
}
if ($module_dirs === null) {
$module_dirs = array_filter(scandir($base . 'modules'), function($item) use ($base) {
return $item !== '.' && $item !== '..' && is_dir($base . 'modules' . DIRECTORY_SEPARATOR . $item);
});
if (defined('redis')) {
try {
redis->set_array('autoload:module_dirs', $module_dirs);
redis->expire('autoload:module_dirs', 3600); // Cache for 1 hour
} catch (\Throwable $e) {}
}
}
}
foreach ($module_dirs as $mod) {
$candidates[] = $base . 'modules' . DIRECTORY_SEPARATOR . $mod . DIRECTORY_SEPARATOR . str_replace('\\', DIRECTORY_SEPARATOR, $class);
}
}
$suffixes = ['', '_t', '_o', '_s', '_i', '_c', '_m'];
foreach ($candidates as $path) {
if (empty($path)) continue;
foreach ($suffixes as $suffix) {
$file = $path . $suffix . '.php';
if (is_file($file)) {
require_once $file;
if (class_exists($class, false) || interface_exists($class, false) || trait_exists($class, false) || (function_exists('enum_exists') && enum_exists($class, false))) {
if (defined('redis')) {
try {
redis->setEx('autoload:' . $class, $file, 86400); // Cache for 24 hours
} catch (\Throwable $e) {}
}
return;
}
}
}
}
});
use classes\db;
use classes\redis;
use classes\request;
use classes\response;
use classes\router;
// Start the session
$router = new router();
$response = new response();
$request = new request();
$db = new db($CONFIG_DB);
// Ensure Redis is connected
if (!defined('redis')) {
try {
define("redis", (new redis())->connect());
} catch (Exception $e) {
$response->error($e->getMessage(), 500);
}
}
// Connect to the database, and ensure the connection is successful
try {
$db->connect();
} catch (Exception $e) {
$response->error($e->getMessage(), 500);
}
// If the program was called from the command line, run the cli script
if (php_sapi_name() === 'cli' || isset($_GET['internalCronCall'])) {
require_once 'cli.php';
exit;
}
// If the route ends with .php, then require the file_server.php
if (str_contains($_SERVER['REQUEST_URI'], '.pdf')) {
require_once 'file_server.php';
exit;
}
// If the route ends with a MIME type, then require the file_server.php
if ((preg_match('/\.(jpg|jpeg|png)$/', $_SERVER['REQUEST_URI']) || str_contains($_SERVER['REQUEST_URI'], '/files/'))) {
require_once 'file_server.php';
exit;
}
// Autoload all the routes
$router->auto_load_routes(WD . '/routes');