From 2e38866507e469d61967b5fb54d3af13c6466992 Mon Sep 17 00:00:00 2001 From: Kilian Trunk Date: Sat, 9 Aug 2025 15:52:37 +0200 Subject: [PATCH 01/45] feat(worbench-setup): prepare base setup --- .lando.dist.yml | 24 +++++++++++++++---- README.md | 21 ++++++++++++++++ testbench.yaml | 2 ++ .../app/Providers/AdminPanelProvider.php | 3 +-- 4 files changed, 44 insertions(+), 6 deletions(-) diff --git a/.lando.dist.yml b/.lando.dist.yml index 736c72f..b20e847 100644 --- a/.lando.dist.yml +++ b/.lando.dist.yml @@ -1,12 +1,28 @@ -#file: noinspection ComposeUnknownKeys -name: eclipse-world +#file: noinspection ComposeUnknownKeys,YAMLSchemaValidation +name: eclipse-world-plugin +recipe: laravel +config: + webroot: vendor/orchestra/testbench-core/laravel/public + php: '8.3' + via: nginx services: appserver: type: php:custom xdebug: "debug,develop,coverage" - via: cli + environment: + TZ: "Europe/Ljubljana" + APP_BASE_PATH: "/app/workbench" overrides: - image: slimdeluxe/php:8.2-v1.1 + image: slimdeluxe/php:8.3-v1.2 + platform: linux/amd64 + run: + - composer install --no-interaction --prefer-dist + - composer run setup --no-interaction + - composer run build --no-interaction + appserver_nginx: + scanner: + path: /admin/login + retry: 5 tooling: php: service: appserver diff --git a/README.md b/README.md index d955857..cf3f5b9 100644 --- a/README.md +++ b/README.md @@ -59,6 +59,27 @@ Should you want to contribute, please see the development guidelines in the [Dat ```` 4. You can now develop and run tests. Happy coding 😉 +#### Workbench + Lando (browser testing) + +This package ships with a minimal Testbench Workbench so you can run the Filament UI without a separate app: + +1. Start the container + ```shell + lando start + ``` +2. Install PHP deps and build the workbench skeleton (runs migrations and seeds a test user) + ```shell + lando composer install + ``` +3. Open the admin panel at `https://eclipse-world-plugin.lndo.site/admin/login` + - User: `test@example.com` + - Password: `password` + +Notes: +- The container serves `workbench/public` as the webroot. +- Use `lando test` for `package:test` and `lando testbench` for other Testbench commands. +- No Telescope, websockets or health checks are enabled to keep the setup minimal. + 💡 To manually test the plugin in the browser, see our [recommendation](https://github.com/DataLinx/eclipsephp-core/blob/main/docs/Documentation.md#-plugin-development), which is also [how Filament suggests package development](https://filamentphp.com/docs/3.x/support/contributing#developing-with-a-local-copy-of-filament). However, the plugin should be universal and not dependent on our app setup or core package. diff --git a/testbench.yaml b/testbench.yaml index 64a584a..e908f12 100644 --- a/testbench.yaml +++ b/testbench.yaml @@ -4,6 +4,7 @@ providers: - Workbench\App\Providers\WorkbenchServiceProvider migrations: + - vendor/orchestra/testbench-core/laravel/migrations - workbench/database/migrations seeders: @@ -24,6 +25,7 @@ workbench: - create-sqlite-db - db-wipe - migrate-fresh + - db-seed assets: - laravel-assets sync: diff --git a/workbench/app/Providers/AdminPanelProvider.php b/workbench/app/Providers/AdminPanelProvider.php index 538c5f7..9b140d8 100644 --- a/workbench/app/Providers/AdminPanelProvider.php +++ b/workbench/app/Providers/AdminPanelProvider.php @@ -47,6 +47,7 @@ public function panel(Panel $panel): Panel FilamentShieldPlugin::make(), EclipseWorld::make(), ]) + ->viteTheme(false) ->pages([ Dashboard::class, ]); @@ -55,7 +56,5 @@ public function panel(Panel $panel): Panel public function register(): void { parent::register(); - - FilamentView::registerRenderHook('panels::body.end', fn (): string => Blade::render("@vite('resources/js/app.js')")); } } From 64917d2c60a830eb0a23335291f1d191dbdb901b Mon Sep 17 00:00:00 2001 From: Kilian Trunk Date: Sat, 9 Aug 2025 17:01:57 +0200 Subject: [PATCH 02/45] chore(workbench-setup): setup fixes --- .lando.dist.yml | 1 + testbench.yaml | 4 - workbench/.env.example | 2 +- .../Providers/WorkbenchServiceProvider.php | 5 + workbench/config/app.php | 126 ++++++++++ workbench/config/auth.php | 115 ++++++++++ workbench/config/cache.php | 108 +++++++++ workbench/config/concurrency.php | 20 ++ workbench/config/cors.php | 34 +++ workbench/config/database.php | 173 ++++++++++++++ workbench/config/eclipse.php | 138 +++++++++++ workbench/config/filament-shield.php | 93 ++++++++ workbench/config/filament.php | 102 ++++++++ workbench/config/filesystems.php | 80 +++++++ workbench/config/hashing.php | 67 ++++++ workbench/config/horizon.php | 213 +++++++++++++++++ workbench/config/logging.php | 132 +++++++++++ workbench/config/permission.php | 202 ++++++++++++++++ workbench/config/queue.php | 112 +++++++++ workbench/config/session.php | 217 ++++++++++++++++++ workbench/config/themes.php | 36 +++ workbench/config/view.php | 36 +++ ..._08_09_143147_create_permission_tables.php | 136 +++++++++++ 23 files changed, 2147 insertions(+), 5 deletions(-) create mode 100644 workbench/config/app.php create mode 100644 workbench/config/auth.php create mode 100644 workbench/config/cache.php create mode 100644 workbench/config/concurrency.php create mode 100644 workbench/config/cors.php create mode 100644 workbench/config/database.php create mode 100644 workbench/config/eclipse.php create mode 100644 workbench/config/filament-shield.php create mode 100644 workbench/config/filament.php create mode 100644 workbench/config/filesystems.php create mode 100644 workbench/config/hashing.php create mode 100644 workbench/config/horizon.php create mode 100644 workbench/config/logging.php create mode 100644 workbench/config/permission.php create mode 100644 workbench/config/queue.php create mode 100644 workbench/config/session.php create mode 100644 workbench/config/themes.php create mode 100644 workbench/config/view.php create mode 100644 workbench/database/migrations/2025_08_09_143147_create_permission_tables.php diff --git a/.lando.dist.yml b/.lando.dist.yml index b20e847..a54a808 100644 --- a/.lando.dist.yml +++ b/.lando.dist.yml @@ -12,6 +12,7 @@ services: environment: TZ: "Europe/Ljubljana" APP_BASE_PATH: "/app/workbench" + TESTBENCH_WORKING_PATH: "/app" overrides: image: slimdeluxe/php:8.3-v1.2 platform: linux/amd64 diff --git a/testbench.yaml b/testbench.yaml index e908f12..e51e3a9 100644 --- a/testbench.yaml +++ b/testbench.yaml @@ -28,7 +28,3 @@ workbench: - db-seed assets: - laravel-assets - sync: - - from: storage - to: workbench/storage - reverse: true diff --git a/workbench/.env.example b/workbench/.env.example index e5488ab..284f43b 100644 --- a/workbench/.env.example +++ b/workbench/.env.example @@ -37,7 +37,7 @@ BROADCAST_CONNECTION=log FILESYSTEM_DISK=local QUEUE_CONNECTION=database -CACHE_STORE=database +CACHE_STORE=array # CACHE_PREFIX= MEMCACHED_HOST=127.0.0.1 diff --git a/workbench/app/Providers/WorkbenchServiceProvider.php b/workbench/app/Providers/WorkbenchServiceProvider.php index 5c21824..c5b013b 100644 --- a/workbench/app/Providers/WorkbenchServiceProvider.php +++ b/workbench/app/Providers/WorkbenchServiceProvider.php @@ -3,6 +3,7 @@ namespace Workbench\App\Providers; use Illuminate\Support\ServiceProvider; +use Workbench\Database\Seeders\DatabaseSeeder; class WorkbenchServiceProvider extends ServiceProvider { @@ -12,6 +13,10 @@ class WorkbenchServiceProvider extends ServiceProvider public function register(): void { $this->app->register(AdminPanelProvider::class); + + $this->app->bind('DatabaseSeeder', function ($app) { + return new DatabaseSeeder; + }); } /** diff --git a/workbench/config/app.php b/workbench/config/app.php new file mode 100644 index 0000000..f467267 --- /dev/null +++ b/workbench/config/app.php @@ -0,0 +1,126 @@ + env('APP_NAME', 'Laravel'), + + /* + |-------------------------------------------------------------------------- + | Application Environment + |-------------------------------------------------------------------------- + | + | This value determines the "environment" your application is currently + | running in. This may determine how you prefer to configure various + | services the application utilizes. Set this in your ".env" file. + | + */ + + 'env' => env('APP_ENV', 'production'), + + /* + |-------------------------------------------------------------------------- + | Application Debug Mode + |-------------------------------------------------------------------------- + | + | When your application is in debug mode, detailed error messages with + | stack traces will be shown on every error that occurs within your + | application. If disabled, a simple generic error page is shown. + | + */ + + 'debug' => (bool) env('APP_DEBUG', false), + + /* + |-------------------------------------------------------------------------- + | Application URL + |-------------------------------------------------------------------------- + | + | This URL is used by the console to properly generate URLs when using + | the Artisan command line tool. You should set this to the root of + | the application so that it's available within Artisan commands. + | + */ + + 'url' => env('APP_URL', 'http://localhost'), + + /* + |-------------------------------------------------------------------------- + | Application Timezone + |-------------------------------------------------------------------------- + | + | Here you may specify the default timezone for your application, which + | will be used by the PHP date and date-time functions. The timezone + | is set to "UTC" by default as it is suitable for most use cases. + | + */ + + 'timezone' => env('APP_TIMEZONE', 'UTC'), + + /* + |-------------------------------------------------------------------------- + | Application Locale Configuration + |-------------------------------------------------------------------------- + | + | The application locale determines the default locale that will be used + | by Laravel's translation / localization methods. This option can be + | set to any locale for which you plan to have translation strings. + | + */ + + 'locale' => env('APP_LOCALE', 'en'), + + 'fallback_locale' => env('APP_FALLBACK_LOCALE', 'en'), + + 'faker_locale' => env('APP_FAKER_LOCALE', 'en_US'), + + /* + |-------------------------------------------------------------------------- + | Encryption Key + |-------------------------------------------------------------------------- + | + | This key is utilized by Laravel's encryption services and should be set + | to a random, 32 character string to ensure that all encrypted values + | are secure. You should do this prior to deploying the application. + | + */ + + 'cipher' => 'AES-256-CBC', + + 'key' => env('APP_KEY'), + + 'previous_keys' => [ + ...array_filter( + explode(',', env('APP_PREVIOUS_KEYS', '')) + ), + ], + + /* + |-------------------------------------------------------------------------- + | Maintenance Mode Driver + |-------------------------------------------------------------------------- + | + | These configuration options determine the driver used to determine and + | manage Laravel's "maintenance mode" status. The "cache" driver will + | allow maintenance mode to be controlled across multiple machines. + | + | Supported drivers: "file", "cache" + | + */ + + 'maintenance' => [ + 'driver' => env('APP_MAINTENANCE_DRIVER', 'file'), + 'store' => env('APP_MAINTENANCE_STORE', 'database'), + ], + +]; diff --git a/workbench/config/auth.php b/workbench/config/auth.php new file mode 100644 index 0000000..14ee76c --- /dev/null +++ b/workbench/config/auth.php @@ -0,0 +1,115 @@ + [ + 'guard' => env('AUTH_GUARD', 'web'), + 'passwords' => env('AUTH_PASSWORD_BROKER', 'users'), + ], + + /* + |-------------------------------------------------------------------------- + | Authentication Guards + |-------------------------------------------------------------------------- + | + | Next, you may define every authentication guard for your application. + | Of course, a great default configuration has been defined for you + | which utilizes session storage plus the Eloquent user provider. + | + | All authentication guards have a user provider, which defines how the + | users are actually retrieved out of your database or other storage + | system used by the application. Typically, Eloquent is utilized. + | + | Supported: "session" + | + */ + + 'guards' => [ + 'web' => [ + 'driver' => 'session', + 'provider' => 'users', + ], + ], + + /* + |-------------------------------------------------------------------------- + | User Providers + |-------------------------------------------------------------------------- + | + | All authentication guards have a user provider, which defines how the + | users are actually retrieved out of your database or other storage + | system used by the application. Typically, Eloquent is utilized. + | + | If you have multiple user tables or models you may configure multiple + | providers to represent the model / table. These providers may then + | be assigned to any extra authentication guards you have defined. + | + | Supported: "database", "eloquent" + | + */ + + 'providers' => [ + 'users' => [ + 'driver' => 'eloquent', + 'model' => env('AUTH_MODEL', \Eclipse\Core\Models\User::class), + ], + + // 'users' => [ + // 'driver' => 'database', + // 'table' => 'users', + // ], + ], + + /* + |-------------------------------------------------------------------------- + | Resetting Passwords + |-------------------------------------------------------------------------- + | + | These configuration options specify the behavior of Laravel's password + | reset functionality, including the table utilized for token storage + | and the user provider that is invoked to actually retrieve users. + | + | The expiry time is the number of minutes that each reset token will be + | considered valid. This security feature keeps tokens short-lived so + | they have less time to be guessed. You may change this as needed. + | + | The throttle setting is the number of seconds a user must wait before + | generating more password reset tokens. This prevents the user from + | quickly generating a very large amount of password reset tokens. + | + */ + + 'passwords' => [ + 'users' => [ + 'provider' => 'users', + 'table' => env('AUTH_PASSWORD_RESET_TOKEN_TABLE', 'password_reset_tokens'), + 'expire' => 60, + 'throttle' => 60, + ], + ], + + /* + |-------------------------------------------------------------------------- + | Password Confirmation Timeout + |-------------------------------------------------------------------------- + | + | Here you may define the amount of seconds before a password confirmation + | window expires and users are asked to re-enter their password via the + | confirmation screen. By default, the timeout lasts for three hours. + | + */ + + 'password_timeout' => env('AUTH_PASSWORD_TIMEOUT', 10800), + +]; diff --git a/workbench/config/cache.php b/workbench/config/cache.php new file mode 100644 index 0000000..4319e97 --- /dev/null +++ b/workbench/config/cache.php @@ -0,0 +1,108 @@ + env('CACHE_STORE', 'array'), + + /* + |-------------------------------------------------------------------------- + | Cache Stores + |-------------------------------------------------------------------------- + | + | Here you may define all of the cache "stores" for your application as + | well as their drivers. You may even define multiple stores for the + | same cache driver to group types of items stored in your caches. + | + | Supported drivers: "array", "database", "file", "memcached", + | "redis", "dynamodb", "octane", "null" + | + */ + + 'stores' => [ + + 'array' => [ + 'driver' => 'array', + 'serialize' => false, + ], + + 'database' => [ + 'driver' => 'database', + 'connection' => env('DB_CACHE_CONNECTION'), + 'table' => env('DB_CACHE_TABLE', 'cache'), + 'lock_connection' => env('DB_CACHE_LOCK_CONNECTION'), + 'lock_table' => env('DB_CACHE_LOCK_TABLE'), + ], + + 'file' => [ + 'driver' => 'file', + 'path' => storage_path('framework/cache/data'), + 'lock_path' => storage_path('framework/cache/data'), + ], + + 'memcached' => [ + 'driver' => 'memcached', + 'persistent_id' => env('MEMCACHED_PERSISTENT_ID'), + 'sasl' => [ + env('MEMCACHED_USERNAME'), + env('MEMCACHED_PASSWORD'), + ], + 'options' => [ + // Memcached::OPT_CONNECT_TIMEOUT => 2000, + ], + 'servers' => [ + [ + 'host' => env('MEMCACHED_HOST', '127.0.0.1'), + 'port' => env('MEMCACHED_PORT', 11211), + 'weight' => 100, + ], + ], + ], + + 'redis' => [ + 'driver' => 'redis', + 'connection' => env('REDIS_CACHE_CONNECTION', 'cache'), + 'lock_connection' => env('REDIS_CACHE_LOCK_CONNECTION', 'default'), + ], + + 'dynamodb' => [ + 'driver' => 'dynamodb', + 'key' => env('AWS_ACCESS_KEY_ID'), + 'secret' => env('AWS_SECRET_ACCESS_KEY'), + 'region' => env('AWS_DEFAULT_REGION', 'us-east-1'), + 'table' => env('DYNAMODB_CACHE_TABLE', 'cache'), + 'endpoint' => env('DYNAMODB_ENDPOINT'), + ], + + 'octane' => [ + 'driver' => 'octane', + ], + + ], + + /* + |-------------------------------------------------------------------------- + | Cache Key Prefix + |-------------------------------------------------------------------------- + | + | When utilizing the APC, database, memcached, Redis, and DynamoDB cache + | stores, there might be other applications using the same cache. For + | that reason, you may prefix every cache key to avoid collisions. + | + */ + + 'prefix' => env('CACHE_PREFIX', Str::slug(env('APP_NAME', 'laravel'), '_').'_cache_'), + +]; diff --git a/workbench/config/concurrency.php b/workbench/config/concurrency.php new file mode 100644 index 0000000..cb8022b --- /dev/null +++ b/workbench/config/concurrency.php @@ -0,0 +1,20 @@ + env('CONCURRENCY_DRIVER', 'process'), + +]; diff --git a/workbench/config/cors.php b/workbench/config/cors.php new file mode 100644 index 0000000..8a39e6d --- /dev/null +++ b/workbench/config/cors.php @@ -0,0 +1,34 @@ + ['api/*', 'sanctum/csrf-cookie'], + + 'allowed_methods' => ['*'], + + 'allowed_origins' => ['*'], + + 'allowed_origins_patterns' => [], + + 'allowed_headers' => ['*'], + + 'exposed_headers' => [], + + 'max_age' => 0, + + 'supports_credentials' => false, + +]; diff --git a/workbench/config/database.php b/workbench/config/database.php new file mode 100644 index 0000000..125949e --- /dev/null +++ b/workbench/config/database.php @@ -0,0 +1,173 @@ + env('DB_CONNECTION', 'sqlite'), + + /* + |-------------------------------------------------------------------------- + | Database Connections + |-------------------------------------------------------------------------- + | + | Below are all of the database connections defined for your application. + | An example configuration is provided for each database system which + | is supported by Laravel. You're free to add / remove connections. + | + */ + + 'connections' => [ + + 'sqlite' => [ + 'driver' => 'sqlite', + 'url' => env('DB_URL'), + 'database' => env('DB_DATABASE', database_path('database.sqlite')), + 'prefix' => '', + 'foreign_key_constraints' => env('DB_FOREIGN_KEYS', true), + 'busy_timeout' => null, + 'journal_mode' => null, + 'synchronous' => null, + ], + + 'mysql' => [ + 'driver' => 'mysql', + 'url' => env('DB_URL'), + 'host' => env('DB_HOST', '127.0.0.1'), + 'port' => env('DB_PORT', '3306'), + 'database' => env('DB_DATABASE', 'laravel'), + 'username' => env('DB_USERNAME', 'root'), + 'password' => env('DB_PASSWORD', ''), + 'unix_socket' => env('DB_SOCKET', ''), + 'charset' => env('DB_CHARSET', 'utf8mb4'), + 'collation' => env('DB_COLLATION', 'utf8mb4_unicode_ci'), + 'prefix' => '', + 'prefix_indexes' => true, + 'strict' => true, + 'engine' => null, + 'options' => extension_loaded('pdo_mysql') ? array_filter([ + PDO::MYSQL_ATTR_SSL_CA => env('MYSQL_ATTR_SSL_CA'), + ]) : [], + ], + + 'mariadb' => [ + 'driver' => 'mariadb', + 'url' => env('DB_URL'), + 'host' => env('DB_HOST', '127.0.0.1'), + 'port' => env('DB_PORT', '3306'), + 'database' => env('DB_DATABASE', 'laravel'), + 'username' => env('DB_USERNAME', 'root'), + 'password' => env('DB_PASSWORD', ''), + 'unix_socket' => env('DB_SOCKET', ''), + 'charset' => env('DB_CHARSET', 'utf8mb4'), + 'collation' => env('DB_COLLATION', 'utf8mb4_unicode_ci'), + 'prefix' => '', + 'prefix_indexes' => true, + 'strict' => true, + 'engine' => null, + 'options' => extension_loaded('pdo_mysql') ? array_filter([ + PDO::MYSQL_ATTR_SSL_CA => env('MYSQL_ATTR_SSL_CA'), + ]) : [], + ], + + 'pgsql' => [ + 'driver' => 'pgsql', + 'url' => env('DB_URL'), + 'host' => env('DB_HOST', '127.0.0.1'), + 'port' => env('DB_PORT', '5432'), + 'database' => env('DB_DATABASE', 'laravel'), + 'username' => env('DB_USERNAME', 'root'), + 'password' => env('DB_PASSWORD', ''), + 'charset' => env('DB_CHARSET', 'utf8'), + 'prefix' => '', + 'prefix_indexes' => true, + 'search_path' => 'public', + 'sslmode' => 'prefer', + ], + + 'sqlsrv' => [ + 'driver' => 'sqlsrv', + 'url' => env('DB_URL'), + 'host' => env('DB_HOST', 'localhost'), + 'port' => env('DB_PORT', '1433'), + 'database' => env('DB_DATABASE', 'laravel'), + 'username' => env('DB_USERNAME', 'root'), + 'password' => env('DB_PASSWORD', ''), + 'charset' => env('DB_CHARSET', 'utf8'), + 'prefix' => '', + 'prefix_indexes' => true, + // 'encrypt' => env('DB_ENCRYPT', 'yes'), + // 'trust_server_certificate' => env('DB_TRUST_SERVER_CERTIFICATE', 'false'), + ], + + ], + + /* + |-------------------------------------------------------------------------- + | Migration Repository Table + |-------------------------------------------------------------------------- + | + | This table keeps track of all the migrations that have already run for + | your application. Using this information, we can determine which of + | the migrations on disk haven't actually been run on the database. + | + */ + + 'migrations' => [ + 'table' => 'migrations', + 'update_date_on_publish' => true, + ], + + /* + |-------------------------------------------------------------------------- + | Redis Databases + |-------------------------------------------------------------------------- + | + | Redis is an open source, fast, and advanced key-value store that also + | provides a richer body of commands than a typical key-value system + | such as Memcached. You may define your connection settings here. + | + */ + + 'redis' => [ + + 'client' => env('REDIS_CLIENT', 'phpredis'), + + 'options' => [ + 'cluster' => env('REDIS_CLUSTER', 'redis'), + 'prefix' => env('REDIS_PREFIX', Str::slug(env('APP_NAME', 'laravel'), '_').'_database_'), + ], + + 'default' => [ + 'url' => env('REDIS_URL'), + 'host' => env('REDIS_HOST', '127.0.0.1'), + 'username' => env('REDIS_USERNAME'), + 'password' => env('REDIS_PASSWORD'), + 'port' => env('REDIS_PORT', '6379'), + 'database' => env('REDIS_DB', '0'), + ], + + 'cache' => [ + 'url' => env('REDIS_URL'), + 'host' => env('REDIS_HOST', '127.0.0.1'), + 'username' => env('REDIS_USERNAME'), + 'password' => env('REDIS_PASSWORD'), + 'port' => env('REDIS_PORT', '6379'), + 'database' => env('REDIS_CACHE_DB', '1'), + ], + + ], + +]; diff --git a/workbench/config/eclipse.php b/workbench/config/eclipse.php new file mode 100644 index 0000000..c8bc38a --- /dev/null +++ b/workbench/config/eclipse.php @@ -0,0 +1,138 @@ + (bool) env('ECLIPSE_MULTI_SITE', false), + + /* + |-------------------------------------------------------------------------- + | Enable/disable user email verification + |-------------------------------------------------------------------------- + | Set this boolean to true if you want to enable parts of the application + | related to user email address verification + */ + 'email_verification' => (bool) env('ECLIPSE_EMAIL_VERIFICATION', false), + + /* + |-------------------------------------------------------------------------- + | Seeder setup + |-------------------------------------------------------------------------- + | Here you can specify any data you want seeded by default. + | All settings are optional. + */ + 'seed' => [ + 'roles' => [ + // Number of randomly generated roles + 'count' => 2, + // Roles with preset data + // Required attributes: name, guard_name + 'presets' => [ + [ + 'data' => [ + 'name' => 'admin', + 'guard_name' => 'web', + ], + ], + ], + ], + 'users' => [ + // Number of randomly generated users + 'count' => 5, + // Users with preset data + 'presets' => [ + [ + 'data' => [ + // Email is required + 'email' => 'test@example.com', + // Additional attributes — if any is omitted, faker will be used + 'first_name' => 'Test', + 'last_name' => 'User', + 'password' => 'test123', + ], + // Optional role(s) to set (for multiple, use an array) + 'role' => 'super_admin', + ], + [ + 'data' => [ + 'email' => 'admin@example.com', + ], + 'role' => 'admin', + ], + ], + ], + // Sites — only used if the multi-site feature is enabled above + 'sites' => [ + // Number of randomly generated sites + 'count' => 0, + // Sites with preset data + 'presets' => [ + [ + 'data' => [ + 'domain' => basename(config('app.url')), + 'name' => config('app.name'), + ], + ], + [ + 'data' => [ + 'domain' => 'another.lndo.site', + 'name' => 'Another site', + ], + ], + ], + ], + ], + + /* + |-------------------------------------------------------------------------- + | Developer logins + |-------------------------------------------------------------------------- + | Provide a list of users to use as config for the "Developer logins" + | Filament plugin + */ + 'developer_logins' => [ + 'Super admin' => 'test@example.com', + 'Admin' => 'admin@example.com', + ], + + /* + |-------------------------------------------------------------------------- + | Telescope package additional configuration + |-------------------------------------------------------------------------- + */ + 'telescope' => [ + /* + * Enable dark theme? + */ + 'dark_theme' => (bool) env('TELESCOPE_DARK_THEME', false), + ], + + /* + |-------------------------------------------------------------------------- + | Horizon package additional configuration + |-------------------------------------------------------------------------- + */ + 'horizon' => [ + /* + * List of email addresses of users that are allowed to view the Horizon + * panel in non-local environments + */ + 'emails' => [ + // + ], + ], + + /* + |-------------------------------------------------------------------------- + | Additional tools to be displayed in the "Tools" menu of the admin panel + |-------------------------------------------------------------------------- + */ + 'tools' => [ + 'phpmyadmin' => env('PHPMYADMIN_URL'), + ], + +]; diff --git a/workbench/config/filament-shield.php b/workbench/config/filament-shield.php new file mode 100644 index 0000000..85a08f0 --- /dev/null +++ b/workbench/config/filament-shield.php @@ -0,0 +1,93 @@ + [ + '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' => false, + 'cluster' => null, + ], + + 'tenant_model' => \Eclipse\Core\Models\Site::class, + + 'auth_provider_model' => [ + 'fqcn' => \Eclipse\Core\Models\User::class, + ], + + '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_any', + 'view', + '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' => [ + 'MailLogResource', + ], + ], + + 'discovery' => [ + 'discover_all_resources' => false, + 'discover_all_widgets' => false, + 'discover_all_pages' => false, + ], + + 'register_role_policy' => [ + 'enabled' => true, + ], + +]; diff --git a/workbench/config/filament.php b/workbench/config/filament.php new file mode 100644 index 0000000..e8fea8a --- /dev/null +++ b/workbench/config/filament.php @@ -0,0 +1,102 @@ + [ + + 'echo' => [ + 'broadcaster' => 'reverb', + 'key' => env('VITE_REVERB_APP_KEY'), + 'secret' => env('VITE_REVERB_APP_SECRET'), + 'app_id' => env('VITE_REVERB_APP_ID'), + 'wsHost' => env('VITE_REVERB_HOST'), + 'wsPort' => env('VITE_REVERB_PORT', 8080), + 'wssPort' => env('VITE_REVERB_PORT', 8080), + 'authEndpoint' => '/broadcasting/auth', + 'disableStats' => true, + 'encrypted' => true, + 'forceTLS' => true, + ], + + ], + + /* + |-------------------------------------------------------------------------- + | Default Filesystem Disk + |-------------------------------------------------------------------------- + | + | This is the storage disk Filament will use to store files. You may use + | any of the disks defined in the `config/filesystems.php`. + | + */ + + 'default_filesystem_disk' => env('FILAMENT_FILESYSTEM_DISK', 'public'), + + /* + |-------------------------------------------------------------------------- + | Assets Path + |-------------------------------------------------------------------------- + | + | This is the directory where Filament's assets will be published to. It + | is relative to the `public` directory of your Laravel application. + | + | After changing the path, you should run `php artisan filament:assets`. + | + */ + + 'assets_path' => null, + + /* + |-------------------------------------------------------------------------- + | Cache Path + |-------------------------------------------------------------------------- + | + | This is the directory that Filament will use to store cache files that + | are used to optimize the registration of components. + | + | After changing the path, you should run `php artisan filament:cache-components`. + | + */ + + 'cache_path' => base_path('bootstrap/cache/filament'), + + /* + |-------------------------------------------------------------------------- + | Livewire Loading Delay + |-------------------------------------------------------------------------- + | + | This sets the delay before loading indicators appear. + | + | Setting this to 'none' makes indicators appear immediately, which can be + | desirable for high-latency connections. Setting it to 'default' applies + | Livewire's standard 200ms delay. + | + */ + + 'livewire_loading_delay' => 'default', + + /* + |-------------------------------------------------------------------------- + | System Route Prefix + |-------------------------------------------------------------------------- + | + | This is the prefix used for the system routes that Filament registers, + | such as the routes for downloading exports and failed import rows. + | + */ + + 'system_route_prefix' => 'filament', + +]; diff --git a/workbench/config/filesystems.php b/workbench/config/filesystems.php new file mode 100644 index 0000000..3d671bd --- /dev/null +++ b/workbench/config/filesystems.php @@ -0,0 +1,80 @@ + env('FILESYSTEM_DISK', 'local'), + + /* + |-------------------------------------------------------------------------- + | Filesystem Disks + |-------------------------------------------------------------------------- + | + | Below you may configure as many filesystem disks as necessary, and you + | may even configure multiple disks for the same driver. Examples for + | most supported storage drivers are configured here for reference. + | + | Supported drivers: "local", "ftp", "sftp", "s3" + | + */ + + 'disks' => [ + + 'local' => [ + 'driver' => 'local', + 'root' => storage_path('app/private'), + 'serve' => true, + 'throw' => false, + 'report' => false, + ], + + 'public' => [ + 'driver' => 'local', + 'root' => storage_path('app/public'), + 'url' => env('APP_URL').'/storage', + 'visibility' => 'public', + 'throw' => false, + 'report' => false, + ], + + 's3' => [ + 'driver' => 's3', + 'key' => env('AWS_ACCESS_KEY_ID'), + 'secret' => env('AWS_SECRET_ACCESS_KEY'), + 'region' => env('AWS_DEFAULT_REGION'), + 'bucket' => env('AWS_BUCKET'), + 'url' => env('AWS_URL'), + 'endpoint' => env('AWS_ENDPOINT'), + 'use_path_style_endpoint' => env('AWS_USE_PATH_STYLE_ENDPOINT', false), + 'throw' => false, + 'report' => false, + ], + + ], + + /* + |-------------------------------------------------------------------------- + | Symbolic Links + |-------------------------------------------------------------------------- + | + | Here you may configure the symbolic links that will be created when the + | `storage:link` Artisan command is executed. The array keys should be + | the locations of the links and the values should be their targets. + | + */ + + 'links' => [ + public_path('storage') => storage_path('app/public'), + ], + +]; diff --git a/workbench/config/hashing.php b/workbench/config/hashing.php new file mode 100644 index 0000000..9eb408e --- /dev/null +++ b/workbench/config/hashing.php @@ -0,0 +1,67 @@ + env('HASH_DRIVER', 'bcrypt'), + + /* + |-------------------------------------------------------------------------- + | Bcrypt Options + |-------------------------------------------------------------------------- + | + | Here you may specify the configuration options that should be used when + | passwords are hashed using the Bcrypt algorithm. This will allow you + | to control the amount of time it takes to hash the given password. + | + */ + + 'bcrypt' => [ + 'rounds' => env('BCRYPT_ROUNDS', 12), + 'verify' => env('HASH_VERIFY', true), + ], + + /* + |-------------------------------------------------------------------------- + | Argon Options + |-------------------------------------------------------------------------- + | + | Here you may specify the configuration options that should be used when + | passwords are hashed using the Argon algorithm. These will allow you + | to control the amount of time it takes to hash the given password. + | + */ + + 'argon' => [ + 'memory' => env('ARGON_MEMORY', 65536), + 'threads' => env('ARGON_THREADS', 1), + 'time' => env('ARGON_TIME', 4), + 'verify' => env('HASH_VERIFY', true), + ], + + /* + |-------------------------------------------------------------------------- + | Rehash On Login + |-------------------------------------------------------------------------- + | + | Setting this option to true will tell Laravel to automatically rehash + | the user's password during login if the configured work factor for + | the algorithm has changed, allowing graceful upgrades of hashes. + | + */ + + 'rehash_on_login' => true, + +]; diff --git a/workbench/config/horizon.php b/workbench/config/horizon.php new file mode 100644 index 0000000..5101f6f --- /dev/null +++ b/workbench/config/horizon.php @@ -0,0 +1,213 @@ + env('HORIZON_DOMAIN'), + + /* + |-------------------------------------------------------------------------- + | Horizon Path + |-------------------------------------------------------------------------- + | + | This is the URI path where Horizon 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('HORIZON_PATH', 'horizon'), + + /* + |-------------------------------------------------------------------------- + | Horizon Redis Connection + |-------------------------------------------------------------------------- + | + | This is the name of the Redis connection where Horizon will store the + | meta information required for it to function. It includes the list + | of supervisors, failed jobs, job metrics, and other information. + | + */ + + 'use' => 'default', + + /* + |-------------------------------------------------------------------------- + | Horizon Redis Prefix + |-------------------------------------------------------------------------- + | + | This prefix will be used when storing all Horizon data in Redis. You + | may modify the prefix when you are running multiple installations + | of Horizon on the same server so that they don't have problems. + | + */ + + 'prefix' => env( + 'HORIZON_PREFIX', + Str::slug(env('APP_NAME', 'laravel'), '_').'_horizon:' + ), + + /* + |-------------------------------------------------------------------------- + | Horizon Route Middleware + |-------------------------------------------------------------------------- + | + | These middleware will get attached onto each Horizon 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'], + + /* + |-------------------------------------------------------------------------- + | Queue Wait Time Thresholds + |-------------------------------------------------------------------------- + | + | This option allows you to configure when the LongWaitDetected event + | will be fired. Every connection / queue combination may have its + | own, unique threshold (in seconds) before this event is fired. + | + */ + + 'waits' => [ + 'redis:default' => 60, + ], + + /* + |-------------------------------------------------------------------------- + | Job Trimming Times + |-------------------------------------------------------------------------- + | + | Here you can configure for how long (in minutes) you desire Horizon to + | persist the recent and failed jobs. Typically, recent jobs are kept + | for one hour while all failed jobs are stored for an entire week. + | + */ + + 'trim' => [ + 'recent' => 60, + 'pending' => 60, + 'completed' => 60, + 'recent_failed' => 10080, + 'failed' => 10080, + 'monitored' => 10080, + ], + + /* + |-------------------------------------------------------------------------- + | Silenced Jobs + |-------------------------------------------------------------------------- + | + | Silencing a job will instruct Horizon to not place the job in the list + | of completed jobs within the Horizon dashboard. This setting may be + | used to fully remove any noisy jobs from the completed jobs list. + | + */ + + 'silenced' => [ + // App\Jobs\ExampleJob::class, + ], + + /* + |-------------------------------------------------------------------------- + | Metrics + |-------------------------------------------------------------------------- + | + | Here you can configure how many snapshots should be kept to display in + | the metrics graph. This will get used in combination with Horizon's + | `horizon:snapshot` schedule to define how long to retain metrics. + | + */ + + 'metrics' => [ + 'trim_snapshots' => [ + 'job' => 24, + 'queue' => 24, + ], + ], + + /* + |-------------------------------------------------------------------------- + | Fast Termination + |-------------------------------------------------------------------------- + | + | When this option is enabled, Horizon's "terminate" command will not + | wait on all of the workers to terminate unless the --wait option + | is provided. Fast termination can shorten deployment delay by + | allowing a new instance of Horizon to start while the last + | instance will continue to terminate each of its workers. + | + */ + + 'fast_termination' => false, + + /* + |-------------------------------------------------------------------------- + | Memory Limit (MB) + |-------------------------------------------------------------------------- + | + | This value describes the maximum amount of memory the Horizon master + | supervisor may consume before it is terminated and restarted. For + | configuring these limits on your workers, see the next section. + | + */ + + 'memory_limit' => 64, + + /* + |-------------------------------------------------------------------------- + | Queue Worker Configuration + |-------------------------------------------------------------------------- + | + | Here you may define the queue worker settings used by your application + | in all environments. These supervisors and settings handle all your + | queued jobs and will be provisioned by Horizon during deployment. + | + */ + + 'defaults' => [ + 'supervisor-1' => [ + 'connection' => 'redis', + 'queue' => ['default'], + 'balance' => 'auto', + 'autoScalingStrategy' => 'time', + 'maxProcesses' => 1, + 'maxTime' => 0, + 'maxJobs' => 0, + 'memory' => 128, + 'tries' => 1, + 'timeout' => 60, + 'nice' => 0, + ], + ], + + 'environments' => [ + 'production' => [ + 'supervisor-1' => [ + 'maxProcesses' => 10, + 'balanceMaxShift' => 1, + 'balanceCooldown' => 3, + ], + ], + + 'local' => [ + 'supervisor-1' => [ + 'maxProcesses' => 3, + ], + ], + ], +]; diff --git a/workbench/config/logging.php b/workbench/config/logging.php new file mode 100644 index 0000000..8d94292 --- /dev/null +++ b/workbench/config/logging.php @@ -0,0 +1,132 @@ + env('LOG_CHANNEL', 'stack'), + + /* + |-------------------------------------------------------------------------- + | Deprecations Log Channel + |-------------------------------------------------------------------------- + | + | This option controls the log channel that should be used to log warnings + | regarding deprecated PHP and library features. This allows you to get + | your application ready for upcoming major versions of dependencies. + | + */ + + 'deprecations' => [ + 'channel' => env('LOG_DEPRECATIONS_CHANNEL', 'null'), + 'trace' => env('LOG_DEPRECATIONS_TRACE', false), + ], + + /* + |-------------------------------------------------------------------------- + | Log Channels + |-------------------------------------------------------------------------- + | + | Here you may configure the log channels for your application. Laravel + | utilizes the Monolog PHP logging library, which includes a variety + | of powerful log handlers and formatters that you're free to use. + | + | Available drivers: "single", "daily", "slack", "syslog", + | "errorlog", "monolog", "custom", "stack" + | + */ + + 'channels' => [ + + 'stack' => [ + 'driver' => 'stack', + 'channels' => explode(',', env('LOG_STACK', 'single')), + 'ignore_exceptions' => false, + ], + + 'single' => [ + 'driver' => 'single', + 'path' => storage_path('logs/laravel.log'), + 'level' => env('LOG_LEVEL', 'debug'), + 'replace_placeholders' => true, + ], + + 'daily' => [ + 'driver' => 'daily', + 'path' => storage_path('logs/laravel.log'), + 'level' => env('LOG_LEVEL', 'debug'), + 'days' => env('LOG_DAILY_DAYS', 14), + 'replace_placeholders' => true, + ], + + 'slack' => [ + 'driver' => 'slack', + 'url' => env('LOG_SLACK_WEBHOOK_URL'), + 'username' => env('LOG_SLACK_USERNAME', 'Laravel Log'), + 'emoji' => env('LOG_SLACK_EMOJI', ':boom:'), + 'level' => env('LOG_LEVEL', 'critical'), + 'replace_placeholders' => true, + ], + + 'papertrail' => [ + 'driver' => 'monolog', + 'level' => env('LOG_LEVEL', 'debug'), + 'handler' => env('LOG_PAPERTRAIL_HANDLER', SyslogUdpHandler::class), + 'handler_with' => [ + 'host' => env('PAPERTRAIL_URL'), + 'port' => env('PAPERTRAIL_PORT'), + 'connectionString' => 'tls://'.env('PAPERTRAIL_URL').':'.env('PAPERTRAIL_PORT'), + ], + 'processors' => [PsrLogMessageProcessor::class], + ], + + 'stderr' => [ + 'driver' => 'monolog', + 'level' => env('LOG_LEVEL', 'debug'), + 'handler' => StreamHandler::class, + 'formatter' => env('LOG_STDERR_FORMATTER'), + 'with' => [ + 'stream' => 'php://stderr', + ], + 'processors' => [PsrLogMessageProcessor::class], + ], + + 'syslog' => [ + 'driver' => 'syslog', + 'level' => env('LOG_LEVEL', 'debug'), + 'facility' => env('LOG_SYSLOG_FACILITY', LOG_USER), + 'replace_placeholders' => true, + ], + + 'errorlog' => [ + 'driver' => 'errorlog', + 'level' => env('LOG_LEVEL', 'debug'), + 'replace_placeholders' => true, + ], + + 'null' => [ + 'driver' => 'monolog', + 'handler' => NullHandler::class, + ], + + 'emergency' => [ + 'path' => storage_path('logs/laravel.log'), + ], + + ], + +]; diff --git a/workbench/config/permission.php b/workbench/config/permission.php new file mode 100644 index 0000000..2112cca --- /dev/null +++ b/workbench/config/permission.php @@ -0,0 +1,202 @@ + [ + + /* + * 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' => \Eclipse\Core\Models\User\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' => \Eclipse\Core\Models\User\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' => 'site_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, + + /* + * Events will fire when a role or permission is assigned/unassigned: + * \Spatie\Permission\Events\RoleAttached + * \Spatie\Permission\Events\RoleDetached + * \Spatie\Permission\Events\PermissionAttached + * \Spatie\Permission\Events\PermissionDetached + * + * To enable, set to true, and then create listeners to watch these events. + */ + 'events_enabled' => 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' => true, + + /* + * The class to use to resolve the permissions team id + */ + 'team_resolver' => \Spatie\Permission\DefaultTeamResolver::class, + + /* + * 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/workbench/config/queue.php b/workbench/config/queue.php new file mode 100644 index 0000000..116bd8d --- /dev/null +++ b/workbench/config/queue.php @@ -0,0 +1,112 @@ + env('QUEUE_CONNECTION', 'database'), + + /* + |-------------------------------------------------------------------------- + | Queue Connections + |-------------------------------------------------------------------------- + | + | Here you may configure the connection options for every queue backend + | used by your application. An example configuration is provided for + | each backend supported by Laravel. You're also free to add more. + | + | Drivers: "sync", "database", "beanstalkd", "sqs", "redis", "null" + | + */ + + 'connections' => [ + + 'sync' => [ + 'driver' => 'sync', + ], + + 'database' => [ + 'driver' => 'database', + 'connection' => env('DB_QUEUE_CONNECTION'), + 'table' => env('DB_QUEUE_TABLE', 'jobs'), + 'queue' => env('DB_QUEUE', 'default'), + 'retry_after' => (int) env('DB_QUEUE_RETRY_AFTER', 90), + 'after_commit' => false, + ], + + 'beanstalkd' => [ + 'driver' => 'beanstalkd', + 'host' => env('BEANSTALKD_QUEUE_HOST', 'localhost'), + 'queue' => env('BEANSTALKD_QUEUE', 'default'), + 'retry_after' => (int) env('BEANSTALKD_QUEUE_RETRY_AFTER', 90), + 'block_for' => 0, + 'after_commit' => false, + ], + + 'sqs' => [ + 'driver' => 'sqs', + 'key' => env('AWS_ACCESS_KEY_ID'), + 'secret' => env('AWS_SECRET_ACCESS_KEY'), + 'prefix' => env('SQS_PREFIX', 'https://sqs.us-east-1.amazonaws.com/your-account-id'), + 'queue' => env('SQS_QUEUE', 'default'), + 'suffix' => env('SQS_SUFFIX'), + 'region' => env('AWS_DEFAULT_REGION', 'us-east-1'), + 'after_commit' => false, + ], + + 'redis' => [ + 'driver' => 'redis', + 'connection' => env('REDIS_QUEUE_CONNECTION', 'default'), + 'queue' => env('REDIS_QUEUE', 'default'), + 'retry_after' => (int) env('REDIS_QUEUE_RETRY_AFTER', 90), + 'block_for' => null, + 'after_commit' => false, + ], + + ], + + /* + |-------------------------------------------------------------------------- + | Job Batching + |-------------------------------------------------------------------------- + | + | The following options configure the database and table that store job + | batching information. These options can be updated to any database + | connection and table which has been defined by your application. + | + */ + + 'batching' => [ + 'database' => env('DB_CONNECTION', 'sqlite'), + 'table' => 'job_batches', + ], + + /* + |-------------------------------------------------------------------------- + | Failed Queue Jobs + |-------------------------------------------------------------------------- + | + | These options configure the behavior of failed queue job logging so you + | can control how and where failed jobs are stored. Laravel ships with + | support for storing failed jobs in a simple file or in a database. + | + | Supported drivers: "database-uuids", "dynamodb", "file", "null" + | + */ + + 'failed' => [ + 'driver' => env('QUEUE_FAILED_DRIVER', 'database-uuids'), + 'database' => env('DB_CONNECTION', 'sqlite'), + 'table' => 'failed_jobs', + ], + +]; diff --git a/workbench/config/session.php b/workbench/config/session.php new file mode 100644 index 0000000..ba0aa60 --- /dev/null +++ b/workbench/config/session.php @@ -0,0 +1,217 @@ + env('SESSION_DRIVER', 'database'), + + /* + |-------------------------------------------------------------------------- + | Session Lifetime + |-------------------------------------------------------------------------- + | + | Here you may specify the number of minutes that you wish the session + | to be allowed to remain idle before it expires. If you want them + | to expire immediately when the browser is closed then you may + | indicate that via the expire_on_close configuration option. + | + */ + + 'lifetime' => (int) env('SESSION_LIFETIME', 120), + + 'expire_on_close' => env('SESSION_EXPIRE_ON_CLOSE', false), + + /* + |-------------------------------------------------------------------------- + | Session Encryption + |-------------------------------------------------------------------------- + | + | This option allows you to easily specify that all of your session data + | should be encrypted before it's stored. All encryption is performed + | automatically by Laravel and you may use the session like normal. + | + */ + + 'encrypt' => env('SESSION_ENCRYPT', false), + + /* + |-------------------------------------------------------------------------- + | Session File Location + |-------------------------------------------------------------------------- + | + | When utilizing the "file" session driver, the session files are placed + | on disk. The default storage location is defined here; however, you + | are free to provide another location where they should be stored. + | + */ + + 'files' => storage_path('framework/sessions'), + + /* + |-------------------------------------------------------------------------- + | Session Database Connection + |-------------------------------------------------------------------------- + | + | When using the "database" or "redis" session drivers, you may specify a + | connection that should be used to manage these sessions. This should + | correspond to a connection in your database configuration options. + | + */ + + 'connection' => env('SESSION_CONNECTION'), + + /* + |-------------------------------------------------------------------------- + | Session Database Table + |-------------------------------------------------------------------------- + | + | When using the "database" session driver, you may specify the table to + | be used to store sessions. Of course, a sensible default is defined + | for you; however, you're welcome to change this to another table. + | + */ + + 'table' => env('SESSION_TABLE', 'sessions'), + + /* + |-------------------------------------------------------------------------- + | Session Cache Store + |-------------------------------------------------------------------------- + | + | When using one of the framework's cache driven session backends, you may + | define the cache store which should be used to store the session data + | between requests. This must match one of your defined cache stores. + | + | Affects: "apc", "dynamodb", "memcached", "redis" + | + */ + + 'store' => env('SESSION_STORE'), + + /* + |-------------------------------------------------------------------------- + | Session Sweeping Lottery + |-------------------------------------------------------------------------- + | + | Some session drivers must manually sweep their storage location to get + | rid of old sessions from storage. Here are the chances that it will + | happen on a given request. By default, the odds are 2 out of 100. + | + */ + + 'lottery' => [2, 100], + + /* + |-------------------------------------------------------------------------- + | Session Cookie Name + |-------------------------------------------------------------------------- + | + | Here you may change the name of the session cookie that is created by + | the framework. Typically, you should not need to change this value + | since doing so does not grant a meaningful security improvement. + | + */ + + 'cookie' => env( + 'SESSION_COOKIE', + Str::slug(env('APP_NAME', 'laravel'), '_').'_session' + ), + + /* + |-------------------------------------------------------------------------- + | Session Cookie Path + |-------------------------------------------------------------------------- + | + | The session cookie path determines the path for which the cookie will + | be regarded as available. Typically, this will be the root path of + | your application, but you're free to change this when necessary. + | + */ + + 'path' => env('SESSION_PATH', '/'), + + /* + |-------------------------------------------------------------------------- + | Session Cookie Domain + |-------------------------------------------------------------------------- + | + | This value determines the domain and subdomains the session cookie is + | available to. By default, the cookie will be available to the root + | domain and all subdomains. Typically, this shouldn't be changed. + | + */ + + 'domain' => env('SESSION_DOMAIN'), + + /* + |-------------------------------------------------------------------------- + | HTTPS Only Cookies + |-------------------------------------------------------------------------- + | + | By setting this option to true, session cookies will only be sent back + | to the server if the browser has a HTTPS connection. This will keep + | the cookie from being sent to you when it can't be done securely. + | + */ + + 'secure' => env('SESSION_SECURE_COOKIE'), + + /* + |-------------------------------------------------------------------------- + | HTTP Access Only + |-------------------------------------------------------------------------- + | + | Setting this value to true will prevent JavaScript from accessing the + | value of the cookie and the cookie will only be accessible through + | the HTTP protocol. It's unlikely you should disable this option. + | + */ + + 'http_only' => env('SESSION_HTTP_ONLY', true), + + /* + |-------------------------------------------------------------------------- + | Same-Site Cookies + |-------------------------------------------------------------------------- + | + | This option determines how your cookies behave when cross-site requests + | take place, and can be used to mitigate CSRF attacks. By default, we + | will set this value to "lax" to permit secure cross-site requests. + | + | See: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie#samesitesamesite-value + | + | Supported: "lax", "strict", "none", null + | + */ + + 'same_site' => env('SESSION_SAME_SITE', 'lax'), + + /* + |-------------------------------------------------------------------------- + | Partitioned Cookies + |-------------------------------------------------------------------------- + | + | Setting this value to true will tie the cookie to the top-level site for + | a cross-site context. Partitioned cookies are accepted by the browser + | when flagged "secure" and the Same-Site attribute is set to "none". + | + */ + + 'partitioned' => env('SESSION_PARTITIONED_COOKIE', false), + +]; diff --git a/workbench/config/themes.php b/workbench/config/themes.php new file mode 100644 index 0000000..03fae51 --- /dev/null +++ b/workbench/config/themes.php @@ -0,0 +1,36 @@ + 'user', + + /* + |-------------------------------------------------------------------------- + | Theme Icon + |-------------------------------------------------------------------------- + */ + + 'icon' => 'heroicon-o-swatch', + + /* + |-------------------------------------------------------------------------- + | Default Theme + |-------------------------------------------------------------------------- + */ + + 'default' => [ + 'theme' => 'default', + 'theme_color' => 'blue', + ], +]; diff --git a/workbench/config/view.php b/workbench/config/view.php new file mode 100644 index 0000000..98dd666 --- /dev/null +++ b/workbench/config/view.php @@ -0,0 +1,36 @@ + [ + resource_path('views'), + ], + + /* + |-------------------------------------------------------------------------- + | Compiled View Path + |-------------------------------------------------------------------------- + | + | This option determines where all the compiled Blade templates will be + | stored for your application. Typically, this is within the storage + | directory. However, as usual, you are free to change this value. + | + */ + + 'compiled' => env( + 'VIEW_COMPILED_PATH', + storage_path('framework/views') + ), + +]; diff --git a/workbench/database/migrations/2025_08_09_143147_create_permission_tables.php b/workbench/database/migrations/2025_08_09_143147_create_permission_tables.php new file mode 100644 index 0000000..ce4d9d2 --- /dev/null +++ b/workbench/database/migrations/2025_08_09_143147_create_permission_tables.php @@ -0,0 +1,136 @@ +engine('InnoDB'); + $table->bigIncrements('id'); // permission id + $table->string('name'); // For MyISAM use string('name', 225); // (or 166 for InnoDB with Redundant/Compact row format) + $table->string('guard_name'); // For MyISAM use string('guard_name', 25); + $table->timestamps(); + + $table->unique(['name', 'guard_name']); + }); + + Schema::create($tableNames['roles'], static function (Blueprint $table) use ($teams, $columnNames) { + // $table->engine('InnoDB'); + $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 MyISAM use string('name', 225); // (or 166 for InnoDB with Redundant/Compact row format) + $table->string('guard_name'); // For MyISAM use string('guard_name', 25); + $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'], static 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'], static 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'], static 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']); + } +}; From d92393a5d31694775b7ced8b037f27919da3838c Mon Sep 17 00:00:00 2001 From: Kilian Trunk Date: Sun, 10 Aug 2025 12:12:48 +0200 Subject: [PATCH 03/45] chore(workbench-setup): fixes --- .lando.dist.yml | 2 +- workbench/.gitignore | 4 + workbench/config/blade-heroicons.php | 57 ++++++ workbench/config/blade-icons.php | 183 ++++++++++++++++++ workbench/config/eclipse-world.php | 5 + workbench/config/filament-shield.php | 13 +- workbench/config/filament.php | 25 ++- workbench/config/livewire.php | 160 +++++++++++++++ workbench/config/permission.php | 10 +- workbench/config/tinker.php | 50 +++++ workbench/database/seeders/DatabaseSeeder.php | 9 +- .../resources/views/errors/401.blade.php | 5 + .../resources/views/errors/402.blade.php | 5 + .../resources/views/errors/403.blade.php | 5 + .../resources/views/errors/404.blade.php | 5 + .../resources/views/errors/419.blade.php | 5 + .../resources/views/errors/429.blade.php | 5 + .../resources/views/errors/500.blade.php | 5 + .../resources/views/errors/503.blade.php | 5 + .../resources/views/errors/layout.blade.php | 53 +++++ .../resources/views/errors/minimal.blade.php | 34 ++++ workbench/resources/views/welcome.blade.php | 56 ++++++ 22 files changed, 671 insertions(+), 30 deletions(-) create mode 100644 workbench/config/blade-heroicons.php create mode 100644 workbench/config/blade-icons.php create mode 100644 workbench/config/eclipse-world.php create mode 100644 workbench/config/livewire.php create mode 100644 workbench/config/tinker.php create mode 100644 workbench/resources/views/errors/401.blade.php create mode 100644 workbench/resources/views/errors/402.blade.php create mode 100644 workbench/resources/views/errors/403.blade.php create mode 100644 workbench/resources/views/errors/404.blade.php create mode 100644 workbench/resources/views/errors/419.blade.php create mode 100644 workbench/resources/views/errors/429.blade.php create mode 100644 workbench/resources/views/errors/500.blade.php create mode 100644 workbench/resources/views/errors/503.blade.php create mode 100644 workbench/resources/views/errors/layout.blade.php create mode 100644 workbench/resources/views/errors/minimal.blade.php create mode 100644 workbench/resources/views/welcome.blade.php diff --git a/.lando.dist.yml b/.lando.dist.yml index a54a808..408fd9f 100644 --- a/.lando.dist.yml +++ b/.lando.dist.yml @@ -2,7 +2,7 @@ name: eclipse-world-plugin recipe: laravel config: - webroot: vendor/orchestra/testbench-core/laravel/public + webroot: workbench/public php: '8.3' via: nginx services: diff --git a/workbench/.gitignore b/workbench/.gitignore index 3badc3a..30e4180 100644 --- a/workbench/.gitignore +++ b/workbench/.gitignore @@ -1,3 +1,7 @@ .env .env.dusk storage +stubs/ +resources/views/vendor/ +lang/ +public/ \ No newline at end of file diff --git a/workbench/config/blade-heroicons.php b/workbench/config/blade-heroicons.php new file mode 100644 index 0000000..e517a97 --- /dev/null +++ b/workbench/config/blade-heroicons.php @@ -0,0 +1,57 @@ + 'heroicon', + + /* + |----------------------------------------------------------------- + | Fallback Icon + |----------------------------------------------------------------- + | + | This config option allows you to define a fallback + | icon when an icon in this set cannot be found. + | + */ + + 'fallback' => '', + + /* + |----------------------------------------------------------------- + | Default Set Classes + |----------------------------------------------------------------- + | + | This config option allows you to define some classes which + | will be applied by default to all icons within this set. + | + */ + + 'class' => '', + + /* + |----------------------------------------------------------------- + | Default Set Attributes + |----------------------------------------------------------------- + | + | This config option allows you to define some attributes which + | will be applied by default to all icons within this set. + | + */ + + 'attributes' => [ + // 'width' => 50, + // 'height' => 50, + ], + +]; diff --git a/workbench/config/blade-icons.php b/workbench/config/blade-icons.php new file mode 100644 index 0000000..5aade2a --- /dev/null +++ b/workbench/config/blade-icons.php @@ -0,0 +1,183 @@ + [ + + // 'default' => [ + // + // /* + // |----------------------------------------------------------------- + // | Icons Path + // |----------------------------------------------------------------- + // | + // | Provide the relative path from your app root to your SVG icons + // | directory. Icons are loaded recursively so there's no need to + // | list every sub-directory. + // | + // | Relative to the disk root when the disk option is set. + // | + // */ + // + // 'path' => 'resources/svg', + // + // /* + // |----------------------------------------------------------------- + // | Filesystem Disk + // |----------------------------------------------------------------- + // | + // | Optionally, provide a specific filesystem disk to read + // | icons from. When defining a disk, the "path" option + // | starts relatively from the disk root. + // | + // */ + // + // 'disk' => '', + // + // /* + // |----------------------------------------------------------------- + // | Default Prefix + // |----------------------------------------------------------------- + // | + // | This config option allows you to define a default prefix for + // | your icons. The dash separator will be applied automatically + // | to every icon name. It's required and needs to be unique. + // | + // */ + // + // 'prefix' => 'icon', + // + // /* + // |----------------------------------------------------------------- + // | Fallback Icon + // |----------------------------------------------------------------- + // | + // | This config option allows you to define a fallback + // | icon when an icon in this set cannot be found. + // | + // */ + // + // 'fallback' => '', + // + // /* + // |----------------------------------------------------------------- + // | Default Set Classes + // |----------------------------------------------------------------- + // | + // | This config option allows you to define some classes which + // | will be applied by default to all icons within this set. + // | + // */ + // + // 'class' => '', + // + // /* + // |----------------------------------------------------------------- + // | Default Set Attributes + // |----------------------------------------------------------------- + // | + // | This config option allows you to define some attributes which + // | will be applied by default to all icons within this set. + // | + // */ + // + // 'attributes' => [ + // // 'width' => 50, + // // 'height' => 50, + // ], + // + // ], + + ], + + /* + |-------------------------------------------------------------------------- + | Global Default Classes + |-------------------------------------------------------------------------- + | + | This config option allows you to define some classes which + | will be applied by default to all icons. + | + */ + + 'class' => '', + + /* + |-------------------------------------------------------------------------- + | Global Default Attributes + |-------------------------------------------------------------------------- + | + | This config option allows you to define some attributes which + | will be applied by default to all icons. + | + */ + + 'attributes' => [ + // 'width' => 50, + // 'height' => 50, + ], + + /* + |-------------------------------------------------------------------------- + | Global Fallback Icon + |-------------------------------------------------------------------------- + | + | This config option allows you to define a global fallback + | icon when an icon in any set cannot be found. It can + | reference any icon from any configured set. + | + */ + + 'fallback' => '', + + /* + |-------------------------------------------------------------------------- + | Components + |-------------------------------------------------------------------------- + | + | These config options allow you to define some + | settings related to Blade Components. + | + */ + + 'components' => [ + + /* + |---------------------------------------------------------------------- + | Disable Components + |---------------------------------------------------------------------- + | + | This config option allows you to disable Blade components + | completely. It's useful to avoid performance problems + | when working with large icon libraries. + | + */ + + 'disabled' => false, + + /* + |---------------------------------------------------------------------- + | Default Icon Component Name + |---------------------------------------------------------------------- + | + | This config option allows you to define the name + | for the default Icon class component. + | + */ + + 'default' => 'icon', + + ], + +]; diff --git a/workbench/config/eclipse-world.php b/workbench/config/eclipse-world.php new file mode 100644 index 0000000..3ac44ad --- /dev/null +++ b/workbench/config/eclipse-world.php @@ -0,0 +1,5 @@ + -1, 'navigation_badge' => true, 'navigation_group' => true, + 'sub_navigation_position' => null, 'is_globally_searchable' => false, 'show_model_path' => true, - 'is_scoped_to_tenant' => false, + 'is_scoped_to_tenant' => true, 'cluster' => null, ], - 'tenant_model' => \Eclipse\Core\Models\Site::class, + 'tenant_model' => null, 'auth_provider_model' => [ - 'fqcn' => \Eclipse\Core\Models\User::class, + 'fqcn' => 'App\\Models\\User', ], 'super_admin' => [ @@ -33,8 +34,8 @@ 'permission_prefixes' => [ 'resource' => [ - 'view_any', 'view', + 'view_any', 'create', 'update', 'restore', @@ -75,9 +76,7 @@ 'AccountWidget', 'FilamentInfoWidget', ], - 'resources' => [ - 'MailLogResource', - ], + 'resources' => [], ], 'discovery' => [ diff --git a/workbench/config/filament.php b/workbench/config/filament.php index e8fea8a..488a01f 100644 --- a/workbench/config/filament.php +++ b/workbench/config/filament.php @@ -16,19 +16,18 @@ 'broadcasting' => [ - 'echo' => [ - 'broadcaster' => 'reverb', - 'key' => env('VITE_REVERB_APP_KEY'), - 'secret' => env('VITE_REVERB_APP_SECRET'), - 'app_id' => env('VITE_REVERB_APP_ID'), - 'wsHost' => env('VITE_REVERB_HOST'), - 'wsPort' => env('VITE_REVERB_PORT', 8080), - 'wssPort' => env('VITE_REVERB_PORT', 8080), - 'authEndpoint' => '/broadcasting/auth', - 'disableStats' => true, - 'encrypted' => true, - 'forceTLS' => true, - ], + // 'echo' => [ + // 'broadcaster' => 'pusher', + // 'key' => env('VITE_PUSHER_APP_KEY'), + // 'cluster' => env('VITE_PUSHER_APP_CLUSTER'), + // 'wsHost' => env('VITE_PUSHER_HOST'), + // 'wsPort' => env('VITE_PUSHER_PORT'), + // 'wssPort' => env('VITE_PUSHER_PORT'), + // 'authEndpoint' => '/broadcasting/auth', + // 'disableStats' => true, + // 'encrypted' => true, + // 'forceTLS' => true, + // ], ], diff --git a/workbench/config/livewire.php b/workbench/config/livewire.php new file mode 100644 index 0000000..0d2ba89 --- /dev/null +++ b/workbench/config/livewire.php @@ -0,0 +1,160 @@ + 'App\\Livewire', + + /* + |--------------------------------------------------------------------------- + | View Path + |--------------------------------------------------------------------------- + | + | This value is used to specify where Livewire component Blade templates are + | stored when running file creation commands like `artisan make:livewire`. + | It is also used if you choose to omit a component's render() method. + | + */ + + 'view_path' => resource_path('views/livewire'), + + /* + |--------------------------------------------------------------------------- + | Layout + |--------------------------------------------------------------------------- + | The view that will be used as the layout when rendering a single component + | as an entire page via `Route::get('/post/create', CreatePost::class);`. + | In this case, the view returned by CreatePost will render into $slot. + | + */ + + 'layout' => 'components.layouts.app', + + /* + |--------------------------------------------------------------------------- + | Lazy Loading Placeholder + |--------------------------------------------------------------------------- + | Livewire allows you to lazy load components that would otherwise slow down + | the initial page load. Every component can have a custom placeholder or + | you can define the default placeholder view for all components below. + | + */ + + 'lazy_placeholder' => null, + + /* + |--------------------------------------------------------------------------- + | Temporary File Uploads + |--------------------------------------------------------------------------- + | + | Livewire handles file uploads by storing uploads in a temporary directory + | before the file is stored permanently. All file uploads are directed to + | a global endpoint for temporary storage. You may configure this below: + | + */ + + 'temporary_file_upload' => [ + 'disk' => null, // Example: 'local', 's3' | Default: 'default' + 'rules' => null, // Example: ['file', 'mimes:png,jpg'] | Default: ['required', 'file', 'max:12288'] (12MB) + 'directory' => null, // Example: 'tmp' | Default: 'livewire-tmp' + 'middleware' => null, // Example: 'throttle:5,1' | Default: 'throttle:60,1' + 'preview_mimes' => [ // Supported file types for temporary pre-signed file URLs... + 'png', 'gif', 'bmp', 'svg', 'wav', 'mp4', + 'mov', 'avi', 'wmv', 'mp3', 'm4a', + 'jpg', 'jpeg', 'mpga', 'webp', 'wma', + ], + 'max_upload_time' => 5, // Max duration (in minutes) before an upload is invalidated... + 'cleanup' => true, // Should cleanup temporary uploads older than 24 hrs... + ], + + /* + |--------------------------------------------------------------------------- + | Render On Redirect + |--------------------------------------------------------------------------- + | + | This value determines if Livewire will run a component's `render()` method + | after a redirect has been triggered using something like `redirect(...)` + | Setting this to true will render the view once more before redirecting + | + */ + + 'render_on_redirect' => false, + + /* + |--------------------------------------------------------------------------- + | Eloquent Model Binding + |--------------------------------------------------------------------------- + | + | Previous versions of Livewire supported binding directly to eloquent model + | properties using wire:model by default. However, this behavior has been + | deemed too "magical" and has therefore been put under a feature flag. + | + */ + + 'legacy_model_binding' => false, + + /* + |--------------------------------------------------------------------------- + | Auto-inject Frontend Assets + |--------------------------------------------------------------------------- + | + | By default, Livewire automatically injects its JavaScript and CSS into the + | and of pages containing Livewire components. By disabling + | this behavior, you need to use @livewireStyles and @livewireScripts. + | + */ + + 'inject_assets' => true, + + /* + |--------------------------------------------------------------------------- + | Navigate (SPA mode) + |--------------------------------------------------------------------------- + | + | By adding `wire:navigate` to links in your Livewire application, Livewire + | will prevent the default link handling and instead request those pages + | via AJAX, creating an SPA-like effect. Configure this behavior here. + | + */ + + 'navigate' => [ + 'show_progress_bar' => true, + 'progress_bar_color' => '#2299dd', + ], + + /* + |--------------------------------------------------------------------------- + | HTML Morph Markers + |--------------------------------------------------------------------------- + | + | Livewire intelligently "morphs" existing HTML into the newly rendered HTML + | after each update. To make this process more reliable, Livewire injects + | "markers" into the rendered Blade surrounding @if, @class & @foreach. + | + */ + + 'inject_morph_markers' => true, + + /* + |--------------------------------------------------------------------------- + | Pagination Theme + |--------------------------------------------------------------------------- + | + | When enabling Livewire's pagination feature by using the `WithPagination` + | trait, Livewire will use Tailwind templates to render pagination views + | on the page. If you want Bootstrap CSS, you can specify: "bootstrap" + | + */ + + 'pagination_theme' => 'tailwind', +]; diff --git a/workbench/config/permission.php b/workbench/config/permission.php index 2112cca..f39f6b5 100644 --- a/workbench/config/permission.php +++ b/workbench/config/permission.php @@ -13,7 +13,7 @@ * `Spatie\Permission\Contracts\Permission` contract. */ - 'permission' => \Eclipse\Core\Models\User\Permission::class, + 'permission' => Spatie\Permission\Models\Permission::class, /* * When using the "HasRoles" trait from this package, we need to know which @@ -24,7 +24,7 @@ * `Spatie\Permission\Contracts\Role` contract. */ - 'role' => \Eclipse\Core\Models\User\Role::class, + 'role' => Spatie\Permission\Models\Role::class, ], @@ -93,7 +93,7 @@ * foreign key is other than `team_id`. */ - 'team_foreign_key' => 'site_id', + 'team_foreign_key' => 'team_id', ], /* @@ -131,7 +131,7 @@ * (view the latest version of this package's migration file) */ - 'teams' => true, + 'teams' => false, /* * The class to use to resolve the permissions team id @@ -172,7 +172,7 @@ * 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, + // 'wildcard_permission' => Spatie\Permission\WildcardPermission::class, /* Cache-specific settings */ diff --git a/workbench/config/tinker.php b/workbench/config/tinker.php new file mode 100644 index 0000000..c187942 --- /dev/null +++ b/workbench/config/tinker.php @@ -0,0 +1,50 @@ + [ + // App\Console\Commands\ExampleCommand::class, + ], + + /* + |-------------------------------------------------------------------------- + | Auto Aliased Classes + |-------------------------------------------------------------------------- + | + | Tinker will not automatically alias classes in your vendor namespaces + | but you may explicitly allow a subset of classes to get aliased by + | adding the names of each of those classes to the following list. + | + */ + + 'alias' => [ + // + ], + + /* + |-------------------------------------------------------------------------- + | Classes That Should Not Be Aliased + |-------------------------------------------------------------------------- + | + | Typically, Tinker automatically aliases classes as you require them in + | Tinker. However, you may wish to never alias certain classes, which + | you may accomplish by listing the classes in the following array. + | + */ + + 'dont_alias' => [ + 'App\Nova', + ], + +]; diff --git a/workbench/database/seeders/DatabaseSeeder.php b/workbench/database/seeders/DatabaseSeeder.php index f10adbb..a1c0425 100644 --- a/workbench/database/seeders/DatabaseSeeder.php +++ b/workbench/database/seeders/DatabaseSeeder.php @@ -15,9 +15,10 @@ public function run(): void { // UserFactory::new()->times(10)->create(); - UserFactory::new()->create([ - 'name' => 'Test User', - 'email' => 'test@example.com', - ]); + // User is already created by Eclipse configuration + // UserFactory::new()->create([ + // 'name' => 'Test User', + // 'email' => 'test@example.com', + // ]); } } diff --git a/workbench/resources/views/errors/401.blade.php b/workbench/resources/views/errors/401.blade.php new file mode 100644 index 0000000..5c586db --- /dev/null +++ b/workbench/resources/views/errors/401.blade.php @@ -0,0 +1,5 @@ +@extends('errors::minimal') + +@section('title', __('Unauthorized')) +@section('code', '401') +@section('message', __('Unauthorized')) diff --git a/workbench/resources/views/errors/402.blade.php b/workbench/resources/views/errors/402.blade.php new file mode 100644 index 0000000..3bc23ef --- /dev/null +++ b/workbench/resources/views/errors/402.blade.php @@ -0,0 +1,5 @@ +@extends('errors::minimal') + +@section('title', __('Payment Required')) +@section('code', '402') +@section('message', __('Payment Required')) diff --git a/workbench/resources/views/errors/403.blade.php b/workbench/resources/views/errors/403.blade.php new file mode 100644 index 0000000..a5506f0 --- /dev/null +++ b/workbench/resources/views/errors/403.blade.php @@ -0,0 +1,5 @@ +@extends('errors::minimal') + +@section('title', __('Forbidden')) +@section('code', '403') +@section('message', __($exception->getMessage() ?: 'Forbidden')) diff --git a/workbench/resources/views/errors/404.blade.php b/workbench/resources/views/errors/404.blade.php new file mode 100644 index 0000000..7549540 --- /dev/null +++ b/workbench/resources/views/errors/404.blade.php @@ -0,0 +1,5 @@ +@extends('errors::minimal') + +@section('title', __('Not Found')) +@section('code', '404') +@section('message', __('Not Found')) diff --git a/workbench/resources/views/errors/419.blade.php b/workbench/resources/views/errors/419.blade.php new file mode 100644 index 0000000..c09216e --- /dev/null +++ b/workbench/resources/views/errors/419.blade.php @@ -0,0 +1,5 @@ +@extends('errors::minimal') + +@section('title', __('Page Expired')) +@section('code', '419') +@section('message', __('Page Expired')) diff --git a/workbench/resources/views/errors/429.blade.php b/workbench/resources/views/errors/429.blade.php new file mode 100644 index 0000000..f01b07b --- /dev/null +++ b/workbench/resources/views/errors/429.blade.php @@ -0,0 +1,5 @@ +@extends('errors::minimal') + +@section('title', __('Too Many Requests')) +@section('code', '429') +@section('message', __('Too Many Requests')) diff --git a/workbench/resources/views/errors/500.blade.php b/workbench/resources/views/errors/500.blade.php new file mode 100644 index 0000000..d9e95d9 --- /dev/null +++ b/workbench/resources/views/errors/500.blade.php @@ -0,0 +1,5 @@ +@extends('errors::minimal') + +@section('title', __('Server Error')) +@section('code', '500') +@section('message', __('Server Error')) diff --git a/workbench/resources/views/errors/503.blade.php b/workbench/resources/views/errors/503.blade.php new file mode 100644 index 0000000..c5a9dde --- /dev/null +++ b/workbench/resources/views/errors/503.blade.php @@ -0,0 +1,5 @@ +@extends('errors::minimal') + +@section('title', __('Service Unavailable')) +@section('code', '503') +@section('message', __('Service Unavailable')) diff --git a/workbench/resources/views/errors/layout.blade.php b/workbench/resources/views/errors/layout.blade.php new file mode 100644 index 0000000..019c2cd --- /dev/null +++ b/workbench/resources/views/errors/layout.blade.php @@ -0,0 +1,53 @@ + + + + + + + @yield('title') + + + + + +
+
+
+ @yield('message') +
+
+
+ + diff --git a/workbench/resources/views/errors/minimal.blade.php b/workbench/resources/views/errors/minimal.blade.php new file mode 100644 index 0000000..db69f25 --- /dev/null +++ b/workbench/resources/views/errors/minimal.blade.php @@ -0,0 +1,34 @@ + + + + + + + @yield('title') + + + + + + +
+
+
+
+ @yield('code') +
+ +
+ @yield('message') +
+
+
+
+ + diff --git a/workbench/resources/views/welcome.blade.php b/workbench/resources/views/welcome.blade.php new file mode 100644 index 0000000..5c34b66 --- /dev/null +++ b/workbench/resources/views/welcome.blade.php @@ -0,0 +1,56 @@ + + + + + + Eclipse World Plugin + + + +
+

