Primo rilascio

This commit is contained in:
2026-03-07 00:15:59 +01:00
commit dd5282dd69
609 changed files with 75246 additions and 0 deletions

229
backend/public/index.php Normal file
View File

@@ -0,0 +1,229 @@
<?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;
}