229 lines
10 KiB
PHP
229 lines
10 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
use App\Database;
|
|
use App\Controllers\AuthController;
|
|
use App\Controllers\ChildController;
|
|
use App\Controllers\SchoolYearController;
|
|
use App\Controllers\ShiftDefinitionController;
|
|
use App\Controllers\StructureController;
|
|
use App\Controllers\TeacherContractController;
|
|
use App\Controllers\TeacherController;
|
|
use Firebase\JWT\JWT; // Importa JWT
|
|
use Firebase\JWT\Key; // Importa Key
|
|
use Firebase\JWT\ExpiredException;
|
|
use Firebase\JWT\SignatureInvalidException;
|
|
|
|
// 1. Carica Autoloader Composer
|
|
require __DIR__ . '/../vendor/autoload.php';
|
|
|
|
// 2. Carica Variabili d'Ambiente
|
|
try {
|
|
$dotenv = Dotenv\Dotenv::createImmutable(__DIR__ . '/../');
|
|
$dotenv->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 <token>"
|
|
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;
|
|
} |