diff --git a/composer.json b/composer.json index af04be2..1122e44 100644 --- a/composer.json +++ b/composer.json @@ -52,6 +52,7 @@ }, "require-dev": { "laravel/pint": "^1.21", + "laravel/telescope": "^5.14", "orchestra/testbench": "^9.9|^10.0", "pestphp/pest": "^3.7", "pestphp/pest-plugin-livewire": "^3.0" diff --git a/src/EclipseWorldServiceProvider.php b/src/EclipseWorldServiceProvider.php index 837780b..20b5ebf 100644 --- a/src/EclipseWorldServiceProvider.php +++ b/src/EclipseWorldServiceProvider.php @@ -5,11 +5,11 @@ use Eclipse\World\Console\Commands\ImportCommand; use Eclipse\World\Console\Commands\ImportPostsCommand; use Eclipse\World\Console\Commands\ImportTariffCodesCommand; -use Eclipse\World\Filament\Resources\CountryResource; -use Eclipse\World\Filament\Resources\CurrencyResource; -use Eclipse\World\Filament\Resources\PostResource; -use Eclipse\World\Filament\Resources\RegionResource; -use Eclipse\World\Filament\Resources\TariffCodeResource; +use Eclipse\World\Filament\Clusters\World\Resources\CountryResource; +use Eclipse\World\Filament\Clusters\World\Resources\CurrencyResource; +use Eclipse\World\Filament\Clusters\World\Resources\PostResource; +use Eclipse\World\Filament\Clusters\World\Resources\RegionResource; +use Eclipse\World\Filament\Clusters\World\Resources\TariffCodeResource; use Spatie\LaravelPackageTools\Package; use Spatie\LaravelPackageTools\PackageServiceProvider; @@ -39,69 +39,27 @@ public function boot(): void $this->app->booted(function () { $manage = config('filament-shield.resources.manage', []); - $pluginManage = [ - CountryResource::class => [ - 'viewAny', - 'view', - 'create', - 'update', - 'restore', - 'restoreAny', - 'delete', - 'deleteAny', - 'forceDelete', - 'forceDeleteAny', - ], - RegionResource::class => [ - 'viewAny', - 'view', - 'create', - 'update', - 'restore', - 'restoreAny', - 'delete', - 'deleteAny', - 'forceDelete', - 'forceDeleteAny', - ], - CurrencyResource::class => [ - 'viewAny', - 'view', - 'create', - 'update', - 'restore', - 'restoreAny', - 'delete', - 'deleteAny', - 'forceDelete', - 'forceDeleteAny', - ], - TariffCodeResource::class => [ - 'viewAny', - 'view', - 'create', - 'update', - 'restore', - 'restoreAny', - 'delete', - 'deleteAny', - 'forceDelete', - 'forceDeleteAny', - ], - PostResource::class => [ - 'viewAny', - 'view', - 'create', - 'update', - 'restore', - 'restoreAny', - 'delete', - 'deleteAny', - 'forceDelete', - 'forceDeleteAny', - ], + $standardPermissions = [ + 'viewAny', + 'view', + 'create', + 'update', + 'restore', + 'restoreAny', + 'delete', + 'deleteAny', + 'forceDelete', + 'forceDeleteAny', ]; + $pluginManage = array_fill_keys([ + CountryResource::class, + RegionResource::class, + CurrencyResource::class, + TariffCodeResource::class, + PostResource::class, + ], $standardPermissions); + $manage = array_replace_recursive($manage, $pluginManage); config()->set('filament-shield.resources.manage', $manage); }); diff --git a/src/Filament/Clusters/World/Resources/CountryResource.php b/src/Filament/Clusters/World/Resources/CountryResource.php index 90208e6..4be5011 100644 --- a/src/Filament/Clusters/World/Resources/CountryResource.php +++ b/src/Filament/Clusters/World/Resources/CountryResource.php @@ -3,6 +3,7 @@ namespace Eclipse\World\Filament\Clusters\World\Resources; use Closure; +use Eclipse\Common\Filament\Concerns\HasCachedAbilityChecks; use Eclipse\World\Filament\Clusters\World; use Eclipse\World\Filament\Clusters\World\Resources\CountryResource\Pages\ListCountries; use Eclipse\World\Models\Country; @@ -28,11 +29,14 @@ use Filament\Tables\Filters\TrashedFilter; use Filament\Tables\Table; use Illuminate\Database\Eloquent\Builder; +use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\SoftDeletingScope; use TangoDevIt\FilamentEmojiPicker\EmojiPickerAction; class CountryResource extends Resource { + use HasCachedAbilityChecks; + protected static ?string $model = Country::class; protected static ?string $slug = 'countries'; @@ -41,6 +45,51 @@ class CountryResource extends Resource protected static ?string $cluster = World::class; + public static function canUpdateAny(): bool + { + return static::canOnce('update_country'); + } + + public static function canDeleteAny(): bool + { + return static::canOnce('delete_country'); + } + + public static function canRestoreAny(): bool + { + return static::canOnce('restore_country'); + } + + public static function canForceDeleteAny(): bool + { + return static::canOnce('force_delete_country'); + } + + public static function canBulkDelete(): bool + { + return static::canOnce('delete_any_country'); + } + + public static function canEdit(Model $record): bool + { + return static::canUpdateAny(); + } + + public static function canDelete(Model $record): bool + { + return static::canDeleteAny() && ! $record->trashed(); + } + + public static function canRestore(Model $record): bool + { + return static::canRestoreAny() && $record->trashed(); + } + + public static function canForceDelete(Model $record): bool + { + return static::canForceDeleteAny() && $record->trashed(); + } + public static function form(Schema $schema): Schema { return $schema @@ -146,7 +195,6 @@ function ($get) { public static function table(Table $table): Table { return $table - ->defaultPaginationPageOption(50) ->defaultSort('name') ->striped() ->columns([ @@ -185,7 +233,7 @@ public static function table(Table $table): Table TextColumn::make('special_regions') ->label(__('eclipse-world::countries.table.special_regions.label')) - ->getStateUsing(fn ($record) => $record->getSpecialRegionsAt()->pluck('name')->join(', ')) + ->getStateUsing(fn ($record) => $record->specialRegions->pluck('name')->join(', ')) ->placeholder('—') ->wrap(), ]) @@ -223,30 +271,45 @@ public static function table(Table $table): Table ->recordActions([ EditAction::make() ->label(__('eclipse-world::countries.actions.edit.label')) - ->modalHeading(__('eclipse-world::countries.actions.edit.heading')), + ->modalHeading(__('eclipse-world::countries.actions.edit.heading')) + ->authorize(fn () => self::canUpdateAny()), + ActionGroup::make([ DeleteAction::make() ->label(__('eclipse-world::countries.actions.delete.label')) - ->modalHeading(__('eclipse-world::countries.actions.delete.heading')), + ->modalHeading(__('eclipse-world::countries.actions.delete.heading')) + ->visible(fn (Country $record) => ! $record->trashed()) + ->authorize(fn () => self::canDeleteAny()), + RestoreAction::make() ->label(__('eclipse-world::countries.actions.restore.label')) - ->modalHeading(__('eclipse-world::countries.actions.restore.heading')), + ->modalHeading(__('eclipse-world::countries.actions.restore.heading')) + ->visible(fn (Country $record) => $record->trashed()) + ->authorize(fn () => self::canRestoreAny()), + ForceDeleteAction::make() ->label(__('eclipse-world::countries.actions.force_delete.label')) ->modalHeading(__('eclipse-world::countries.actions.force_delete.heading')) ->modalDescription(fn (Country $record): string => __('eclipse-world::countries.actions.force_delete.description', [ 'name' => $record->name, - ])), + ])) + ->visible(fn (Country $record) => $record->trashed()) + ->authorize(fn () => self::canForceDeleteAny()), ]), ]) ->toolbarActions([ BulkActionGroup::make([ DeleteBulkAction::make() - ->label(__('eclipse-world::countries.actions.delete.label')), + ->label(__('eclipse-world::countries.actions.delete.label')) + ->authorize(fn () => self::canBulkDelete()), + RestoreBulkAction::make() - ->label(__('eclipse-world::countries.actions.restore.label')), + ->label(__('eclipse-world::countries.actions.restore.label')) + ->authorize(fn () => self::canRestoreAny()), + ForceDeleteBulkAction::make() - ->label(__('eclipse-world::countries.actions.force_delete.label')), + ->label(__('eclipse-world::countries.actions.force_delete.label')) + ->authorize(fn () => self::canForceDeleteAny()), ]), ]); } @@ -263,6 +326,16 @@ public static function getEloquentQuery(): Builder return parent::getEloquentQuery() ->withoutGlobalScopes([ SoftDeletingScope::class, + ]) + ->with([ + 'region', + 'specialRegions' => function ($query) { + $query->wherePivot('start_date', '<=', now()) + ->where(function ($query) { + $query->whereNull('world_country_in_special_region.end_date') + ->orWhere('world_country_in_special_region.end_date', '>=', now()); + }); + }, ]); } diff --git a/src/Filament/Clusters/World/Resources/CurrencyResource.php b/src/Filament/Clusters/World/Resources/CurrencyResource.php index 7ef6e17..2e2ca5b 100644 --- a/src/Filament/Clusters/World/Resources/CurrencyResource.php +++ b/src/Filament/Clusters/World/Resources/CurrencyResource.php @@ -2,6 +2,7 @@ namespace Eclipse\World\Filament\Clusters\World\Resources; +use Eclipse\Common\Filament\Concerns\HasCachedAbilityChecks; use Eclipse\World\Filament\Clusters\World; use Eclipse\World\Filament\Clusters\World\Resources\CurrencyResource\Pages\ListCurrencies; use Eclipse\World\Models\Currency; @@ -23,10 +24,13 @@ use Filament\Tables\Filters\TrashedFilter; use Filament\Tables\Table; use Illuminate\Database\Eloquent\Builder; +use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\SoftDeletingScope; class CurrencyResource extends Resource { + use HasCachedAbilityChecks; + protected static ?string $model = Currency::class; protected static ?string $slug = 'currencies'; @@ -35,6 +39,51 @@ class CurrencyResource extends Resource protected static ?string $cluster = World::class; + public static function canUpdateAny(): bool + { + return static::canOnce('update_currency'); + } + + public static function canDeleteAny(): bool + { + return static::canOnce('delete_currency'); + } + + public static function canRestoreAny(): bool + { + return static::canOnce('restore_currency'); + } + + public static function canForceDeleteAny(): bool + { + return static::canOnce('force_delete_currency'); + } + + public static function canBulkDelete(): bool + { + return static::canOnce('delete_any_currency'); + } + + public static function canEdit(Model $record): bool + { + return static::canUpdateAny(); + } + + public static function canDelete(Model $record): bool + { + return static::canDeleteAny() && ! $record->trashed(); + } + + public static function canRestore(Model $record): bool + { + return static::canRestoreAny() && $record->trashed(); + } + + public static function canForceDelete(Model $record): bool + { + return static::canForceDeleteAny() && $record->trashed(); + } + public static function form(Schema $schema): Schema { return $schema @@ -59,7 +108,6 @@ public static function form(Schema $schema): Schema public static function table(Table $table): Table { return $table - ->defaultPaginationPageOption(50) ->defaultSort('name') ->striped() ->columns([ @@ -85,30 +133,45 @@ public static function table(Table $table): Table ->recordActions([ EditAction::make() ->label(__('eclipse-world::currencies.actions.edit.label')) - ->modalHeading(__('eclipse-world::currencies.actions.edit.heading')), + ->modalHeading(__('eclipse-world::currencies.actions.edit.heading')) + ->authorize(fn () => self::canUpdateAny()), + ActionGroup::make([ DeleteAction::make() ->label(__('eclipse-world::currencies.actions.delete.label')) - ->modalHeading(__('eclipse-world::currencies.actions.delete.heading')), + ->modalHeading(__('eclipse-world::currencies.actions.delete.heading')) + ->visible(fn (Currency $record) => ! $record->trashed()) + ->authorize(fn () => self::canDeleteAny()), + RestoreAction::make() ->label(__('eclipse-world::currencies.actions.restore.label')) - ->modalHeading(__('eclipse-world::currencies.actions.restore.heading')), + ->modalHeading(__('eclipse-world::currencies.actions.restore.heading')) + ->visible(fn (Currency $record) => $record->trashed()) + ->authorize(fn () => self::canRestoreAny()), + ForceDeleteAction::make() ->label(__('eclipse-world::currencies.actions.force_delete.label')) ->modalHeading(__('eclipse-world::currencies.actions.force_delete.heading')) ->modalDescription(fn (Currency $record): string => __('eclipse-world::currencies.actions.force_delete.description', [ 'name' => $record->name, - ])), + ])) + ->visible(fn (Currency $record) => $record->trashed()) + ->authorize(fn () => self::canForceDeleteAny()), ]), ]) ->toolbarActions([ BulkActionGroup::make([ DeleteBulkAction::make() - ->label(__('eclipse-world::currencies.actions.delete.label')), + ->label(__('eclipse-world::currencies.actions.delete.label')) + ->authorize(fn () => self::canBulkDelete()), + RestoreBulkAction::make() - ->label(__('eclipse-world::currencies.actions.restore.label')), + ->label(__('eclipse-world::currencies.actions.restore.label')) + ->authorize(fn () => self::canRestoreAny()), + ForceDeleteBulkAction::make() - ->label(__('eclipse-world::currencies.actions.force_delete.label')), + ->label(__('eclipse-world::currencies.actions.force_delete.label')) + ->authorize(fn () => self::canForceDeleteAny()), ]), ]); } diff --git a/src/Filament/Clusters/World/Resources/PostResource.php b/src/Filament/Clusters/World/Resources/PostResource.php index dcc7552..6d5ef18 100644 --- a/src/Filament/Clusters/World/Resources/PostResource.php +++ b/src/Filament/Clusters/World/Resources/PostResource.php @@ -2,6 +2,7 @@ namespace Eclipse\World\Filament\Clusters\World\Resources; +use Eclipse\Common\Filament\Concerns\HasCachedAbilityChecks; use Eclipse\World\Filament\Clusters\World; use Eclipse\World\Filament\Clusters\World\Resources\PostResource\Pages\ListPosts; use Eclipse\World\Models\Post; @@ -24,11 +25,14 @@ use Filament\Tables\Filters\TrashedFilter; use Filament\Tables\Table; use Illuminate\Database\Eloquent\Builder; +use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\SoftDeletingScope; use Illuminate\Validation\Rule; class PostResource extends Resource { + use HasCachedAbilityChecks; + protected static ?string $model = Post::class; protected static ?string $slug = 'posts'; @@ -37,6 +41,51 @@ class PostResource extends Resource protected static ?string $cluster = World::class; + public static function canUpdateAny(): bool + { + return static::canOnce('update_post'); + } + + public static function canDeleteAny(): bool + { + return static::canOnce('delete_post'); + } + + public static function canRestoreAny(): bool + { + return static::canOnce('restore_post'); + } + + public static function canForceDeleteAny(): bool + { + return static::canOnce('force_delete_post'); + } + + public static function canBulkDelete(): bool + { + return static::canOnce('delete_any_post'); + } + + public static function canEdit(Model $record): bool + { + return static::canUpdateAny(); + } + + public static function canDelete(Model $record): bool + { + return static::canDeleteAny() && ! $record->trashed(); + } + + public static function canRestore(Model $record): bool + { + return static::canRestoreAny() && $record->trashed(); + } + + public static function canForceDelete(Model $record): bool + { + return static::canForceDeleteAny() && $record->trashed(); + } + public static function form(Schema $schema): Schema { return $schema @@ -99,30 +148,45 @@ public static function table(Table $table): Table ->recordActions([ EditAction::make() ->label(__('eclipse-world::posts.actions.edit.label')) - ->modalHeading(__('eclipse-world::posts.actions.edit.heading')), + ->modalHeading(__('eclipse-world::posts.actions.edit.heading')) + ->authorize(fn () => self::canUpdateAny()), + ActionGroup::make([ DeleteAction::make() ->label(__('eclipse-world::posts.actions.delete.label')) - ->modalHeading(__('eclipse-world::posts.actions.delete.heading')), + ->modalHeading(__('eclipse-world::posts.actions.delete.heading')) + ->visible(fn (Post $record) => ! $record->trashed()) + ->authorize(fn () => self::canDeleteAny()), + RestoreAction::make() ->label(__('eclipse-world::posts.actions.restore.label')) - ->modalHeading(__('eclipse-world::posts.actions.restore.heading')), + ->modalHeading(__('eclipse-world::posts.actions.restore.heading')) + ->visible(fn (Post $record) => $record->trashed()) + ->authorize(fn () => self::canRestoreAny()), + ForceDeleteAction::make() ->label(__('eclipse-world::posts.actions.force_delete.label')) ->modalHeading(__('eclipse-world::posts.actions.force_delete.heading')) ->modalDescription(fn (Post $record): string => __('eclipse-world::posts.actions.force_delete.description', [ 'name' => $record->name, - ])), + ])) + ->visible(fn (Post $record) => $record->trashed()) + ->authorize(fn () => self::canForceDeleteAny()), ]), ]) ->toolbarActions([ BulkActionGroup::make([ DeleteBulkAction::make() - ->label(__('eclipse-world::posts.actions.delete.label')), + ->label(__('eclipse-world::posts.actions.delete.label')) + ->authorize(fn () => self::canBulkDelete()), + RestoreBulkAction::make() - ->label(__('eclipse-world::posts.actions.restore.label')), + ->label(__('eclipse-world::posts.actions.restore.label')) + ->authorize(fn () => self::canRestoreAny()), + ForceDeleteBulkAction::make() - ->label(__('eclipse-world::posts.actions.force_delete.label')), + ->label(__('eclipse-world::posts.actions.force_delete.label')) + ->authorize(fn () => self::canForceDeleteAny()), ]), ]); } @@ -139,7 +203,8 @@ public static function getEloquentQuery(): Builder return parent::getEloquentQuery() ->withoutGlobalScopes([ SoftDeletingScope::class, - ]); + ]) + ->with('country'); } public static function getNavigationLabel(): string diff --git a/src/Filament/Clusters/World/Resources/RegionResource.php b/src/Filament/Clusters/World/Resources/RegionResource.php index 807ecda..15c9049 100644 --- a/src/Filament/Clusters/World/Resources/RegionResource.php +++ b/src/Filament/Clusters/World/Resources/RegionResource.php @@ -2,6 +2,7 @@ namespace Eclipse\World\Filament\Clusters\World\Resources; +use Eclipse\Common\Filament\Concerns\HasCachedAbilityChecks; use Eclipse\World\Filament\Clusters\World; use Eclipse\World\Filament\Clusters\World\Resources\RegionResource\Pages\ListRegions; use Eclipse\World\Models\Region; @@ -25,10 +26,13 @@ use Filament\Tables\Filters\TrashedFilter; use Filament\Tables\Table; use Illuminate\Database\Eloquent\Builder; +use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\SoftDeletingScope; class RegionResource extends Resource { + use HasCachedAbilityChecks; + protected static ?string $model = Region::class; protected static ?string $slug = 'regions'; @@ -37,6 +41,51 @@ class RegionResource extends Resource protected static ?string $cluster = World::class; + public static function canUpdateAny(): bool + { + return static::canOnce('update_region'); + } + + public static function canDeleteAny(): bool + { + return static::canOnce('delete_region'); + } + + public static function canRestoreAny(): bool + { + return static::canOnce('restore_region'); + } + + public static function canForceDeleteAny(): bool + { + return static::canOnce('force_delete_region'); + } + + public static function canBulkDelete(): bool + { + return static::canOnce('delete_any_region'); + } + + public static function canEdit(Model $record): bool + { + return static::canUpdateAny(); + } + + public static function canDelete(Model $record): bool + { + return static::canDeleteAny() && ! $record->trashed(); + } + + public static function canRestore(Model $record): bool + { + return static::canRestoreAny() && $record->trashed(); + } + + public static function canForceDelete(Model $record): bool + { + return static::canForceDeleteAny() && $record->trashed(); + } + public static function form(Schema $schema): Schema { return $schema @@ -68,7 +117,6 @@ public static function form(Schema $schema): Schema public static function table(Table $table): Table { return $table - ->defaultPaginationPageOption(50) ->defaultSort('name') ->striped() ->columns([ @@ -97,23 +145,10 @@ public static function table(Table $table): Table TextColumn::make('countries_count') ->label(__('eclipse-world::regions.table.countries_count.label')) ->getStateUsing(function (Region $record): int { - if ($record->is_special) { - return $record->getCountriesInSpecialRegion()->count(); - } - - return $record->countries()->count(); + return $record->is_special ? $record->special_countries_count : $record->countries_count; }) ->sortable(query: function (Builder $query, string $direction): Builder { - return $query->withCount([ - 'countries', - 'specialCountries' => function ($query) { - $query->where('world_country_in_special_region.start_date', '<=', now()) - ->where(function ($query) { - $query->whereNull('world_country_in_special_region.end_date') - ->orWhere('world_country_in_special_region.end_date', '>=', now()); - }); - }, - ])->orderBy('countries_count', $direction); + return $query->orderBy('countries_count', $direction); }), TextColumn::make('children_count') @@ -138,30 +173,45 @@ public static function table(Table $table): Table ->recordActions([ EditAction::make() ->label(__('eclipse-world::regions.actions.edit.label')) - ->modalHeading(__('eclipse-world::regions.actions.edit.heading')), + ->modalHeading(__('eclipse-world::regions.actions.edit.heading')) + ->authorize(fn () => self::canUpdateAny()), + ActionGroup::make([ DeleteAction::make() ->label(__('eclipse-world::regions.actions.delete.label')) - ->modalHeading(__('eclipse-world::regions.actions.delete.heading')), + ->modalHeading(__('eclipse-world::regions.actions.delete.heading')) + ->visible(fn (Region $record) => ! $record->trashed()) + ->authorize(fn () => self::canDeleteAny()), + RestoreAction::make() ->label(__('eclipse-world::regions.actions.restore.label')) - ->modalHeading(__('eclipse-world::regions.actions.restore.heading')), + ->modalHeading(__('eclipse-world::regions.actions.restore.heading')) + ->visible(fn (Region $record) => $record->trashed()) + ->authorize(fn () => self::canRestoreAny()), + ForceDeleteAction::make() ->label(__('eclipse-world::regions.actions.force_delete.label')) ->modalHeading(__('eclipse-world::regions.actions.force_delete.heading')) ->modalDescription(fn (Region $record): string => __('eclipse-world::regions.actions.force_delete.description', [ 'name' => $record->name, - ])), + ])) + ->visible(fn (Region $record) => $record->trashed()) + ->authorize(fn () => self::canForceDeleteAny()), ]), ]) ->toolbarActions([ BulkActionGroup::make([ DeleteBulkAction::make() - ->label(__('eclipse-world::regions.actions.delete.label')), + ->label(__('eclipse-world::regions.actions.delete.label')) + ->authorize(fn () => self::canBulkDelete()), + RestoreBulkAction::make() - ->label(__('eclipse-world::regions.actions.restore.label')), + ->label(__('eclipse-world::regions.actions.restore.label')) + ->authorize(fn () => self::canRestoreAny()), + ForceDeleteBulkAction::make() - ->label(__('eclipse-world::regions.actions.force_delete.label')), + ->label(__('eclipse-world::regions.actions.force_delete.label')) + ->authorize(fn () => self::canForceDeleteAny()), ]), ]); } @@ -178,6 +228,17 @@ public static function getEloquentQuery(): Builder return parent::getEloquentQuery() ->withoutGlobalScopes([ SoftDeletingScope::class, + ]) + ->with('parent') + ->withCount([ + 'countries', + 'specialCountries' => function ($query) { + $query->where('world_country_in_special_region.start_date', '<=', now()) + ->where(function ($query) { + $query->whereNull('world_country_in_special_region.end_date') + ->orWhere('world_country_in_special_region.end_date', '>=', now()); + }); + }, ]); } diff --git a/src/Filament/Clusters/World/Resources/TariffCodeResource.php b/src/Filament/Clusters/World/Resources/TariffCodeResource.php index 43bc4ca..0dc7f09 100644 --- a/src/Filament/Clusters/World/Resources/TariffCodeResource.php +++ b/src/Filament/Clusters/World/Resources/TariffCodeResource.php @@ -2,6 +2,7 @@ namespace Eclipse\World\Filament\Clusters\World\Resources; +use Eclipse\Common\Filament\Concerns\HasCachedAbilityChecks; use Eclipse\World\Filament\Clusters\World; use Eclipse\World\Filament\Clusters\World\Resources\TariffCodeResource\Pages\ListTariffCodes; use Eclipse\World\Models\TariffCode; @@ -21,11 +22,13 @@ use Filament\Tables\Filters\TrashedFilter; use Filament\Tables\Table; use Illuminate\Database\Eloquent\Builder; +use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\SoftDeletingScope; use LaraZeus\SpatieTranslatable\Resources\Concerns\Translatable; class TariffCodeResource extends Resource { + use HasCachedAbilityChecks; use Translatable; protected static ?string $model = TariffCode::class; @@ -36,6 +39,51 @@ class TariffCodeResource extends Resource protected static ?string $cluster = World::class; + public static function canUpdateAny(): bool + { + return static::canOnce('update_tariff_code'); + } + + public static function canDeleteAny(): bool + { + return static::canOnce('delete_tariff_code'); + } + + public static function canRestoreAny(): bool + { + return static::canOnce('restore_tariff_code'); + } + + public static function canForceDeleteAny(): bool + { + return static::canOnce('force_delete_tariff_code'); + } + + public static function canBulkDelete(): bool + { + return static::canOnce('delete_any_tariff_code'); + } + + public static function canEdit(Model $record): bool + { + return static::canUpdateAny(); + } + + public static function canDelete(Model $record): bool + { + return static::canDeleteAny() && ! $record->trashed(); + } + + public static function canRestore(Model $record): bool + { + return static::canRestoreAny() && $record->trashed(); + } + + public static function canForceDelete(Model $record): bool + { + return static::canForceDeleteAny() && $record->trashed(); + } + public static function form(Schema $schema): Schema { return $schema->components([ @@ -65,7 +113,6 @@ public static function form(Schema $schema): Schema public static function table(Table $table): Table { return $table - ->defaultPaginationPageOption(50) ->defaultSort('code') ->striped() ->columns([ @@ -100,18 +147,33 @@ public static function table(Table $table): Table TrashedFilter::make(), ]) ->recordActions([ - EditAction::make(), + EditAction::make() + ->authorize(fn () => self::canUpdateAny()), + ActionGroup::make([ - DeleteAction::make(), - RestoreAction::make(), - ForceDeleteAction::make(), + DeleteAction::make() + ->visible(fn (TariffCode $record) => ! $record->trashed()) + ->authorize(fn () => self::canDeleteAny()), + + RestoreAction::make() + ->visible(fn (TariffCode $record) => $record->trashed()) + ->authorize(fn () => self::canRestoreAny()), + + ForceDeleteAction::make() + ->visible(fn (TariffCode $record) => $record->trashed()) + ->authorize(fn () => self::canForceDeleteAny()), ]), ]) ->toolbarActions([ BulkActionGroup::make([ - DeleteBulkAction::make(), - RestoreBulkAction::make(), - ForceDeleteBulkAction::make(), + DeleteBulkAction::make() + ->authorize(fn () => self::canBulkDelete()), + + RestoreBulkAction::make() + ->authorize(fn () => self::canRestoreAny()), + + ForceDeleteBulkAction::make() + ->authorize(fn () => self::canForceDeleteAny()), ]), ]); } diff --git a/testbench.yaml b/testbench.yaml index 2b8d0ef..0ae22bb 100644 --- a/testbench.yaml +++ b/testbench.yaml @@ -2,7 +2,7 @@ laravel: '@testbench' env: - DB_CONNECTION=sqlite - - DB_DATABASE=/app/workbench/database/database.sqlite + - DB_DATABASE=workbench/database/database.sqlite - APP_KEY=base64:ZQvPGC7uVADkjOgtGIIuCI8u3/Pzu+VaRObIbHsgjCc= - APP_ENV=local - APP_DEBUG=true diff --git a/workbench/app/Providers/AdminPanelProvider.php b/workbench/app/Providers/AdminPanelProvider.php index 9ef49f2..497427f 100644 --- a/workbench/app/Providers/AdminPanelProvider.php +++ b/workbench/app/Providers/AdminPanelProvider.php @@ -17,6 +17,7 @@ use Illuminate\Session\Middleware\AuthenticateSession; use Illuminate\Session\Middleware\StartSession; use Illuminate\View\Middleware\ShareErrorsFromSession; +use Laravel\Telescope\Telescope; use LaraZeus\SpatieTranslatable\SpatieTranslatablePlugin; use Workbench\App\Http\Middleware\WorkbenchBootstrap; @@ -53,11 +54,23 @@ public function panel(Panel $panel): Panel ->viteTheme(false) ->pages([ Dashboard::class, + ]) + ->navigationItems([ + \Filament\Navigation\NavigationItem::make('Telescope') + ->url('/telescope') + ->icon('heroicon-o-magnifying-glass') + ->group('Development') + ->sort(1) + ->visible(fn (): bool => $this->app->environment('local', 'testing')), ]); } public function register(): void { parent::register(); + + if ($this->app->environment('local', 'testing')) { + Telescope::night(); + } } } diff --git a/workbench/app/Providers/WorkbenchServiceProvider.php b/workbench/app/Providers/WorkbenchServiceProvider.php index 62715cf..d925d83 100644 --- a/workbench/app/Providers/WorkbenchServiceProvider.php +++ b/workbench/app/Providers/WorkbenchServiceProvider.php @@ -5,6 +5,7 @@ use BezhanSalleh\FilamentShield\FilamentShieldServiceProvider; use Filament\FilamentServiceProvider; use Illuminate\Support\ServiceProvider; +use Laravel\Telescope\TelescopeServiceProvider; use Livewire\LivewireServiceProvider; use Spatie\Permission\PermissionServiceProvider; @@ -19,6 +20,7 @@ public function register(): void $this->app->register(FilamentShieldServiceProvider::class); $this->app->register(LivewireServiceProvider::class); $this->app->register(FilamentServiceProvider::class); + $this->app->register(TelescopeServiceProvider::class); $this->app->register(AdminPanelProvider::class); $this->app->register(AuthServiceProvider::class); } diff --git a/workbench/config/telescope.php b/workbench/config/telescope.php new file mode 100644 index 0000000..8e6fd1c --- /dev/null +++ b/workbench/config/telescope.php @@ -0,0 +1,206 @@ + env('TELESCOPE_ENABLED', true), + + /* + |-------------------------------------------------------------------------- + | Telescope Domain + |-------------------------------------------------------------------------- + | + | This is the subdomain where Telescope will be accessible from. If the + | setting is null, Telescope will reside under the same domain as the + | application. Otherwise, this value will be used as the subdomain. + | + */ + + 'domain' => env('TELESCOPE_DOMAIN'), + + /* + |-------------------------------------------------------------------------- + | Telescope Path + |-------------------------------------------------------------------------- + | + | This is the URI path where Telescope will be accessible from. Feel free + | to change this path to anything you like. Note that the URI will not + | affect the paths of its internal API that aren't exposed to users. + | + */ + + 'path' => env('TELESCOPE_PATH', 'telescope'), + + /* + |-------------------------------------------------------------------------- + | Telescope Storage Driver + |-------------------------------------------------------------------------- + | + | This configuration options determines the storage driver that will + | be used to store Telescope's data. In addition, you may set any + | custom options as needed by the particular driver you choose. + | + */ + + 'driver' => env('TELESCOPE_DRIVER', 'database'), + + 'storage' => [ + 'database' => [ + 'connection' => env('DB_CONNECTION', 'mysql'), + 'chunk' => 1000, + ], + ], + + /* + |-------------------------------------------------------------------------- + | Telescope Queue + |-------------------------------------------------------------------------- + | + | This configuration options determines the queue connection and queue + | which will be used to process ProcessPendingUpdate jobs. This can + | be changed if you would prefer to use a non-default connection. + | + */ + + 'queue' => [ + 'connection' => env('TELESCOPE_QUEUE_CONNECTION', null), + 'queue' => env('TELESCOPE_QUEUE', null), + 'delay' => env('TELESCOPE_QUEUE_DELAY', 10), + ], + + /* + |-------------------------------------------------------------------------- + | Telescope Route Middleware + |-------------------------------------------------------------------------- + | + | These middleware will be assigned to every Telescope route, giving you + | the chance to add your own middleware to this list or change any of + | the existing middleware. Or, you can simply stick with this list. + | + */ + + 'middleware' => [ + 'web', + Authorize::class, + ], + + /* + |-------------------------------------------------------------------------- + | Allowed / Ignored Paths & Commands + |-------------------------------------------------------------------------- + | + | The following array lists the URI paths and Artisan commands that will + | not be watched by Telescope. In addition to this list, some Laravel + | commands, like migrations and queue commands, are always ignored. + | + */ + + 'only_paths' => [ + // 'api/*' + ], + + 'ignore_paths' => [ + 'livewire*', + 'nova-api*', + 'pulse*', + ], + + 'ignore_commands' => [ + // + ], + + /* + |-------------------------------------------------------------------------- + | Telescope Watchers + |-------------------------------------------------------------------------- + | + | The following array lists the "watchers" that will be registered with + | Telescope. The watchers gather the application's profile data when + | a request or task is executed. Feel free to customize this list. + | + */ + + 'watchers' => [ + Watchers\BatchWatcher::class => env('TELESCOPE_BATCH_WATCHER', true), + + Watchers\CacheWatcher::class => [ + 'enabled' => env('TELESCOPE_CACHE_WATCHER', true), + 'hidden' => [], + ], + + Watchers\ClientRequestWatcher::class => env('TELESCOPE_CLIENT_REQUEST_WATCHER', true), + + Watchers\CommandWatcher::class => [ + 'enabled' => env('TELESCOPE_COMMAND_WATCHER', true), + 'ignore' => [], + ], + + Watchers\DumpWatcher::class => [ + 'enabled' => env('TELESCOPE_DUMP_WATCHER', true), + 'always' => env('TELESCOPE_DUMP_WATCHER_ALWAYS', false), + ], + + Watchers\EventWatcher::class => [ + 'enabled' => env('TELESCOPE_EVENT_WATCHER', true), + 'ignore' => [], + ], + + Watchers\ExceptionWatcher::class => env('TELESCOPE_EXCEPTION_WATCHER', true), + + Watchers\GateWatcher::class => [ + 'enabled' => env('TELESCOPE_GATE_WATCHER', true), + 'ignore_abilities' => [], + 'ignore_packages' => true, + 'ignore_paths' => [], + ], + + Watchers\JobWatcher::class => env('TELESCOPE_JOB_WATCHER', true), + + Watchers\LogWatcher::class => [ + 'enabled' => env('TELESCOPE_LOG_WATCHER', true), + 'level' => 'error', + ], + + Watchers\MailWatcher::class => env('TELESCOPE_MAIL_WATCHER', true), + + Watchers\ModelWatcher::class => [ + 'enabled' => env('TELESCOPE_MODEL_WATCHER', true), + 'events' => ['eloquent.*'], + 'hydrations' => true, + ], + + Watchers\NotificationWatcher::class => env('TELESCOPE_NOTIFICATION_WATCHER', true), + + Watchers\QueryWatcher::class => [ + 'enabled' => env('TELESCOPE_QUERY_WATCHER', true), + 'ignore_packages' => true, + 'ignore_paths' => [], + 'slow' => 100, + ], + + Watchers\RedisWatcher::class => env('TELESCOPE_REDIS_WATCHER', true), + + Watchers\RequestWatcher::class => [ + 'enabled' => env('TELESCOPE_REQUEST_WATCHER', true), + 'size_limit' => env('TELESCOPE_RESPONSE_SIZE_LIMIT', 64), + 'ignore_http_methods' => [], + 'ignore_status_codes' => [], + ], + + Watchers\ScheduleWatcher::class => env('TELESCOPE_SCHEDULE_WATCHER', true), + Watchers\ViewWatcher::class => env('TELESCOPE_VIEW_WATCHER', true), + ], +]; diff --git a/workbench/database/migrations/2025_10_11_161954_create_telescope_entries_table.php b/workbench/database/migrations/2025_10_11_161954_create_telescope_entries_table.php new file mode 100644 index 0000000..700a83f --- /dev/null +++ b/workbench/database/migrations/2025_10_11_161954_create_telescope_entries_table.php @@ -0,0 +1,70 @@ +getConnection()); + + $schema->create('telescope_entries', function (Blueprint $table) { + $table->bigIncrements('sequence'); + $table->uuid('uuid'); + $table->uuid('batch_id'); + $table->string('family_hash')->nullable(); + $table->boolean('should_display_on_index')->default(true); + $table->string('type', 20); + $table->longText('content'); + $table->dateTime('created_at')->nullable(); + + $table->unique('uuid'); + $table->index('batch_id'); + $table->index('family_hash'); + $table->index('created_at'); + $table->index(['type', 'should_display_on_index']); + }); + + $schema->create('telescope_entries_tags', function (Blueprint $table) { + $table->uuid('entry_uuid'); + $table->string('tag'); + + $table->primary(['entry_uuid', 'tag']); + $table->index('tag'); + + $table->foreign('entry_uuid') + ->references('uuid') + ->on('telescope_entries') + ->onDelete('cascade'); + }); + + $schema->create('telescope_monitoring', function (Blueprint $table) { + $table->string('tag')->primary(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + $schema = Schema::connection($this->getConnection()); + + $schema->dropIfExists('telescope_entries_tags'); + $schema->dropIfExists('telescope_entries'); + $schema->dropIfExists('telescope_monitoring'); + } +};