diff --git a/app/Http/Middleware/EnsureActiveLicense.php b/app/Http/Middleware/EnsureActiveLicense.php index 2e98f1b..8e793b7 100644 --- a/app/Http/Middleware/EnsureActiveLicense.php +++ b/app/Http/Middleware/EnsureActiveLicense.php @@ -9,7 +9,21 @@ class EnsureActiveLicense { public function handle(Request $request, Closure $next): mixed { - // Full implementation in Phase 4 (requires License model) + $user = $request->user(); + + if (!$user || !$user->hasRole('child')) { + return $next($request); + } + + $hasActive = $user->licenseAssignments() + ->with('license') + ->get() + ->some(fn($a) => $a->license && $a->license->isActive()); + + if (!$hasActive) { + return redirect('/lizenz-abgelaufen'); + } + return $next($request); } } diff --git a/app/Models/License.php b/app/Models/License.php index 8ade2c4..b122ffc 100644 --- a/app/Models/License.php +++ b/app/Models/License.php @@ -12,20 +12,27 @@ class License extends Model { use HasFactory, HasUuid, BelongsToTenant, SoftDeletes; - protected $fillable = ['type', 'seats', 'expires_at', 'active', 'tenant_id']; + protected $fillable = ['tenant_id', 'type', 'seats', 'expires_at', 'active']; protected $casts = [ - 'active' => 'boolean', 'expires_at' => 'date', + 'active' => 'boolean', ]; - public function tenant() + public function isActive(): bool { - return $this->belongsTo(Tenant::class); + if (!$this->active) return false; + if ($this->expires_at && $this->expires_at->isPast()) return false; + return true; } public function assignments() { return $this->hasMany(LicenseAssignment::class); } + + public function tenant() + { + return $this->belongsTo(Tenant::class); + } } diff --git a/database/factories/LicenseFactory.php b/database/factories/LicenseFactory.php new file mode 100644 index 0000000..a722454 --- /dev/null +++ b/database/factories/LicenseFactory.php @@ -0,0 +1,41 @@ + Tenant::factory(), + 'type' => 'per_student', + 'seats' => 30, + 'expires_at' => now()->addYear(), + 'active' => true, + ]; + } + + public function active(): static + { + return $this->state([ + 'active' => true, + 'expires_at' => now()->addYear(), + ]); + } + + public function expired(): static + { + return $this->state([ + 'active' => true, + 'expires_at' => now()->subDay(), + ]); + } + + public function inactive(): static + { + return $this->state(['active' => false]); + } +} diff --git a/routes/web.php b/routes/web.php index 5cdd3ce..aa40706 100644 --- a/routes/web.php +++ b/routes/web.php @@ -10,3 +10,9 @@ Route::get('/', function () { Route::middleware(['auth', 'role:school-admin,teacher'])->group(function () { Route::get('/school/dashboard', fn() => response('ok'))->name('school.dashboard'); }); + +Route::middleware(['auth', 'license'])->group(function () { + Route::get('/dashboard', fn() => response('ok'))->name('dashboard'); +}); + +Route::get('/lizenz-abgelaufen', fn() => response('Lizenz abgelaufen', 200))->name('lizenz.abgelaufen'); diff --git a/tests/Feature/License/EnsureActiveLicenseTest.php b/tests/Feature/License/EnsureActiveLicenseTest.php new file mode 100644 index 0000000..3994dcc --- /dev/null +++ b/tests/Feature/License/EnsureActiveLicenseTest.php @@ -0,0 +1,27 @@ +create(); + $license = License::factory()->for($tenant)->expired()->create(); + $child = User::factory()->for($tenant)->withRole('child')->withLicense($license)->create(); + actingAs($child)->get('/dashboard')->assertRedirect('/lizenz-abgelaufen'); +}); + +it('erlaubt child-Zugriff bei aktiver Lizenz', function () { + $tenant = Tenant::factory()->create(); + $license = License::factory()->for($tenant)->active()->create(); + $child = User::factory()->for($tenant)->withRole('child')->withLicense($license)->create(); + actingAs($child)->get('/dashboard')->assertOk(); +}); + +it('erlaubt Lehrer-Zugriff ohne Lizenz-Check', function () { + $teacher = User::factory()->withRole('teacher')->create(); + actingAs($teacher)->get('/dashboard')->assertOk(); +});