Slim API deployment - composer install + full CRUD endpoints

This commit is contained in:
Hermes Agent
2026-05-20 14:36:05 +02:00
parent 48bf9c7088
commit 8ab4e532fd
1727 changed files with 7746 additions and 7 deletions

View File

@@ -0,0 +1,27 @@
<?php
declare(strict_types=1);
$bootstrap = require __DIR__ . '/../app/bootstrap.php';
use App\Support\Database;
$db = Database::connection();
$migrations = glob(__DIR__ . '/../database/migrations/*.sql');
sort($migrations);
foreach ($migrations as $migration) {
$statements = array_filter(array_map('trim', explode(';', file_get_contents($migration))));
foreach ($statements as $statement) {
if (preg_match('/^ALTER\s+TABLE\s+([A-Za-z0-9_]+)\s+ADD\s+COLUMN\s+([A-Za-z0-9_]+)/i', $statement, $matches) === 1) {
$columns = $db->query('PRAGMA table_info(' . $matches[1] . ')')->fetchAll();
$columnNames = array_map(static fn (array $column): string => strtolower((string) $column['name']), $columns);
if (in_array(strtolower($matches[2]), $columnNames, true)) {
continue;
}
}
$db->exec($statement);
}
fwrite(STDOUT, "Applied: " . basename($migration) . PHP_EOL);
}

View File

