Compare commits

1 Commits

Author SHA1 Message Date
3570f442f5 Basic tenant structure 2026-01-31 20:07:41 +01:00
35 changed files with 634 additions and 144 deletions

1
.gitignore vendored
View File

@@ -22,3 +22,4 @@
Homestead.json
Homestead.yaml
Thumbs.db
/docker-compose.yaml

View File

@@ -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 && \

View File

@@ -0,0 +1,21 @@
<?php
namespace App\Enumerations;
use App\Scopes\CommonModel;
use Illuminate\Database\Eloquent\Factories\HasFactory;
/**
* @property string $slug
* @property string $name
*/
class CostUnitType extends CommonModel
{
public const COST_UNIT_TYPE_EVENT = 'event';
public const COST_UNIT_TYPE_RUNNING_JOB = 'running_job';
protected $fillable = [
'slug',
'name',
];
}

View File

@@ -0,0 +1,22 @@
<?php
namespace App\Enumerations;
use App\Scopes\CommonModel;
use Illuminate\Database\Eloquent\Factories\HasFactory;
/**
* @property string $slug
* @property string $name
*/
class EatingHabit extends CommonModel
{
public const string EATING_HABIT_OMNIVOR = 'EATING_HABIT_OMNIVOR';
public const string EATING_HABIT_VEGETARIAN = 'EATING_HABIT_VEGETARIAN';
public const string EATING_HABIT_VEGAN = 'EATING_HABIT_VEGAN';
protected $fillable = [
'slug',
'name',
];
}

View File

@@ -0,0 +1,22 @@
<?php
namespace App\Enumerations;
use App\Scopes\CommonModel;
/**
* @property string $slug
* @property string $name
*/
class FirstAidPermission extends CommonModel
{
public const string FIRST_AID_PERMISSION_ALLOWED = 'FIRST_AID_PERMISSION_ALLOWED';
public const string FIRST_AID_PERMISSION_DENIED = 'FIRST_AID_PERMISSION_DENIED';
protected $fillable = [
'slug',
'name',
'description'
];
}

View File

@@ -0,0 +1,26 @@
<?php
namespace App\Enumerations;
use App\Scopes\CommonModel;
use Illuminate\Database\Eloquent\Factories\HasFactory;
/**
* @property string $slug
* @property string $name
*/
class SwimmingPermission extends CommonModel
{
public const string SWIMMING_PERMISSION_ALLOWED = 'SWIMMING_PERMISSION_ALLOWED';
public const string SWIMMING_PERMISSION_LIMITED = 'SWIMMING_PERMISSION_LIMITED';
public const string SWIMMING_PERMISSION_DENIED = 'SWIMMING_PERMISSION_DENIED';
use HasFactory;
protected $fillable = [
'slug',
'name',
];
}

View File

@@ -0,0 +1,17 @@
<?php
namespace App\Enumerations;
use App\Scopes\CommonModel;
class UserRole extends CommonModel {
public const USER_ROLE_ADMIN = 'ROLE_ADMINISTRATOR';
public const USER_ROLE_GROUP_LEADER = 'ROLE_GROUP_LEADER';
public const USER_ROLE_USER = 'ROLE_USER';
protected $fillable = [
'name',
'slug'
];
}

View File

@@ -1,8 +0,0 @@
<?php
namespace App\Http\Controllers;
abstract class Controller
{
//
}

View File

@@ -0,0 +1,43 @@
<?php
namespace App\Installer;
use App\Enumerations\UserRole;
use App\Models\Tenant;
use App\Models\User;
class DevelopmentDataSeeder {
public function execute() {
$this->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',
]);
}
}

View File

@@ -0,0 +1,71 @@
<?php
namespace App\Installer;
use App\Enumerations\CostUnitType;
use App\Enumerations\EatingHabit;
use App\Enumerations\FirstAidPermission;
use App\Enumerations\SwimmingPermission;
use App\Enumerations\UserRole;
use App\Models\Tenant;
class ProductionDataSeeder {
public function execute() {
$this->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,
]);
}
}

View File

@@ -1,6 +1,6 @@
<?php
namespace App\Http\Middleware;
namespace App\Middleware;
use Illuminate\Http\Request;
use Inertia\Middleware;

View File

@@ -0,0 +1,24 @@
<?php
namespace App\Middleware;
use App\Models\Tenant;
use Closure;
use Illuminate\Http\Request;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
class IdentifyTenant
{
public function handle(Request $request, Closure $next)
{
$host = $request->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);
}
}

39
app/Models/Tenant.php Normal file
View File

@@ -0,0 +1,39 @@
<?php
namespace App\Models;
use App\Scopes\CommonModel;
/**
* @property string $slug
* @property string $local_group
* @property string $email
* @property string $url
* @property string $account_iban
* @property string $city
* @property string $postcode
* @property string $gdpr_text
* @property string $impress_text
* @property string $url_participation_rules
* @property boolean $events_allowed
* @property boolean $has_active_instance
*/
class Tenant extends CommonModel
{
public const PRIMARY_TENANT_NAME = 'LV';
protected $fillable = [
'slug',
'local_group_name',
'email',
'url',
'account_iban',
'city',
'postcode',
'gdpr_text',
'impress_text',
'url_participation_rules',
'is_active_local_group',
'has_active_instance'
];
}

