diff --git a/README.md b/README.md index c48dd19..fee049b 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,8 @@ https://github.com/aiiddqd/casetracker ![collage](docs/files/collage.jpg) +![collage](docs/files/collage.jpg) + # Core Concepts - inspired by GitHub Issues Tracker, Notion Tables, Jira and FreeScout - Use Adaptive Case Managment as key ideas diff --git a/code/app/AppPanelProvider.php b/code/app/AppPanelProvider.php index 7e1d744..4553656 100644 --- a/code/app/AppPanelProvider.php +++ b/code/app/AppPanelProvider.php @@ -46,6 +46,9 @@ public function panel(Panel $panel): Panel Widgets\AccountWidget::class, Widgets\FilamentInfoWidget::class, ]) + ->plugins([ + \BezhanSalleh\FilamentShield\FilamentShieldPlugin::make() + ]) ->middleware([ EncryptCookies::class, AddQueuedCookiesToResponse::class, diff --git a/code/app/Models/User.php b/code/app/Models/User.php index 84565a4..89ee3af 100644 --- a/code/app/Models/User.php +++ b/code/app/Models/User.php @@ -8,12 +8,14 @@ use Illuminate\Notifications\Notifiable; use Laravel\Sanctum\HasApiTokens; use Filament\Models\Contracts\FilamentUser; -use Filament\Panel; +use Spatie\Permission\Traits\HasRoles; + class User extends Authenticatable { use HasApiTokens, HasFactory, Notifiable; + use HasRoles; /** * The attributes that are mass assignable. diff --git a/code/app/Policies/GroupPolicy.php b/code/app/Policies/GroupPolicy.php new file mode 100644 index 0000000..b981544 --- /dev/null +++ b/code/app/Policies/GroupPolicy.php @@ -0,0 +1,108 @@ +can('view_any_group'); + } + + /** + * Determine whether the user can view the model. + */ + public function view(User $user, Group $group): bool + { + return $user->can('view_group'); + } + + /** + * Determine whether the user can create models. + */ + public function create(User $user): bool + { + return $user->can('create_group'); + } + + /** + * Determine whether the user can update the model. + */ + public function update(User $user, Group $group): bool + { + return $user->can('update_group'); + } + + /** + * Determine whether the user can delete the model. + */ + public function delete(User $user, Group $group): bool + { + return $user->can('delete_group'); + } + + /** + * Determine whether the user can bulk delete. + */ + public function deleteAny(User $user): bool + { + return $user->can('delete_any_group'); + } + + /** + * Determine whether the user can permanently delete. + */ + public function forceDelete(User $user, Group $group): bool + { + return $user->can('force_delete_group'); + } + + /** + * Determine whether the user can permanently bulk delete. + */ + public function forceDeleteAny(User $user): bool + { + return $user->can('force_delete_any_group'); + } + + /** + * Determine whether the user can restore. + */ + public function restore(User $user, Group $group): bool + { + return $user->can('restore_group'); + } + + /** + * Determine whether the user can bulk restore. + */ + public function restoreAny(User $user): bool + { + return $user->can('restore_any_group'); + } + + /** + * Determine whether the user can replicate. + */ + public function replicate(User $user, Group $group): bool + { + return $user->can('replicate_group'); + } + + /** + * Determine whether the user can reorder. + */ + public function reorder(User $user): bool + { + return $user->can('reorder_group'); + } +} diff --git a/code/app/Policies/IssuePolicy.php b/code/app/Policies/IssuePolicy.php new file mode 100644 index 0000000..d67c60a --- /dev/null +++ b/code/app/Policies/IssuePolicy.php @@ -0,0 +1,108 @@ +can('view_any_issue'); + } + + /** + * Determine whether the user can view the model. + */ + public function view(User $user, Issue $issue): bool + { + return $user->can('view_issue'); + } + + /** + * Determine whether the user can create models. + */ + public function create(User $user): bool + { + return $user->can('create_issue'); + } + + /** + * Determine whether the user can update the model. + */ + public function update(User $user, Issue $issue): bool + { + return $user->can('update_issue'); + } + + /** + * Determine whether the user can delete the model. + */ + public function delete(User $user, Issue $issue): bool + { + return $user->can('delete_issue'); + } + + /** + * Determine whether the user can bulk delete. + */ + public function deleteAny(User $user): bool + { + return $user->can('delete_any_issue'); + } + + /** + * Determine whether the user can permanently delete. + */ + public function forceDelete(User $user, Issue $issue): bool + { + return $user->can('force_delete_issue'); + } + + /** + * Determine whether the user can permanently bulk delete. + */ + public function forceDeleteAny(User $user): bool + { + return $user->can('force_delete_any_issue'); + } + + /** + * Determine whether the user can restore. + */ + public function restore(User $user, Issue $issue): bool + { + return $user->can('restore_issue'); + } + + /** + * Determine whether the user can bulk restore. + */ + public function restoreAny(User $user): bool + { + return $user->can('restore_any_issue'); + } + + /** + * Determine whether the user can replicate. + */ + public function replicate(User $user, Issue $issue): bool + { + return $user->can('replicate_issue'); + } + + /** + * Determine whether the user can reorder. + */ + public function reorder(User $user): bool + { + return $user->can('reorder_issue'); + } +} diff --git a/code/app/Policies/RolePolicy.php b/code/app/Policies/RolePolicy.php new file mode 100644 index 0000000..ec0381d --- /dev/null +++ b/code/app/Policies/RolePolicy.php @@ -0,0 +1,108 @@ +can('view_any_role'); + } + + /** + * Determine whether the user can view the model. + */ + public function view(User $user, Role $role): bool + { + return $user->can('view_role'); + } + + /** + * Determine whether the user can create models. + */ + public function create(User $user): bool + { + return $user->can('create_role'); + } + + /** + * Determine whether the user can update the model. + */ + public function update(User $user, Role $role): bool + { + return $user->can('update_role'); + } + + /** + * Determine whether the user can delete the model. + */ + public function delete(User $user, Role $role): bool + { + return $user->can('delete_role'); + } + + /** + * Determine whether the user can bulk delete. + */ + public function deleteAny(User $user): bool + { + return $user->can('delete_any_role'); + } + + /** + * Determine whether the user can permanently delete. + */ + public function forceDelete(User $user, Role $role): bool + { + return $user->can('{{ ForceDelete }}'); + } + + /** + * Determine whether the user can permanently bulk delete. + */ + public function forceDeleteAny(User $user): bool + { + return $user->can('{{ ForceDeleteAny }}'); + } + + /** + * Determine whether the user can restore. + */ + public function restore(User $user, Role $role): bool + { + return $user->can('{{ Restore }}'); + } + + /** + * Determine whether the user can bulk restore. + */ + public function restoreAny(User $user): bool + { + return $user->can('{{ RestoreAny }}'); + } + + /** + * Determine whether the user can replicate. + */ + public function replicate(User $user, Role $role): bool + { + return $user->can('{{ Replicate }}'); + } + + /** + * Determine whether the user can reorder. + */ + public function reorder(User $user): bool + { + return $user->can('{{ Reorder }}'); + } +} diff --git a/code/app/Resources/GroupResource.php b/code/app/Resources/GroupResource.php index 7d63e10..6bdc599 100644 --- a/code/app/Resources/GroupResource.php +++ b/code/app/Resources/GroupResource.php @@ -18,7 +18,7 @@ class GroupResource extends Resource protected static ?string $model = Group::class; protected static ?array $types = ['project', 'process', 'misc']; - protected static ?string $navigationIcon = 'heroicon-o-rectangle-stack'; + protected static ?string $navigationIcon = 'heroicon-o-rectangle-group'; protected static ?int $navigationSort = 10; diff --git a/code/app/Resources/IssueResource.php b/code/app/Resources/IssueResource.php index 4ac6f78..dc42a61 100644 --- a/code/app/Resources/IssueResource.php +++ b/code/app/Resources/IssueResource.php @@ -58,8 +58,7 @@ public static function form(Form $form) : Form ->columnSpan('full') ->maxLength(255), Forms\Components\MarkdownEditor::make('description') - ->columnSpan('full') - ->required(), + ->columnSpan('full'), Forms\Components\Select::make('group_id') ->relationship('group', 'name') diff --git a/code/composer.json b/code/composer.json index 4dbbc79..aea1aa8 100644 --- a/code/composer.json +++ b/code/composer.json @@ -1,12 +1,13 @@ { "name": "case-tracker/core", - "version": "0.1.0-alfa2405-1", + "version": "0.1.240525", "type": "project", "description": "The skeleton application for the Laravel framework.", "keywords": ["laravel", "framework"], "license": "MIT", "require": { "php": "^8.1", + "bezhansalleh/filament-shield": "^3.2", "filament/filament": "^3.2", "guzzlehttp/guzzle": "^7.2", "laravel/framework": "^10.10", diff --git a/code/composer.lock b/code/composer.lock index 2168b87..d0aaaa0 100644 --- a/code/composer.lock +++ b/code/composer.lock @@ -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": "b2e3440667747ec2a10d7db930ce3520", + "content-hash": "37ff278b8734d4148e047a50cc685f5e", "packages": [ { "name": "anourvalar/eloquent-serialize", @@ -72,6 +72,92 @@ }, "time": "2024-03-22T12:56:46+00:00" }, + { + "name": "bezhansalleh/filament-shield", + "version": "3.2.5", + "source": { + "type": "git", + "url": "https://github.com/bezhanSalleh/filament-shield.git", + "reference": "aa1046dac93350da5dec62753783ea28a56310dc" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/bezhanSalleh/filament-shield/zipball/aa1046dac93350da5dec62753783ea28a56310dc", + "reference": "aa1046dac93350da5dec62753783ea28a56310dc", + "shasum": "" + }, + "require": { + "filament/filament": "^3.2", + "php": "^8.1", + "spatie/laravel-package-tools": "^1.9", + "spatie/laravel-permission": "^6.0" + }, + "require-dev": { + "larastan/larastan": "^2.0", + "laravel/pint": "^1.0", + "nunomaduro/collision": "^7.0|^8.0", + "orchestra/testbench": "^8.0|^9.0", + "pestphp/pest": "^2.34", + "pestphp/pest-plugin-laravel": "^2.3", + "phpstan/extension-installer": "^1.3", + "phpstan/phpstan-deprecation-rules": "^1.1", + "phpstan/phpstan-phpunit": "^1.3", + "phpunit/phpunit": "^10.1" + }, + "type": "library", + "extra": { + "laravel": { + "providers": [ + "BezhanSalleh\\FilamentShield\\FilamentShieldServiceProvider" + ], + "aliases": { + "FilamentShield": "BezhanSalleh\\FilamentShield\\Facades\\FilamentShield" + } + } + }, + "autoload": { + "psr-4": { + "BezhanSalleh\\FilamentShield\\": "src", + "BezhanSalleh\\FilamentShield\\Database\\Factories\\": "database/factories" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Bezhan Salleh", + "email": "bezhan_salleh@yahoo.com", + "role": "Developer" + } + ], + "description": "Filament support for `spatie/laravel-permission`.", + "homepage": "https://github.com/bezhansalleh/filament-shield", + "keywords": [ + "acl", + "bezhanSalleh", + "filament", + "filament-shield", + "laravel", + "permission", + "permissions", + "rbac", + "roles", + "security" + ], + "support": { + "issues": "https://github.com/bezhanSalleh/filament-shield/issues", + "source": "https://github.com/bezhanSalleh/filament-shield/tree/3.2.5" + }, + "funding": [ + { + "url": "https://github.com/bezhanSalleh", + "type": "github" + } + ], + "time": "2024-05-14T00:32:23+00:00" + }, { "name": "blade-ui-kit/blade-heroicons", "version": "2.3.0", @@ -5154,6 +5240,88 @@ ], "time": "2024-03-20T07:29:11+00:00" }, + { + "name": "spatie/laravel-permission", + "version": "6.7.0", + "source": { + "type": "git", + "url": "https://github.com/spatie/laravel-permission.git", + "reference": "17607924aa0aa89bc0153c2ce45ed7c55083367b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/spatie/laravel-permission/zipball/17607924aa0aa89bc0153c2ce45ed7c55083367b", + "reference": "17607924aa0aa89bc0153c2ce45ed7c55083367b", + "shasum": "" + }, + "require": { + "illuminate/auth": "^8.12|^9.0|^10.0|^11.0", + "illuminate/container": "^8.12|^9.0|^10.0|^11.0", + "illuminate/contracts": "^8.12|^9.0|^10.0|^11.0", + "illuminate/database": "^8.12|^9.0|^10.0|^11.0", + "php": "^8.0" + }, + "require-dev": { + "laravel/passport": "^11.0|^12.0", + "orchestra/testbench": "^6.23|^7.0|^8.0|^9.0", + "phpunit/phpunit": "^9.4|^10.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "6.x-dev", + "dev-master": "6.x-dev" + }, + "laravel": { + "providers": [ + "Spatie\\Permission\\PermissionServiceProvider" + ] + } + }, + "autoload": { + "files": [ + "src/helpers.php" + ], + "psr-4": { + "Spatie\\Permission\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Freek Van der Herten", + "email": "freek@spatie.be", + "homepage": "https://spatie.be", + "role": "Developer" + } + ], + "description": "Permission handling for Laravel 8.0 and up", + "homepage": "https://github.com/spatie/laravel-permission", + "keywords": [ + "acl", + "laravel", + "permission", + "permissions", + "rbac", + "roles", + "security", + "spatie" + ], + "support": { + "issues": "https://github.com/spatie/laravel-permission/issues", + "source": "https://github.com/spatie/laravel-permission/tree/6.7.0" + }, + "funding": [ + { + "url": "https://github.com/spatie", + "type": "github" + } + ], + "time": "2024-04-19T12:35:28+00:00" + }, { "name": "symfony/console", "version": "v6.4.7", diff --git a/code/config/filament-shield.php b/code/config/filament-shield.php new file mode 100644 index 0000000..5e1ce76 --- /dev/null +++ b/code/config/filament-shield.php @@ -0,0 +1,89 @@ + [ + 'should_register_navigation' => true, + 'slug' => 'shield/roles', + 'navigation_sort' => -1, + 'navigation_badge' => true, + 'navigation_group' => true, + 'is_globally_searchable' => false, + 'show_model_path' => true, + 'is_scoped_to_tenant' => true, + 'cluster' => null, + ], + + 'auth_provider_model' => [ + 'fqcn' => 'App\\Models\\User', + ], + + 'super_admin' => [ + 'enabled' => true, + 'name' => 'super_admin', + 'define_via_gate' => false, + 'intercept_gate' => 'before', // after + ], + + 'panel_user' => [ + 'enabled' => true, + 'name' => 'panel_user', + ], + + 'permission_prefixes' => [ + 'resource' => [ + 'view', + 'view_any', + 'create', + 'update', + 'restore', + 'restore_any', + 'replicate', + 'reorder', + 'delete', + 'delete_any', + 'force_delete', + 'force_delete_any', + ], + + 'page' => 'page', + 'widget' => 'widget', + ], + + 'entities' => [ + 'pages' => true, + 'widgets' => true, + 'resources' => true, + 'custom_permissions' => false, + ], + + 'generator' => [ + 'option' => 'policies_and_permissions', + 'policy_directory' => 'Policies', + 'policy_namespace' => 'Policies', + ], + + 'exclude' => [ + 'enabled' => true, + + 'pages' => [ + 'Dashboard', + ], + + 'widgets' => [ + 'AccountWidget', 'FilamentInfoWidget', + ], + + 'resources' => [], + ], + + 'discovery' => [ + 'discover_all_resources' => false, + 'discover_all_widgets' => false, + 'discover_all_pages' => false, + ], + + 'register_role_policy' => [ + 'enabled' => false, + ], + +]; diff --git a/code/config/permission.php b/code/config/permission.php new file mode 100644 index 0000000..2a520f3 --- /dev/null +++ b/code/config/permission.php @@ -0,0 +1,186 @@ + [ + + /* + * When using the "HasPermissions" trait from this package, we need to know which + * Eloquent model should be used to retrieve your permissions. Of course, it + * is often just the "Permission" model but you may use whatever you like. + * + * The model you want to use as a Permission model needs to implement the + * `Spatie\Permission\Contracts\Permission` contract. + */ + + 'permission' => Spatie\Permission\Models\Permission::class, + + /* + * When using the "HasRoles" trait from this package, we need to know which + * Eloquent model should be used to retrieve your roles. Of course, it + * is often just the "Role" model but you may use whatever you like. + * + * The model you want to use as a Role model needs to implement the + * `Spatie\Permission\Contracts\Role` contract. + */ + + 'role' => Spatie\Permission\Models\Role::class, + + ], + + 'table_names' => [ + + /* + * When using the "HasRoles" trait from this package, we need to know which + * table should be used to retrieve your roles. We have chosen a basic + * default value but you may easily change it to any table you like. + */ + + 'roles' => 'roles', + + /* + * When using the "HasPermissions" trait from this package, we need to know which + * table should be used to retrieve your permissions. We have chosen a basic + * default value but you may easily change it to any table you like. + */ + + 'permissions' => 'permissions', + + /* + * When using the "HasPermissions" trait from this package, we need to know which + * table should be used to retrieve your models permissions. We have chosen a + * basic default value but you may easily change it to any table you like. + */ + + 'model_has_permissions' => 'model_has_permissions', + + /* + * When using the "HasRoles" trait from this package, we need to know which + * table should be used to retrieve your models roles. We have chosen a + * basic default value but you may easily change it to any table you like. + */ + + 'model_has_roles' => 'model_has_roles', + + /* + * When using the "HasRoles" trait from this package, we need to know which + * table should be used to retrieve your roles permissions. We have chosen a + * basic default value but you may easily change it to any table you like. + */ + + 'role_has_permissions' => 'role_has_permissions', + ], + + 'column_names' => [ + /* + * Change this if you want to name the related pivots other than defaults + */ + 'role_pivot_key' => null, //default 'role_id', + 'permission_pivot_key' => null, //default 'permission_id', + + /* + * Change this if you want to name the related model primary key other than + * `model_id`. + * + * For example, this would be nice if your primary keys are all UUIDs. In + * that case, name this `model_uuid`. + */ + + 'model_morph_key' => 'model_id', + + /* + * Change this if you want to use the teams feature and your related model's + * foreign key is other than `team_id`. + */ + + 'team_foreign_key' => 'team_id', + ], + + /* + * When set to true, the method for checking permissions will be registered on the gate. + * Set this to false if you want to implement custom logic for checking permissions. + */ + + 'register_permission_check_method' => true, + + /* + * When set to true, Laravel\Octane\Events\OperationTerminated event listener will be registered + * this will refresh permissions on every TickTerminated, TaskTerminated and RequestTerminated + * NOTE: This should not be needed in most cases, but an Octane/Vapor combination benefited from it. + */ + 'register_octane_reset_listener' => false, + + /* + * Teams Feature. + * When set to true the package implements teams using the 'team_foreign_key'. + * If you want the migrations to register the 'team_foreign_key', you must + * set this to true before doing the migration. + * If you already did the migration then you must make a new migration to also + * add 'team_foreign_key' to 'roles', 'model_has_roles', and 'model_has_permissions' + * (view the latest version of this package's migration file) + */ + + 'teams' => false, + + /* + * Passport Client Credentials Grant + * When set to true the package will use Passports Client to check permissions + */ + + 'use_passport_client_credentials' => false, + + /* + * When set to true, the required permission names are added to exception messages. + * This could be considered an information leak in some contexts, so the default + * setting is false here for optimum safety. + */ + + 'display_permission_in_exception' => false, + + /* + * When set to true, the required role names are added to exception messages. + * This could be considered an information leak in some contexts, so the default + * setting is false here for optimum safety. + */ + + 'display_role_in_exception' => false, + + /* + * By default wildcard permission lookups are disabled. + * See documentation to understand supported syntax. + */ + + 'enable_wildcard_permission' => false, + + /* + * The class to use for interpreting wildcard permissions. + * If you need to modify delimiters, override the class and specify its name here. + */ + // 'permission.wildcard_permission' => Spatie\Permission\WildcardPermission::class, + + /* Cache-specific settings */ + + 'cache' => [ + + /* + * By default all permissions are cached for 24 hours to speed up performance. + * When permissions or roles are updated the cache is flushed automatically. + */ + + 'expiration_time' => \DateInterval::createFromDateString('24 hours'), + + /* + * The cache key used to store all permissions. + */ + + 'key' => 'spatie.permission.cache', + + /* + * You may optionally indicate a specific cache driver to use for permission and + * role caching using any of the `store` drivers listed in the cache.php config + * file. Using 'default' here means to use the `default` set in cache.php. + */ + + 'store' => 'default', + ], +]; diff --git a/code/database/migrations/2024_05_25_052152_create_permission_tables.php b/code/database/migrations/2024_05_25_052152_create_permission_tables.php new file mode 100644 index 0000000..b865d48 --- /dev/null +++ b/code/database/migrations/2024_05_25_052152_create_permission_tables.php @@ -0,0 +1,138 @@ +bigIncrements('id'); // permission id + $table->string('name'); // For MySQL 8.0 use string('name', 125); + $table->string('guard_name'); // For MySQL 8.0 use string('guard_name', 125); + $table->timestamps(); + + $table->unique(['name', 'guard_name']); + }); + + Schema::create($tableNames['roles'], function (Blueprint $table) use ($teams, $columnNames) { + $table->bigIncrements('id'); // role id + if ($teams || config('permission.testing')) { // permission.testing is a fix for sqlite testing + $table->unsignedBigInteger($columnNames['team_foreign_key'])->nullable(); + $table->index($columnNames['team_foreign_key'], 'roles_team_foreign_key_index'); + } + $table->string('name'); // For MySQL 8.0 use string('name', 125); + $table->string('guard_name'); // For MySQL 8.0 use string('guard_name', 125); + $table->timestamps(); + if ($teams || config('permission.testing')) { + $table->unique([$columnNames['team_foreign_key'], 'name', 'guard_name']); + } else { + $table->unique(['name', 'guard_name']); + } + }); + + Schema::create($tableNames['model_has_permissions'], function (Blueprint $table) use ($tableNames, $columnNames, $pivotPermission, $teams) { + $table->unsignedBigInteger($pivotPermission); + + $table->string('model_type'); + $table->unsignedBigInteger($columnNames['model_morph_key']); + $table->index([$columnNames['model_morph_key'], 'model_type'], 'model_has_permissions_model_id_model_type_index'); + + $table->foreign($pivotPermission) + ->references('id') // permission id + ->on($tableNames['permissions']) + ->onDelete('cascade'); + if ($teams) { + $table->unsignedBigInteger($columnNames['team_foreign_key']); + $table->index($columnNames['team_foreign_key'], 'model_has_permissions_team_foreign_key_index'); + + $table->primary([$columnNames['team_foreign_key'], $pivotPermission, $columnNames['model_morph_key'], 'model_type'], + 'model_has_permissions_permission_model_type_primary'); + } else { + $table->primary([$pivotPermission, $columnNames['model_morph_key'], 'model_type'], + 'model_has_permissions_permission_model_type_primary'); + } + + }); + + Schema::create($tableNames['model_has_roles'], function (Blueprint $table) use ($tableNames, $columnNames, $pivotRole, $teams) { + $table->unsignedBigInteger($pivotRole); + + $table->string('model_type'); + $table->unsignedBigInteger($columnNames['model_morph_key']); + $table->index([$columnNames['model_morph_key'], 'model_type'], 'model_has_roles_model_id_model_type_index'); + + $table->foreign($pivotRole) + ->references('id') // role id + ->on($tableNames['roles']) + ->onDelete('cascade'); + if ($teams) { + $table->unsignedBigInteger($columnNames['team_foreign_key']); + $table->index($columnNames['team_foreign_key'], 'model_has_roles_team_foreign_key_index'); + + $table->primary([$columnNames['team_foreign_key'], $pivotRole, $columnNames['model_morph_key'], 'model_type'], + 'model_has_roles_role_model_type_primary'); + } else { + $table->primary([$pivotRole, $columnNames['model_morph_key'], 'model_type'], + 'model_has_roles_role_model_type_primary'); + } + }); + + Schema::create($tableNames['role_has_permissions'], function (Blueprint $table) use ($tableNames, $pivotRole, $pivotPermission) { + $table->unsignedBigInteger($pivotPermission); + $table->unsignedBigInteger($pivotRole); + + $table->foreign($pivotPermission) + ->references('id') // permission id + ->on($tableNames['permissions']) + ->onDelete('cascade'); + + $table->foreign($pivotRole) + ->references('id') // role id + ->on($tableNames['roles']) + ->onDelete('cascade'); + + $table->primary([$pivotPermission, $pivotRole], 'role_has_permissions_permission_id_role_id_primary'); + }); + + app('cache') + ->store(config('permission.cache.store') != 'default' ? config('permission.cache.store') : null) + ->forget(config('permission.cache.key')); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + $tableNames = config('permission.table_names'); + + if (empty($tableNames)) { + throw new \Exception('Error: config/permission.php not found and defaults could not be merged. Please publish the package configuration before proceeding, or drop the tables manually.'); + } + + Schema::drop($tableNames['role_has_permissions']); + Schema::drop($tableNames['model_has_roles']); + Schema::drop($tableNames['model_has_permissions']); + Schema::drop($tableNames['roles']); + Schema::drop($tableNames['permissions']); + } +};