Eclipse World Plugin

+

Welcome to your Laravel workbench environment

+ Access Admin Panel +
+ + \ No newline at end of file From 919cb870239ab2c714de0953527845d20e3a5e28 Mon Sep 17 00:00:00 2001 From: Kilian Trunk Date: Sun, 10 Aug 2025 12:44:17 +0200 Subject: [PATCH 04/45] chore(workbench-setup): further fixes --- .lando.yml | 42 +++++++++++++++++++ workbench/.env.example | 9 ++-- workbench/bootstrap/app.php | 4 ++ ...025_08_10_104332_create_sessions_table.php | 31 ++++++++++++++ 4 files changed, 83 insertions(+), 3 deletions(-) create mode 100644 .lando.yml create mode 100644 workbench/database/migrations/2025_08_10_104332_create_sessions_table.php diff --git a/.lando.yml b/.lando.yml new file mode 100644 index 0000000..14312f4 --- /dev/null +++ b/.lando.yml @@ -0,0 +1,42 @@ +#file: noinspection ComposeUnknownKeys,YAMLSchemaValidation +name: eclipse-world-plugin +recipe: laravel +config: + webroot: workbench/public + php: '8.3' + via: nginx +services: + appserver: + type: php:custom + xdebug: "debug,develop,coverage" + environment: + TZ: "Europe/Ljubljana" + TESTBENCH_WORKING_PATH: "/app" + overrides: + image: slimdeluxe/php:8.3-v1.2 + platform: linux/amd64 + run: + - composer install --no-interaction --prefer-dist + - composer run setup --no-interaction + - composer run build --no-interaction + appserver_nginx: + scanner: + path: /admin/login + retry: 5 +tooling: + php: + service: appserver + composer: + service: appserver + test: + service: appserver + description: Run tests + cmd: "composer test" + format: + service: appserver + description: Fix code style issues + cmd: "composer format" + testbench: + service: appserver + description: Run testbench CLI + cmd: "vendor/bin/testbench" diff --git a/workbench/.env.example b/workbench/.env.example index 284f43b..138db09 100644 --- a/workbench/.env.example +++ b/workbench/.env.example @@ -2,7 +2,7 @@ APP_NAME=Laravel APP_ENV=local APP_KEY=AckfSECXIvnK5r28GVIWUAxmbBSjTsmF APP_DEBUG=true -APP_URL=http://localhost +APP_URL=https://eclipse-world-plugin.lndo.site APP_LOCALE=en APP_FALLBACK_LOCALE=en @@ -27,11 +27,14 @@ DB_CONNECTION=sqlite # DB_USERNAME=root # DB_PASSWORD= -SESSION_DRIVER=cookie +SESSION_DRIVER=database SESSION_LIFETIME=120 SESSION_ENCRYPT=false SESSION_PATH=/ -SESSION_DOMAIN=null +SESSION_DOMAIN=.eclipse-world-plugin.lndo.site +SESSION_SECURE_COOKIE=true +SESSION_HTTP_ONLY=true +SESSION_SAME_SITE=lax BROADCAST_CONNECTION=log FILESYSTEM_DISK=local diff --git a/workbench/bootstrap/app.php b/workbench/bootstrap/app.php index 6ead72a..4c2b7f1 100644 --- a/workbench/bootstrap/app.php +++ b/workbench/bootstrap/app.php @@ -3,10 +3,14 @@ use Illuminate\Foundation\Application; use Illuminate\Foundation\Configuration\Exceptions; use Illuminate\Foundation\Configuration\Middleware; +use Workbench\App\Providers\WorkbenchServiceProvider; use function Orchestra\Testbench\default_skeleton_path; return Application::configure(basePath: $APP_BASE_PATH ?? default_skeleton_path()) + ->withProviders([ + WorkbenchServiceProvider::class, + ]) ->withRouting( web: __DIR__.'/../routes/web.php', commands: __DIR__.'/../routes/console.php', diff --git a/workbench/database/migrations/2025_08_10_104332_create_sessions_table.php b/workbench/database/migrations/2025_08_10_104332_create_sessions_table.php new file mode 100644 index 0000000..f60625b --- /dev/null +++ b/workbench/database/migrations/2025_08_10_104332_create_sessions_table.php @@ -0,0 +1,31 @@ +string('id')->primary(); + $table->foreignId('user_id')->nullable()->index(); + $table->string('ip_address', 45)->nullable(); + $table->text('user_agent')->nullable(); + $table->longText('payload'); + $table->integer('last_activity')->index(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('sessions'); + } +}; From ac3d8c96e4fce61653a3cafdfbb94695070ba174 Mon Sep 17 00:00:00 2001 From: Kilian Trunk Date: Sun, 10 Aug 2025 12:49:50 +0200 Subject: [PATCH 05/45] fix(migrations): only create table if not already --- ...2025_08_10_104332_create_sessions_table.php | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/workbench/database/migrations/2025_08_10_104332_create_sessions_table.php b/workbench/database/migrations/2025_08_10_104332_create_sessions_table.php index f60625b..25053ca 100644 --- a/workbench/database/migrations/2025_08_10_104332_create_sessions_table.php +++ b/workbench/database/migrations/2025_08_10_104332_create_sessions_table.php @@ -11,14 +11,16 @@ */ public function up(): void { - Schema::create('sessions', function (Blueprint $table) { - $table->string('id')->primary(); - $table->foreignId('user_id')->nullable()->index(); - $table->string('ip_address', 45)->nullable(); - $table->text('user_agent')->nullable(); - $table->longText('payload'); - $table->integer('last_activity')->index(); - }); + if (!Schema::hasTable('sessions')) { + Schema::create('sessions', function (Blueprint $table) { + $table->string('id')->primary(); + $table->foreignId('user_id')->nullable()->index(); + $table->string('ip_address', 45)->nullable(); + $table->text('user_agent')->nullable(); + $table->longText('payload'); + $table->integer('last_activity')->index(); + }); + } } /** From f73d10a6d49a4e26b681c38a0dffd8c723d7cfaa Mon Sep 17 00:00:00 2001 From: Kilian Trunk Date: Mon, 11 Aug 2025 09:24:44 +0200 Subject: [PATCH 06/45] chore(workbench-setup): final fixes --- composer.json | 2 + workbench/app/Policies/RolePolicy.php | 108 ++++++++++++++ .../app/Providers/AuthServiceProvider.php | 27 ++++ .../Providers/WorkbenchServiceProvider.php | 2 + workbench/config/auth.php | 2 +- workbench/config/filament-shield.php | 4 +- ..._08_09_143147_create_permission_tables.php | 136 ------------------ workbench/database/seeders/DatabaseSeeder.php | 112 +++++++++++++-- 8 files changed, 246 insertions(+), 147 deletions(-) create mode 100644 workbench/app/Policies/RolePolicy.php create mode 100644 workbench/app/Providers/AuthServiceProvider.php delete mode 100644 workbench/database/migrations/2025_08_09_143147_create_permission_tables.php diff --git a/composer.json b/composer.json index 8fa8245..51538f3 100644 --- a/composer.json +++ b/composer.json @@ -71,6 +71,8 @@ "@php vendor/bin/testbench serve --ansi" ], "setup": [ + "@php -r \"if (!file_exists('.env')) { copy('.env.example', '.env'); echo '.env file created from .env.example\\n'; }\"", + "@php vendor/bin/testbench key:generate --ansi", "npm install", "@php vendor/bin/testbench vendor:publish --provider='Spatie\\Permission\\PermissionServiceProvider'", "@php vendor/bin/testbench vendor:publish --tag='filament-shield-config'", diff --git a/workbench/app/Policies/RolePolicy.php b/workbench/app/Policies/RolePolicy.php new file mode 100644 index 0000000..ad2a069 --- /dev/null +++ b/workbench/app/Policies/RolePolicy.php @@ -0,0 +1,108 @@ +can('view_any_role'); + } + + /** + * Determine whether the user can view the model. + */ + public function view(Authorizable $user, Role $role): bool + { + return $user->can('view_role'); + } + + /** + * Determine whether the user can create models. + */ + public function create(Authorizable $user): bool + { + return $user->can('create_role'); + } + + /** + * Determine whether the user can update the model. + */ + public function update(Authorizable $user, Role $role): bool + { + return $user->can('update_role'); + } + + /** + * Determine whether the user can delete the model. + */ + public function delete(Authorizable $user, Role $role): bool + { + return $user->can('delete_role'); + } + + /** + * Determine whether the user can bulk delete. + */ + public function deleteAny(Authorizable $user): bool + { + return $user->can('delete_any_role'); + } + + /** + * Determine whether the user can permanently delete. + */ + public function forceDelete(Authorizable $user, Role $role): bool + { + return $user->can('force_delete_role'); + } + + /** + * Determine whether the user can permanently bulk delete. + */ + public function forceDeleteAny(Authorizable $user): bool + { + return $user->can('force_delete_any_role'); + } + + /** + * Determine whether the user can restore. + */ + public function restore(Authorizable $user, Role $role): bool + { + return $user->can('restore_role'); + } + + /** + * Determine whether the user can bulk restore. + */ + public function restoreAny(Authorizable $user): bool + { + return $user->can('restore_any_role'); + } + + /** + * Determine whether the user can replicate. + */ + public function replicate(Authorizable $user, Role $role): bool + { + return $user->can('replicate_role'); + } + + /** + * Determine whether the user can reorder. + */ + public function reorder(Authorizable $user): bool + { + return $user->can('reorder_role'); + } +} diff --git a/workbench/app/Providers/AuthServiceProvider.php b/workbench/app/Providers/AuthServiceProvider.php new file mode 100644 index 0000000..0d0599d --- /dev/null +++ b/workbench/app/Providers/AuthServiceProvider.php @@ -0,0 +1,27 @@ + + */ + protected $policies = [ + Role::class => RolePolicy::class, + ]; + + /** + * Register any authentication / authorization services. + */ + public function boot(): void + { + $this->registerPolicies(); + } +} diff --git a/workbench/app/Providers/WorkbenchServiceProvider.php b/workbench/app/Providers/WorkbenchServiceProvider.php index c5b013b..a86bbd7 100644 --- a/workbench/app/Providers/WorkbenchServiceProvider.php +++ b/workbench/app/Providers/WorkbenchServiceProvider.php @@ -3,6 +3,7 @@ namespace Workbench\App\Providers; use Illuminate\Support\ServiceProvider; +use Workbench\App\Providers\AuthServiceProvider; use Workbench\Database\Seeders\DatabaseSeeder; class WorkbenchServiceProvider extends ServiceProvider @@ -13,6 +14,7 @@ class WorkbenchServiceProvider extends ServiceProvider public function register(): void { $this->app->register(AdminPanelProvider::class); + $this->app->register(AuthServiceProvider::class); $this->app->bind('DatabaseSeeder', function ($app) { return new DatabaseSeeder; diff --git a/workbench/config/auth.php b/workbench/config/auth.php index 14ee76c..8337867 100644 --- a/workbench/config/auth.php +++ b/workbench/config/auth.php @@ -62,7 +62,7 @@ 'providers' => [ 'users' => [ 'driver' => 'eloquent', - 'model' => env('AUTH_MODEL', \Eclipse\Core\Models\User::class), + 'model' => env('AUTH_MODEL', \Workbench\App\Models\User::class), ], // 'users' => [ diff --git a/workbench/config/filament-shield.php b/workbench/config/filament-shield.php index 5ff2584..53532a6 100644 --- a/workbench/config/filament-shield.php +++ b/workbench/config/filament-shield.php @@ -17,7 +17,7 @@ 'tenant_model' => null, 'auth_provider_model' => [ - 'fqcn' => 'App\\Models\\User', + 'fqcn' => 'Workbench\\App\\Models\\User', ], 'super_admin' => [ @@ -60,7 +60,7 @@ ], 'generator' => [ - 'option' => 'policies_and_permissions', + 'option' => 'permissions', 'policy_directory' => 'Policies', 'policy_namespace' => 'Policies', ], diff --git a/workbench/database/migrations/2025_08_09_143147_create_permission_tables.php b/workbench/database/migrations/2025_08_09_143147_create_permission_tables.php deleted file mode 100644 index ce4d9d2..0000000 --- a/workbench/database/migrations/2025_08_09_143147_create_permission_tables.php +++ /dev/null @@ -1,136 +0,0 @@ -engine('InnoDB'); - $table->bigIncrements('id'); // permission id - $table->string('name'); // For MyISAM use string('name', 225); // (or 166 for InnoDB with Redundant/Compact row format) - $table->string('guard_name'); // For MyISAM use string('guard_name', 25); - $table->timestamps(); - - $table->unique(['name', 'guard_name']); - }); - - Schema::create($tableNames['roles'], static function (Blueprint $table) use ($teams, $columnNames) { - // $table->engine('InnoDB'); - $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 MyISAM use string('name', 225); // (or 166 for InnoDB with Redundant/Compact row format) - $table->string('guard_name'); // For MyISAM use string('guard_name', 25); - $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'], static 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'], static 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'], static 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']); - } -}; diff --git a/workbench/database/seeders/DatabaseSeeder.php b/workbench/database/seeders/DatabaseSeeder.php index a1c0425..14aa87a 100644 --- a/workbench/database/seeders/DatabaseSeeder.php +++ b/workbench/database/seeders/DatabaseSeeder.php @@ -3,7 +3,12 @@ namespace Workbench\Database\Seeders; use Illuminate\Database\Seeder; -// use Illuminate\Database\Console\Seeds\WithoutModelEvents; +use Illuminate\Support\Facades\Artisan; +use Illuminate\Support\Facades\Log; +use Illuminate\Support\Facades\Hash; +use Spatie\Permission\Models\Role; +use Spatie\Permission\Models\Permission; +use Workbench\App\Models\User; use Workbench\Database\Factories\UserFactory; class DatabaseSeeder extends Seeder @@ -13,12 +18,103 @@ class DatabaseSeeder extends Seeder */ public function run(): void { - // UserFactory::new()->times(10)->create(); - - // User is already created by Eclipse configuration - // UserFactory::new()->create([ - // 'name' => 'Test User', - // 'email' => 'test@example.com', - // ]); + $this->command->info('Starting database seeding...'); + // Log effective DB path to ensure seeding matches runtime + try { + $connection = config('database.default'); + $driver = config("database.connections.$connection.driver"); + $database = config("database.connections.$connection.database"); + Log::info('[Workbench][Seeder] Database configuration', [ + 'connection' => $connection, + 'driver' => $driver, + 'database' => $database, + ]); + } catch (\Throwable $e) { + // ignore + } + + // Generate Filament Shield permissions for all resources + $this->command->info('🛡️ Generating Filament Shield permissions...'); + Artisan::call('shield:generate', [ + '--all' => true, + '--panel' => 'admin', + ]); + $this->command->info('✓ Shield permissions generated'); + + // Get or create admin user + $adminEmail = 'test@example.com'; + $adminPassword = 'password'; + + $existingUser = User::where('email', $adminEmail)->first(); + + if (!$existingUser) { + $this->command->info('Creating new admin user...'); + $user = UserFactory::new()->create([ + 'name' => 'Admin User', + 'email' => $adminEmail, + 'password' => Hash::make($adminPassword), + 'email_verified_at' => now(), + ]); + $this->command->info("Admin user created with ID: {$user->id}"); + } else { + $this->command->info('Using existing admin user...'); + // Ensure the user has the correct password + $existingUser->update([ + 'password' => Hash::make($adminPassword), + 'email_verified_at' => now(), + ]); + $user = $existingUser; + $this->command->info("Admin user found with ID: {$user->id}"); + } + + // Create super_admin role if it doesn't exist + $superAdminRole = Role::firstOrCreate([ + 'name' => 'super_admin', + 'guard_name' => 'web' + ]); + $this->command->info("✓ Super admin role ensured: {$superAdminRole->name}"); + + // Create panel_user role if it doesn't exist (required by Filament Shield) + $panelUserRole = Role::firstOrCreate([ + 'name' => 'panel_user', + 'guard_name' => 'web' + ]); + $this->command->info("✓ Panel user role ensured: {$panelUserRole->name}"); + + // Get all existing roles + $allRoles = Role::all(); + $this->command->info("Found {$allRoles->count()} total roles"); + + // Assign all roles to the user + if ($allRoles->count() > 0) { + $user->syncRoles($allRoles); + $assignedRoles = $user->getRoleNames()->toArray(); + $this->command->info("✓ Assigned roles to user: " . implode(', ', $assignedRoles)); + } + + // Get all permissions and assign them directly to the user (ensures super admin access) + $allPermissions = Permission::all(); + if ($allPermissions->count() > 0) { + $user->syncPermissions($allPermissions); + $this->command->info("✓ Assigned {$allPermissions->count()} permissions directly to user"); + } else { + $this->command->info("No permissions found in database"); + } + + // Verify the user was created/updated + if ($user) { + $this->command->info("✓ Admin user verified: {$user->email} (ID: {$user->id})"); + $this->command->info("✓ User roles: " . $user->getRoleNames()->implode(', ')); + $this->command->info("✓ Direct permissions: " . $user->getDirectPermissions()->count()); + $this->command->info("✓ All permissions: " . $user->getAllPermissions()->count()); + } else { + $this->command->error("✗ Failed to create/update admin user!"); + } + + $this->command->info('Database seeding completed.'); + $this->command->info(''); + $this->command->info('🎯 Login Credentials:'); + $this->command->info(" Email: {$adminEmail}"); + $this->command->info(" Password: {$adminPassword}"); } } From 4fe23d8adb0088e81cb763f20e4882d9ef06c125 Mon Sep 17 00:00:00 2001 From: Kilian Trunk Date: Mon, 11 Aug 2025 09:28:56 +0200 Subject: [PATCH 07/45] fix(workbench-setup): fix composer test and format --- testbench.yaml | 11 +++++ .../app/Providers/AdminPanelProvider.php | 2 - .../Providers/WorkbenchServiceProvider.php | 1 - ...025_08_10_104332_create_sessions_table.php | 2 +- workbench/database/seeders/DatabaseSeeder.php | 44 +++++++++---------- 5 files changed, 34 insertions(+), 26 deletions(-) diff --git a/testbench.yaml b/testbench.yaml index e51e3a9..9c4e4d0 100644 --- a/testbench.yaml +++ b/testbench.yaml @@ -1,11 +1,22 @@ laravel: '@testbench' +env: + - DB_CONNECTION=sqlite + - DB_DATABASE=workbench/database/database.sqlite + - APP_KEY=base64:ZQvPGC7uVADkjOgtGIIuCI8u3/Pzu+VaRObIbHsgjCc= + - APP_ENV=local + - APP_DEBUG=true + - SESSION_DRIVER=database + - CACHE_STORE=database + - QUEUE_CONNECTION=database + providers: - Workbench\App\Providers\WorkbenchServiceProvider migrations: - vendor/orchestra/testbench-core/laravel/migrations - workbench/database/migrations + - database/migrations seeders: - Workbench\Database\Seeders\DatabaseSeeder diff --git a/workbench/app/Providers/AdminPanelProvider.php b/workbench/app/Providers/AdminPanelProvider.php index 9b140d8..beaf61d 100644 --- a/workbench/app/Providers/AdminPanelProvider.php +++ b/workbench/app/Providers/AdminPanelProvider.php @@ -10,14 +10,12 @@ use Filament\Pages\Dashboard; use Filament\Panel; use Filament\PanelProvider; -use Filament\Support\Facades\FilamentView; use Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse; use Illuminate\Cookie\Middleware\EncryptCookies; use Illuminate\Foundation\Http\Middleware\VerifyCsrfToken; use Illuminate\Routing\Middleware\SubstituteBindings; use Illuminate\Session\Middleware\AuthenticateSession; use Illuminate\Session\Middleware\StartSession; -use Illuminate\Support\Facades\Blade; use Illuminate\View\Middleware\ShareErrorsFromSession; class AdminPanelProvider extends PanelProvider diff --git a/workbench/app/Providers/WorkbenchServiceProvider.php b/workbench/app/Providers/WorkbenchServiceProvider.php index a86bbd7..e99a010 100644 --- a/workbench/app/Providers/WorkbenchServiceProvider.php +++ b/workbench/app/Providers/WorkbenchServiceProvider.php @@ -3,7 +3,6 @@ namespace Workbench\App\Providers; use Illuminate\Support\ServiceProvider; -use Workbench\App\Providers\AuthServiceProvider; use Workbench\Database\Seeders\DatabaseSeeder; class WorkbenchServiceProvider extends ServiceProvider diff --git a/workbench/database/migrations/2025_08_10_104332_create_sessions_table.php b/workbench/database/migrations/2025_08_10_104332_create_sessions_table.php index 25053ca..ca5922d 100644 --- a/workbench/database/migrations/2025_08_10_104332_create_sessions_table.php +++ b/workbench/database/migrations/2025_08_10_104332_create_sessions_table.php @@ -11,7 +11,7 @@ */ public function up(): void { - if (!Schema::hasTable('sessions')) { + if (! Schema::hasTable('sessions')) { Schema::create('sessions', function (Blueprint $table) { $table->string('id')->primary(); $table->foreignId('user_id')->nullable()->index(); diff --git a/workbench/database/seeders/DatabaseSeeder.php b/workbench/database/seeders/DatabaseSeeder.php index 14aa87a..2943886 100644 --- a/workbench/database/seeders/DatabaseSeeder.php +++ b/workbench/database/seeders/DatabaseSeeder.php @@ -4,10 +4,10 @@ use Illuminate\Database\Seeder; use Illuminate\Support\Facades\Artisan; -use Illuminate\Support\Facades\Log; use Illuminate\Support\Facades\Hash; -use Spatie\Permission\Models\Role; +use Illuminate\Support\Facades\Log; use Spatie\Permission\Models\Permission; +use Spatie\Permission\Models\Role; use Workbench\App\Models\User; use Workbench\Database\Factories\UserFactory; @@ -32,7 +32,7 @@ public function run(): void } catch (\Throwable $e) { // ignore } - + // Generate Filament Shield permissions for all resources $this->command->info('🛡️ Generating Filament Shield permissions...'); Artisan::call('shield:generate', [ @@ -40,14 +40,14 @@ public function run(): void '--panel' => 'admin', ]); $this->command->info('✓ Shield permissions generated'); - + // Get or create admin user $adminEmail = 'test@example.com'; $adminPassword = 'password'; - + $existingUser = User::where('email', $adminEmail)->first(); - - if (!$existingUser) { + + if (! $existingUser) { $this->command->info('Creating new admin user...'); $user = UserFactory::new()->create([ 'name' => 'Admin User', @@ -66,51 +66,51 @@ public function run(): void $user = $existingUser; $this->command->info("Admin user found with ID: {$user->id}"); } - + // Create super_admin role if it doesn't exist $superAdminRole = Role::firstOrCreate([ 'name' => 'super_admin', - 'guard_name' => 'web' + 'guard_name' => 'web', ]); $this->command->info("✓ Super admin role ensured: {$superAdminRole->name}"); - + // Create panel_user role if it doesn't exist (required by Filament Shield) $panelUserRole = Role::firstOrCreate([ 'name' => 'panel_user', - 'guard_name' => 'web' + 'guard_name' => 'web', ]); $this->command->info("✓ Panel user role ensured: {$panelUserRole->name}"); - + // Get all existing roles $allRoles = Role::all(); $this->command->info("Found {$allRoles->count()} total roles"); - + // Assign all roles to the user if ($allRoles->count() > 0) { $user->syncRoles($allRoles); $assignedRoles = $user->getRoleNames()->toArray(); - $this->command->info("✓ Assigned roles to user: " . implode(', ', $assignedRoles)); + $this->command->info('✓ Assigned roles to user: '.implode(', ', $assignedRoles)); } - + // Get all permissions and assign them directly to the user (ensures super admin access) $allPermissions = Permission::all(); if ($allPermissions->count() > 0) { $user->syncPermissions($allPermissions); $this->command->info("✓ Assigned {$allPermissions->count()} permissions directly to user"); } else { - $this->command->info("No permissions found in database"); + $this->command->info('No permissions found in database'); } - + // Verify the user was created/updated if ($user) { $this->command->info("✓ Admin user verified: {$user->email} (ID: {$user->id})"); - $this->command->info("✓ User roles: " . $user->getRoleNames()->implode(', ')); - $this->command->info("✓ Direct permissions: " . $user->getDirectPermissions()->count()); - $this->command->info("✓ All permissions: " . $user->getAllPermissions()->count()); + $this->command->info('✓ User roles: '.$user->getRoleNames()->implode(', ')); + $this->command->info('✓ Direct permissions: '.$user->getDirectPermissions()->count()); + $this->command->info('✓ All permissions: '.$user->getAllPermissions()->count()); } else { - $this->command->error("✗ Failed to create/update admin user!"); + $this->command->error('✗ Failed to create/update admin user!'); } - + $this->command->info('Database seeding completed.'); $this->command->info(''); $this->command->info('🎯 Login Credentials:'); From 2488f551530b6255c8a6890b43e01d8bb7114add Mon Sep 17 00:00:00 2001 From: Kilian Trunk Date: Mon, 11 Aug 2025 09:32:44 +0200 Subject: [PATCH 08/45] fix(workflows): fix github workflows --- .github/workflows/linter.yml | 2 +- .github/workflows/test-runner.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/linter.yml b/.github/workflows/linter.yml index a548779..737b3a5 100644 --- a/.github/workflows/linter.yml +++ b/.github/workflows/linter.yml @@ -22,7 +22,7 @@ jobs: - name: Checkout code uses: actions/checkout@v4 with: - ref: ${{ github.head_ref }} + ref: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.sha || github.sha }} - name: Run Laravel Pint uses: aglipanci/laravel-pint-action@latest diff --git a/.github/workflows/test-runner.yml b/.github/workflows/test-runner.yml index 4a1f073..b81008c 100644 --- a/.github/workflows/test-runner.yml +++ b/.github/workflows/test-runner.yml @@ -41,7 +41,7 @@ jobs: - name: Checkout code uses: actions/checkout@v4 with: - ref: ${{ github.head_ref }} + ref: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.sha || github.sha }} - name: Validate composer.json and composer.lock run: composer validate --strict From 9de446656510acfe4ce3c4daa587706bfadc1be1 Mon Sep 17 00:00:00 2001 From: Kilian Trunk Date: Sat, 16 Aug 2025 10:49:06 +0200 Subject: [PATCH 09/45] chore(workbench-setup): remove config files --- workbench/config/eclipse.php | 138 ----------------------- workbench/config/horizon.php | 213 ----------------------------------- workbench/config/themes.php | 36 ------ 3 files changed, 387 deletions(-) delete mode 100644 workbench/config/eclipse.php delete mode 100644 workbench/config/horizon.php delete mode 100644 workbench/config/themes.php diff --git a/workbench/config/eclipse.php b/workbench/config/eclipse.php deleted file mode 100644 index c8bc38a..0000000 --- a/workbench/config/eclipse.php +++ /dev/null @@ -1,138 +0,0 @@ - (bool) env('ECLIPSE_MULTI_SITE', false), - - /* - |-------------------------------------------------------------------------- - | Enable/disable user email verification - |-------------------------------------------------------------------------- - | Set this boolean to true if you want to enable parts of the application - | related to user email address verification - */ - 'email_verification' => (bool) env('ECLIPSE_EMAIL_VERIFICATION', false), - - /* - |-------------------------------------------------------------------------- - | Seeder setup - |-------------------------------------------------------------------------- - | Here you can specify any data you want seeded by default. - | All settings are optional. - */ - 'seed' => [ - 'roles' => [ - // Number of randomly generated roles - 'count' => 2, - // Roles with preset data - // Required attributes: name, guard_name - 'presets' => [ - [ - 'data' => [ - 'name' => 'admin', - 'guard_name' => 'web', - ], - ], - ], - ], - 'users' => [ - // Number of randomly generated users - 'count' => 5, - // Users with preset data - 'presets' => [ - [ - 'data' => [ - // Email is required - 'email' => 'test@example.com', - // Additional attributes — if any is omitted, faker will be used - 'first_name' => 'Test', - 'last_name' => 'User', - 'password' => 'test123', - ], - // Optional role(s) to set (for multiple, use an array) - 'role' => 'super_admin', - ], - [ - 'data' => [ - 'email' => 'admin@example.com', - ], - 'role' => 'admin', - ], - ], - ], - // Sites — only used if the multi-site feature is enabled above - 'sites' => [ - // Number of randomly generated sites - 'count' => 0, - // Sites with preset data - 'presets' => [ - [ - 'data' => [ - 'domain' => basename(config('app.url')), - 'name' => config('app.name'), - ], - ], - [ - 'data' => [ - 'domain' => 'another.lndo.site', - 'name' => 'Another site', - ], - ], - ], - ], - ], - - /* - |-------------------------------------------------------------------------- - | Developer logins - |-------------------------------------------------------------------------- - | Provide a list of users to use as config for the "Developer logins" - | Filament plugin - */ - 'developer_logins' => [ - 'Super admin' => 'test@example.com', - 'Admin' => 'admin@example.com', - ], - - /* - |-------------------------------------------------------------------------- - | Telescope package additional configuration - |-------------------------------------------------------------------------- - */ - 'telescope' => [ - /* - * Enable dark theme? - */ - 'dark_theme' => (bool) env('TELESCOPE_DARK_THEME', false), - ], - - /* - |-------------------------------------------------------------------------- - | Horizon package additional configuration - |-------------------------------------------------------------------------- - */ - 'horizon' => [ - /* - * List of email addresses of users that are allowed to view the Horizon - * panel in non-local environments - */ - 'emails' => [ - // - ], - ], - - /* - |-------------------------------------------------------------------------- - | Additional tools to be displayed in the "Tools" menu of the admin panel - |-------------------------------------------------------------------------- - */ - 'tools' => [ - 'phpmyadmin' => env('PHPMYADMIN_URL'), - ], - -]; diff --git a/workbench/config/horizon.php b/workbench/config/horizon.php deleted file mode 100644 index 5101f6f..0000000 --- a/workbench/config/horizon.php +++ /dev/null @@ -1,213 +0,0 @@ - env('HORIZON_DOMAIN'), - - /* - |-------------------------------------------------------------------------- - | Horizon Path - |-------------------------------------------------------------------------- - | - | This is the URI path where Horizon 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('HORIZON_PATH', 'horizon'), - - /* - |-------------------------------------------------------------------------- - | Horizon Redis Connection - |-------------------------------------------------------------------------- - | - | This is the name of the Redis connection where Horizon will store the - | meta information required for it to function. It includes the list - | of supervisors, failed jobs, job metrics, and other information. - | - */ - - 'use' => 'default', - - /* - |-------------------------------------------------------------------------- - | Horizon Redis Prefix - |-------------------------------------------------------------------------- - | - | This prefix will be used when storing all Horizon data in Redis. You - | may modify the prefix when you are running multiple installations - | of Horizon on the same server so that they don't have problems. - | - */ - - 'prefix' => env( - 'HORIZON_PREFIX', - Str::slug(env('APP_NAME', 'laravel'), '_').'_horizon:' - ), - - /* - |-------------------------------------------------------------------------- - | Horizon Route Middleware - |-------------------------------------------------------------------------- - | - | These middleware will get attached onto each Horizon 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'], - - /* - |-------------------------------------------------------------------------- - | Queue Wait Time Thresholds - |-------------------------------------------------------------------------- - | - | This option allows you to configure when the LongWaitDetected event - | will be fired. Every connection / queue combination may have its - | own, unique threshold (in seconds) before this event is fired. - | - */ - - 'waits' => [ - 'redis:default' => 60, - ], - - /* - |-------------------------------------------------------------------------- - | Job Trimming Times - |-------------------------------------------------------------------------- - | - | Here you can configure for how long (in minutes) you desire Horizon to - | persist the recent and failed jobs. Typically, recent jobs are kept - | for one hour while all failed jobs are stored for an entire week. - | - */ - - 'trim' => [ - 'recent' => 60, - 'pending' => 60, - 'completed' => 60, - 'recent_failed' => 10080, - 'failed' => 10080, - 'monitored' => 10080, - ], - - /* - |-------------------------------------------------------------------------- - | Silenced Jobs - |-------------------------------------------------------------------------- - | - | Silencing a job will instruct Horizon to not place the job in the list - | of completed jobs within the Horizon dashboard. This setting may be - | used to fully remove any noisy jobs from the completed jobs list. - | - */ - - 'silenced' => [ - // App\Jobs\ExampleJob::class, - ], - - /* - |-------------------------------------------------------------------------- - | Metrics - |-------------------------------------------------------------------------- - | - | Here you can configure how many snapshots should be kept to display in - | the metrics graph. This will get used in combination with Horizon's - | `horizon:snapshot` schedule to define how long to retain metrics. - | - */ - - 'metrics' => [ - 'trim_snapshots' => [ - 'job' => 24, - 'queue' => 24, - ], - ], - - /* - |-------------------------------------------------------------------------- - | Fast Termination - |-------------------------------------------------------------------------- - | - | When this option is enabled, Horizon's "terminate" command will not - | wait on all of the workers to terminate unless the --wait option - | is provided. Fast termination can shorten deployment delay by - | allowing a new instance of Horizon to start while the last - | instance will continue to terminate each of its workers. - | - */ - - 'fast_termination' => false, - - /* - |-------------------------------------------------------------------------- - | Memory Limit (MB) - |-------------------------------------------------------------------------- - | - | This value describes the maximum amount of memory the Horizon master - | supervisor may consume before it is terminated and restarted. For - | configuring these limits on your workers, see the next section. - | - */ - - 'memory_limit' => 64, - - /* - |-------------------------------------------------------------------------- - | Queue Worker Configuration - |-------------------------------------------------------------------------- - | - | Here you may define the queue worker settings used by your application - | in all environments. These supervisors and settings handle all your - | queued jobs and will be provisioned by Horizon during deployment. - | - */ - - 'defaults' => [ - 'supervisor-1' => [ - 'connection' => 'redis', - 'queue' => ['default'], - 'balance' => 'auto', - 'autoScalingStrategy' => 'time', - 'maxProcesses' => 1, - 'maxTime' => 0, - 'maxJobs' => 0, - 'memory' => 128, - 'tries' => 1, - 'timeout' => 60, - 'nice' => 0, - ], - ], - - 'environments' => [ - 'production' => [ - 'supervisor-1' => [ - 'maxProcesses' => 10, - 'balanceMaxShift' => 1, - 'balanceCooldown' => 3, - ], - ], - - 'local' => [ - 'supervisor-1' => [ - 'maxProcesses' => 3, - ], - ], - ], -]; diff --git a/workbench/config/themes.php b/workbench/config/themes.php deleted file mode 100644 index 03fae51..0000000 --- a/workbench/config/themes.php +++ /dev/null @@ -1,36 +0,0 @@ - 'user', - - /* - |-------------------------------------------------------------------------- - | Theme Icon - |-------------------------------------------------------------------------- - */ - - 'icon' => 'heroicon-o-swatch', - - /* - |-------------------------------------------------------------------------- - | Default Theme - |-------------------------------------------------------------------------- - */ - - 'default' => [ - 'theme' => 'default', - 'theme_color' => 'blue', - ], -]; From ed3b2b00b3863f9318d50dfccde0c38c17f5ca2f Mon Sep 17 00:00:00 2001 From: Kilian Trunk Date: Sat, 16 Aug 2025 11:08:08 +0200 Subject: [PATCH 10/45] chore(workbench-setup): seeder fixes --- workbench/database/seeders/DatabaseSeeder.php | 56 +++++++------------ 1 file changed, 20 insertions(+), 36 deletions(-) diff --git a/workbench/database/seeders/DatabaseSeeder.php b/workbench/database/seeders/DatabaseSeeder.php index 2943886..8f6f511 100644 --- a/workbench/database/seeders/DatabaseSeeder.php +++ b/workbench/database/seeders/DatabaseSeeder.php @@ -5,7 +5,6 @@ use Illuminate\Database\Seeder; use Illuminate\Support\Facades\Artisan; use Illuminate\Support\Facades\Hash; -use Illuminate\Support\Facades\Log; use Spatie\Permission\Models\Permission; use Spatie\Permission\Models\Role; use Workbench\App\Models\User; @@ -19,19 +18,6 @@ class DatabaseSeeder extends Seeder public function run(): void { $this->command->info('Starting database seeding...'); - // Log effective DB path to ensure seeding matches runtime - try { - $connection = config('database.default'); - $driver = config("database.connections.$connection.driver"); - $database = config("database.connections.$connection.database"); - Log::info('[Workbench][Seeder] Database configuration', [ - 'connection' => $connection, - 'driver' => $driver, - 'database' => $database, - ]); - } catch (\Throwable $e) { - // ignore - } // Generate Filament Shield permissions for all resources $this->command->info('🛡️ Generating Filament Shield permissions...'); @@ -41,6 +27,10 @@ public function run(): void ]); $this->command->info('✓ Shield permissions generated'); + // Flush Spatie permission cache before assigning roles/permissions + Artisan::call('permission:cache-reset'); + $this->command->info('✓ Spatie permission cache reset'); + // Get or create admin user $adminEmail = 'test@example.com'; $adminPassword = 'password'; @@ -67,46 +57,40 @@ public function run(): void $this->command->info("Admin user found with ID: {$user->id}"); } - // Create super_admin role if it doesn't exist + // Ensure required roles exist $superAdminRole = Role::firstOrCreate([ 'name' => 'super_admin', 'guard_name' => 'web', ]); $this->command->info("✓ Super admin role ensured: {$superAdminRole->name}"); - // Create panel_user role if it doesn't exist (required by Filament Shield) $panelUserRole = Role::firstOrCreate([ 'name' => 'panel_user', 'guard_name' => 'web', ]); $this->command->info("✓ Panel user role ensured: {$panelUserRole->name}"); - // Get all existing roles - $allRoles = Role::all(); - $this->command->info("Found {$allRoles->count()} total roles"); - - // Assign all roles to the user - if ($allRoles->count() > 0) { - $user->syncRoles($allRoles); - $assignedRoles = $user->getRoleNames()->toArray(); - $this->command->info('✓ Assigned roles to user: '.implode(', ', $assignedRoles)); - } - - // Get all permissions and assign them directly to the user (ensures super admin access) + // Attach ALL permissions to the super_admin role (single source of truth) $allPermissions = Permission::all(); if ($allPermissions->count() > 0) { - $user->syncPermissions($allPermissions); - $this->command->info("✓ Assigned {$allPermissions->count()} permissions directly to user"); + $superAdminRole->syncPermissions($allPermissions); + $this->command->info("✓ Synced {$allPermissions->count()} permissions to role: {$superAdminRole->name}"); } else { - $this->command->info('No permissions found in database'); + $this->command->warn('No permissions found in database to attach to super_admin role.'); } - // Verify the user was created/updated + // Assign only the roles we actually need to the user + $user->syncRoles([$superAdminRole->name, $panelUserRole->name]); + $this->command->info('✓ Assigned roles to user: '.implode(', ', $user->getRoleNames()->toArray())); + + // Refresh cache once more to reflect the latest assignments during this run + Artisan::call('permission:cache-reset'); + if ($user) { $this->command->info("✓ Admin user verified: {$user->email} (ID: {$user->id})"); $this->command->info('✓ User roles: '.$user->getRoleNames()->implode(', ')); - $this->command->info('✓ Direct permissions: '.$user->getDirectPermissions()->count()); - $this->command->info('✓ All permissions: '.$user->getAllPermissions()->count()); + $this->command->info('✓ Direct permissions: '.$user->getDirectPermissions()->count().' (expected 0)'); + $this->command->info('✓ All permissions via roles: '.$user->getAllPermissions()->count()); } else { $this->command->error('✗ Failed to create/update admin user!'); } @@ -114,7 +98,7 @@ public function run(): void $this->command->info('Database seeding completed.'); $this->command->info(''); $this->command->info('🎯 Login Credentials:'); - $this->command->info(" Email: {$adminEmail}"); - $this->command->info(" Password: {$adminPassword}"); + $this->command->info(" Email: {$adminEmail}"); + $this->command->info(" Password: {$adminPassword}"); } } From 3c46c3cca0c8ef2d8b0be4f734f087edbbb0f2ed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Omer=20=C5=A0abi=C4=87?= Date: Wed, 20 Aug 2025 21:03:24 +0200 Subject: [PATCH 11/45] build(workbench): set more gitignores --- workbench/.gitignore | 2 +- workbench/bootstrap/cache/.gitignore | 2 ++ workbench/database/.gitignore | 1 + 3 files changed, 4 insertions(+), 1 deletion(-) create mode 100644 workbench/bootstrap/cache/.gitignore create mode 100644 workbench/database/.gitignore diff --git a/workbench/.gitignore b/workbench/.gitignore index 30e4180..7df807a 100644 --- a/workbench/.gitignore +++ b/workbench/.gitignore @@ -4,4 +4,4 @@ storage stubs/ resources/views/vendor/ lang/ -public/ \ No newline at end of file +vendor diff --git a/workbench/bootstrap/cache/.gitignore b/workbench/bootstrap/cache/.gitignore new file mode 100644 index 0000000..d6b7ef3 --- /dev/null +++ b/workbench/bootstrap/cache/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore diff --git a/workbench/database/.gitignore b/workbench/database/.gitignore new file mode 100644 index 0000000..9b19b93 --- /dev/null +++ b/workbench/database/.gitignore @@ -0,0 +1 @@ +*.sqlite* From d865d36c0f63449c48f3bf1ec4ab34cc9a9a763c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Omer=20=C5=A0abi=C4=87?= Date: Wed, 20 Aug 2025 21:04:04 +0200 Subject: [PATCH 12/45] build(workbench): fix workbench .env file setup --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 51538f3..bf73a88 100644 --- a/composer.json +++ b/composer.json @@ -71,7 +71,7 @@ "@php vendor/bin/testbench serve --ansi" ], "setup": [ - "@php -r \"if (!file_exists('.env')) { copy('.env.example', '.env'); echo '.env file created from .env.example\\n'; }\"", + "@php -r \"if (!file_exists('workbench/.env')) { copy('workbench/.env.example', 'workbench/.env'); echo '.env file created from .env.example\\n'; }\"", "@php vendor/bin/testbench key:generate --ansi", "npm install", "@php vendor/bin/testbench vendor:publish --provider='Spatie\\Permission\\PermissionServiceProvider'", From 496d65ca8985acfcbd0e76c2be99755014f00304 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Omer=20=C5=A0abi=C4=87?= Date: Wed, 20 Aug 2025 21:05:25 +0200 Subject: [PATCH 13/45] build(workbench): add permissions migration --- ..._08_20_121559_create_permission_tables.php | 136 ++++++++++++++++++ 1 file changed, 136 insertions(+) create mode 100644 workbench/database/migrations/2025_08_20_121559_create_permission_tables.php diff --git a/workbench/database/migrations/2025_08_20_121559_create_permission_tables.php b/workbench/database/migrations/2025_08_20_121559_create_permission_tables.php new file mode 100644 index 0000000..ce4d9d2 --- /dev/null +++ b/workbench/database/migrations/2025_08_20_121559_create_permission_tables.php @@ -0,0 +1,136 @@ +engine('InnoDB'); + $table->bigIncrements('id'); // permission id + $table->string('name'); // For MyISAM use string('name', 225); // (or 166 for InnoDB with Redundant/Compact row format) + $table->string('guard_name'); // For MyISAM use string('guard_name', 25); + $table->timestamps(); + + $table->unique(['name', 'guard_name']); + }); + + Schema::create($tableNames['roles'], static function (Blueprint $table) use ($teams, $columnNames) { + // $table->engine('InnoDB'); + $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 MyISAM use string('name', 225); // (or 166 for InnoDB with Redundant/Compact row format) + $table->string('guard_name'); // For MyISAM use string('guard_name', 25); + $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'], static 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'], static 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'], static 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']); + } +}; From c0449c150bdee1affbff7d2ba4bde48876b07f31 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Omer=20=C5=A0abi=C4=87?= Date: Wed, 20 Aug 2025 21:19:45 +0200 Subject: [PATCH 14/45] build(workbench): fix app migrations --- testbench.yaml | 1 + ..._08_20_121559_create_permission_tables.php | 136 ------------------ 2 files changed, 1 insertion(+), 136 deletions(-) delete mode 100644 workbench/database/migrations/2025_08_20_121559_create_permission_tables.php diff --git a/testbench.yaml b/testbench.yaml index 9c4e4d0..0f7aabb 100644 --- a/testbench.yaml +++ b/testbench.yaml @@ -15,6 +15,7 @@ providers: migrations: - vendor/orchestra/testbench-core/laravel/migrations + - vendor/orchestra/testbench-core/laravel/database/migrations - workbench/database/migrations - database/migrations diff --git a/workbench/database/migrations/2025_08_20_121559_create_permission_tables.php b/workbench/database/migrations/2025_08_20_121559_create_permission_tables.php deleted file mode 100644 index ce4d9d2..0000000 --- a/workbench/database/migrations/2025_08_20_121559_create_permission_tables.php +++ /dev/null @@ -1,136 +0,0 @@ -engine('InnoDB'); - $table->bigIncrements('id'); // permission id - $table->string('name'); // For MyISAM use string('name', 225); // (or 166 for InnoDB with Redundant/Compact row format) - $table->string('guard_name'); // For MyISAM use string('guard_name', 25); - $table->timestamps(); - - $table->unique(['name', 'guard_name']); - }); - - Schema::create($tableNames['roles'], static function (Blueprint $table) use ($teams, $columnNames) { - // $table->engine('InnoDB'); - $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 MyISAM use string('name', 225); // (or 166 for InnoDB with Redundant/Compact row format) - $table->string('guard_name'); // For MyISAM use string('guard_name', 25); - $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'], static 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'], static 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'], static 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']); - } -}; From 509d473e148cd7e0a4108d03dede81a4d77aca19 Mon Sep 17 00:00:00 2001 From: Kilian Trunk Date: Thu, 21 Aug 2025 12:16:02 +0200 Subject: [PATCH 15/45] fix: setup fixes --- .lando.yml | 1 + workbench/.gitignore | 21 ++++++++- workbench/config/settings.php | 86 +++++++++++++++++++++++++++++++++++ workbench/public/.htaccess | 21 +++++++++ workbench/public/index.php | 55 ++++++++++++++++++++++ workbench/public/robots.txt | 2 + 6 files changed, 185 insertions(+), 1 deletion(-) create mode 100644 workbench/config/settings.php create mode 100644 workbench/public/.htaccess create mode 100644 workbench/public/index.php create mode 100644 workbench/public/robots.txt diff --git a/.lando.yml b/.lando.yml index 14312f4..408fd9f 100644 --- a/.lando.yml +++ b/.lando.yml @@ -11,6 +11,7 @@ services: xdebug: "debug,develop,coverage" environment: TZ: "Europe/Ljubljana" + APP_BASE_PATH: "/app/workbench" TESTBENCH_WORKING_PATH: "/app" overrides: image: slimdeluxe/php:8.3-v1.2 diff --git a/workbench/.gitignore b/workbench/.gitignore index 7df807a..1afdc2e 100644 --- a/workbench/.gitignore +++ b/workbench/.gitignore @@ -1,7 +1,26 @@ .env .env.dusk -storage +storage/app/* +!storage/app/.gitkeep +!storage/app/public/ +storage/app/public/* +!storage/app/public/.gitkeep +storage/framework/cache/* +!storage/framework/cache/.gitkeep +!storage/framework/cache/data/ +storage/framework/cache/data/* +!storage/framework/cache/data/.gitkeep +storage/framework/sessions/* +!storage/framework/sessions/.gitkeep +storage/framework/testing/* +!storage/framework/testing/.gitkeep +storage/framework/views/* +!storage/framework/views/.gitkeep +storage/logs/* +!storage/logs/.gitkeep stubs/ resources/views/vendor/ lang/ vendor +public/build +public/hot diff --git a/workbench/config/settings.php b/workbench/config/settings.php new file mode 100644 index 0000000..30f723b --- /dev/null +++ b/workbench/config/settings.php @@ -0,0 +1,86 @@ + [ + // + ], + + /* + * The path where the settings classes will be created. + */ + 'setting_class_path' => app_path('Settings'), + + /* + * In production, the settings are cached to speed up performance. If you + * want to add extra flags these can be set in the following array. + */ + 'cache_path' => storage_path('app/laravel-settings'), + + /* + * Here you can specify which settings should be encrypted when stored. + */ + 'encrypted' => [ + // 'some_secret_setting', + ], + + /* + * Drivers can be used to store settings in different ways. Each driver + * has its own configuration. + */ + 'drivers' => [ + 'database' => [ + 'driver' => Spatie\LaravelSettings\SettingsRepositories\DatabaseSettingsRepository::class, + 'table' => 'settings', + 'connection' => null, + ], + 'redis' => [ + 'driver' => Spatie\LaravelSettings\SettingsRepositories\RedisSettingsRepository::class, + 'connection' => null, + 'prefix' => null, + ], + ], + + /* + * The default driver to use when no driver has been specified. + */ + 'default' => 'database', + + /* + * If you're using the database driver, you can specify here if the + * migrations should be published. + */ + 'migrations' => [ + 'settings_table' => true, + ], + + /* + * Global casts will be applied to all settings properties. + */ + 'global_casts' => [ + DateTimeInterface::class => Spatie\LaravelSettings\SettingsCasts\DateTimeInterfaceCast::class, + DateTimeImmutable::class => Spatie\LaravelSettings\SettingsCasts\DateTimeWrapperCast::class . ':' . DateTimeImmutable::class, + DateTime::class => Spatie\LaravelSettings\SettingsCasts\DateTimeWrapperCast::class . ':' . DateTime::class, + // Illuminate\Support\Collection::class => Spatie\LaravelSettings\SettingsCasts\CollectionCast::class, + ], + + /* + * The lock store is used to ensure that in concurrent applications, settings + * are stored in a safe way. + */ + 'lock_store' => 'default', + + /* + * Milliseconds to wait in case the store is locked. + */ + 'lock_timeout' => 5000, + + /* + * The cache store is used to cache settings. This speeds up the retrieval + * of settings. By default the default cache store is used. + */ + 'cache_store' => 'default', +]; \ No newline at end of file diff --git a/workbench/public/.htaccess b/workbench/public/.htaccess new file mode 100644 index 0000000..49cb758 --- /dev/null +++ b/workbench/public/.htaccess @@ -0,0 +1,21 @@ + + + Options -MultiViews -Indexes + + + RewriteEngine On + + # Handle Authorization Header + RewriteCond %{HTTP:Authorization} . + RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}] + + # Redirect Trailing Slashes If Not A Folder... + RewriteCond %{REQUEST_FILENAME} !-d + RewriteCond %{REQUEST_URI} (.+)/$ + RewriteRule ^ %1 [L,R=301] + + # Send Requests To Front Controller... + RewriteCond %{REQUEST_FILENAME} !-d + RewriteCond %{REQUEST_FILENAME} !-f + RewriteRule ^ index.php [L] + \ No newline at end of file diff --git a/workbench/public/index.php b/workbench/public/index.php new file mode 100644 index 0000000..553cd74 --- /dev/null +++ b/workbench/public/index.php @@ -0,0 +1,55 @@ +make(Kernel::class); + +$response = $kernel->handle( + $request = Request::capture() +)->send(); + +$kernel->terminate($request, $response); \ No newline at end of file diff --git a/workbench/public/robots.txt b/workbench/public/robots.txt new file mode 100644 index 0000000..6f27bb6 --- /dev/null +++ b/workbench/public/robots.txt @@ -0,0 +1,2 @@ +User-agent: * +Disallow: \ No newline at end of file From dbd851665a0902f24f6bf0d9eab660a0eb30fed2 Mon Sep 17 00:00:00 2001 From: Kilian Trunk Date: Thu, 21 Aug 2025 12:35:59 +0200 Subject: [PATCH 16/45] fix: more fixes :( --- testbench.yaml | 2 +- workbench/.env.example | 2 +- .../Providers/WorkbenchServiceProvider.php | 4 + workbench/config/settings.php | 86 ----------- ..._08_21_103034_create_permission_tables.php | 144 ++++++++++++++++++ workbench/public/index.php | 2 +- 6 files changed, 151 insertions(+), 89 deletions(-) delete mode 100644 workbench/config/settings.php create mode 100644 workbench/database/migrations/2025_08_21_103034_create_permission_tables.php diff --git a/testbench.yaml b/testbench.yaml index 0f7aabb..1ea616a 100644 --- a/testbench.yaml +++ b/testbench.yaml @@ -29,7 +29,7 @@ workbench: discovers: web: true api: false - commands: false + commands: true components: false views: false build: diff --git a/workbench/.env.example b/workbench/.env.example index 138db09..34acb1e 100644 --- a/workbench/.env.example +++ b/workbench/.env.example @@ -21,9 +21,9 @@ LOG_DEPRECATIONS_CHANNEL=null LOG_LEVEL=debug DB_CONNECTION=sqlite +DB_DATABASE=/app/workbench/database/database.sqlite # DB_HOST=127.0.0.1 # DB_PORT=3306 -# DB_DATABASE=laravel # DB_USERNAME=root # DB_PASSWORD= diff --git a/workbench/app/Providers/WorkbenchServiceProvider.php b/workbench/app/Providers/WorkbenchServiceProvider.php index e99a010..63cc6a7 100644 --- a/workbench/app/Providers/WorkbenchServiceProvider.php +++ b/workbench/app/Providers/WorkbenchServiceProvider.php @@ -12,6 +12,10 @@ class WorkbenchServiceProvider extends ServiceProvider */ public function register(): void { + $this->app->register(\Spatie\Permission\PermissionServiceProvider::class); + $this->app->register(\BezhanSalleh\FilamentShield\FilamentShieldServiceProvider::class); + $this->app->register(\Livewire\LivewireServiceProvider::class); + $this->app->register(\Filament\FilamentServiceProvider::class); $this->app->register(AdminPanelProvider::class); $this->app->register(AuthServiceProvider::class); diff --git a/workbench/config/settings.php b/workbench/config/settings.php deleted file mode 100644 index 30f723b..0000000 --- a/workbench/config/settings.php +++ /dev/null @@ -1,86 +0,0 @@ - [ - // - ], - - /* - * The path where the settings classes will be created. - */ - 'setting_class_path' => app_path('Settings'), - - /* - * In production, the settings are cached to speed up performance. If you - * want to add extra flags these can be set in the following array. - */ - 'cache_path' => storage_path('app/laravel-settings'), - - /* - * Here you can specify which settings should be encrypted when stored. - */ - 'encrypted' => [ - // 'some_secret_setting', - ], - - /* - * Drivers can be used to store settings in different ways. Each driver - * has its own configuration. - */ - 'drivers' => [ - 'database' => [ - 'driver' => Spatie\LaravelSettings\SettingsRepositories\DatabaseSettingsRepository::class, - 'table' => 'settings', - 'connection' => null, - ], - 'redis' => [ - 'driver' => Spatie\LaravelSettings\SettingsRepositories\RedisSettingsRepository::class, - 'connection' => null, - 'prefix' => null, - ], - ], - - /* - * The default driver to use when no driver has been specified. - */ - 'default' => 'database', - - /* - * If you're using the database driver, you can specify here if the - * migrations should be published. - */ - 'migrations' => [ - 'settings_table' => true, - ], - - /* - * Global casts will be applied to all settings properties. - */ - 'global_casts' => [ - DateTimeInterface::class => Spatie\LaravelSettings\SettingsCasts\DateTimeInterfaceCast::class, - DateTimeImmutable::class => Spatie\LaravelSettings\SettingsCasts\DateTimeWrapperCast::class . ':' . DateTimeImmutable::class, - DateTime::class => Spatie\LaravelSettings\SettingsCasts\DateTimeWrapperCast::class . ':' . DateTime::class, - // Illuminate\Support\Collection::class => Spatie\LaravelSettings\SettingsCasts\CollectionCast::class, - ], - - /* - * The lock store is used to ensure that in concurrent applications, settings - * are stored in a safe way. - */ - 'lock_store' => 'default', - - /* - * Milliseconds to wait in case the store is locked. - */ - 'lock_timeout' => 5000, - - /* - * The cache store is used to cache settings. This speeds up the retrieval - * of settings. By default the default cache store is used. - */ - 'cache_store' => 'default', -]; \ No newline at end of file diff --git a/workbench/database/migrations/2025_08_21_103034_create_permission_tables.php b/workbench/database/migrations/2025_08_21_103034_create_permission_tables.php new file mode 100644 index 0000000..968d610 --- /dev/null +++ b/workbench/database/migrations/2025_08_21_103034_create_permission_tables.php @@ -0,0 +1,144 @@ +bigIncrements('id'); + $table->string('name'); + $table->string('guard_name'); + $table->timestamps(); + + $table->unique(['name', 'guard_name']); + }); + } + + if (! Schema::hasTable($tableNames['roles'])) { + Schema::create($tableNames['roles'], static function (Blueprint $table) use ($teams, $columnNames) { + $table->bigIncrements('id'); + if ($teams || config('permission.testing')) { + $table->unsignedBigInteger($columnNames['team_foreign_key'])->nullable(); + $table->index($columnNames['team_foreign_key'], 'roles_team_foreign_key_index'); + } + $table->string('name'); + $table->string('guard_name'); + $table->timestamps(); + + if ($teams || config('permission.testing')) { + $table->unique([$columnNames['team_foreign_key'], 'name', 'guard_name']); + } else { + $table->unique(['name', 'guard_name']); + } + }); + } + + if (! Schema::hasTable($tableNames['model_has_permissions'])) { + Schema::create($tableNames['model_has_permissions'], static 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') + ->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'); + } + }); + } + + if (! Schema::hasTable($tableNames['model_has_roles'])) { + Schema::create($tableNames['model_has_roles'], static 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') + ->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'); + } + }); + } + + if (! Schema::hasTable($tableNames['role_has_permissions'])) { + Schema::create($tableNames['role_has_permissions'], static function (Blueprint $table) use ($tableNames, $pivotRole, $pivotPermission) { + $table->unsignedBigInteger($pivotPermission); + $table->unsignedBigInteger($pivotRole); + + $table->foreign($pivotPermission) + ->references('id') + ->on($tableNames['permissions']) + ->onDelete('cascade'); + + $table->foreign($pivotRole) + ->references('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::dropIfExists($tableNames['role_has_permissions']); + Schema::dropIfExists($tableNames['model_has_roles']); + Schema::dropIfExists($tableNames['model_has_permissions']); + Schema::dropIfExists($tableNames['roles']); + Schema::dropIfExists($tableNames['permissions']); + } +}; diff --git a/workbench/public/index.php b/workbench/public/index.php index 553cd74..6d3fffa 100644 --- a/workbench/public/index.php +++ b/workbench/public/index.php @@ -52,4 +52,4 @@ $request = Request::capture() )->send(); -$kernel->terminate($request, $response); \ No newline at end of file +$kernel->terminate($request, $response); From 4b4c382fc205615978e9c322f23413042c8a769e Mon Sep 17 00:00:00 2001 From: Kilian Trunk Date: Thu, 21 Aug 2025 12:56:34 +0200 Subject: [PATCH 17/45] fix: remove duplicate lando file --- .lando.yml | 43 ------------------------------------------- 1 file changed, 43 deletions(-) delete mode 100644 .lando.yml diff --git a/.lando.yml b/.lando.yml deleted file mode 100644 index 408fd9f..0000000 --- a/.lando.yml +++ /dev/null @@ -1,43 +0,0 @@ -#file: noinspection ComposeUnknownKeys,YAMLSchemaValidation -name: eclipse-world-plugin -recipe: laravel -config: - webroot: workbench/public - php: '8.3' - via: nginx -services: - appserver: - type: php:custom - xdebug: "debug,develop,coverage" - environment: - TZ: "Europe/Ljubljana" - APP_BASE_PATH: "/app/workbench" - TESTBENCH_WORKING_PATH: "/app" - overrides: - image: slimdeluxe/php:8.3-v1.2 - platform: linux/amd64 - run: - - composer install --no-interaction --prefer-dist - - composer run setup --no-interaction - - composer run build --no-interaction - appserver_nginx: - scanner: - path: /admin/login - retry: 5 -tooling: - php: - service: appserver - composer: - service: appserver - test: - service: appserver - description: Run tests - cmd: "composer test" - format: - service: appserver - description: Fix code style issues - cmd: "composer format" - testbench: - service: appserver - description: Run testbench CLI - cmd: "vendor/bin/testbench" From 787aeceac6adacfb0d7e3506e499921dbd97b286 Mon Sep 17 00:00:00 2001 From: Kilian Trunk Date: Thu, 21 Aug 2025 12:57:09 +0200 Subject: [PATCH 18/45] fix: remove duplicate migration --- ..._08_21_103034_create_permission_tables.php | 144 ------------------ 1 file changed, 144 deletions(-) delete mode 100644 workbench/database/migrations/2025_08_21_103034_create_permission_tables.php diff --git a/workbench/database/migrations/2025_08_21_103034_create_permission_tables.php b/workbench/database/migrations/2025_08_21_103034_create_permission_tables.php deleted file mode 100644 index 968d610..0000000 --- a/workbench/database/migrations/2025_08_21_103034_create_permission_tables.php +++ /dev/null @@ -1,144 +0,0 @@ -bigIncrements('id'); - $table->string('name'); - $table->string('guard_name'); - $table->timestamps(); - - $table->unique(['name', 'guard_name']); - }); - } - - if (! Schema::hasTable($tableNames['roles'])) { - Schema::create($tableNames['roles'], static function (Blueprint $table) use ($teams, $columnNames) { - $table->bigIncrements('id'); - if ($teams || config('permission.testing')) { - $table->unsignedBigInteger($columnNames['team_foreign_key'])->nullable(); - $table->index($columnNames['team_foreign_key'], 'roles_team_foreign_key_index'); - } - $table->string('name'); - $table->string('guard_name'); - $table->timestamps(); - - if ($teams || config('permission.testing')) { - $table->unique([$columnNames['team_foreign_key'], 'name', 'guard_name']); - } else { - $table->unique(['name', 'guard_name']); - } - }); - } - - if (! Schema::hasTable($tableNames['model_has_permissions'])) { - Schema::create($tableNames['model_has_permissions'], static 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') - ->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'); - } - }); - } - - if (! Schema::hasTable($tableNames['model_has_roles'])) { - Schema::create($tableNames['model_has_roles'], static 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') - ->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'); - } - }); - } - - if (! Schema::hasTable($tableNames['role_has_permissions'])) { - Schema::create($tableNames['role_has_permissions'], static function (Blueprint $table) use ($tableNames, $pivotRole, $pivotPermission) { - $table->unsignedBigInteger($pivotPermission); - $table->unsignedBigInteger($pivotRole); - - $table->foreign($pivotPermission) - ->references('id') - ->on($tableNames['permissions']) - ->onDelete('cascade'); - - $table->foreign($pivotRole) - ->references('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::dropIfExists($tableNames['role_has_permissions']); - Schema::dropIfExists($tableNames['model_has_roles']); - Schema::dropIfExists($tableNames['model_has_permissions']); - Schema::dropIfExists($tableNames['roles']); - Schema::dropIfExists($tableNames['permissions']); - } -}; From 527b92a3493d2181064dfc61b5798f54bcebcf4c Mon Sep 17 00:00:00 2001 From: Kilian Trunk Date: Thu, 21 Aug 2025 13:01:21 +0200 Subject: [PATCH 19/45] fix: remove publishing from composer scirpt --- composer.json | 1 - 1 file changed, 1 deletion(-) diff --git a/composer.json b/composer.json index bf73a88..56321de 100644 --- a/composer.json +++ b/composer.json @@ -74,7 +74,6 @@ "@php -r \"if (!file_exists('workbench/.env')) { copy('workbench/.env.example', 'workbench/.env'); echo '.env file created from .env.example\\n'; }\"", "@php vendor/bin/testbench key:generate --ansi", "npm install", - "@php vendor/bin/testbench vendor:publish --provider='Spatie\\Permission\\PermissionServiceProvider'", "@php vendor/bin/testbench vendor:publish --tag='filament-shield-config'", "@php vendor/bin/testbench package:sync-skeleton" ] From 07d02961489151c36bad0f7bbd1bfc7bcab75403 Mon Sep 17 00:00:00 2001 From: Kilian Trunk Date: Thu, 21 Aug 2025 13:06:05 +0200 Subject: [PATCH 20/45] fix: fix ci --- tests/Pest.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/Pest.php b/tests/Pest.php index 34e804d..6849765 100644 --- a/tests/Pest.php +++ b/tests/Pest.php @@ -15,8 +15,10 @@ */ uses(TestCase::class) - ->use(Illuminate\Foundation\Testing\RefreshDatabase::class) ->beforeEach(function () { + // Run migrations to ensure all tables exist + Artisan::call('migrate:fresh'); + // Seed roles and permissions with Filament Shield plugin Artisan::call('shield:generate', [ '--all' => null, From d6dd7b6cff2e312668c3b561353858392ec01471 Mon Sep 17 00:00:00 2001 From: Kilian Trunk Date: Thu, 21 Aug 2025 13:08:27 +0200 Subject: [PATCH 21/45] fix: more test fixes --- tests/Pest.php | 3 --- tests/TestCase.php | 3 +++ 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/Pest.php b/tests/Pest.php index 6849765..e1fafac 100644 --- a/tests/Pest.php +++ b/tests/Pest.php @@ -16,9 +16,6 @@ uses(TestCase::class) ->beforeEach(function () { - // Run migrations to ensure all tables exist - Artisan::call('migrate:fresh'); - // Seed roles and permissions with Filament Shield plugin Artisan::call('shield:generate', [ '--all' => null, diff --git a/tests/TestCase.php b/tests/TestCase.php index 68f5785..d486b4a 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -23,6 +23,9 @@ protected function setUp(): void parent::setUp(); $this->withoutVite(); + + // Ensure migrations are run + $this->artisan('migrate:fresh'); } /** From 74934678ab411f58bc3d2f16e1dfb1189eff948d Mon Sep 17 00:00:00 2001 From: Kilian Trunk Date: Thu, 21 Aug 2025 13:09:45 +0200 Subject: [PATCH 22/45] fix: revert tests changes --- tests/Pest.php | 1 + tests/TestCase.php | 3 --- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/tests/Pest.php b/tests/Pest.php index e1fafac..34e804d 100644 --- a/tests/Pest.php +++ b/tests/Pest.php @@ -15,6 +15,7 @@ */ uses(TestCase::class) + ->use(Illuminate\Foundation\Testing\RefreshDatabase::class) ->beforeEach(function () { // Seed roles and permissions with Filament Shield plugin Artisan::call('shield:generate', [ diff --git a/tests/TestCase.php b/tests/TestCase.php index d486b4a..68f5785 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -23,9 +23,6 @@ protected function setUp(): void parent::setUp(); $this->withoutVite(); - - // Ensure migrations are run - $this->artisan('migrate:fresh'); } /** From 6cf7a1c5c5fc0b9807d04fa3abb04242aeae34a9 Mon Sep 17 00:00:00 2001 From: Kilian Trunk Date: Thu, 21 Aug 2025 13:11:25 +0200 Subject: [PATCH 23/45] fix: fix phpunit file --- phpunit.xml.dist | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/phpunit.xml.dist b/phpunit.xml.dist index ee48e7b..2405929 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -19,7 +19,7 @@ - + From 8d7615512151ee8a21a20f5eda2d78d799e42f68 Mon Sep 17 00:00:00 2001 From: Kilian Trunk Date: Thu, 21 Aug 2025 13:14:07 +0200 Subject: [PATCH 24/45] fix: add ci setup composer --- composer.json | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/composer.json b/composer.json index 56321de..8d14344 100644 --- a/composer.json +++ b/composer.json @@ -76,6 +76,14 @@ "npm install", "@php vendor/bin/testbench vendor:publish --tag='filament-shield-config'", "@php vendor/bin/testbench package:sync-skeleton" + ], + "setup-ci": [ + "@php -r \"if (!file_exists('workbench/.env')) { copy('workbench/.env.example', 'workbench/.env'); echo '.env file created from .env.example\\n'; }\"", + "@php vendor/bin/testbench key:generate --ansi", + "npm install", + "@php vendor/bin/testbench vendor:publish --provider='Spatie\\Permission\\PermissionServiceProvider' --force", + "@php vendor/bin/testbench vendor:publish --tag='filament-shield-config'", + "@php vendor/bin/testbench package:sync-skeleton" ] }, "extra": { From 0503693ddcef282482a5a11add69077641d71b3f Mon Sep 17 00:00:00 2001 From: Kilian Trunk Date: Thu, 21 Aug 2025 13:15:12 +0200 Subject: [PATCH 25/45] Revert "fix: add ci setup composer" This reverts commit 8d7615512151ee8a21a20f5eda2d78d799e42f68. --- composer.json | 8 -------- 1 file changed, 8 deletions(-) diff --git a/composer.json b/composer.json index 8d14344..56321de 100644 --- a/composer.json +++ b/composer.json @@ -76,14 +76,6 @@ "npm install", "@php vendor/bin/testbench vendor:publish --tag='filament-shield-config'", "@php vendor/bin/testbench package:sync-skeleton" - ], - "setup-ci": [ - "@php -r \"if (!file_exists('workbench/.env')) { copy('workbench/.env.example', 'workbench/.env'); echo '.env file created from .env.example\\n'; }\"", - "@php vendor/bin/testbench key:generate --ansi", - "npm install", - "@php vendor/bin/testbench vendor:publish --provider='Spatie\\Permission\\PermissionServiceProvider' --force", - "@php vendor/bin/testbench vendor:publish --tag='filament-shield-config'", - "@php vendor/bin/testbench package:sync-skeleton" ] }, "extra": { From 099069a274476083cf1ebde2c40d3c466882e604 Mon Sep 17 00:00:00 2001 From: Kilian Trunk Date: Thu, 21 Aug 2025 13:24:41 +0200 Subject: [PATCH 26/45] fix: conditional publishing of permissions --- composer.json | 1 + 1 file changed, 1 insertion(+) diff --git a/composer.json b/composer.json index 56321de..951cea6 100644 --- a/composer.json +++ b/composer.json @@ -74,6 +74,7 @@ "@php -r \"if (!file_exists('workbench/.env')) { copy('workbench/.env.example', 'workbench/.env'); echo '.env file created from .env.example\\n'; }\"", "@php vendor/bin/testbench key:generate --ansi", "npm install", + "@php -r \"if (!file_exists('workbench/.env')) { passthru('vendor/bin/testbench vendor:publish --provider=Spatie\\\\Permission\\\\PermissionServiceProvider'); }\"", "@php vendor/bin/testbench vendor:publish --tag='filament-shield-config'", "@php vendor/bin/testbench package:sync-skeleton" ] From f5712840418ff00f8960cfebee5cb188a75193a1 Mon Sep 17 00:00:00 2001 From: Kilian Trunk Date: Thu, 21 Aug 2025 13:26:15 +0200 Subject: [PATCH 27/45] Revert "fix: conditional publishing of permissions" This reverts commit 099069a274476083cf1ebde2c40d3c466882e604. --- composer.json | 1 - 1 file changed, 1 deletion(-) diff --git a/composer.json b/composer.json index 951cea6..56321de 100644 --- a/composer.json +++ b/composer.json @@ -74,7 +74,6 @@ "@php -r \"if (!file_exists('workbench/.env')) { copy('workbench/.env.example', 'workbench/.env'); echo '.env file created from .env.example\\n'; }\"", "@php vendor/bin/testbench key:generate --ansi", "npm install", - "@php -r \"if (!file_exists('workbench/.env')) { passthru('vendor/bin/testbench vendor:publish --provider=Spatie\\\\Permission\\\\PermissionServiceProvider'); }\"", "@php vendor/bin/testbench vendor:publish --tag='filament-shield-config'", "@php vendor/bin/testbench package:sync-skeleton" ] From 2b8ad0745fb42933cf416dfa97ff1d8761a9c14c Mon Sep 17 00:00:00 2001 From: Kilian Trunk Date: Thu, 21 Aug 2025 13:27:20 +0200 Subject: [PATCH 28/45] Revert "fix: remove publishing from composer scirpt" This reverts commit 527b92a3493d2181064dfc61b5798f54bcebcf4c. --- composer.json | 1 + 1 file changed, 1 insertion(+) diff --git a/composer.json b/composer.json index 56321de..bf73a88 100644 --- a/composer.json +++ b/composer.json @@ -74,6 +74,7 @@ "@php -r \"if (!file_exists('workbench/.env')) { copy('workbench/.env.example', 'workbench/.env'); echo '.env file created from .env.example\\n'; }\"", "@php vendor/bin/testbench key:generate --ansi", "npm install", + "@php vendor/bin/testbench vendor:publish --provider='Spatie\\Permission\\PermissionServiceProvider'", "@php vendor/bin/testbench vendor:publish --tag='filament-shield-config'", "@php vendor/bin/testbench package:sync-skeleton" ] From bd344170a13aa8e8eba5884042ced88a27f7a7a9 Mon Sep 17 00:00:00 2001 From: Kilian Trunk Date: Thu, 21 Aug 2025 13:48:48 +0200 Subject: [PATCH 29/45] fix: add empty migration, fix db namespace We need to add the /app prefix because that's the path where our project folder is mounted inside the container. --- testbench.yaml | 2 +- ...25_08_21_112822_create_permission_tables.php | 17 +++++++++++++++++ 2 files changed, 18 insertions(+), 1 deletion(-) create mode 100644 workbench/database/migrations/2025_08_21_112822_create_permission_tables.php diff --git a/testbench.yaml b/testbench.yaml index 1ea616a..3c4ddda 100644 --- a/testbench.yaml +++ b/testbench.yaml @@ -2,7 +2,7 @@ laravel: '@testbench' env: - DB_CONNECTION=sqlite - - DB_DATABASE=workbench/database/database.sqlite + - DB_DATABASE=/app/workbench/database/database.sqlite - APP_KEY=base64:ZQvPGC7uVADkjOgtGIIuCI8u3/Pzu+VaRObIbHsgjCc= - APP_ENV=local - APP_DEBUG=true diff --git a/workbench/database/migrations/2025_08_21_112822_create_permission_tables.php b/workbench/database/migrations/2025_08_21_112822_create_permission_tables.php new file mode 100644 index 0000000..0eb532e --- /dev/null +++ b/workbench/database/migrations/2025_08_21_112822_create_permission_tables.php @@ -0,0 +1,17 @@ + Date: Thu, 28 Aug 2025 11:36:10 +0200 Subject: [PATCH 30/45] chore: add spatie laravel permission migration --- testbench.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/testbench.yaml b/testbench.yaml index 3c4ddda..03dba09 100644 --- a/testbench.yaml +++ b/testbench.yaml @@ -16,6 +16,7 @@ providers: migrations: - vendor/orchestra/testbench-core/laravel/migrations - vendor/orchestra/testbench-core/laravel/database/migrations + - vendor/spatie/laravel-permission/database/migrations - workbench/database/migrations - database/migrations From 9723879cdf9bea20244b2ec10d679bc438ce0fa8 Mon Sep 17 00:00:00 2001 From: Kilian Trunk Date: Thu, 28 Aug 2025 12:51:34 +0200 Subject: [PATCH 31/45] chore: fix attempt of db setup --- composer.json | 1 + testbench.yaml | 4 - workbench/.gitignore | 2 + workbench/app/Http/Middleware/AutoLogin.php | 118 ++++++++++++++++++ .../app/Providers/AdminPanelProvider.php | 2 + workbench/database/seeders/DatabaseSeeder.php | 104 --------------- 6 files changed, 123 insertions(+), 108 deletions(-) create mode 100644 workbench/app/Http/Middleware/AutoLogin.php delete mode 100644 workbench/database/seeders/DatabaseSeeder.php diff --git a/composer.json b/composer.json index bf73a88..1f76fc3 100644 --- a/composer.json +++ b/composer.json @@ -76,6 +76,7 @@ "npm install", "@php vendor/bin/testbench vendor:publish --provider='Spatie\\Permission\\PermissionServiceProvider'", "@php vendor/bin/testbench vendor:publish --tag='filament-shield-config'", + "@php vendor/bin/testbench filament:assets", "@php vendor/bin/testbench package:sync-skeleton" ] }, diff --git a/testbench.yaml b/testbench.yaml index 03dba09..0c5930a 100644 --- a/testbench.yaml +++ b/testbench.yaml @@ -20,9 +20,6 @@ migrations: - workbench/database/migrations - database/migrations -seeders: - - Workbench\Database\Seeders\DatabaseSeeder - workbench: start: '/' install: true @@ -38,6 +35,5 @@ workbench: - create-sqlite-db - db-wipe - migrate-fresh - - db-seed assets: - laravel-assets diff --git a/workbench/.gitignore b/workbench/.gitignore index 1afdc2e..93a68e6 100644 --- a/workbench/.gitignore +++ b/workbench/.gitignore @@ -24,3 +24,5 @@ lang/ vendor public/build public/hot +/public/css +/public/js diff --git a/workbench/app/Http/Middleware/AutoLogin.php b/workbench/app/Http/Middleware/AutoLogin.php new file mode 100644 index 0000000..e50b1b6 --- /dev/null +++ b/workbench/app/Http/Middleware/AutoLogin.php @@ -0,0 +1,118 @@ + config('app.env'), + 'authenticated' => Auth::check(), + 'guard' => config('auth.defaults.guard'), + 'path' => $request->path(), + ]); + + if (config('app.env') === 'local' && ! Auth::guard('web')->check()) { + $dbPath = config('database.connections.sqlite.database'); + $userCount = (int) DB::table('users')->count(); + Log::debug('[Workbench] AutoLogin DB info', [ + 'sqlite_path' => $dbPath, + 'sqlite_exists' => is_string($dbPath) ? file_exists($dbPath) : null, + 'users_count' => $userCount, + ]); + + $user = User::query()->first(); + Log::debug('[Workbench] AutoLogin user lookup', ['found' => (bool) $user]); + + if ($user) { + $this->bootstrapPermissionsAndAssign($user); + Auth::guard('web')->login($user); + $request->session()->regenerate(); + Log::info('[Workbench] AutoLogin successfully logged in first user', ['user_id' => $user->id]); + + // If we are on the login page, skip it after auto-login + if ($request->is('admin/login')) { + return redirect()->to('/admin'); + } + } else { + Log::warning('[Workbench] AutoLogin could not find any users to login — creating default admin user'); + $created = User::query()->create([ + 'name' => 'Admin User', + 'email' => 'test@example.com', + 'password' => Hash::make('password'), + 'email_verified_at' => now(), + ]); + if ($created) { + $this->bootstrapPermissionsAndAssign($created); + Auth::guard('web')->login($created); + $request->session()->regenerate(); + Log::info('[Workbench] AutoLogin created and logged in default user', ['user_id' => $created->id]); + if ($request->is('admin/login')) { + return redirect()->to('/admin'); + } + } + } + } + + return $next($request); + } + + private function bootstrapPermissionsAndAssign(User $user): void + { + try { + // Normalize guards first + DB::table('permissions')->whereNull('guard_name')->orWhere('guard_name', '')->update(['guard_name' => 'web']); + DB::table('roles')->whereNull('guard_name')->orWhere('guard_name', '')->update(['guard_name' => 'web']); + + // Generate Filament Shield permissions if none exist yet + if (Permission::query()->count() === 0) { + Artisan::call('shield:generate', [ + '--all' => true, + '--panel' => 'admin', + ]); + Log::info('[Workbench] Generated Shield permissions'); + } + + // Reset caches/registrar to ensure guards are picked up + Artisan::call('permission:cache-reset'); + app(PermissionRegistrar::class)->forgetCachedPermissions(); + + // Ensure roles with correct guard + $existingSuper = Role::where('name', 'super_admin')->first(); + if ($existingSuper && $existingSuper->guard_name !== 'web') { + $existingSuper->guard_name = 'web'; + $existingSuper->save(); + } + $existingPanel = Role::where('name', 'panel_user')->first(); + if ($existingPanel && $existingPanel->guard_name !== 'web') { + $existingPanel->guard_name = 'web'; + $existingPanel->save(); + } + + $superAdmin = Role::firstOrCreate(['name' => 'super_admin', 'guard_name' => 'web']); + $panelUser = Role::firstOrCreate(['name' => 'panel_user', 'guard_name' => 'web']); + + $all = Permission::query()->where('guard_name', 'web')->get(); + if ($all->isNotEmpty()) { + $superAdmin->syncPermissions($all->pluck('name')->all()); + } + + $user->syncRoles([$superAdmin->name, $panelUser->name]); + } catch (\Throwable $e) { + Log::error('[Workbench] Bootstrap permissions failed', ['message' => $e->getMessage()]); + } + } +} diff --git a/workbench/app/Providers/AdminPanelProvider.php b/workbench/app/Providers/AdminPanelProvider.php index beaf61d..a2b69bc 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 Workbench\App\Http\Middleware\AutoLogin; class AdminPanelProvider extends PanelProvider { @@ -37,6 +38,7 @@ public function panel(Panel $panel): Panel SubstituteBindings::class, DisableBladeIconComponents::class, DispatchServingFilamentEvent::class, + AutoLogin::class, ]) ->authMiddleware([ Authenticate::class, diff --git a/workbench/database/seeders/DatabaseSeeder.php b/workbench/database/seeders/DatabaseSeeder.php deleted file mode 100644 index 8f6f511..0000000 --- a/workbench/database/seeders/DatabaseSeeder.php +++ /dev/null @@ -1,104 +0,0 @@ -command->info('Starting database seeding...'); - - // Generate Filament Shield permissions for all resources - $this->command->info('🛡️ Generating Filament Shield permissions...'); - Artisan::call('shield:generate', [ - '--all' => true, - '--panel' => 'admin', - ]); - $this->command->info('✓ Shield permissions generated'); - - // Flush Spatie permission cache before assigning roles/permissions - Artisan::call('permission:cache-reset'); - $this->command->info('✓ Spatie permission cache reset'); - - // Get or create admin user - $adminEmail = 'test@example.com'; - $adminPassword = 'password'; - - $existingUser = User::where('email', $adminEmail)->first(); - - if (! $existingUser) { - $this->command->info('Creating new admin user...'); - $user = UserFactory::new()->create([ - 'name' => 'Admin User', - 'email' => $adminEmail, - 'password' => Hash::make($adminPassword), - 'email_verified_at' => now(), - ]); - $this->command->info("Admin user created with ID: {$user->id}"); - } else { - $this->command->info('Using existing admin user...'); - // Ensure the user has the correct password - $existingUser->update([ - 'password' => Hash::make($adminPassword), - 'email_verified_at' => now(), - ]); - $user = $existingUser; - $this->command->info("Admin user found with ID: {$user->id}"); - } - - // Ensure required roles exist - $superAdminRole = Role::firstOrCreate([ - 'name' => 'super_admin', - 'guard_name' => 'web', - ]); - $this->command->info("✓ Super admin role ensured: {$superAdminRole->name}"); - - $panelUserRole = Role::firstOrCreate([ - 'name' => 'panel_user', - 'guard_name' => 'web', - ]); - $this->command->info("✓ Panel user role ensured: {$panelUserRole->name}"); - - // Attach ALL permissions to the super_admin role (single source of truth) - $allPermissions = Permission::all(); - if ($allPermissions->count() > 0) { - $superAdminRole->syncPermissions($allPermissions); - $this->command->info("✓ Synced {$allPermissions->count()} permissions to role: {$superAdminRole->name}"); - } else { - $this->command->warn('No permissions found in database to attach to super_admin role.'); - } - - // Assign only the roles we actually need to the user - $user->syncRoles([$superAdminRole->name, $panelUserRole->name]); - $this->command->info('✓ Assigned roles to user: '.implode(', ', $user->getRoleNames()->toArray())); - - // Refresh cache once more to reflect the latest assignments during this run - Artisan::call('permission:cache-reset'); - - if ($user) { - $this->command->info("✓ Admin user verified: {$user->email} (ID: {$user->id})"); - $this->command->info('✓ User roles: '.$user->getRoleNames()->implode(', ')); - $this->command->info('✓ Direct permissions: '.$user->getDirectPermissions()->count().' (expected 0)'); - $this->command->info('✓ All permissions via roles: '.$user->getAllPermissions()->count()); - } else { - $this->command->error('✗ Failed to create/update admin user!'); - } - - $this->command->info('Database seeding completed.'); - $this->command->info(''); - $this->command->info('🎯 Login Credentials:'); - $this->command->info(" Email: {$adminEmail}"); - $this->command->info(" Password: {$adminPassword}"); - } -} From d54f0af2bf96d7291b5b6b6bfc6ff4ae4736769e Mon Sep 17 00:00:00 2001 From: Kilian Trunk Date: Thu, 28 Aug 2025 13:30:09 +0200 Subject: [PATCH 32/45] chore: more fixes --- workbench/.env.example | 2 +- workbench/app/Http/Middleware/AutoLogin.php | 40 +++++++++++---------- workbench/bootstrap/app.php | 4 +-- 3 files changed, 23 insertions(+), 23 deletions(-) diff --git a/workbench/.env.example b/workbench/.env.example index 34acb1e..875e230 100644 --- a/workbench/.env.example +++ b/workbench/.env.example @@ -1,6 +1,6 @@ APP_NAME=Laravel APP_ENV=local -APP_KEY=AckfSECXIvnK5r28GVIWUAxmbBSjTsmF +APP_KEY= APP_DEBUG=true APP_URL=https://eclipse-world-plugin.lndo.site diff --git a/workbench/app/Http/Middleware/AutoLogin.php b/workbench/app/Http/Middleware/AutoLogin.php index e50b1b6..4a49941 100644 --- a/workbench/app/Http/Middleware/AutoLogin.php +++ b/workbench/app/Http/Middleware/AutoLogin.php @@ -37,33 +37,35 @@ public function handle(Request $request, Closure $next) $user = User::query()->first(); Log::debug('[Workbench] AutoLogin user lookup', ['found' => (bool) $user]); + if (! $user) { + Log::warning('[Workbench] AutoLogin could not find any users to login — creating default admin user'); + try { + $user = User::query()->firstOrCreate( + ['email' => 'test@example.com'], + [ + 'name' => 'Admin User', + 'password' => Hash::make('password'), + 'email_verified_at' => now(), + ], + ); + } catch (\Throwable $e) { + // In case of a race/unique constraint, fetch the existing one + $user = User::query()->where('email', 'test@example.com')->first(); + } + if ($user) { + Log::info('[Workbench] AutoLogin ensured default user exists', ['user_id' => $user->id]); + } + } + if ($user) { $this->bootstrapPermissionsAndAssign($user); Auth::guard('web')->login($user); $request->session()->regenerate(); - Log::info('[Workbench] AutoLogin successfully logged in first user', ['user_id' => $user->id]); + Log::info('[Workbench] AutoLogin successfully logged in user', ['user_id' => $user->id]); - // If we are on the login page, skip it after auto-login if ($request->is('admin/login')) { return redirect()->to('/admin'); } - } else { - Log::warning('[Workbench] AutoLogin could not find any users to login — creating default admin user'); - $created = User::query()->create([ - 'name' => 'Admin User', - 'email' => 'test@example.com', - 'password' => Hash::make('password'), - 'email_verified_at' => now(), - ]); - if ($created) { - $this->bootstrapPermissionsAndAssign($created); - Auth::guard('web')->login($created); - $request->session()->regenerate(); - Log::info('[Workbench] AutoLogin created and logged in default user', ['user_id' => $created->id]); - if ($request->is('admin/login')) { - return redirect()->to('/admin'); - } - } } } diff --git a/workbench/bootstrap/app.php b/workbench/bootstrap/app.php index 4c2b7f1..3fa4760 100644 --- a/workbench/bootstrap/app.php +++ b/workbench/bootstrap/app.php @@ -5,9 +5,7 @@ use Illuminate\Foundation\Configuration\Middleware; use Workbench\App\Providers\WorkbenchServiceProvider; -use function Orchestra\Testbench\default_skeleton_path; - -return Application::configure(basePath: $APP_BASE_PATH ?? default_skeleton_path()) +return Application::configure(basePath: $APP_BASE_PATH ?? dirname(__DIR__)) ->withProviders([ WorkbenchServiceProvider::class, ]) From a71716929b0a462125da64ae781b2bd5f36b5afe Mon Sep 17 00:00:00 2001 From: Kilian Trunk Date: Thu, 28 Aug 2025 13:32:51 +0200 Subject: [PATCH 33/45] chore: add minimal composer json for workbench --- workbench/composer.json | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 workbench/composer.json diff --git a/workbench/composer.json b/workbench/composer.json new file mode 100644 index 0000000..f1779e0 --- /dev/null +++ b/workbench/composer.json @@ -0,0 +1,11 @@ +{ + "name": "workbench/app", + "type": "project", + "autoload": { + "psr-4": { + "Workbench\\App\\": "app/" + } + } +} + + From 79e0c668ed968045bd71ef8619603e0943b78b9c Mon Sep 17 00:00:00 2001 From: Kilian Trunk Date: Thu, 28 Aug 2025 14:06:20 +0200 Subject: [PATCH 34/45] chore: setup improvements & fixes --- testbench.yaml | 2 +- workbench/.env.example | 2 +- workbench/app/Http/Middleware/AutoLogin.php | 23 +------------- .../Providers/WorkbenchServiceProvider.php | 5 --- ...8_28_113759_create_notifications_table.php | 31 +++++++++++++++++++ 5 files changed, 34 insertions(+), 29 deletions(-) create mode 100644 workbench/database/migrations/2025_08_28_113759_create_notifications_table.php diff --git a/testbench.yaml b/testbench.yaml index 0c5930a..51d3f33 100644 --- a/testbench.yaml +++ b/testbench.yaml @@ -8,7 +8,7 @@ env: - APP_DEBUG=true - SESSION_DRIVER=database - CACHE_STORE=database - - QUEUE_CONNECTION=database + - QUEUE_CONNECTION=sync providers: - Workbench\App\Providers\WorkbenchServiceProvider diff --git a/workbench/.env.example b/workbench/.env.example index 875e230..589f315 100644 --- a/workbench/.env.example +++ b/workbench/.env.example @@ -38,7 +38,7 @@ SESSION_SAME_SITE=lax BROADCAST_CONNECTION=log FILESYSTEM_DISK=local -QUEUE_CONNECTION=database +QUEUE_CONNECTION=sync CACHE_STORE=array # CACHE_PREFIX= diff --git a/workbench/app/Http/Middleware/AutoLogin.php b/workbench/app/Http/Middleware/AutoLogin.php index 4a49941..920fa58 100644 --- a/workbench/app/Http/Middleware/AutoLogin.php +++ b/workbench/app/Http/Middleware/AutoLogin.php @@ -18,27 +18,10 @@ class AutoLogin { public function handle(Request $request, Closure $next) { - Log::debug('[Workbench] AutoLogin middleware triggered', [ - 'env' => config('app.env'), - 'authenticated' => Auth::check(), - 'guard' => config('auth.defaults.guard'), - 'path' => $request->path(), - ]); - if (config('app.env') === 'local' && ! Auth::guard('web')->check()) { - $dbPath = config('database.connections.sqlite.database'); - $userCount = (int) DB::table('users')->count(); - Log::debug('[Workbench] AutoLogin DB info', [ - 'sqlite_path' => $dbPath, - 'sqlite_exists' => is_string($dbPath) ? file_exists($dbPath) : null, - 'users_count' => $userCount, - ]); - $user = User::query()->first(); - Log::debug('[Workbench] AutoLogin user lookup', ['found' => (bool) $user]); if (! $user) { - Log::warning('[Workbench] AutoLogin could not find any users to login — creating default admin user'); try { $user = User::query()->firstOrCreate( ['email' => 'test@example.com'], @@ -49,19 +32,16 @@ public function handle(Request $request, Closure $next) ], ); } catch (\Throwable $e) { + Log::error('[Workbench] AutoLogin user creation failed', ['message' => $e->getMessage()]); // In case of a race/unique constraint, fetch the existing one $user = User::query()->where('email', 'test@example.com')->first(); } - if ($user) { - Log::info('[Workbench] AutoLogin ensured default user exists', ['user_id' => $user->id]); - } } if ($user) { $this->bootstrapPermissionsAndAssign($user); Auth::guard('web')->login($user); $request->session()->regenerate(); - Log::info('[Workbench] AutoLogin successfully logged in user', ['user_id' => $user->id]); if ($request->is('admin/login')) { return redirect()->to('/admin'); @@ -85,7 +65,6 @@ private function bootstrapPermissionsAndAssign(User $user): void '--all' => true, '--panel' => 'admin', ]); - Log::info('[Workbench] Generated Shield permissions'); } // Reset caches/registrar to ensure guards are picked up diff --git a/workbench/app/Providers/WorkbenchServiceProvider.php b/workbench/app/Providers/WorkbenchServiceProvider.php index 63cc6a7..bd60efb 100644 --- a/workbench/app/Providers/WorkbenchServiceProvider.php +++ b/workbench/app/Providers/WorkbenchServiceProvider.php @@ -3,7 +3,6 @@ namespace Workbench\App\Providers; use Illuminate\Support\ServiceProvider; -use Workbench\Database\Seeders\DatabaseSeeder; class WorkbenchServiceProvider extends ServiceProvider { @@ -18,10 +17,6 @@ public function register(): void $this->app->register(\Filament\FilamentServiceProvider::class); $this->app->register(AdminPanelProvider::class); $this->app->register(AuthServiceProvider::class); - - $this->app->bind('DatabaseSeeder', function ($app) { - return new DatabaseSeeder; - }); } /** diff --git a/workbench/database/migrations/2025_08_28_113759_create_notifications_table.php b/workbench/database/migrations/2025_08_28_113759_create_notifications_table.php new file mode 100644 index 0000000..d738032 --- /dev/null +++ b/workbench/database/migrations/2025_08_28_113759_create_notifications_table.php @@ -0,0 +1,31 @@ +uuid('id')->primary(); + $table->string('type'); + $table->morphs('notifiable'); + $table->text('data'); + $table->timestamp('read_at')->nullable(); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('notifications'); + } +}; From af00a7fd24d398a14797444b1d6f9e713fc2c15e Mon Sep 17 00:00:00 2001 From: Kilian Trunk Date: Thu, 28 Aug 2025 14:36:12 +0200 Subject: [PATCH 35/45] chore: permission fixes try --- testbench.yaml | 2 - ..._08_21_112822_create_permission_tables.php | 17 -- ..._08_28_142604_create_permission_tables.php | 146 ++++++++++++++++++ 3 files changed, 146 insertions(+), 19 deletions(-) delete mode 100644 workbench/database/migrations/2025_08_21_112822_create_permission_tables.php create mode 100644 workbench/database/migrations/2025_08_28_142604_create_permission_tables.php diff --git a/testbench.yaml b/testbench.yaml index 51d3f33..ce54001 100644 --- a/testbench.yaml +++ b/testbench.yaml @@ -14,8 +14,6 @@ providers: - Workbench\App\Providers\WorkbenchServiceProvider migrations: - - vendor/orchestra/testbench-core/laravel/migrations - - vendor/orchestra/testbench-core/laravel/database/migrations - vendor/spatie/laravel-permission/database/migrations - workbench/database/migrations - database/migrations diff --git a/workbench/database/migrations/2025_08_21_112822_create_permission_tables.php b/workbench/database/migrations/2025_08_21_112822_create_permission_tables.php deleted file mode 100644 index 0eb532e..0000000 --- a/workbench/database/migrations/2025_08_21_112822_create_permission_tables.php +++ /dev/null @@ -1,17 +0,0 @@ -engine('InnoDB'); + $table->bigIncrements('id'); // permission id + $table->string('name'); // For MyISAM use string('name', 225); // (or 166 for InnoDB with Redundant/Compact row format) + $table->string('guard_name'); // For MyISAM use string('guard_name', 25); + $table->timestamps(); + + $table->unique(['name', 'guard_name']); + }); + } + + if (! Schema::hasTable($tableNames['roles'])) { + Schema::create($tableNames['roles'], static function (Blueprint $table) use ($teams, $columnNames) { + // $table->engine('InnoDB'); + $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 MyISAM use string('name', 225); // (or 166 for InnoDB with Redundant/Compact row format) + $table->string('guard_name'); // For MyISAM use string('guard_name', 25); + $table->timestamps(); + if ($teams || config('permission.testing')) { + $table->unique([$columnNames['team_foreign_key'], 'name', 'guard_name']); + } else { + $table->unique(['name', 'guard_name']); + } + }); + } + + if (! Schema::hasTable($tableNames['model_has_permissions'])) { + Schema::create($tableNames['model_has_permissions'], static 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'); + } + + }); + } + + if (! Schema::hasTable($tableNames['model_has_roles'])) { + Schema::create($tableNames['model_has_roles'], static 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'); + } + }); + } + + if (! Schema::hasTable($tableNames['role_has_permissions'])) { + Schema::create($tableNames['role_has_permissions'], static 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']); + } +}; From c9c9b1e5e511f51ebbf3ab10c798e7ecca7210ce Mon Sep 17 00:00:00 2001 From: Kilian Trunk Date: Thu, 28 Aug 2025 14:39:56 +0200 Subject: [PATCH 36/45] chore: re-add migration --- testbench.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/testbench.yaml b/testbench.yaml index ce54001..8cfeb85 100644 --- a/testbench.yaml +++ b/testbench.yaml @@ -14,6 +14,7 @@ providers: - Workbench\App\Providers\WorkbenchServiceProvider migrations: + - vendor/orchestra/testbench-core/laravel/database/migrations - vendor/spatie/laravel-permission/database/migrations - workbench/database/migrations - database/migrations From af1291c2d6a5672a61a2e5f1b8b6b530b7011d85 Mon Sep 17 00:00:00 2001 From: Kilian Trunk Date: Thu, 28 Aug 2025 15:04:56 +0200 Subject: [PATCH 37/45] chore: more fixes --- testbench.yaml | 1 + workbench/app/Http/Middleware/AutoLogin.php | 99 ---------- .../Http/Middleware/WorkbenchBootstrap.php | 179 ++++++++++++++++++ .../app/Providers/AdminPanelProvider.php | 4 +- 4 files changed, 182 insertions(+), 101 deletions(-) delete mode 100644 workbench/app/Http/Middleware/AutoLogin.php create mode 100644 workbench/app/Http/Middleware/WorkbenchBootstrap.php diff --git a/testbench.yaml b/testbench.yaml index 8cfeb85..51d3f33 100644 --- a/testbench.yaml +++ b/testbench.yaml @@ -14,6 +14,7 @@ providers: - Workbench\App\Providers\WorkbenchServiceProvider migrations: + - vendor/orchestra/testbench-core/laravel/migrations - vendor/orchestra/testbench-core/laravel/database/migrations - vendor/spatie/laravel-permission/database/migrations - workbench/database/migrations diff --git a/workbench/app/Http/Middleware/AutoLogin.php b/workbench/app/Http/Middleware/AutoLogin.php deleted file mode 100644 index 920fa58..0000000 --- a/workbench/app/Http/Middleware/AutoLogin.php +++ /dev/null @@ -1,99 +0,0 @@ -check()) { - $user = User::query()->first(); - - if (! $user) { - try { - $user = User::query()->firstOrCreate( - ['email' => 'test@example.com'], - [ - 'name' => 'Admin User', - 'password' => Hash::make('password'), - 'email_verified_at' => now(), - ], - ); - } catch (\Throwable $e) { - Log::error('[Workbench] AutoLogin user creation failed', ['message' => $e->getMessage()]); - // In case of a race/unique constraint, fetch the existing one - $user = User::query()->where('email', 'test@example.com')->first(); - } - } - - if ($user) { - $this->bootstrapPermissionsAndAssign($user); - Auth::guard('web')->login($user); - $request->session()->regenerate(); - - if ($request->is('admin/login')) { - return redirect()->to('/admin'); - } - } - } - - return $next($request); - } - - private function bootstrapPermissionsAndAssign(User $user): void - { - try { - // Normalize guards first - DB::table('permissions')->whereNull('guard_name')->orWhere('guard_name', '')->update(['guard_name' => 'web']); - DB::table('roles')->whereNull('guard_name')->orWhere('guard_name', '')->update(['guard_name' => 'web']); - - // Generate Filament Shield permissions if none exist yet - if (Permission::query()->count() === 0) { - Artisan::call('shield:generate', [ - '--all' => true, - '--panel' => 'admin', - ]); - } - - // Reset caches/registrar to ensure guards are picked up - Artisan::call('permission:cache-reset'); - app(PermissionRegistrar::class)->forgetCachedPermissions(); - - // Ensure roles with correct guard - $existingSuper = Role::where('name', 'super_admin')->first(); - if ($existingSuper && $existingSuper->guard_name !== 'web') { - $existingSuper->guard_name = 'web'; - $existingSuper->save(); - } - $existingPanel = Role::where('name', 'panel_user')->first(); - if ($existingPanel && $existingPanel->guard_name !== 'web') { - $existingPanel->guard_name = 'web'; - $existingPanel->save(); - } - - $superAdmin = Role::firstOrCreate(['name' => 'super_admin', 'guard_name' => 'web']); - $panelUser = Role::firstOrCreate(['name' => 'panel_user', 'guard_name' => 'web']); - - $all = Permission::query()->where('guard_name', 'web')->get(); - if ($all->isNotEmpty()) { - $superAdmin->syncPermissions($all->pluck('name')->all()); - } - - $user->syncRoles([$superAdmin->name, $panelUser->name]); - } catch (\Throwable $e) { - Log::error('[Workbench] Bootstrap permissions failed', ['message' => $e->getMessage()]); - } - } -} diff --git a/workbench/app/Http/Middleware/WorkbenchBootstrap.php b/workbench/app/Http/Middleware/WorkbenchBootstrap.php new file mode 100644 index 0000000..adddb77 --- /dev/null +++ b/workbench/app/Http/Middleware/WorkbenchBootstrap.php @@ -0,0 +1,179 @@ +check()) { + $user = User::query()->first(); + + if (! $user) { + try { + $user = User::query()->firstOrCreate( + ['email' => 'test@example.com'], + [ + 'name' => 'Admin User', + 'password' => Hash::make('password'), + 'email_verified_at' => now(), + ], + ); + } catch (\Throwable $e) { + Log::error('[Workbench] User creation failed', ['message' => $e->getMessage()]); + // In case of a race/unique constraint, fetch the existing one + $user = User::query()->where('email', 'test@example.com')->first(); + } + } + + if ($user) { + $this->bootstrapPermissionsAndAssign($user); + Auth::guard('web')->login($user); + $request->session()->regenerate(); + + if ($request->is('admin/login')) { + return redirect()->to('/admin'); + } + } + } + + return $next($request); + } + + private function bootstrapPermissionsAndAssign(User $user): void + { + // Use cache to prevent running this multiple times + $cacheKey = 'workbench:permissions:bootstrapped'; + + if (Cache::has($cacheKey)) { + // Just ensure user has roles if already bootstrapped + $this->ensureUserHasRoles($user); + + return; + } + + try { + // Use lock to prevent concurrent execution + Cache::lock('workbench:bootstrap-permissions', 30)->block(10, function () use ($user, $cacheKey) { + // Double-check inside the lock + if (Cache::has($cacheKey)) { + return; + } + + // Normalize guards first + DB::table('permissions')->whereNull('guard_name')->orWhere('guard_name', '')->update(['guard_name' => 'web']); + DB::table('roles')->whereNull('guard_name')->orWhere('guard_name', '')->update(['guard_name' => 'web']); + + // Generate Filament Shield permissions if none exist yet + if (Permission::query()->count() === 0) { + Artisan::call('shield:generate', [ + '--all' => true, + '--panel' => 'admin', + ]); + } + + // Reset caches/registrar to ensure guards are picked up + Artisan::call('permission:cache-reset'); + app(PermissionRegistrar::class)->forgetCachedPermissions(); + + // Ensure roles with correct guard + $this->ensureRolesHaveCorrectGuard(); + + // Create roles + $superAdmin = Role::firstOrCreate(['name' => 'super_admin', 'guard_name' => 'web']); + $panelUser = Role::firstOrCreate(['name' => 'panel_user', 'guard_name' => 'web']); + + // Only assign permissions if the role doesn't already have them + $this->assignPermissionsToRole($superAdmin); + + // Assign roles to user + $this->ensureUserHasRoles($user); + + // Mark as bootstrapped (cache for 1 hour) + Cache::put($cacheKey, true, 3600); + }); + } catch (\Throwable $e) { + Log::error('[Workbench] Bootstrap permissions failed', ['message' => $e->getMessage()]); + } + } + + private function ensureRolesHaveCorrectGuard(): void + { + $existingSuper = Role::where('name', 'super_admin')->first(); + if ($existingSuper && $existingSuper->guard_name !== 'web') { + $existingSuper->guard_name = 'web'; + $existingSuper->save(); + } + + $existingPanel = Role::where('name', 'panel_user')->first(); + if ($existingPanel && $existingPanel->guard_name !== 'web') { + $existingPanel->guard_name = 'web'; + $existingPanel->save(); + } + } + + private function assignPermissionsToRole(Role $role): void + { + // Check if role already has permissions to avoid duplicate inserts + if ($role->permissions()->count() > 0) { + return; + } + + $permissions = Permission::query()->where('guard_name', 'web')->get(); + if ($permissions->isNotEmpty()) { + // Use DB transaction to ensure atomicity + DB::transaction(function () use ($role, $permissions) { + // Clear existing permissions first to avoid duplicates + $role->permissions()->detach(); + + // Batch insert to avoid individual constraint violations + $pivotData = $permissions->map(function ($permission) use ($role) { + return [ + 'role_id' => $role->id, + 'permission_id' => $permission->id, + ]; + })->toArray(); + + // Use insert ignore equivalent for SQLite + foreach ($pivotData as $data) { + DB::table('role_has_permissions') + ->insertOrIgnore($data); + } + }); + } + } + + private function ensureUserHasRoles(User $user): void + { + $superAdmin = Role::where('name', 'super_admin')->where('guard_name', 'web')->first(); + $panelUser = Role::where('name', 'panel_user')->where('guard_name', 'web')->first(); + + $rolesToAssign = collect([$superAdmin, $panelUser]) + ->filter() + ->pluck('name') + ->toArray(); + + if (! empty($rolesToAssign)) { + // Only sync if user doesn't already have these roles + $existingRoles = $user->roles()->pluck('name')->toArray(); + $missingRoles = array_diff($rolesToAssign, $existingRoles); + + if (! empty($missingRoles)) { + $user->assignRole($missingRoles); + } + } + } +} diff --git a/workbench/app/Providers/AdminPanelProvider.php b/workbench/app/Providers/AdminPanelProvider.php index a2b69bc..53d4427 100644 --- a/workbench/app/Providers/AdminPanelProvider.php +++ b/workbench/app/Providers/AdminPanelProvider.php @@ -17,7 +17,7 @@ use Illuminate\Session\Middleware\AuthenticateSession; use Illuminate\Session\Middleware\StartSession; use Illuminate\View\Middleware\ShareErrorsFromSession; -use Workbench\App\Http\Middleware\AutoLogin; +use Workbench\App\Http\Middleware\WorkbenchBootstrap; class AdminPanelProvider extends PanelProvider { @@ -38,7 +38,7 @@ public function panel(Panel $panel): Panel SubstituteBindings::class, DisableBladeIconComponents::class, DispatchServingFilamentEvent::class, - AutoLogin::class, + WorkbenchBootstrap::class, ]) ->authMiddleware([ Authenticate::class, From 16e07f6cda7948a99853b93efdc1331e5332cbaa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Omer=20=C5=A0abi=C4=87?= Date: Thu, 28 Aug 2025 17:05:45 +0200 Subject: [PATCH 38/45] build(lando): update landofile --- .lando.dist.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.lando.dist.yml b/.lando.dist.yml index 408fd9f..049fa0b 100644 --- a/.lando.dist.yml +++ b/.lando.dist.yml @@ -5,6 +5,7 @@ config: webroot: workbench/public php: '8.3' via: nginx + database: mariadb:10.11 services: appserver: type: php:custom @@ -14,7 +15,7 @@ services: APP_BASE_PATH: "/app/workbench" TESTBENCH_WORKING_PATH: "/app" overrides: - image: slimdeluxe/php:8.3-v1.2 + image: slimdeluxe/php:8.3-v1.3 platform: linux/amd64 run: - composer install --no-interaction --prefer-dist From fbf57b8a2c51b9ddc5822f7f73bffafb11d86521 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Omer=20=C5=A0abi=C4=87?= Date: Thu, 28 Aug 2025 17:34:42 +0200 Subject: [PATCH 39/45] build(workbench): add cache dir in storage --- workbench/.gitignore | 5 ----- workbench/storage/framework/cache/.gitignore | 3 +++ workbench/storage/framework/cache/data/.gitignore | 2 ++ 3 files changed, 5 insertions(+), 5 deletions(-) create mode 100644 workbench/storage/framework/cache/.gitignore create mode 100644 workbench/storage/framework/cache/data/.gitignore diff --git a/workbench/.gitignore b/workbench/.gitignore index 93a68e6..4f2455e 100644 --- a/workbench/.gitignore +++ b/workbench/.gitignore @@ -5,11 +5,6 @@ storage/app/* !storage/app/public/ storage/app/public/* !storage/app/public/.gitkeep -storage/framework/cache/* -!storage/framework/cache/.gitkeep -!storage/framework/cache/data/ -storage/framework/cache/data/* -!storage/framework/cache/data/.gitkeep storage/framework/sessions/* !storage/framework/sessions/.gitkeep storage/framework/testing/* diff --git a/workbench/storage/framework/cache/.gitignore b/workbench/storage/framework/cache/.gitignore new file mode 100644 index 0000000..01e4a6c --- /dev/null +++ b/workbench/storage/framework/cache/.gitignore @@ -0,0 +1,3 @@ +* +!data/ +!.gitignore diff --git a/workbench/storage/framework/cache/data/.gitignore b/workbench/storage/framework/cache/data/.gitignore new file mode 100644 index 0000000..d6b7ef3 --- /dev/null +++ b/workbench/storage/framework/cache/data/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore From 50df5bfa273af1ae2ae4f331b456a71c48925b2d Mon Sep 17 00:00:00 2001 From: Kilian Trunk Date: Thu, 28 Aug 2025 17:44:25 +0200 Subject: [PATCH 40/45] chore(readme): update workbench instructions --- README.md | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index cf3f5b9..0134e2f 100644 --- a/README.md +++ b/README.md @@ -63,17 +63,15 @@ Should you want to contribute, please see the development guidelines in the [Dat This package ships with a minimal Testbench Workbench so you can run the Filament UI without a separate app: -1. Start the container +1. Clone the repository +2. Start the container ```shell lando start ``` -2. Install PHP deps and build the workbench skeleton (runs migrations and seeds a test user) - ```shell - lando composer install - ``` -3. Open the admin panel at `https://eclipse-world-plugin.lndo.site/admin/login` - - User: `test@example.com` - - Password: `password` + +3. Open the admin panel at `https://eclipse-world-plugin.lndo.site/admin` + +**No login required** - you'll be automatically signed in as a test user with full permissions. Notes: - The container serves `workbench/public` as the webroot. From 2ff16eed78b59701ce8d02acb651d140088a3814 Mon Sep 17 00:00:00 2001 From: Kilian Trunk Date: Thu, 28 Aug 2025 17:48:01 +0200 Subject: [PATCH 41/45] chore: temp commit --- .../migrations/2025_08_10_104332_create_sessions_table.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/workbench/database/migrations/2025_08_10_104332_create_sessions_table.php b/workbench/database/migrations/2025_08_10_104332_create_sessions_table.php index ca5922d..63e6a5d 100644 --- a/workbench/database/migrations/2025_08_10_104332_create_sessions_table.php +++ b/workbench/database/migrations/2025_08_10_104332_create_sessions_table.php @@ -24,7 +24,7 @@ public function up(): void } /** - * Reverse the migrations. + * Reverse the migrations.. */ public function down(): void { From 9763f18c9463372b2446753f9bbdc856586c0cc5 Mon Sep 17 00:00:00 2001 From: Kilian Trunk Date: Thu, 28 Aug 2025 17:48:12 +0200 Subject: [PATCH 42/45] Revert "chore: temp commit" This reverts commit 2ff16eed78b59701ce8d02acb651d140088a3814. --- .../migrations/2025_08_10_104332_create_sessions_table.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/workbench/database/migrations/2025_08_10_104332_create_sessions_table.php b/workbench/database/migrations/2025_08_10_104332_create_sessions_table.php index 63e6a5d..ca5922d 100644 --- a/workbench/database/migrations/2025_08_10_104332_create_sessions_table.php +++ b/workbench/database/migrations/2025_08_10_104332_create_sessions_table.php @@ -24,7 +24,7 @@ public function up(): void } /** - * Reverse the migrations.. + * Reverse the migrations. */ public function down(): void { From eb5e9cdb90a80bdaff4ef676b8cd596fe9376bb9 Mon Sep 17 00:00:00 2001 From: Kilian Trunk Date: Thu, 28 Aug 2025 18:28:58 +0200 Subject: [PATCH 43/45] fix: use local migrations --- testbench.yaml | 3 - ...01_000000_testbench_create_users_table.php | 49 +++++ ...01_000001_testbench_create_cache_table.php | 35 ++++ ..._01_000002_testbench_create_jobs_table.php | 57 ++++++ ..._08_28_142604_create_permission_tables.php | 188 +++++++++--------- 5 files changed, 230 insertions(+), 102 deletions(-) create mode 100644 workbench/database/migrations/0001_01_01_000000_testbench_create_users_table.php create mode 100644 workbench/database/migrations/0001_01_01_000001_testbench_create_cache_table.php create mode 100644 workbench/database/migrations/0001_01_01_000002_testbench_create_jobs_table.php diff --git a/testbench.yaml b/testbench.yaml index 51d3f33..2b8d0ef 100644 --- a/testbench.yaml +++ b/testbench.yaml @@ -14,9 +14,6 @@ providers: - Workbench\App\Providers\WorkbenchServiceProvider migrations: - - vendor/orchestra/testbench-core/laravel/migrations - - vendor/orchestra/testbench-core/laravel/database/migrations - - vendor/spatie/laravel-permission/database/migrations - workbench/database/migrations - database/migrations diff --git a/workbench/database/migrations/0001_01_01_000000_testbench_create_users_table.php b/workbench/database/migrations/0001_01_01_000000_testbench_create_users_table.php new file mode 100644 index 0000000..05fb5d9 --- /dev/null +++ b/workbench/database/migrations/0001_01_01_000000_testbench_create_users_table.php @@ -0,0 +1,49 @@ +id(); + $table->string('name'); + $table->string('email')->unique(); + $table->timestamp('email_verified_at')->nullable(); + $table->string('password'); + $table->rememberToken(); + $table->timestamps(); + }); + + Schema::create('password_reset_tokens', function (Blueprint $table) { + $table->string('email')->primary(); + $table->string('token'); + $table->timestamp('created_at')->nullable(); + }); + + Schema::create('sessions', function (Blueprint $table) { + $table->string('id')->primary(); + $table->foreignId('user_id')->nullable()->index(); + $table->string('ip_address', 45)->nullable(); + $table->text('user_agent')->nullable(); + $table->longText('payload'); + $table->integer('last_activity')->index(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('users'); + Schema::dropIfExists('password_reset_tokens'); + Schema::dropIfExists('sessions'); + } +}; diff --git a/workbench/database/migrations/0001_01_01_000001_testbench_create_cache_table.php b/workbench/database/migrations/0001_01_01_000001_testbench_create_cache_table.php new file mode 100644 index 0000000..b9c106b --- /dev/null +++ b/workbench/database/migrations/0001_01_01_000001_testbench_create_cache_table.php @@ -0,0 +1,35 @@ +string('key')->primary(); + $table->mediumText('value'); + $table->integer('expiration'); + }); + + Schema::create('cache_locks', function (Blueprint $table) { + $table->string('key')->primary(); + $table->string('owner'); + $table->integer('expiration'); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('cache'); + Schema::dropIfExists('cache_locks'); + } +}; diff --git a/workbench/database/migrations/0001_01_01_000002_testbench_create_jobs_table.php b/workbench/database/migrations/0001_01_01_000002_testbench_create_jobs_table.php new file mode 100644 index 0000000..425e705 --- /dev/null +++ b/workbench/database/migrations/0001_01_01_000002_testbench_create_jobs_table.php @@ -0,0 +1,57 @@ +id(); + $table->string('queue')->index(); + $table->longText('payload'); + $table->unsignedTinyInteger('attempts'); + $table->unsignedInteger('reserved_at')->nullable(); + $table->unsignedInteger('available_at'); + $table->unsignedInteger('created_at'); + }); + + Schema::create('job_batches', function (Blueprint $table) { + $table->string('id')->primary(); + $table->string('name'); + $table->integer('total_jobs'); + $table->integer('pending_jobs'); + $table->integer('failed_jobs'); + $table->longText('failed_job_ids'); + $table->mediumText('options')->nullable(); + $table->integer('cancelled_at')->nullable(); + $table->integer('created_at'); + $table->integer('finished_at')->nullable(); + }); + + Schema::create('failed_jobs', function (Blueprint $table) { + $table->id(); + $table->string('uuid')->unique(); + $table->text('connection'); + $table->text('queue'); + $table->longText('payload'); + $table->longText('exception'); + $table->timestamp('failed_at')->useCurrent(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('jobs'); + Schema::dropIfExists('job_batches'); + Schema::dropIfExists('failed_jobs'); + } +}; diff --git a/workbench/database/migrations/2025_08_28_142604_create_permission_tables.php b/workbench/database/migrations/2025_08_28_142604_create_permission_tables.php index db0d8d2..ce4d9d2 100644 --- a/workbench/database/migrations/2025_08_28_142604_create_permission_tables.php +++ b/workbench/database/migrations/2025_08_28_142604_create_permission_tables.php @@ -20,106 +20,96 @@ public function up(): void throw_if(empty($tableNames), new Exception('Error: config/permission.php not loaded. Run [php artisan config:clear] and try again.')); throw_if($teams && empty($columnNames['team_foreign_key'] ?? null), new Exception('Error: team_foreign_key on config/permission.php not loaded. Run [php artisan config:clear] and try again.')); - if (! Schema::hasTable($tableNames['permissions'])) { - Schema::create($tableNames['permissions'], static function (Blueprint $table) { - // $table->engine('InnoDB'); - $table->bigIncrements('id'); // permission id - $table->string('name'); // For MyISAM use string('name', 225); // (or 166 for InnoDB with Redundant/Compact row format) - $table->string('guard_name'); // For MyISAM use string('guard_name', 25); - $table->timestamps(); - + Schema::create($tableNames['permissions'], static function (Blueprint $table) { + // $table->engine('InnoDB'); + $table->bigIncrements('id'); // permission id + $table->string('name'); // For MyISAM use string('name', 225); // (or 166 for InnoDB with Redundant/Compact row format) + $table->string('guard_name'); // For MyISAM use string('guard_name', 25); + $table->timestamps(); + + $table->unique(['name', 'guard_name']); + }); + + Schema::create($tableNames['roles'], static function (Blueprint $table) use ($teams, $columnNames) { + // $table->engine('InnoDB'); + $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 MyISAM use string('name', 225); // (or 166 for InnoDB with Redundant/Compact row format) + $table->string('guard_name'); // For MyISAM use string('guard_name', 25); + $table->timestamps(); + if ($teams || config('permission.testing')) { + $table->unique([$columnNames['team_foreign_key'], 'name', 'guard_name']); + } else { $table->unique(['name', 'guard_name']); - }); - } - - if (! Schema::hasTable($tableNames['roles'])) { - Schema::create($tableNames['roles'], static function (Blueprint $table) use ($teams, $columnNames) { - // $table->engine('InnoDB'); - $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 MyISAM use string('name', 225); // (or 166 for InnoDB with Redundant/Compact row format) - $table->string('guard_name'); // For MyISAM use string('guard_name', 25); - $table->timestamps(); - if ($teams || config('permission.testing')) { - $table->unique([$columnNames['team_foreign_key'], 'name', 'guard_name']); - } else { - $table->unique(['name', 'guard_name']); - } - }); - } - - if (! Schema::hasTable($tableNames['model_has_permissions'])) { - Schema::create($tableNames['model_has_permissions'], static 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'); - } - - }); - } - - if (! Schema::hasTable($tableNames['model_has_roles'])) { - Schema::create($tableNames['model_has_roles'], static 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'); - } - }); - } - - if (! Schema::hasTable($tableNames['role_has_permissions'])) { - Schema::create($tableNames['role_has_permissions'], static 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'); - }); - } + } + }); + + Schema::create($tableNames['model_has_permissions'], static 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'], static 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'], static 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) From 20193a3aa2b5f07265731d5a3de6408776d56f7b Mon Sep 17 00:00:00 2001 From: Kilian Trunk Date: Thu, 28 Aug 2025 18:31:21 +0200 Subject: [PATCH 44/45] chore: fix spatie permission publishing --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 6146844..661e881 100644 --- a/composer.json +++ b/composer.json @@ -75,7 +75,7 @@ "@php -r \"if (!file_exists('workbench/.env')) { copy('workbench/.env.example', 'workbench/.env'); echo '.env file created from .env.example\\n'; }\"", "@php vendor/bin/testbench key:generate --ansi", "npm install", - "@php vendor/bin/testbench vendor:publish --provider='Spatie\\Permission\\PermissionServiceProvider'", + "@php vendor/bin/testbench vendor:publish --provider='Spatie\\Permission\\PermissionServiceProvider' --tag=config", "@php vendor/bin/testbench vendor:publish --tag='filament-shield-config'", "@php vendor/bin/testbench filament:assets", "@php vendor/bin/testbench package:sync-skeleton" From 1e2758a351811bbc2c4dba3184e69277a27745a8 Mon Sep 17 00:00:00 2001 From: Kilian Trunk Date: Thu, 28 Aug 2025 18:39:20 +0200 Subject: [PATCH 45/45] chore: remove Spatie PermissionServiceProvider from composer setup --- composer.json | 1 - 1 file changed, 1 deletion(-) diff --git a/composer.json b/composer.json index 661e881..66eea59 100644 --- a/composer.json +++ b/composer.json @@ -75,7 +75,6 @@ "@php -r \"if (!file_exists('workbench/.env')) { copy('workbench/.env.example', 'workbench/.env'); echo '.env file created from .env.example\\n'; }\"", "@php vendor/bin/testbench key:generate --ansi", "npm install", - "@php vendor/bin/testbench vendor:publish --provider='Spatie\\Permission\\PermissionServiceProvider' --tag=config", "@php vendor/bin/testbench vendor:publish --tag='filament-shield-config'", "@php vendor/bin/testbench filament:assets", "@php vendor/bin/testbench package:sync-skeleton"