diff --git a/app/Filament/Resources/FeatureFlagOverrides/FeatureFlagOverrideResource.php b/app/Filament/Resources/FeatureFlagOverrides/FeatureFlagOverrideResource.php new file mode 100644 index 0000000..060e437 --- /dev/null +++ b/app/Filament/Resources/FeatureFlagOverrides/FeatureFlagOverrideResource.php @@ -0,0 +1,51 @@ + ListFeatureFlagOverrides::route('/'), + 'create' => CreateFeatureFlagOverride::route('/create'), + 'edit' => EditFeatureFlagOverride::route('/{record}/edit'), + ]; + } +} diff --git a/app/Filament/Resources/FeatureFlagOverrides/Pages/CreateFeatureFlagOverride.php b/app/Filament/Resources/FeatureFlagOverrides/Pages/CreateFeatureFlagOverride.php new file mode 100644 index 0000000..3ec34a9 --- /dev/null +++ b/app/Filament/Resources/FeatureFlagOverrides/Pages/CreateFeatureFlagOverride.php @@ -0,0 +1,11 @@ +components([ + Select::make('feature_flag_id') + ->label('Feature flag') + ->searchable() + ->options(fn () => FeatureFlag::pluck('label', 'id')) + ->disabledOn('edit') + ->required(), + Select::make('user_id') + ->label('Felhasználó') + ->searchable() + ->options(fn () => User::pluck('name', 'id')) + ->disabledOn('edit') + ->required(), + Toggle::make('active') + ->label('Bekapcsolva') + ->required(), + ]); + } +} diff --git a/app/Filament/Resources/FeatureFlagOverrides/Tables/FeatureFlagOverridesTable.php b/app/Filament/Resources/FeatureFlagOverrides/Tables/FeatureFlagOverridesTable.php new file mode 100644 index 0000000..484d5c0 --- /dev/null +++ b/app/Filament/Resources/FeatureFlagOverrides/Tables/FeatureFlagOverridesTable.php @@ -0,0 +1,55 @@ +columns([ + TextColumn::make('featureFlag.label') + ->label('Feature flag') + ->searchable() + ->sortable(), + TextColumn::make('user.name') + ->label('Felhasználó') + ->searchable() + ->sortable(), + IconColumn::make('active') + ->label('Állapot') + ->boolean(), + TextColumn::make('updated_at') + ->label('Módosítva') + ->dateTime() + ->sortable(), + ]) + ->filters([ + SelectFilter::make('feature_flag_id') + ->label('Feature flag') + ->options(fn () => FeatureFlag::pluck('label', 'id')), + SelectFilter::make('user_id') + ->label('Felhasználó') + ->options(fn () => User::pluck('name', 'id')) + ->searchable(), + ]) + ->recordActions([ + EditAction::make(), + ]) + ->toolbarActions([ + BulkActionGroup::make([ + DeleteBulkAction::make(), + ]), + ]); + } +} diff --git a/app/Filament/Resources/FeatureFlags/FeatureFlagResource.php b/app/Filament/Resources/FeatureFlags/FeatureFlagResource.php index 4acbdda..e7eb786 100644 --- a/app/Filament/Resources/FeatureFlags/FeatureFlagResource.php +++ b/app/Filament/Resources/FeatureFlags/FeatureFlagResource.php @@ -5,6 +5,7 @@ use App\Filament\Resources\FeatureFlags\Pages\CreateFeatureFlag; use App\Filament\Resources\FeatureFlags\Pages\EditFeatureFlag; use App\Filament\Resources\FeatureFlags\Pages\ListFeatureFlags; +use App\Filament\Resources\FeatureFlags\RelationManagers\OverridesRelationManager; use App\Filament\Resources\FeatureFlags\Schemas\FeatureFlagForm; use App\Filament\Resources\FeatureFlags\Tables\FeatureFlagsTable; use App\Models\FeatureFlag; @@ -36,7 +37,7 @@ public static function table(Table $table): Table public static function getRelations(): array { return [ - // + OverridesRelationManager::class, ]; } diff --git a/app/Filament/Resources/FeatureFlags/RelationManagers/OverridesRelationManager.php b/app/Filament/Resources/FeatureFlags/RelationManagers/OverridesRelationManager.php new file mode 100644 index 0000000..1023a1c --- /dev/null +++ b/app/Filament/Resources/FeatureFlags/RelationManagers/OverridesRelationManager.php @@ -0,0 +1,63 @@ +components([ + Select::make('user_id') + ->label('Felhasználó') + ->searchable() + ->options(fn () => User::pluck('name', 'id')) + ->disabledOn('edit') + ->required(), + Toggle::make('active') + ->label('Bekapcsolva') + ->required(), + ]); + } + + public function table(Table $table): Table + { + return $table + ->recordTitleAttribute('user.name') + ->columns([ + TextColumn::make('user.name') + ->label('Felhasználó') + ->searchable(), + IconColumn::make('active') + ->label('Állapot') + ->boolean(), + TextColumn::make('updated_at') + ->label('Módosítva') + ->dateTime() + ->sortable(), + ]) + ->headerActions([ + CreateAction::make(), + ]) + ->recordActions([ + EditAction::make(), + DeleteAction::make(), + ]); + } +} diff --git a/app/Filament/Resources/FeatureFlags/Tables/FeatureFlagsTable.php b/app/Filament/Resources/FeatureFlags/Tables/FeatureFlagsTable.php index 0eb0349..0b31def 100644 --- a/app/Filament/Resources/FeatureFlags/Tables/FeatureFlagsTable.php +++ b/app/Filament/Resources/FeatureFlags/Tables/FeatureFlagsTable.php @@ -14,7 +14,6 @@ use Filament\Tables\Columns\TextColumn; use Filament\Tables\Columns\ToggleColumn; use Filament\Tables\Table; -use Laravel\Pennant\Feature; class FeatureFlagsTable { @@ -69,19 +68,19 @@ public static function configure(Table $table): Table $user = User::findOrFail($data['user_id']); if ($data['override'] === 'inherit') { + // model-instance delete() kell, hogy a FeatureFlagOverrideObserver + // (Pennant purge) ténylegesen kiváltódjon - a query builder delete() + // nem hívja meg a modell eseményeit FeatureFlagOverride::where('feature_flag_id', $record->id) ->where('user_id', $user->id) - ->delete(); + ->first() + ?->delete(); } else { FeatureFlagOverride::updateOrCreate( ['feature_flag_id' => $record->id, 'user_id' => $user->id], ['active' => $data['override'] === 'on'], ); } - - // csak ennek az egy usernek a gyorsítótárazott Pennant-feloldását - // érvénytelenítjük, a többi felhasználót nem érinti - Feature::for($user)->forget($record->name); }), ]) ->toolbarActions([ diff --git a/app/Models/FeatureFlag.php b/app/Models/FeatureFlag.php index 7261fee..4421f52 100644 --- a/app/Models/FeatureFlag.php +++ b/app/Models/FeatureFlag.php @@ -2,6 +2,8 @@ namespace App\Models; +use Illuminate\Database\Eloquent\Relations\HasMany; + class FeatureFlag extends BaseAuditable { protected $casts = [ @@ -9,4 +11,9 @@ class FeatureFlag extends BaseAuditable 'stages' => 'array', 'roles' => 'array', ]; + + public function overrides(): HasMany + { + return $this->hasMany(FeatureFlagOverride::class); + } } diff --git a/app/Observers/FeatureFlagOverrideObserver.php b/app/Observers/FeatureFlagOverrideObserver.php new file mode 100644 index 0000000..d170ae1 --- /dev/null +++ b/app/Observers/FeatureFlagOverrideObserver.php @@ -0,0 +1,33 @@ +forget($override); + } + + public function deleted(FeatureFlagOverride $override): void + { + $this->forget($override); + } + + private function forget(FeatureFlagOverride $override): void + { + $user = $override->user; + $flag = $override->featureFlag; + + if ($user && $flag) { + Feature::for($user)->forget($flag->name); + } + } +} diff --git a/app/Policies/FeatureFlagOverridePolicy.php b/app/Policies/FeatureFlagOverridePolicy.php new file mode 100644 index 0000000..944468d --- /dev/null +++ b/app/Policies/FeatureFlagOverridePolicy.php @@ -0,0 +1,50 @@ +active('FeatureFlagAdmin'); + } + + public function viewAny(User $user): bool + { + return $this->hasAccess($user); + } + + public function view(User $user, FeatureFlagOverride $featureFlagOverride): bool + { + return $this->hasAccess($user); + } + + public function create(User $user): bool + { + return $this->hasAccess($user); + } + + public function update(User $user, FeatureFlagOverride $featureFlagOverride): bool + { + return $this->hasAccess($user); + } + + public function delete(User $user, FeatureFlagOverride $featureFlagOverride): bool + { + return $this->hasAccess($user); + } + + public function deleteAny(User $user): bool + { + return $this->hasAccess($user); + } +} diff --git a/app/Policies/FeatureFlagPolicy.php b/app/Policies/FeatureFlagPolicy.php new file mode 100644 index 0000000..b9d6b6e --- /dev/null +++ b/app/Policies/FeatureFlagPolicy.php @@ -0,0 +1,50 @@ +active('FeatureFlagAdmin'); + } + + public function viewAny(User $user): bool + { + return $this->hasAccess($user); + } + + public function view(User $user, FeatureFlag $featureFlag): bool + { + return $this->hasAccess($user); + } + + public function create(User $user): bool + { + return $this->hasAccess($user); + } + + public function update(User $user, FeatureFlag $featureFlag): bool + { + return $this->hasAccess($user); + } + + public function delete(User $user, FeatureFlag $featureFlag): bool + { + return $this->hasAccess($user); + } + + public function deleteAny(User $user): bool + { + return $this->hasAccess($user); + } +} diff --git a/app/Providers/AppServiceProvider.php b/app/Providers/AppServiceProvider.php index 93b779d..3dc16a7 100644 --- a/app/Providers/AppServiceProvider.php +++ b/app/Providers/AppServiceProvider.php @@ -3,7 +3,9 @@ namespace App\Providers; use App\Models\FeatureFlag; +use App\Models\FeatureFlagOverride; use App\Observers\FeatureFlagObserver; +use App\Observers\FeatureFlagOverrideObserver; use App\Repositories\AddressRepository; use App\Repositories\AddressRepositoryInterface; use App\Repositories\ContactRepository; @@ -58,6 +60,7 @@ public function boot(): void URL::forceScheme('https'); FeatureFlag::observe(FeatureFlagObserver::class); + FeatureFlagOverride::observe(FeatureFlagOverrideObserver::class); // Schema::hasTable védi a friss telepítést / migrate-et megelőző state-et, // amikor a feature_flags tábla még nem létezik. @@ -103,6 +106,11 @@ public function boot(): void \Livewire\Livewire::component('app.filament.resources.feature-flags.pages.list-feature-flags', \App\Filament\Resources\FeatureFlags\Pages\ListFeatureFlags::class); \Livewire\Livewire::component('app.filament.resources.feature-flags.pages.create-feature-flag', \App\Filament\Resources\FeatureFlags\Pages\CreateFeatureFlag::class); \Livewire\Livewire::component('app.filament.resources.feature-flags.pages.edit-feature-flag', \App\Filament\Resources\FeatureFlags\Pages\EditFeatureFlag::class); + \Livewire\Livewire::component('app.filament.resources.feature-flags.relation-managers.overrides-relation-manager', \App\Filament\Resources\FeatureFlags\RelationManagers\OverridesRelationManager::class); + + \Livewire\Livewire::component('app.filament.resources.feature-flag-overrides.pages.list-feature-flag-overrides', \App\Filament\Resources\FeatureFlagOverrides\Pages\ListFeatureFlagOverrides::class); + \Livewire\Livewire::component('app.filament.resources.feature-flag-overrides.pages.create-feature-flag-override', \App\Filament\Resources\FeatureFlagOverrides\Pages\CreateFeatureFlagOverride::class); + \Livewire\Livewire::component('app.filament.resources.feature-flag-overrides.pages.edit-feature-flag-override', \App\Filament\Resources\FeatureFlagOverrides\Pages\EditFeatureFlagOverride::class); \Livewire\Livewire::component('filament.livewire.notifications', \Filament\Livewire\Notifications::class); \Livewire\Livewire::component('filament.livewire.database-notifications', \Filament\Livewire\DatabaseNotifications::class); diff --git a/app/Providers/AuthServiceProvider.php b/app/Providers/AuthServiceProvider.php index 5522aa2..61c49bf 100644 --- a/app/Providers/AuthServiceProvider.php +++ b/app/Providers/AuthServiceProvider.php @@ -13,7 +13,8 @@ class AuthServiceProvider extends ServiceProvider * @var array */ protected $policies = [ - // 'App\Models\Model' => 'App\Policies\ModelPolicy', + \App\Models\FeatureFlag::class => \App\Policies\FeatureFlagPolicy::class, + \App\Models\FeatureFlagOverride::class => \App\Policies\FeatureFlagOverridePolicy::class, ]; /** diff --git a/resources/views/filament/components/speed-button-nav-bar.blade.php b/resources/views/filament/components/speed-button-nav-bar.blade.php index 9ed7a9e..2fe1ee6 100644 --- a/resources/views/filament/components/speed-button-nav-bar.blade.php +++ b/resources/views/filament/components/speed-button-nav-bar.blade.php @@ -20,8 +20,18 @@ ['displayName' => 'Munkanaptárak', 'link' => \App\Filament\Resources\WorkCalendars\WorkCalendarResource::getUrl()], ]], ['name' => 'calendarTest', 'displayName' => 'Naptár Teszt', 'icon' => 'idopontok', 'link' => \App\Filament\Pages\CalendarTest::getUrl(), 'roles' => ['root','developer','admin']], - ['name' => 'featureFlags', 'displayName' => 'Feature flagek', 'icon' => 'setup', 'link' => \App\Filament\Resources\FeatureFlags\FeatureFlagResource::getUrl(), 'roles' => ['root','developer']], ]; + + // A feature flag admin felület megjelenítését nem statikus role-listával, + // hanem a FeatureFlagResource jogosultság-ellenőrzésével (FeatureFlagPolicy, + // ami a "FeatureFlagAdmin" flaget nézi) döntjük el - így a hozzáférés + // deploy nélkül, az admin felületről szabályozható. + if (\App\Filament\Resources\FeatureFlags\FeatureFlagResource::canViewAny()) { + $modules[] = ['name' => 'featureFlags', 'displayName' => 'Feature flagek', 'icon' => 'setup', 'roles' => ['root','developer','admin','profit-center'], 'children' => [ + ['displayName' => 'Flagek', 'link' => \App\Filament\Resources\FeatureFlags\FeatureFlagResource::getUrl()], + ['displayName' => 'Egyéni felülbírálások', 'link' => \App\Filament\Resources\FeatureFlagOverrides\FeatureFlagOverrideResource::getUrl()], + ]]; + } @endphp