View File

@@ -2,15 +2,12 @@
namespace App\Models;
// use Illuminate\Contracts\Auth\MustVerifyEmail;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
class User extends Authenticatable
{
/** @use HasFactory<\Database\Factories\UserFactory> */
use HasFactory, Notifiable;
use Notifiable;
/**
* The attributes that are mass assignable.
@@ -18,8 +15,28 @@ class User extends Authenticatable
* @var list<string>
*/
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<string>
*/
protected $hidden = [
'password',
'remember_token',
];

View File

@@ -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']
);
});
}
}

View File

@@ -0,0 +1,26 @@
<?php
namespace App\Providers;
use Inertia\Inertia;
use Inertia\Response;
final class InertiaProvider
{
private string $vueFile;
private array $props;
public function __construct(string $vueFile, array $props) {
$this->vueFile = $vueFile;
$this->props = $props;
}
public function render() : Response {
return Inertia::render(
str_replace('/', '/Views/', $this->vueFile),
$this->props
);
}
}

View File

@@ -0,0 +1,23 @@
<?php
namespace App\Providers;
use Illuminate\Auth\EloquentUserProvider;
class TenantUserProvider extends EloquentUserProvider
{
public function retrieveByCredentials(array $credentials)
{
$query = $this->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();
}
}

View File

@@ -0,0 +1,10 @@
<?php
namespace App\Scopes;
use Illuminate\Database\Eloquent\Model;
abstract class CommonModel extends Model
{
}

View File

@@ -0,0 +1,13 @@
<?php
namespace App\Scopes;
use Illuminate\Database\Eloquent\Model;
abstract class InstancedModel extends Model
{
protected static function booted()
{
static::addGlobalScope(new SiteScope());
}
}

14
app/Scopes/SiteScope.php Normal file
View File

@@ -0,0 +1,14 @@
<?php
namespace App\Scopes;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Scope;
class SiteScope implements Scope
{
public function apply(Builder $builder, Model $model): void
{
$builder->where($model->getTable() . '.tenant', app('tenant')->slug);
}
}

View File

@@ -1,5 +1,6 @@
<?php
use App\Middleware\IdentifyTenant;
use Illuminate\Foundation\Application;
use Illuminate\Foundation\Configuration\Exceptions;
use Illuminate\Foundation\Configuration\Middleware;
@@ -8,10 +9,11 @@ return Application::configure(basePath: dirname(__DIR__))
->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 {
//

View File

@@ -2,4 +2,5 @@
return [
App\Providers\AppServiceProvider::class,
];

View File

@@ -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"

20
composer.lock generated
View File

@@ -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"

View File

@@ -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' => [

View File

@@ -1,44 +0,0 @@
<?php
namespace Database\Factories;
use Illuminate\Database\Eloquent\Factories\Factory;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Str;
/**
* @extends \Illuminate\Database\Eloquent\Factories\Factory<\App\Models\User>
*/
class UserFactory extends Factory
{
/**
* The current password being used by the factory.
*/
protected static ?string $password;
/**
* Define the model's default state.
*
* @return array<string, mixed>
*/
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,
]);
}
}

View File

@@ -1,49 +0,0 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::create('users', function (Blueprint $table) {
$table->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');
}
};

View File

@@ -0,0 +1,32 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration {
public function up(): void
{
Schema::create('tenants', function (Blueprint $table) {
$table->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');
}
};

View File

@@ -0,0 +1,106 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::create('user_roles', function (Blueprint $table) {
$table->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');
}
};

View File

@@ -0,0 +1,43 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration {
/**
* Run the migrations.
*/
public function up(): void
{
Schema::create('cost_unit_types', function (Blueprint $table) {
$table->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');
}
};

View File

@@ -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();
}
}
}

View File

@@ -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"

View File

@@ -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 <script setup> als auch klassische Exports
return pages[key]()
},
// Setup der App
setup({ el, App, props, plugin }) {
const vueApp = createApp({ render: () => h(App, props) })
// Inertia Plugin
vueApp.use(plugin)
// Toastify global verfügbar machen
vueApp.use(Vue3Toastify, {
autoClose: 3000,
position: 'top-right',
pauseOnHover: true,
})
vueApp.config.globalProperties.$toast = toast
// Mounten auf das DOM
vueApp.config.globalProperties.$toast = toast
vueApp.mount(el)
},
})

15
routes/api.php Normal file
View File

@@ -0,0 +1,15 @@
<?php
use Illuminate\Support\Facades\Route;
use Inertia\Inertia;
Route::get('/', ['TestController', 'index']);
Route::get('/inertia', function () {
return Inertia::render('Pages/Home', [
'appName' => config('app.name'),
]);
});

View File

@@ -15,6 +15,7 @@ export default defineConfig({
alias: {
'@': path.resolve(__dirname, 'resources/js'),
'@views': path.resolve(__dirname, 'app/Views'),
'@domains': path.resolve(__dirname, 'app/Domains'),
},
},
})