@@ -0,0 +1,208 @@
<?php
declare(strict_types=1);
$bootstrap = require __DIR__ . '/../app/bootstrap.php';
use App\Repositories\InstructorRepository;
use App\Repositories\ReferenceDataRepository;
use App\Repositories\StudentRepository;
use App\Repositories\TenantRepository;
use App\Repositories\UserRepository;
use App\Support\Database;
$db = Database::connection();
$tenantRepository = new TenantRepository();
$userRepository = new UserRepository();
$referenceRepository = new ReferenceDataRepository();
$instructorRepository = new InstructorRepository();
$studentRepository = new StudentRepository();
$existing = $db->query('SELECT COUNT(*) AS count FROM users')->fetch();
if ((int) $existing['count'] > 0) {
fwrite(STDOUT, "Seed skipped: data already exists." . PHP_EOL);
exit(0);
}
$tenant = $tenantRepository->create([
'name' => 'Fahrschule Nordring',
'timezone' => 'Europe/Berlin',
'is_active' => true,
]);
$db->prepare('INSERT INTO settings (tenant_id, key_name, value) VALUES (:tenant_id, :key_name, :value)')
->execute([
'tenant_id' => $tenant['id'],
'key_name' => 'app.theme',
'value' => 'signal',
]);
$superadmin = $userRepository->create([
'tenant_id' => null,
'role' => 'superadmin',
'email' => 'superadmin@example.com',
'password' => 'secret123',
'first_name' => 'System',
'last_name' => 'Admin',
'is_active' => true,
]);
$tenantAdmin = $userRepository->create([
'tenant_id' => $tenant['id'],
'role' => 'tenant_admin',
'email' => 'admin@nordring.test',
'password' => 'secret123',
'first_name' => 'Jana',
'last_name' => 'Koester',
'is_active' => true,
]);
$dispatcher = $userRepository->create([
'tenant_id' => $tenant['id'],
'role' => 'dispatcher',
'email' => 'planung@nordring.test',
'password' => 'secret123',
'first_name' => 'Mara',
'last_name' => 'Dispo',
'is_active' => true,
]);
$instructorUser = $userRepository->create([
'tenant_id' => $tenant['id'],
'role' => 'instructor',
'email' => 'fahrlehrer@nordring.test',
'password' => 'secret123',
'first_name' => 'Tobias',
'last_name' => 'Fahr',
'is_active' => true,
]);
$instructors = [
['user_id' => $instructorUser['id'], 'first_name' => 'Tobias', 'last_name' => 'Fahr', 'color' => '#0E5D80'],
['user_id' => null, 'first_name' => 'Leonie', 'last_name' => 'Kraft', 'color' => '#A74826'],
];
foreach ($instructors as $instructor) {
$instructorRepository->create($tenant['id'], $instructor + ['notes' => '', 'is_active' => true]);
}
$lessonTypes = [
['name' => 'Einweisung', 'color' => '#0E5D80', 'default_duration' => 45, 'category' => 'student', 'requirement_key' => 'intro', 'is_billable' => 1, 'is_counted' => 1, 'is_rest_relevant' => 1, 'fixed_duration' => 0, 'sort_order' => 1],
['name' => 'Uebungsstunde', 'color' => '#2C7A4B', 'default_duration' => 45, 'category' => 'student', 'requirement_key' => 'practice', 'is_billable' => 1, 'is_counted' => 1, 'is_rest_relevant' => 1, 'fixed_duration' => 0, 'sort_order' => 2],
['name' => 'Bundes- / Landstrasse', 'color' => '#D99A1A', 'default_duration' => 45, 'category' => 'student', 'requirement_key' => 'rural', 'is_billable' => 1, 'is_counted' => 1, 'is_rest_relevant' => 1, 'fixed_duration' => 0, 'sort_order' => 3],
['name' => 'Autobahn', 'color' => '#C86C1E', 'default_duration' => 45, 'category' => 'student', 'requirement_key' => 'highway', 'is_billable' => 1, 'is_counted' => 1, 'is_rest_relevant' => 1, 'fixed_duration' => 0, 'sort_order' => 4],
['name' => 'Daemmerung / Dunkelheit', 'color' => '#4B356B', 'default_duration' => 45, 'category' => 'student', 'requirement_key' => 'night', 'is_billable' => 1, 'is_counted' => 1, 'is_rest_relevant' => 1, 'fixed_duration' => 0, 'sort_order' => 5],
['name' => 'Pruefungsfahrt', 'color' => '#A83333', 'default_duration' => 70, 'category' => 'student', 'requirement_key' => null, 'is_billable' => 1, 'is_counted' => 0, 'is_rest_relevant' => 1, 'fixed_duration' => 1, 'sort_order' => 6],
['name' => 'Theorieunterricht', 'color' => '#2B8793', 'default_duration' => 90, 'category' => 'other', 'requirement_key' => null, 'is_billable' => 0, 'is_counted' => 0, 'is_rest_relevant' => 0, 'fixed_duration' => 1, 'sort_order' => 7],
['name' => 'Sonstige Arbeiten', 'color' => '#70747C', 'default_duration' => 60, 'category' => 'other', 'requirement_key' => null, 'is_billable' => 0, 'is_counted' => 0, 'is_rest_relevant' => 0, 'fixed_duration' => 1, 'sort_order' => 8],
['name' => 'Privater Termin', 'color' => '#44444A', 'default_duration' => 60, 'category' => 'private', 'requirement_key' => null, 'is_billable' => 0, 'is_counted' => 0, 'is_rest_relevant' => 0, 'fixed_duration' => 1, 'sort_order' => 9],
['name' => 'Sperrzeit / Blockierung', 'color' => '#111111', 'default_duration' => 60, 'category' => 'blocked', 'requirement_key' => null, 'is_billable' => 0, 'is_counted' => 0, 'is_rest_relevant' => 0, 'fixed_duration' => 1, 'sort_order' => 10],
];
foreach ($lessonTypes as $lessonType) {
$referenceRepository->createLessonType($tenant['id'], $lessonType);
}
$templates = [
[
'code' => 'C',
'name' => 'Klasse C',
'is_combination' => false,
'requirements' => [
['requirement_key' => 'practice', 'label' => 'Uebungsstunden', 'required_units' => 8],
['requirement_key' => 'rural', 'label' => 'Ueberland', 'required_units' => 5],
['requirement_key' => 'highway', 'label' => 'Autobahn', 'required_units' => 4],
['requirement_key' => 'night', 'label' => 'Nachtfahrt', 'required_units' => 3],
],
],
[
'code' => 'CE',
'name' => 'Klasse CE',
'is_combination' => false,
'requirements' => [
['requirement_key' => 'practice', 'label' => 'Uebungsstunden', 'required_units' => 10],
['requirement_key' => 'rural', 'label' => 'Ueberland', 'required_units' => 5],
['requirement_key' => 'highway', 'label' => 'Autobahn', 'required_units' => 5],
['requirement_key' => 'night', 'label' => 'Nachtfahrt', 'required_units' => 3],
],
],
[
'code' => 'C+CE',
'name' => 'Klasse C + CE',
'is_combination' => true,
'requirements' => [
['requirement_key' => 'C::practice', 'phase_label' => 'C', 'label' => 'Uebungsstunden', 'required_units' => 8],
['requirement_key' => 'C::rural', 'phase_label' => 'C', 'label' => 'Ueberland', 'required_units' => 5],
['requirement_key' => 'C::highway', 'phase_label' => 'C', 'label' => 'Autobahn', 'required_units' => 4],
['requirement_key' => 'C::night', 'phase_label' => 'C', 'label' => 'Nachtfahrt', 'required_units' => 3],
['requirement_key' => 'CE::practice', 'phase_label' => 'CE', 'label' => 'Uebungsstunden', 'required_units' => 6],
['requirement_key' => 'CE::rural', 'phase_label' => 'CE', 'label' => 'Ueberland', 'required_units' => 3],
['requirement_key' => 'CE::highway', 'phase_label' => 'CE', 'label' => 'Autobahn', 'required_units' => 3],
['requirement_key' => 'CE::night', 'phase_label' => 'CE', 'label' => 'Nachtfahrt', 'required_units' => 2],
],
],
[
'code' => 'C1',
'name' => 'Klasse C1',
'is_combination' => false,
'requirements' => [
['requirement_key' => 'practice', 'label' => 'Uebungsstunden', 'required_units' => 6],
['requirement_key' => 'rural', 'label' => 'Ueberland', 'required_units' => 4],
['requirement_key' => 'highway', 'label' => 'Autobahn', 'required_units' => 3],
['requirement_key' => 'night', 'label' => 'Nachtfahrt', 'required_units' => 2],
],
],
[
'code' => 'C1E',
'name' => 'Klasse C1E',
'is_combination' => false,
'requirements' => [
['requirement_key' => 'practice', 'label' => 'Uebungsstunden', 'required_units' => 7],
['requirement_key' => 'rural', 'label' => 'Ueberland', 'required_units' => 4],
['requirement_key' => 'highway', 'label' => 'Autobahn', 'required_units' => 3],
['requirement_key' => 'night', 'label' => 'Nachtfahrt', 'required_units' => 2],
],
],
[
'code' => 'C1+C1E',
'name' => 'Klasse C1 + C1E',
'is_combination' => true,
'requirements' => [
['requirement_key' => 'C1::practice', 'phase_label' => 'C1', 'label' => 'Uebungsstunden', 'required_units' => 6],
['requirement_key' => 'C1::rural', 'phase_label' => 'C1', 'label' => 'Ueberland', 'required_units' => 4],
['requirement_key' => 'C1::highway', 'phase_label' => 'C1', 'label' => 'Autobahn', 'required_units' => 3],
['requirement_key' => 'C1::night', 'phase_label' => 'C1', 'label' => 'Nachtfahrt', 'required_units' => 2],
['requirement_key' => 'C1E::practice', 'phase_label' => 'C1E', 'label' => 'Uebungsstunden', 'required_units' => 4],
['requirement_key' => 'C1E::rural', 'phase_label' => 'C1E', 'label' => 'Ueberland', 'required_units' => 2],
['requirement_key' => 'C1E::highway', 'phase_label' => 'C1E', 'label' => 'Autobahn', 'required_units' => 2],
['requirement_key' => 'C1E::night', 'phase_label' => 'C1E', 'label' => 'Nachtfahrt', 'required_units' => 1],
],
],
];
$templateIds = [];
foreach ($templates as $template) {
$record = $referenceRepository->createTemplate($tenant['id'], $template);
$templateIds[$record['code']] = $record['id'];
}
$studentRepository->create($tenant['id'], [
'template_id' => $templateIds['C+CE'],
'first_name' => 'Mila',
'last_name' => 'Hansen',
'phone' => '0170 1234567',
'notes' => 'Startet naechste Woche mit Nachtfahrten.',
'status' => 'active',
]);
$studentRepository->create($tenant['id'], [
'template_id' => $templateIds['C1'],
'first_name' => 'Jonas',
'last_name' => 'Winter',
'phone' => '0172 999000',
'notes' => 'Braucht fruehe Termine.',
'status' => 'active',
]);
fwrite(STDOUT, "Seed complete." . PHP_EOL);