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,4 +1,9 @@
|
||||
RewriteEngine On
|
||||
|
||||
# Proxy API requests to api.fahrschultermin.de
|
||||
RewriteRule ^api/v1/(.*)$ https://api.fahrschultermin.de/api/v1/$1 [P,L]
|
||||
|
||||
# Serve static assets directly
|
||||
RewriteCond %{REQUEST_FILENAME} !-f
|
||||
RewriteCond %{REQUEST_FILENAME} !-d
|
||||
RewriteRule ^ index.php [QSA,L]
|
||||
RewriteRule ^ index.php [QSA,L]
|
||||
@@ -1,74 +1,12 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
/**
|
||||
* fahrschultermin.de — Frontend entry point
|
||||
* Patches fetch() to proxy /api/v1/* calls to api.fahrschultermin.de
|
||||
* so the JS bundle (with hardcoded /api/v1 paths) just works.
|
||||
*/
|
||||
|
||||
$bootstrap = require __DIR__ . '/../app/bootstrap.php';
|
||||
|
||||
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;
|
||||
|
||||
$router = $bootstrap['router'];
|
||||
$request = new App\Support\Request();
|
||||
$path = $request->path();
|
||||
|
||||
if ($request->method() === 'OPTIONS') {
|
||||
http_response_code(204);
|
||||
exit;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
const API_BASE = 'https://api.fahrschultermin.de';
|
||||
|
||||
$assetManifestPath = __DIR__ . '/assets/.vite/manifest.json';
|
||||
$mainAsset = '/assets/index.js';
|
||||
@@ -95,6 +33,21 @@ if (is_file($assetManifestPath)) {
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
<script>
|
||||
// Patch fetch to redirect /api/v1/* → api.fahrschultermin.de/api/v1/*
|
||||
window.__API_BASE__ = '<?= API_BASE ?>';
|
||||
const _fetch = window.fetch.bind(window);
|
||||
window.fetch = function (resource, options = {}) {
|
||||
let url = typeof resource === 'string' ? resource : resource.url;
|
||||
if (typeof url === 'string' && url.startsWith('/api/v1')) {
|
||||
url = '<?= API_BASE ?>' + url;
|
||||
const req = new Request(url, options);
|
||||
req.credentials = 'include';
|
||||
return _fetch(req);
|
||||
}
|
||||
return _fetch(resource, options);
|
||||
};
|
||||
</script>
|
||||
<script type="module" src="<?= htmlspecialchars($mainAsset, ENT_QUOTES) ?>"></script>
|
||||
</body>
|
||||
</html>
|
||||
</html>
|
||||
Reference in New Issue
Block a user