load(); } catch (\Throwable $th) { error_log("Could not load .env file: " . $th->getMessage()); } // --- Configurazione JWT --- $jwtSecret = $_ENV['JWT_SECRET'] ?? 'default-secret-change-me'; // --- Funzione Guardia JWT --- /** * Verifica il token JWT dall'header Authorization. * Restituisce i dati utente dal payload se valido, altrimenti termina con errore 401. * @return array Dati utente dal payload JWT ('sub', 'data') */ function authenticate(): array { global $jwtSecret; // Rende visibile la chiave segreta definita sopra $authHeader = $_SERVER['HTTP_AUTHORIZATION'] ?? null; if (!$authHeader) { http_response_code(401); echo json_encode(['error' => 'Authorization header missing']); exit; } // Estrai il token "Bearer " if (!preg_match('/Bearer\s(\S+)/', $authHeader, $matches)) { http_response_code(401); echo json_encode(['error' => 'Malformed Authorization header']); exit; } $jwt = $matches[1]; if (!$jwt) { http_response_code(401); echo json_encode(['error' => 'Access token missing']); exit; } try { $decoded = JWT::decode($jwt, new Key($jwtSecret, 'HS256')); // Restituisce l'intero payload decodificato come array associativo return (array)$decoded; } catch (ExpiredException $e) { http_response_code(401); echo json_encode(['error' => 'Token expired']); exit; } catch (SignatureInvalidException $e) { http_response_code(401); echo json_encode(['error' => 'Invalid token signature']); exit; } catch (\Throwable $e) { // Cattura altri errori JWT http_response_code(401); error_log("JWT Decode Error: " . $e->getMessage()); echo json_encode(['error' => 'Invalid token']); exit; } } // 3. Imposta Headers CORS $allowedOrigin = $_ENV['FRONTEND_URL'] ?? 'http://localhost:4200'; header("Access-Control-Allow-Origin: " . $allowedOrigin); header("Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS"); header("Access-Control-Allow-Headers: Content-Type, Authorization, X-Requested-With"); header("Access-Control-Allow-Credentials: true"); if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') { http_response_code(204); exit; } header('Content-Type: application/json'); // 4. Definizione Route $dispatcher = FastRoute\simpleDispatcher(function(FastRoute\RouteCollector $r) { // --- Public Routes --- $r->addRoute('POST', '/api/login', [AuthController::class, 'login']); $r->addRoute('GET', '/api/ping', function () { echo json_encode(['message' => 'pong']); }); // --- Protected Routes (richiedono autenticazione) --- // Aggiungiamo un gruppo per applicare la guardia a più route $r->addGroup('/api', function (FastRoute\RouteCollector $r) { // La guardia verrà chiamata prima dell'handler per queste route // Route /api/me (protetta) $r->addRoute('GET', '/me', [AuthController::class, 'me']); // Structures $r->addRoute('GET', '/structures', [StructureController::class, 'index']); $r->addRoute('POST', '/structures', [StructureController::class, 'store']); $r->addRoute('GET', '/structures/{id:\d+}', [StructureController::class, 'show']); $r->addRoute('PUT', '/structures/{id:\d+}', [StructureController::class, 'update']); $r->addRoute('DELETE', '/structures/{id:\d+}', [StructureController::class, 'delete']); // Teachers $r->addRoute('GET', '/teachers', [TeacherController::class, 'index']); $r->addRoute('POST', '/teachers', [TeacherController::class, 'store']); $r->addRoute('GET', '/teachers/{id:\d+}', [TeacherController::class, 'show']); $r->addRoute('PUT', '/teachers/{id:\d+}', [TeacherController::class, 'update']); $r->addRoute('DELETE', '/teachers/{id:\d+}', [TeacherController::class, 'delete']); // School Years $r->addRoute('GET', '/school-years', [SchoolYearController::class, 'index']); $r->addRoute('POST', '/school-years', [SchoolYearController::class, 'store']); $r->addRoute('GET', '/school-years/{id:\d+}', [SchoolYearController::class, 'show']); $r->addRoute('PUT', '/school-years/{id:\d+}', [SchoolYearController::class, 'update']); $r->addRoute('DELETE', '/school-years/{id:\d+}', [SchoolYearController::class, 'delete']); // Teacher Contracts $r->addRoute('GET', '/teacher-contracts', [TeacherContractController::class, 'index']); $r->addRoute('POST', '/teacher-contracts', [TeacherContractController::class, 'store']); $r->addRoute('GET', '/teacher-contracts/{id:\d+}', [TeacherContractController::class, 'show']); $r->addRoute('PUT', '/teacher-contracts/{id:\d+}', [TeacherContractController::class, 'update']); $r->addRoute('DELETE', '/teacher-contracts/{id:\d+}', [TeacherContractController::class, 'delete']); // Children $r->addRoute('GET', '/children', [ChildController::class, 'index']); $r->addRoute('POST', '/children', [ChildController::class, 'store']); $r->addRoute('GET', '/children/{id:\d+}', [ChildController::class, 'show']); $r->addRoute('PUT', '/children/{id:\d+}', [ChildController::class, 'update']); $r->addRoute('DELETE', '/children/{id:\d+}', [ChildController::class, 'delete']); // Shift Definitions $r->addRoute('GET', '/shift-definitions', [ShiftDefinitionController::class, 'index']); $r->addRoute('POST', '/shift-definitions', [ShiftDefinitionController::class, 'store']); $r->addRoute('GET', '/shift-definitions/{id:\d+}', [ShiftDefinitionController::class, 'show']); $r->addRoute('PUT', '/shift-definitions/{id:\d+}', [ShiftDefinitionController::class, 'update']); $r->addRoute('DELETE', '/shift-definitions/{id:\d+}', [ShiftDefinitionController::class, 'delete']); }); // Fine gruppo /api }); // 5. Gestione della Richiesta $httpMethod = $_SERVER['REQUEST_METHOD']; $uri = $_SERVER['REQUEST_URI']; if (false !== $pos = strpos($uri, '?')) { $uri = substr($uri, 0, $pos); } $uri = rawurldecode($uri); $routeInfo = $dispatcher->dispatch($httpMethod, $uri); $userData = null; // Inizializza a null switch ($routeInfo[0]) { case FastRoute\Dispatcher::NOT_FOUND: http_response_code(404); echo json_encode(['error' => 'API Endpoint Not Found']); break; case FastRoute\Dispatcher::METHOD_NOT_ALLOWED: $allowedMethods = $routeInfo[1]; http_response_code(405); echo json_encode(['error' => 'Method Not Allowed', 'allowed_methods' => $allowedMethods]); break; case FastRoute\Dispatcher::FOUND: $handler = $routeInfo[1]; $vars = $routeInfo[2]; // --- Applica Guardia JWT se la route è protetta (inizia con /api/ e non è /api/login o /api/ping) --- if (strpos($uri, '/api/') === 0 && !in_array($uri, ['/api/login', '/api/ping'])) { try { $userData = authenticate(); // Verifica token e ottieni dati utente // Potresti aggiungere qui controllo del ruolo se necessario per specifiche route // if ($userData['data']->role !== 'admin' && $uri === '/api/users') { ... } } catch (\Exception $e) { // L'eccezione è già gestita dentro authenticate() con exit // Questo blocco catch è per sicurezza, ma non dovrebbe essere raggiunto http_response_code(500); error_log("Unexpected error during authentication check: " . $e->getMessage()); echo json_encode(['error' => 'Authentication check failed']); exit; } } // --- Esecuzione Handler --- try { if (is_callable($handler)) { call_user_func($handler, $vars); // Passa $vars alla closure } elseif (is_array($handler) && count($handler) === 2 && is_string($handler[0]) && is_string($handler[1])) { [$class, $method] = $handler; if (class_exists($class) && method_exists($class, $method)) { $controller = new $class(); // Passa $vars e $userData (se presente) al metodo del controller // Modifica i metodi del controller per accettare $userData opzionale se serve if ($method === 'me' && $userData !== null) { // Caso speciale per AuthController::me call_user_func([$controller, $method], $userData['data']); // Passa solo il payload 'data' } else { call_user_func([$controller, $method], $vars); // Chiamata standard per gli altri } } else { http_response_code(500); error_log("Handler class/method not found: {$class}::{$method}"); echo json_encode(['error' => 'Internal Server Error']); } } else { http_response_code(500); error_log("Invalid handler type defined."); echo json_encode(['error' => 'Internal Server Error']); } } catch (\Throwable $e) { http_response_code(500); error_log("Unhandled error in route handler: " . $e->getMessage() . "\n" . $e->getTraceAsString()); echo json_encode(['error' => 'Internal Server Error']); } break; default: http_response_code(500); echo json_encode(['error' => 'Internal Server Error - Dispatcher Error']); break; }