Split API and frontend, migrate SQLite to PostgreSQL
- api.fahrschultermin.de: full API entry point with CORS + session auth - fahrschultermin.de: frontend only, no more /api/v1 routing - Database.php: PostgreSQL support (pgsql driver + lastval()) - All repositories: use Database::lastInsertId() for PG compatibility - config/app.php: PostgreSQL connection settings + CORS config - bootstrap.php: pass full $config to Database::configure() - public/index.php: fetch-patch to redirect /api/v1 to api.fahrschultermin.de - database/schema_pgsql.sql: full PostgreSQL schema (BIGSERIAL) - database/import_pgsql.sql: 518 rows migrated from SQLite
This commit is contained in:
@@ -1,2 +1,142 @@
|
||||
<?php
|
||||
echo 'api.fahrschultermin.de - ' . date('Y-m-d H:i:s');
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* api.fahrschultermin.de — Full API entry point
|
||||
* Handles all /api/v1/* routes + CORS + Session auth
|
||||
*/
|
||||
|
||||
const BASE_PATH = __DIR__ . '/..';
|
||||
|
||||
spl_autoload_register(static function (string $class): void {
|
||||
$prefix = 'App\\';
|
||||
|
||||
if (!str_starts_with($class, $prefix)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$relativeClass = substr($class, strlen($prefix));
|
||||
$path = BASE_PATH . '/app/' . str_replace('\\', '/', $relativeClass) . '.php';
|
||||
|
||||
if (is_file($path)) {
|
||||
require_once $path;
|
||||
}
|
||||
});
|
||||
|
||||
$config = require BASE_PATH . '/config/app.php';
|
||||
|
||||
date_default_timezone_set('UTC');
|
||||
session_name($config['session_name']);
|
||||
|
||||
// ── CORS ──────────────────────────────────────────────────────────────────────
|
||||
$origin = $_SERVER['HTTP_ORIGIN'] ?? '';
|
||||
$allowed = $config['cors_allowed_origins'] ?? [];
|
||||
$isAllowed = in_array($origin, $allowed, true);
|
||||
|
||||
header('Access-Control-Allow-Origin: ' . ($isAllowed ? $origin : ''), true);
|
||||
header('Access-Control-Allow-Credentials: true');
|
||||
header('Access-Control-Allow-Methods: GET, POST, PATCH, DELETE, OPTIONS');
|
||||
header('Access-Control-Allow-Headers: Content-Type, Authorization, X-Requested-With');
|
||||
header('Access-Control-Max-Age: 86400');
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') {
|
||||
http_response_code(204);
|
||||
exit;
|
||||
}
|
||||
|
||||
// ── Session ───────────────────────────────────────────────────────────────────
|
||||
$sessionPath = $config['session_path'] ?? BASE_PATH . '/storage/sessions';
|
||||
if (!is_dir($sessionPath)) {
|
||||
mkdir($sessionPath, 0775, true);
|
||||
}
|
||||
|
||||
session_save_path($sessionPath);
|
||||
|
||||
// SameSite=None only works with Secure (HTTPS). Fall back to Lax for local dev.
|
||||
$isHttps = isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off';
|
||||
$sameSite = $isHttps ? 'None' : 'Lax';
|
||||
|
||||
session_set_cookie_params([
|
||||
'lifetime' => 0,
|
||||
'path' => '/',
|
||||
'domain' => $config['session_domain'] ?? '',
|
||||
'secure' => $isHttps,
|
||||
'httponly' => true,
|
||||
'samesite' => $sameSite,
|
||||
]);
|
||||
|
||||
session_start();
|
||||
|
||||
// ── Database ───────────────────────────────────────────────────────────────────
|
||||
\App\Support\Database::configure($config);
|
||||
|
||||
// ── Router ─────────────────────────────────────────────────────────────────────
|
||||
use App\Http\Controllers\AppointmentsController;
|
||||
use App\Http\Controllers\AuthController;
|
||||
use App\Http\Controllers\BootstrapController;
|
||||
use App\Http\Controllers\InstructorsController;
|
||||
use App\Http\Controllers\ReferenceDataController;
|
||||
use App\Http\Controllers\StudentsController;
|
||||
use App\Http\Controllers\TenantsController;
|
||||
use App\Http\Controllers\UsersController;
|
||||
use App\Support\Request;
|
||||
use App\Support\Router;
|
||||
|
||||
$request = new Request();
|
||||
$router = new Router($request);
|
||||
$path = $request->path();
|
||||
|
||||
// ── API Routes ─────────────────────────────────────────────────────────────────
|
||||
if (str_starts_with($path, '/api/v1')) {
|
||||
$auth = new AuthController();
|
||||
$bootstrapController = new BootstrapController();
|
||||
$students = new StudentsController();
|
||||
$references = new ReferenceDataController();
|
||||
$appointments = new AppointmentsController();
|
||||
$users = new UsersController();
|
||||
$tenants = new TenantsController();
|
||||
$instructors = new InstructorsController();
|
||||
|
||||
$router->post('/api/v1/auth/login', [$auth, 'login']);
|
||||
$router->post('/api/v1/auth/logout', [$auth, 'logout']);
|
||||
$router->get('/api/v1/auth/me', [$auth, 'me']);
|
||||
$router->get('/api/v1/bootstrap', $bootstrapController);
|
||||
$router->get('/api/v1/students', [$students, 'index']);
|
||||
$router->post('/api/v1/students', [$students, 'store']);
|
||||
$router->patch('/api/v1/students/{id}/training', [$students, 'updateTraining']);
|
||||
$router->patch('/api/v1/students/{id}', [$students, 'update']);
|
||||
$router->delete('/api/v1/students/{id}', [$students, 'destroy']);
|
||||
$router->get('/api/v1/lesson-types', [$references, 'lessonTypes']);
|
||||
$router->post('/api/v1/lesson-types', [$references, 'storeLessonType']);
|
||||
$router->post('/api/v1/lesson-types/reorder', [$references, 'reorderLessonTypes']);
|
||||
$router->patch('/api/v1/lesson-types/{id}', [$references, 'updateLessonType']);
|
||||
$router->delete('/api/v1/lesson-types/{id}', [$references, 'destroyLessonType']);
|
||||
$router->post('/api/v1/lesson-types/visibility', [$references, 'updateLessonTypeMatrix']);
|
||||
$router->get('/api/v1/license-class-templates', [$references, 'templates']);
|
||||
$router->post('/api/v1/license-class-templates', [$references, 'storeTemplate']);
|
||||
$router->patch('/api/v1/license-class-templates/{id}', [$references, 'updateTemplate']);
|
||||
$router->get('/api/v1/instructors', [$instructors, 'index']);
|
||||
$router->post('/api/v1/instructors', [$instructors, 'store']);
|
||||
$router->patch('/api/v1/instructors/{id}', [$instructors, 'update']);
|
||||
$router->get('/api/v1/users', [$users, 'index']);
|
||||
$router->post('/api/v1/users', [$users, 'store']);
|
||||
$router->patch('/api/v1/users/{id}', [$users, 'update']);
|
||||
$router->get('/api/v1/tenants', [$tenants, 'index']);
|
||||
$router->post('/api/v1/tenants', [$tenants, 'store']);
|
||||
$router->patch('/api/v1/tenants/{id}', [$tenants, 'update']);
|
||||
$router->patch('/api/v1/tenant/profile', [$tenants, 'updateOwn']);
|
||||
$router->get('/api/v1/appointments', [$appointments, 'index']);
|
||||
$router->post('/api/v1/appointments/validate', [$appointments, 'validate']);
|
||||
$router->post('/api/v1/appointments', [$appointments, 'store']);
|
||||
$router->patch('/api/v1/appointments/{id}', [$appointments, 'update']);
|
||||
$router->delete('/api/v1/appointments/{id}', [$appointments, 'destroy']);
|
||||
|
||||
$router->dispatch();
|
||||
exit;
|
||||
}
|
||||
|
||||
// ── 404 ────────────────────────────────────────────────────────────────────────
|
||||
http_response_code(404);
|
||||
header('Content-Type: application/json');
|
||||
echo json_encode(['message' => 'Not Found']);
|
||||
Reference in New Issue
Block a user