Basic tenant structure

This commit is contained in:
2026-01-31 20:07:41 +01:00
parent 825af15962
commit 3570f442f5
35 changed files with 634 additions and 144 deletions

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