From 3570f442f5611a6fbbd5c792669f4aeda4bca9dc Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Thomas=20G=C3=BCnther?=
Date: Sat, 31 Jan 2026 20:07:41 +0100
Subject: [PATCH] Basic tenant structure
---
.gitignore | 1 +
Makefile | 5 +
app/Enumerations/CostUnitType.php | 21 ++++
app/Enumerations/EatingHabit.php | 22 ++++
app/Enumerations/FirstAidPermission.php | 22 ++++
app/Enumerations/SwimmingPermission.php | 26 +++++
app/Enumerations/UserRole.php | 17 +++
app/Http/Controllers/Controller.php | 8 --
app/Installer/DevelopmentDataSeeder.php | 43 +++++++
app/Installer/ProductionDataSeeder.php | 71 ++++++++++++
.../Middleware/HandleInertiaRequests.php | 2 +-
app/Middleware/IdentifyTenant.php | 24 ++++
app/Models/Tenant.php | 39 +++++++
app/Models/User.php | 28 ++++-
app/Providers/AppServiceProvider.php | 8 +-
app/Providers/InertiaProvider.php | 26 +++++
app/Providers/TenantUserProvider.php | 23 ++++
app/Scopes/CommonModel.php | 10 ++
app/Scopes/InstancedModel.php | 13 +++
app/Scopes/SiteScope.php | 14 +++
bootstrap/app.php | 4 +-
bootstrap/providers.php | 1 +
composer.json | 2 +-
composer.lock | 20 ++--
config/auth.php | 4 +-
database/factories/UserFactory.php | 44 --------
.../0001_01_01_000000_create_users_table.php | 49 --------
.../2026_01_30_140001_create_tenants.php | 32 ++++++
.../2026_01_30_140002_create_users_table.php | 106 ++++++++++++++++++
.../2026_01_30_140010_create_cost_units.php | 43 +++++++
database/seeders/DatabaseSeeder.php | 15 ++-
docker-compose.yaml => docker-compose.dev | 4 +-
resources/js/app.js | 15 +--
routes/api.php | 15 +++
vite.config.js | 1 +
35 files changed, 634 insertions(+), 144 deletions(-)
create mode 100644 app/Enumerations/CostUnitType.php
create mode 100644 app/Enumerations/EatingHabit.php
create mode 100644 app/Enumerations/FirstAidPermission.php
create mode 100644 app/Enumerations/SwimmingPermission.php
create mode 100644 app/Enumerations/UserRole.php
delete mode 100644 app/Http/Controllers/Controller.php
create mode 100644 app/Installer/DevelopmentDataSeeder.php
create mode 100644 app/Installer/ProductionDataSeeder.php
rename app/{Http => }/Middleware/HandleInertiaRequests.php (96%)
create mode 100644 app/Middleware/IdentifyTenant.php
create mode 100644 app/Models/Tenant.php
create mode 100644 app/Providers/InertiaProvider.php
create mode 100644 app/Providers/TenantUserProvider.php
create mode 100644 app/Scopes/CommonModel.php
create mode 100644 app/Scopes/InstancedModel.php
create mode 100644 app/Scopes/SiteScope.php
delete mode 100644 database/factories/UserFactory.php
delete mode 100644 database/migrations/0001_01_01_000000_create_users_table.php
create mode 100644 database/migrations/2026_01_30_140001_create_tenants.php
create mode 100644 database/migrations/2026_01_30_140002_create_users_table.php
create mode 100644 database/migrations/2026_01_30_140010_create_cost_units.php
rename docker-compose.yaml => docker-compose.dev (92%)
create mode 100644 routes/api.php
diff --git a/.gitignore b/.gitignore
index b71b1ea..656cf1b 100644
--- a/.gitignore
+++ b/.gitignore
@@ -22,3 +22,4 @@
Homestead.json
Homestead.yaml
Thumbs.db
+/docker-compose.yaml
diff --git a/Makefile b/Makefile
index fbaa382..8baf640 100644
--- a/Makefile
+++ b/Makefile
@@ -2,6 +2,11 @@
FRONTEND_DIR ?= .
+ setup:
+ rm -f docker-compose.yaml
+ cp docker-compose.dev docker-compose.yaml
+ docker-compose up -d
+
frontend:
@cd $(FRONTEND_DIR) && \
export QT_QPA_PLATFORM=offscreen && \
diff --git a/app/Enumerations/CostUnitType.php b/app/Enumerations/CostUnitType.php
new file mode 100644
index 0000000..855409b
--- /dev/null
+++ b/app/Enumerations/CostUnitType.php
@@ -0,0 +1,21 @@
+installTenants();
+ $this->installUsers();
+ }
+
+
+
+ private function installTenants() {
+ Tenant::create([
+ 'slug' => 'wilde-moehre',
+ 'local_group_name' => 'Stamm Wilde Möhre',
+ 'url' => 'wilde-moehre.mareike.local',
+ 'account_iban' => 'DE12345678901234567890',
+ 'email' => 'test@example1.com',
+ 'city' => 'Halle (Saale)',
+ 'postcode' => '06120',
+ 'is_active_local_group' => true,
+ 'has_active_instance' => true,
+ ]);
+ }
+
+ private function installUsers() {
+ User::create([
+ 'firstname' => 'Development',
+ 'lastname' => 'User',
+ 'user_role' => UserRole::USER_ROLE_ADMIN,
+ 'tenant' => 'lv',
+ 'email' => 'th.guenther@saale-mail.de',
+ 'password' => bcrypt('development'),
+ 'local_group_id' => 1,
+ 'username' => 'development',
+ ]);
+ }
+}
diff --git a/app/Installer/ProductionDataSeeder.php b/app/Installer/ProductionDataSeeder.php
new file mode 100644
index 0000000..dcebdd5
--- /dev/null
+++ b/app/Installer/ProductionDataSeeder.php
@@ -0,0 +1,71 @@
+installUserRoles();
+ $this->installCostUnitTypes();
+ $this->installSwimmingPermissions();
+ $this->installEatingHabits();
+ $this->installFirstAidPermissions();
+ $this->installTenants();
+ }
+
+ private function installUserRoles() {
+ UserRole::create(['name' => 'Administrator*in', 'slug' => UserRole::USER_ROLE_ADMIN]);
+ UserRole::create(['name' => 'Vorstandsmitglied', 'slug' => UserRole::USER_ROLE_GROUP_LEADER]);
+ UserRole::create(['name' => 'Benutzer*in', 'slug' => UserRole::USER_ROLE_USER]);
+ }
+
+ private function installSwimmingPermissions() {
+ SwimmingPermission::create(['name' => 'Mein Kind darf baden und kann schwimmen', 'slug' => SwimmingPermission::SWIMMING_PERMISSION_ALLOWED]);
+ SwimmingPermission::create(['name' => 'Mein Kind darf baden und kann NICHT schwimmen', 'slug' => SwimmingPermission::SWIMMING_PERMISSION_LIMITED]);
+ SwimmingPermission::create(['name' => 'Mein Kind darf nicht baden', 'slug' => SwimmingPermission::SWIMMING_PERMISSION_DENIED]);
+ }
+
+ private function installEatingHabits() {
+ EatingHabit::create(['name' => 'Vegan', 'slug' => EatingHabit::EATING_HABIT_VEGAN]);
+ EatingHabit::create(['name' => 'Vegetarisch', 'slug' => EatingHabit::EATING_HABIT_VEGETARIAN]);
+ EatingHabit::create(['name' => 'Omnivor', 'slug' => EatingHabit::EATING_HABIT_OMNIVOR]);
+
+ }
+ private function installFirstAidPermissions() {
+ FirstAidPermission::create([
+ 'name' => 'Zugestimmt',
+ 'description' => 'Ich STIMME der Anwendung von erweiteren Erste-Hilfe-Maßnahmen an meinem Kind explizit ZU.',
+ 'slug' => FirstAidPermission::FIRST_AID_PERMISSION_ALLOWED]);
+
+ FirstAidPermission::create([
+ 'name' => 'Verweigert',
+ 'description' => 'Ich LEHNE die Anwendung von erweiteren Erste-Hilfe-Maßnahmen an meinem Kind explizit AB.',
+ 'slug' => FirstAidPermission::FIRST_AID_PERMISSION_DENIED]);
+ }
+
+ private function installCostUnitTypes() {
+ CostUnitType::create(['slug' => CostUnitType::COST_UNIT_TYPE_EVENT, 'name' => 'Veranstaltung']);
+ CostUnitType::create(['slug' => CostUnitType::COST_UNIT_TYPE_RUNNING_JOB, 'name' => 'Laufende Tätigkeit']);
+
+ }
+
+ private function installTenants() {
+ Tenant::create([
+ 'slug' => 'lv',
+ 'local_group_name' => 'Landesunmittelbare Mitglieder',
+ 'url' => 'mareike.local',
+ 'account_iban' => 'DE12345678901234567890',
+ 'email' => 'test@example.com',
+ 'city' => 'Lommatzsch',
+ 'postcode' => '01623',
+ 'is_active_local_group' => true,
+ 'has_active_instance' => true,
+ ]);
+ }
+}
diff --git a/app/Http/Middleware/HandleInertiaRequests.php b/app/Middleware/HandleInertiaRequests.php
similarity index 96%
rename from app/Http/Middleware/HandleInertiaRequests.php
rename to app/Middleware/HandleInertiaRequests.php
index c19ce18..e20fd21 100644
--- a/app/Http/Middleware/HandleInertiaRequests.php
+++ b/app/Middleware/HandleInertiaRequests.php
@@ -1,6 +1,6 @@
getHost();
+ $tenant = Tenant::where(['url' => $host, 'has_active_instance' => true])->first();
+
+ if (! $tenant) {
+ throw new NotFoundHttpException('Tenant not found');
+ }
+
+ app()->instance('tenant', $tenant);
+ return $next($request);
+ }
+}
diff --git a/app/Models/Tenant.php b/app/Models/Tenant.php
new file mode 100644
index 0000000..35bed28
--- /dev/null
+++ b/app/Models/Tenant.php
@@ -0,0 +1,39 @@
+ */
- use HasFactory, Notifiable;
+ use Notifiable;
/**
* The attributes that are mass assignable.
@@ -18,8 +15,28 @@ class User extends Authenticatable
* @var list
*/
protected $fillable = [
- 'name',
+ 'tenant_id',
+ 'user_role',
+ 'username',
+ 'firstname',
+ 'nickname',
+ 'lastname',
+ 'local_group_id',
+ 'membership_id',
+ 'address_1',
+ 'address_2',
+ 'postcode',
+ 'city',
'email',
+ 'phone',
+ 'birthday',
+ 'medications',
+ 'allergies',
+ 'intolerances',
+ 'eating_habits',
+ 'swimming_permission',
+ 'first_aid_permission',
+ 'bank_account_iban',
'password',
];
@@ -29,7 +46,6 @@ class User extends Authenticatable
* @var list
*/
protected $hidden = [
- 'password',
'remember_token',
];
diff --git a/app/Providers/AppServiceProvider.php b/app/Providers/AppServiceProvider.php
index 452e6b6..7a8c57f 100644
--- a/app/Providers/AppServiceProvider.php
+++ b/app/Providers/AppServiceProvider.php
@@ -2,6 +2,7 @@
namespace App\Providers;
+use Illuminate\Support\Facades\Auth;
use Illuminate\Support\ServiceProvider;
class AppServiceProvider extends ServiceProvider
@@ -19,6 +20,11 @@ class AppServiceProvider extends ServiceProvider
*/
public function boot(): void
{
- //
+ Auth::provider('tenant-users', function ($app, array $config) {
+ return new TenantUserProvider(
+ $app['hash'],
+ $config['model']
+ );
+ });
}
}
diff --git a/app/Providers/InertiaProvider.php b/app/Providers/InertiaProvider.php
new file mode 100644
index 0000000..e4cf233
--- /dev/null
+++ b/app/Providers/InertiaProvider.php
@@ -0,0 +1,26 @@
+vueFile = $vueFile;
+ $this->props = $props;
+
+ }
+
+
+ public function render() : Response {
+ return Inertia::render(
+ str_replace('/', '/Views/', $this->vueFile),
+ $this->props
+ );
+ }
+}
diff --git a/app/Providers/TenantUserProvider.php b/app/Providers/TenantUserProvider.php
new file mode 100644
index 0000000..51702b9
--- /dev/null
+++ b/app/Providers/TenantUserProvider.php
@@ -0,0 +1,23 @@
+createModel()->newQuery();
+
+ foreach ($credentials as $key => $value) {
+ if (! str_contains($key, 'password')) {
+ $query->where($key, $value);
+ }
+ }
+
+ $query->where('tenant', app('tenant')->slug);
+
+ return $query->first();
+ }
+}
diff --git a/app/Scopes/CommonModel.php b/app/Scopes/CommonModel.php
new file mode 100644
index 0000000..b937fc2
--- /dev/null
+++ b/app/Scopes/CommonModel.php
@@ -0,0 +1,10 @@
+where($model->getTable() . '.tenant', app('tenant')->slug);
+ }
+}
diff --git a/bootstrap/app.php b/bootstrap/app.php
index c183276..2b5ff81 100644
--- a/bootstrap/app.php
+++ b/bootstrap/app.php
@@ -1,5 +1,6 @@
withRouting(
web: __DIR__.'/../routes/web.php',
commands: __DIR__.'/../routes/console.php',
+ api: __DIR__.'/../routes/api.php',
health: '/up',
)
->withMiddleware(function (Middleware $middleware): void {
- //
+ $middleware->append(IdentifyTenant::class);
})
->withExceptions(function (Exceptions $exceptions): void {
//
diff --git a/bootstrap/providers.php b/bootstrap/providers.php
index 38b258d..c93fc13 100644
--- a/bootstrap/providers.php
+++ b/bootstrap/providers.php
@@ -2,4 +2,5 @@
return [
App\Providers\AppServiceProvider::class,
+
];
diff --git a/composer.json b/composer.json
index 22311da..e7da8ea 100644
--- a/composer.json
+++ b/composer.json
@@ -6,7 +6,7 @@
"keywords": ["laravel", "framework"],
"license": "MIT",
"require": {
- "php": "^8.2",
+ "php": "^8.5",
"inertiajs/inertia-laravel": "^2.0",
"laravel/framework": "^12.0",
"laravel/tinker": "^2.10.1"
diff --git a/composer.lock b/composer.lock
index 452325b..13f363a 100644
--- a/composer.lock
+++ b/composer.lock
@@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
- "content-hash": "0e42fe1a7066e7a110e956ae26703d94",
+ "content-hash": "587caff9de06de75c1e22cceac366334",
"packages": [
{
"name": "brick/math",
@@ -2194,16 +2194,16 @@
},
{
"name": "nesbot/carbon",
- "version": "3.11.0",
+ "version": "3.11.1",
"source": {
"type": "git",
"url": "https://github.com/CarbonPHP/carbon.git",
- "reference": "bdb375400dcd162624531666db4799b36b64e4a1"
+ "reference": "f438fcc98f92babee98381d399c65336f3a3827f"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/CarbonPHP/carbon/zipball/bdb375400dcd162624531666db4799b36b64e4a1",
- "reference": "bdb375400dcd162624531666db4799b36b64e4a1",
+ "url": "https://api.github.com/repos/CarbonPHP/carbon/zipball/f438fcc98f92babee98381d399c65336f3a3827f",
+ "reference": "f438fcc98f92babee98381d399c65336f3a3827f",
"shasum": ""
},
"require": {
@@ -2227,7 +2227,7 @@
"phpstan/extension-installer": "^1.4.3",
"phpstan/phpstan": "^2.1.22",
"phpunit/phpunit": "^10.5.53",
- "squizlabs/php_codesniffer": "^3.13.4"
+ "squizlabs/php_codesniffer": "^3.13.4 || ^4.0.0"
},
"bin": [
"bin/carbon"
@@ -2270,14 +2270,14 @@
}
],
"description": "An API extension for DateTime that supports 281 different languages.",
- "homepage": "https://carbon.nesbot.com",
+ "homepage": "https://carbonphp.github.io/carbon/",
"keywords": [
"date",
"datetime",
"time"
],
"support": {
- "docs": "https://carbon.nesbot.com/docs",
+ "docs": "https://carbonphp.github.io/carbon/guide/getting-started/introduction.html",
"issues": "https://github.com/CarbonPHP/carbon/issues",
"source": "https://github.com/CarbonPHP/carbon"
},
@@ -2295,7 +2295,7 @@
"type": "tidelift"
}
],
- "time": "2025-12-02T21:04:28+00:00"
+ "time": "2026-01-29T09:26:29+00:00"
},
{
"name": "nette/schema",
@@ -8436,7 +8436,7 @@
"prefer-stable": true,
"prefer-lowest": false,
"platform": {
- "php": "^8.2"
+ "php": "^8.5"
},
"platform-dev": {},
"plugin-api-version": "2.9.0"
diff --git a/config/auth.php b/config/auth.php
index 7d1eb0d..e6e4ace 100644
--- a/config/auth.php
+++ b/config/auth.php
@@ -61,8 +61,8 @@ return [
'providers' => [
'users' => [
- 'driver' => 'eloquent',
- 'model' => env('AUTH_MODEL', App\Models\User::class),
+ 'driver' => 'tenant-users',
+ 'model' => App\Models\User::class,
],
// 'users' => [
diff --git a/database/factories/UserFactory.php b/database/factories/UserFactory.php
deleted file mode 100644
index 584104c..0000000
--- a/database/factories/UserFactory.php
+++ /dev/null
@@ -1,44 +0,0 @@
-
- */
-class UserFactory extends Factory
-{
- /**
- * The current password being used by the factory.
- */
- protected static ?string $password;
-
- /**
- * Define the model's default state.
- *
- * @return array
- */
- public function definition(): array
- {
- return [
- 'name' => fake()->name(),
- 'email' => fake()->unique()->safeEmail(),
- 'email_verified_at' => now(),
- 'password' => static::$password ??= Hash::make('password'),
- 'remember_token' => Str::random(10),
- ];
- }
-
- /**
- * Indicate that the model's email address should be unverified.
- */
- public function unverified(): static
- {
- return $this->state(fn (array $attributes) => [
- 'email_verified_at' => null,
- ]);
- }
-}
diff --git a/database/migrations/0001_01_01_000000_create_users_table.php b/database/migrations/0001_01_01_000000_create_users_table.php
deleted file mode 100644
index 05fb5d9..0000000
--- a/database/migrations/0001_01_01_000000_create_users_table.php
+++ /dev/null
@@ -1,49 +0,0 @@
-id();
- $table->string('name');
- $table->string('email')->unique();
- $table->timestamp('email_verified_at')->nullable();
- $table->string('password');
- $table->rememberToken();
- $table->timestamps();
- });
-
- Schema::create('password_reset_tokens', function (Blueprint $table) {
- $table->string('email')->primary();
- $table->string('token');
- $table->timestamp('created_at')->nullable();
- });
-
- Schema::create('sessions', function (Blueprint $table) {
- $table->string('id')->primary();
- $table->foreignId('user_id')->nullable()->index();
- $table->string('ip_address', 45)->nullable();
- $table->text('user_agent')->nullable();
- $table->longText('payload');
- $table->integer('last_activity')->index();
- });
- }
-
- /**
- * Reverse the migrations.
- */
- public function down(): void
- {
- Schema::dropIfExists('users');
- Schema::dropIfExists('password_reset_tokens');
- Schema::dropIfExists('sessions');
- }
-};
diff --git a/database/migrations/2026_01_30_140001_create_tenants.php b/database/migrations/2026_01_30_140001_create_tenants.php
new file mode 100644
index 0000000..ddd4f7e
--- /dev/null
+++ b/database/migrations/2026_01_30_140001_create_tenants.php
@@ -0,0 +1,32 @@
+id();
+ $table->string('slug')->unique();
+ $table->string('local_group_name');
+ $table->string('email');
+ $table->string('url');
+ $table->string('account_iban');
+ $table->string('city');
+ $table->string('postcode');
+ $table->string('gdpr_text')-> nullable();
+ $table->string('impress_text')->nullable();
+ $table->string('url_participation_rules')->nullable();
+ $table->boolean('has_active_instance')->default(true);
+ $table->boolean('is_active_local_group')->default(true);
+
+ $table->timestamps();
+ });
+ }
+
+ public function down(): void
+ {
+ Schema::dropIfExists('tenants');
+ }
+};
diff --git a/database/migrations/2026_01_30_140002_create_users_table.php b/database/migrations/2026_01_30_140002_create_users_table.php
new file mode 100644
index 0000000..5833b76
--- /dev/null
+++ b/database/migrations/2026_01_30_140002_create_users_table.php
@@ -0,0 +1,106 @@
+string('slug')->unique()->primary();
+ $table->string('name');
+ $table->timestamps();
+ });
+
+ Schema::create('eating_habits', function (Blueprint $table) {
+ $table->string('slug')->unique()->primary();
+ $table->string('name');
+ $table->timestamps();
+ });
+
+ Schema::create('swimming_permissions', function (Blueprint $table) {
+ $table->string('slug')->unique()->primary();
+ $table->string('name');
+ $table->timestamps();
+ });
+
+ Schema::create('first_aid_permissions', function (Blueprint $table) {
+ $table->string('slug')->unique()->primary();
+ $table->string('name');
+ $table->string('description');
+ $table->timestamps();
+ });
+
+ Schema::create('users', function (Blueprint $table) {
+ $table->id();
+ $table->string('tenant');
+ $table->string('user_role');
+ $table->string('username')->unique();
+ $table->string('password')->nullable();
+ $table->string('firstname');
+ $table->string('nickname')->nullable();
+ $table->string('lastname');
+ $table->foreignId('local_group_id')->references('id')->on('tenants')->cascadeOnDelete()->cascadeOnUpdate();
+ $table->string('membership_id')->nullable();
+ $table->string('address_1')->nullable();
+ $table->string('address_2')->nullable();
+ $table->string('postcode')->nullable();
+ $table->string('city')->nullable();
+ $table->string('email')->nullable();
+ $table->string('phone')->nullable();
+ $table->date('birthday')->nullable();
+ $table->string('medications')->nullable();
+ $table->string('allergies')->nullable();
+ $table->string('intolerances')->nullable();
+ $table->string('eating_habits')->nullable();
+ $table->string('swimming_permission')->nullable();
+ $table->string('first_aid_permission')->nullable();
+ $table->string('bank_account_iban')->nullable();
+ $table->string('activation_token')->nullable();
+ $table->boolean('activated')->default(false);
+
+ $table->foreign('tenant')->references('slug')->on('tenants')->cascadeOnDelete()->cascadeOnUpdate();
+ $table->foreign('user_role')->references('slug')->on('user_roles')->cascadeOnDelete()->cascadeOnUpdate();
+ $table->foreign('swimming_permission')->references('slug')->on('swimming_permissions')->cascadeOnDelete()->cascadeOnUpdate();
+ $table->foreign('eating_habits')->references('slug')->on('eating_habits')->cascadeOnDelete()->cascadeOnUpdate();
+ $table->foreign('first_aid_permission')->references('slug')->on('first_aid_permissions')->cascadeOnDelete()->cascadeOnUpdate();
+
+ $table->rememberToken();
+ $table->timestamps();
+ });
+
+ Schema::create('password_reset_tokens', function (Blueprint $table) {
+ $table->string('email')->primary();
+ $table->string('token');
+ $table->timestamp('created_at')->nullable();
+ });
+
+ Schema::create('sessions', function (Blueprint $table) {
+ $table->string('id')->primary();
+ $table->foreignId('user_id')->nullable()->index();
+ $table->string('ip_address', 45)->nullable();
+ $table->text('user_agent')->nullable();
+ $table->longText('payload');
+ $table->integer('last_activity')->index();
+ });
+ }
+
+ /**
+ * Reverse the migrations.
+ */
+ public function down(): void
+ {
+ Schema::dropIfExists('users');
+ Schema::dropIfExists('password_reset_tokens');
+ Schema::dropIfExists('sessions');
+ Schema::dropIfExists('user_roles');
+ Schema::dropIfExists('eating_habits');
+ Schema::dropIfExists('swimming_permissions');
+ Schema::dropIfExists('first_aid_permissions');
+ }
+};
diff --git a/database/migrations/2026_01_30_140010_create_cost_units.php b/database/migrations/2026_01_30_140010_create_cost_units.php
new file mode 100644
index 0000000..8856e89
--- /dev/null
+++ b/database/migrations/2026_01_30_140010_create_cost_units.php
@@ -0,0 +1,43 @@
+string('slug')->primary();
+ $table->string('name');
+ $table->timestamps();
+ });
+
+
+
+ Schema::create('cost_units', function (Blueprint $table) {
+ $table->id();
+ $table->string('tenant');
+ $table->string('name');
+ $table->string('type');
+ $table->dateTime('billing_deadline');
+ $table->float('distance_allowance');
+ $table->boolean('mail_on_new')->default(true);
+ $table->boolean('allow_new')->default(true);
+ $table->boolean('archived')->default(false);
+ $table->string('treasurers');
+
+ $table->foreign('tenant')->references('slug')->on('tenants')->cascadeOnDelete()->cascadeOnUpdate();
+ $table->foreign('type')->references('slug')->on('cost_unit_types')->cascadeOnDelete()->cascadeOnUpdate();
+ $table->timestamps();
+ });
+ }
+
+ public function down(): void
+ {
+ Schema::dropIfExists('cost_units');
+ Schema::dropIfExists('cost_unit_types');
+ }
+};
diff --git a/database/seeders/DatabaseSeeder.php b/database/seeders/DatabaseSeeder.php
index 6b901f8..a8a01af 100644
--- a/database/seeders/DatabaseSeeder.php
+++ b/database/seeders/DatabaseSeeder.php
@@ -2,7 +2,9 @@
namespace Database\Seeders;
-use App\Models\User;
+use App\Installer\DevelopmentDataSeeder;
+use App\Installer\ProductionData;
+use App\Installer\ProductionDataSeeder;
use Illuminate\Database\Console\Seeds\WithoutModelEvents;
use Illuminate\Database\Seeder;
@@ -15,11 +17,12 @@ class DatabaseSeeder extends Seeder
*/
public function run(): void
{
- // User::factory(10)->create();
+ $productionSeeeder = new ProductionDataSeeder();
+ $productionSeeeder->execute();
- User::factory()->create([
- 'name' => 'Test User',
- 'email' => 'test@example.com',
- ]);
+ if (str_ends_with(env('APP_URL'), 'mareike.local')) {
+ $deveopmentDataSeeder = new DevelopmentDataSeeder();
+ $deveopmentDataSeeder->execute();
+ }
}
}
diff --git a/docker-compose.yaml b/docker-compose.dev
similarity index 92%
rename from docker-compose.yaml
rename to docker-compose.dev
index 95a230a..6a7134d 100644
--- a/docker-compose.yaml
+++ b/docker-compose.dev
@@ -18,12 +18,12 @@ services:
labels:
- "traefik.enable=true"
- - "traefik.http.routers.mareike.rule=Host(`mareike.local`)"
+ - "traefik.http.routers.mareike.rule=Host(`mareike.local`) || Host(`admin.mareike.local`) || Host(`wilde-moehre.mareike.local`)"
- "traefik.http.routers.mareike.entrypoints=websecure"
- "traefik.http.routers.mareike.tls=true"
- "traefik.http.services.mareike.loadbalancer.server.port=80"
- - "traefik.http.routers.mareike-http.rule=Host(`mareike.local`)"
+ - "traefik.http.routers.mareike-http.rule=Host(`mareike.local`) || Host(`admin.mareike.local`) || Host(`wilde-moehre.mareike.local`)"
- "traefik.http.routers.mareike-http.entrypoints=web"
- "traefik.http.routers.mareike-http.middlewares=redirect-to-https"
diff --git a/resources/js/app.js b/resources/js/app.js
index 104602f..05e5ddc 100644
--- a/resources/js/app.js
+++ b/resources/js/app.js
@@ -5,43 +5,32 @@ import { InertiaProgress } from '@inertiajs/progress'
import Vue3Toastify, { toast } from 'vue3-toastify'
import 'vue3-toastify/dist/index.css'
-// Optional: Lade-Balken für Inertia
InertiaProgress.init()
-// Inertia App starten
createInertiaApp({
- // Alle Pages in app/Views/Pages/**/*.vue werden automatisch importiert
resolve: name => {
- // Vite scannt die Pages dynamisch
- const pages = import.meta.glob('@views/**/*.vue')
+ const pages = import.meta.glob('@domains/**/*.vue')
- // Suche nach der richtigen Page-Datei
const key = Object.keys(pages).find(k =>
k.endsWith(`/${name}.vue`) || k.endsWith(`/${name}/index.vue`)
)
if (!key) throw new Error(`Page not found: ${name}`)
- // Unterstützt sowohl