diff --git a/.github/workflows/claude-code-review.yml b/.github/workflows/claude-code-review.yml new file mode 100644 index 00000000..b5e8cfd4 --- /dev/null +++ b/.github/workflows/claude-code-review.yml @@ -0,0 +1,44 @@ +name: Claude Code Review + +on: + pull_request: + types: [opened, synchronize, ready_for_review, reopened] + # Optional: Only run on specific file changes + # paths: + # - "src/**/*.ts" + # - "src/**/*.tsx" + # - "src/**/*.js" + # - "src/**/*.jsx" + +jobs: + claude-review: + # Optional: Filter by PR author + # if: | + # github.event.pull_request.user.login == 'external-contributor' || + # github.event.pull_request.user.login == 'new-developer' || + # github.event.pull_request.author_association == 'FIRST_TIME_CONTRIBUTOR' + + runs-on: ubuntu-latest + permissions: + contents: read + pull-requests: read + issues: read + id-token: write + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + fetch-depth: 1 + + - name: Run Claude Code Review + id: claude-review + uses: anthropics/claude-code-action@v1 + with: + claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }} + plugin_marketplaces: 'https://github.com/anthropics/claude-code.git' + plugins: 'code-review@claude-code-plugins' + prompt: '/code-review:code-review ${{ github.repository }}/pull/${{ github.event.pull_request.number }}' + # See https://github.com/anthropics/claude-code-action/blob/main/docs/usage.md + # or https://code.claude.com/docs/en/cli-reference for available options + diff --git a/.github/workflows/claude.yml b/.github/workflows/claude.yml new file mode 100644 index 00000000..d300267f --- /dev/null +++ b/.github/workflows/claude.yml @@ -0,0 +1,50 @@ +name: Claude Code + +on: + issue_comment: + types: [created] + pull_request_review_comment: + types: [created] + issues: + types: [opened, assigned] + pull_request_review: + types: [submitted] + +jobs: + claude: + if: | + (github.event_name == 'issue_comment' && contains(github.event.comment.body, '@claude')) || + (github.event_name == 'pull_request_review_comment' && contains(github.event.comment.body, '@claude')) || + (github.event_name == 'pull_request_review' && contains(github.event.review.body, '@claude')) || + (github.event_name == 'issues' && (contains(github.event.issue.body, '@claude') || contains(github.event.issue.title, '@claude'))) + runs-on: ubuntu-latest + permissions: + contents: read + pull-requests: read + issues: read + id-token: write + actions: read # Required for Claude to read CI results on PRs + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + fetch-depth: 1 + + - name: Run Claude Code + id: claude + uses: anthropics/claude-code-action@v1 + with: + claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }} + + # This is an optional setting that allows Claude to read CI results on PRs + additional_permissions: | + actions: read + + # Optional: Give a custom prompt to Claude. If this is not specified, Claude will perform the instructions specified in the comment that tagged it. + # prompt: 'Update the pull request description to include a summary of changes.' + + # Optional: Add claude_args to customize behavior and configuration + # See https://github.com/anthropics/claude-code-action/blob/main/docs/usage.md + # or https://code.claude.com/docs/en/cli-reference for available options + # claude_args: '--allowed-tools Bash(gh pr:*)' + diff --git a/.github/workflows/dependabot-auto-merge.yml b/.github/workflows/dependabot-auto-merge.yml index 2bd0d1ab..c2406410 100644 --- a/.github/workflows/dependabot-auto-merge.yml +++ b/.github/workflows/dependabot-auto-merge.yml @@ -14,7 +14,7 @@ jobs: - name: Dependabot metadata id: metadata - uses: dependabot/fetch-metadata@v2.4.0 + uses: dependabot/fetch-metadata@v2.5.0 with: github-token: "${{ secrets.GITHUB_TOKEN }}" diff --git a/.github/workflows/setup-in-laravel.yml b/.github/workflows/setup-in-laravel.yml index ed52424e..adc09007 100644 --- a/.github/workflows/setup-in-laravel.yml +++ b/.github/workflows/setup-in-laravel.yml @@ -61,6 +61,6 @@ jobs: composer config minimum-stability dev git clone --branch ${{ steps.extract_branch.outputs.branch }} https://github.com/backstagephp/cms.git composer config repositories.backstage-packages path "cms/packages/*" - composer require backstage/cms:dev-${{ steps.extract_branch.outputs.branch }} + composer require backstage/cms:${{ steps.extract_branch.outputs.branch }}-dev composer update --no-interaction php artisan backstage:install diff --git a/composer.json b/composer.json index 4d840df5..65fe0d66 100644 --- a/composer.json +++ b/composer.json @@ -46,6 +46,7 @@ "codewithdennis/filament-select-tree": "^4.0", "filament/filament": "^4.0", "nette/php-generator": "^4.1", + "phiki/phiki": "^2.0", "saade/filament-adjacency-list": "^4.0", "spatie/laravel-package-tools": "^1.18", "spatie/once": "^3.1", @@ -56,7 +57,7 @@ "laravel/pint": "^1.14", "nunomaduro/collision": "^8.1.1||^7.10.0", "larastan/larastan": "^3.7", - "orchestra/testbench": "^9.0.0||^8.22.0", + "orchestra/testbench": "^10.0", "pestphp/pest": "^4.1", "pestphp/pest-plugin-arch": "^4.0", "pestphp/pest-plugin-laravel": "^4.0", @@ -73,6 +74,7 @@ "Backstage\\Fields\\": "packages/fields/src/", "Backstage\\Fields\\Database\\Factories\\": "packages/fields/database/factories/", "Backstage\\Media\\": "packages/media/src/", + "Backstage\\UploadcareField\\": "packages/uploadcare-field/src/", "Backstage\\Database\\Factories\\": "database/factories/", "Backstage\\Database\\Seeders\\": "database/seeders/" }, @@ -82,7 +84,8 @@ }, "autoload-dev": { "psr-4": { - "Backstage\\Tests\\": "tests/" + "Backstage\\Tests\\": "tests/", + "Backstage\\UploadcareField\\Tests\\": "packages/uploadcare-field/tests/" } }, "scripts": { @@ -124,4 +127,4 @@ ], "minimum-stability": "dev", "prefer-stable": true -} \ No newline at end of file +} diff --git a/config/backstage/media.php b/config/backstage/media.php new file mode 100644 index 00000000..13749395 --- /dev/null +++ b/config/backstage/media.php @@ -0,0 +1,73 @@ + [ + 'image/jpeg', + 'image/png', + 'image/webp', + 'image/svg+xml', + 'video/mp4', + 'video/webm', + 'audio/mpeg', + 'audio/ogg', + 'application/pdf', + ], + + 'directory' => 'media', + + 'disk' => config('filament.filesystem_disk', 'public'), + + 'should_preserve_filenames' => false, + + 'should_register_navigation' => true, + + 'visibility' => 'public', + + /* + |-------------------------------------------------------------------------- + | Tenancy + |-------------------------------------------------------------------------- + | + */ + 'is_tenant_aware' => true, + 'tenant_ownership_relationship_name' => 'site', + 'tenant_relationship' => 'site', + 'tenant_model' => Site::class, + + /* + |-------------------------------------------------------------------------- + | Model and resource + |-------------------------------------------------------------------------- + | + */ + 'model' => \Backstage\Media\Models\Media::class, + + 'user_model' => User::class, + + 'resources' => [ + 'label' => 'Media', + 'plural_label' => 'Media', + 'navigation_group' => null, + 'navigation_label' => 'Media', + 'navigation_icon' => 'heroicon-o-photo', + 'navigation_sort' => null, + 'navigation_count_badge' => false, + 'resource' => \Backstage\Media\Resources\MediaResource::class, + ], + + 'file_upload' => [ + 'models' => [ + Content::class, + ], + ], +]; diff --git a/database/migrations/2025_02_15_123430_create_media_relationships_table.php b/database/migrations/2025_02_15_123430_create_media_relationships_table.php index e7194908..f537b980 100644 --- a/database/migrations/2025_02_15_123430_create_media_relationships_table.php +++ b/database/migrations/2025_02_15_123430_create_media_relationships_table.php @@ -21,8 +21,10 @@ public function up(): void ->on(app(config('backstage.media.model', \Backstage\Media\Models\Media::class))->getTable()) ->cascadeOnDelete(); - // Polymorphic model relationship - $table->morphs('model'); + // Polymorphic model relationship (String ID support) + $table->string('model_type'); + $table->string('model_id', 36); + $table->index(['model_type', 'model_id']); // Optional position for each relationship $table->unsignedInteger('position')->nullable(); diff --git a/database/migrations/2025_11_06_120044_create_translated_attributes_table.php b/database/migrations/2025_11_06_120044_create_translated_attributes_table.php new file mode 100644 index 00000000..c20965a9 --- /dev/null +++ b/database/migrations/2025_11_06_120044_create_translated_attributes_table.php @@ -0,0 +1,35 @@ +id(); + $table->string('code', 5); + + $table->foreign('code') + ->references('code') + ->on('languages') + ->onDelete('cascade'); + + $table->ulidMorphs('translatable'); + + $table->longText('attribute'); + $table->longText('translated_attribute')->nullable(); + $table->timestamp('translated_at')->nullable(); + + $table->timestamps(); + $table->softDeletes(); + }); + } + + public function down() + { + Schema::dropIfExists('translated_attributes'); + } +}; diff --git a/database/migrations/2025_11_06_120623_add_alt_column_to_media_table.php b/database/migrations/2025_11_06_120623_add_alt_column_to_media_table.php new file mode 100644 index 00000000..8e943428 --- /dev/null +++ b/database/migrations/2025_11_06_120623_add_alt_column_to_media_table.php @@ -0,0 +1,24 @@ +getTable(), function (Blueprint $table) { + $table->text('alt')->nullable()->after('height'); + }); + } + + public function down(): void + { + $model = config('backstage.media.model'); + Schema::table((new $model)->getTable(), function (Blueprint $table) { + $table->dropColumn('alt'); + }); + } +}; diff --git a/database/seeders/BackstageSeeder.php b/database/seeders/BackstageSeeder.php index e23fc063..f66e65de 100644 --- a/database/seeders/BackstageSeeder.php +++ b/database/seeders/BackstageSeeder.php @@ -12,7 +12,6 @@ use Backstage\Models\Language; use Backstage\Models\Site; use Backstage\Models\Type; -use Backstage\Models\User; use Illuminate\Database\Seeder; use Illuminate\Support\Str; @@ -173,47 +172,5 @@ public function run(): void (string) Str::uuid() => ['type' => 'form', 'data' => ['slug' => 'contact']], ]), ]), 'values')->create(); - - User::factory([ - 'name' => 'Mark', - 'email' => 'mark@vk10.nl', - 'password' => 'mark@vk10.nl', - ])->create(); - - User::factory([ - 'name' => 'Rob', - 'email' => 'rob@vk10.nl', - 'password' => 'rob@vk10.nl', - ])->create(); - - User::factory([ - 'name' => 'Mathieu', - 'email' => 'mathieu@vk10.nl', - 'password' => 'mathieu@vk10.nl', - ])->create(); - - User::factory([ - 'name' => 'Bas', - 'email' => 'bas@vk10.nl', - 'password' => 'bas@vk10.nl', - ])->create(); - - User::factory([ - 'name' => 'Yoni', - 'email' => 'yoni@vk10.nl', - 'password' => 'yoni@vk10.nl', - ])->create(); - - User::factory([ - 'name' => 'Patrick', - 'email' => 'patrick@vk10.nl', - 'password' => 'patrick@vk10.nl', - ])->create(); - - User::factory([ - 'name' => 'Sandro', - 'email' => 'sandro@vk10.nl', - 'password' => 'sandro@vk10.nl', - ])->create(); } } diff --git a/packages/core/composer.json b/packages/core/composer.json index 66efb195..2aba3df5 100644 --- a/packages/core/composer.json +++ b/packages/core/composer.json @@ -37,6 +37,7 @@ "backstage/media": "self.version", "backstage/redirects": "self.version", "backstage/ai": "self.version", + "backstage/users": "self.version", "baspa/laravel-timezones": "^1.2", "codewithdennis/filament-select-tree": "^4.0", "filament/filament": "^4.0", diff --git a/packages/core/config/backstage/cms.php b/packages/core/config/backstage/cms.php index 4b1001c9..2f566842 100644 --- a/packages/core/config/backstage/cms.php +++ b/packages/core/config/backstage/cms.php @@ -7,6 +7,7 @@ 'plugins' => [ Backstage\Redirects\Filament\RedirectsPlugin::make(), Backstage\Media\MediaPlugin::make(), + Backstage\Filament\Users\UsersPlugin::make(), // Backstage\Translations\Filament\TranslationsPlugin::make(), ], @@ -22,10 +23,10 @@ Backstage\Resources\SettingResource::class, Backstage\Resources\SiteResource::class, Backstage\Resources\TagResource::class, - Backstage\Media\Resources\MediaResource::class, + // Backstage\Resources\MediaResource::class, // Backstage\Resources\TemplateResource::class, Backstage\Resources\TypeResource::class, - Backstage\Resources\UserResource::class, + Backstage\Filament\Users\Resources\UserResource\UserResource::class, ], 'widgets' => [ diff --git a/packages/core/database/migrations/2025_02_15_123430_create_media_relationships_table.php b/packages/core/database/migrations/2025_02_15_123430_create_media_relationships_table.php index e7194908..f537b980 100644 --- a/packages/core/database/migrations/2025_02_15_123430_create_media_relationships_table.php +++ b/packages/core/database/migrations/2025_02_15_123430_create_media_relationships_table.php @@ -21,8 +21,10 @@ public function up(): void ->on(app(config('backstage.media.model', \Backstage\Media\Models\Media::class))->getTable()) ->cascadeOnDelete(); - // Polymorphic model relationship - $table->morphs('model'); + // Polymorphic model relationship (String ID support) + $table->string('model_type'); + $table->string('model_id', 36); + $table->index(['model_type', 'model_id']); // Optional position for each relationship $table->unsignedInteger('position')->nullable(); diff --git a/packages/core/database/migrations/2025_11_06_120044_create_translated_attributes_table.php b/packages/core/database/migrations/2025_11_06_120044_create_translated_attributes_table.php new file mode 100644 index 00000000..8b112883 --- /dev/null +++ b/packages/core/database/migrations/2025_11_06_120044_create_translated_attributes_table.php @@ -0,0 +1,39 @@ +id(); + $table->string('code', 5); + + $table->foreign('code') + ->references('code') + ->on('languages') + ->onDelete('cascade'); + + $table->ulidMorphs('translatable'); + + $table->longText('attribute'); + $table->longText('translated_attribute')->nullable(); + $table->timestamp('translated_at')->nullable(); + + $table->timestamps(); + $table->softDeletes(); + }); + } + + public function down() + { + Schema::dropIfExists('translated_attributes'); + } +}; diff --git a/packages/core/database/migrations/2025_11_06_120623_add_alt_column_to_media_table.php b/packages/core/database/migrations/2025_11_06_120623_add_alt_column_to_media_table.php new file mode 100644 index 00000000..8e943428 --- /dev/null +++ b/packages/core/database/migrations/2025_11_06_120623_add_alt_column_to_media_table.php @@ -0,0 +1,24 @@ +getTable(), function (Blueprint $table) { + $table->text('alt')->nullable()->after('height'); + }); + } + + public function down(): void + { + $model = config('backstage.media.model'); + Schema::table((new $model)->getTable(), function (Blueprint $table) { + $table->dropColumn('alt'); + }); + } +}; diff --git a/packages/core/database/migrations/2025_12_08_000000_make_media_relationships_model_id_string.php b/packages/core/database/migrations/2025_12_08_000000_make_media_relationships_model_id_string.php new file mode 100644 index 00000000..5173344e --- /dev/null +++ b/packages/core/database/migrations/2025_12_08_000000_make_media_relationships_model_id_string.php @@ -0,0 +1,25 @@ +string('model_id', 36)->change(); + $table->string('model_type')->change(); + }); + } + + public function down(): void + { + Schema::table('media_relationships', function (Blueprint $table) { + // Revert to typical big integer if needed (unsafe if data exists) + // $table->unsignedBigInteger('model_id')->change(); + }); + } +}; diff --git a/packages/core/database/migrations/2025_12_10_080000_update_media_relationships_indexes.php b/packages/core/database/migrations/2025_12_10_080000_update_media_relationships_indexes.php new file mode 100644 index 00000000..f94f0cf0 --- /dev/null +++ b/packages/core/database/migrations/2025_12_10_080000_update_media_relationships_indexes.php @@ -0,0 +1,63 @@ +dropForeign(['media_ulid']); + }); + } catch (\Illuminate\Database\QueryException $e) { + // Ignore if foreign key does not exist + } + + // 2. Try to drop unique index (might fail if already dropped) + try { + Schema::table('media_relationships', function (Blueprint $table) { + $table->dropUnique(['media_ulid', 'model_type', 'model_id']); + }); + } catch (\Illuminate\Database\QueryException $e) { + // Ignore if index does not exist + } + + // 3. Try to add new index (might fail if already exists) + try { + Schema::table('media_relationships', function (Blueprint $table) { + $table->index(['model_type', 'model_id', 'position']); + }); + } catch (\Illuminate\Database\QueryException $e) { + // Ignore if index already exists + } + + // 4. Re-add foreign key (using safe 'foreign' method) + Schema::table('media_relationships', function (Blueprint $table) { + // We use a separate call here to ensure we don't catch unexpected errors in the definition + // But we might want to check if it exists? + // Generically adding a FK usually fails if it exists with same name. + // Since we know we tried to drop it in step 1, this should be safe unless step 1 failed unrelatedly. + // However, to be extra safe against "Constraint already exists": + try { + $table->foreign('media_ulid') + ->references('ulid') + ->on(app(config('backstage.media.model', \Backstage\Media\Models\Media::class))->getTable()) + ->cascadeOnDelete(); + } catch (\Illuminate\Database\QueryException $e) { + // assume it exists if it fails + } + }); + } + + public function down(): void + { + Schema::table('media_relationships', function (Blueprint $table) { + $table->dropIndex(['model_type', 'model_id', 'position']); + $table->unique(['media_ulid', 'model_type', 'model_id']); + }); + } +}; diff --git a/packages/core/database/migrations/2025_12_10_080001_update_translatable_column.php b/packages/core/database/migrations/2025_12_10_080001_update_translatable_column.php new file mode 100644 index 00000000..3638e863 --- /dev/null +++ b/packages/core/database/migrations/2025_12_10_080001_update_translatable_column.php @@ -0,0 +1,21 @@ +string('translatable_id', 36)->change(); + $table->string('translatable_type', 36)->change(); + }); + } + + public function down() + { + Schema::dropIfExists('translated_attributes'); + } +}; diff --git a/packages/core/database/migrations/2026_01_13_132457_1_add_sub_navigation_preference_to_users_table.php b/packages/core/database/migrations/2026_01_13_132457_1_add_sub_navigation_preference_to_users_table.php new file mode 100644 index 00000000..aa033921 --- /dev/null +++ b/packages/core/database/migrations/2026_01_13_132457_1_add_sub_navigation_preference_to_users_table.php @@ -0,0 +1,31 @@ +enum('sub_navigation_preference', ['start', 'end', 'top']) + ->default('top') + ->after('remember_token') + ->comment('The user\'s preference for the sub navigation position. The default is top.'); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table(config('users.eloquent.user.table', 'users'), function (Blueprint $table) { + $table->dropColumn('sub_navigation_preference'); + }); + } +}; diff --git a/packages/core/database/migrations/2026_01_13_132457_1_create_user_logins_table.php b/packages/core/database/migrations/2026_01_13_132457_1_create_user_logins_table.php new file mode 100644 index 00000000..3d8736f7 --- /dev/null +++ b/packages/core/database/migrations/2026_01_13_132457_1_create_user_logins_table.php @@ -0,0 +1,41 @@ +id(); + $table->unsignedBigInteger('user_id')->nullable(); + + $table->string('type')->nullable(); + $table->string('url')->nullable(); + $table->string('referrer')->nullable(); + $table->text('inputs')->nullable(); + $table->string('user_agent')->nullable(); + $table->string('ip_address')->nullable(); + $table->string('hostname')->nullable(); + $table->string('isp')->nullable(); + $table->string('org')->nullable(); + $table->string('city')->nullable(); + $table->string('region')->nullable(); + $table->string('country_code', 2)->nullable(); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists(config('users.eloquent.user_login.table', 'user_logins')); + } +}; diff --git a/packages/core/database/migrations/2026_01_13_132457_create_permission_tables.php b/packages/core/database/migrations/2026_01_13_132457_create_permission_tables.php new file mode 100644 index 00000000..aec7e091 --- /dev/null +++ b/packages/core/database/migrations/2026_01_13_132457_create_permission_tables.php @@ -0,0 +1,142 @@ +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'); + + throw_if(empty($tableNames), Exception::class, '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/packages/core/database/migrations/2026_01_13_132458_2_add_width_preference_to_users_table.php b/packages/core/database/migrations/2026_01_13_132458_2_add_width_preference_to_users_table.php new file mode 100644 index 00000000..a10223af --- /dev/null +++ b/packages/core/database/migrations/2026_01_13_132458_2_add_width_preference_to_users_table.php @@ -0,0 +1,31 @@ +enum('width_preference', ['full', '7xl']) + ->default('7xl') + ->after('remember_token') + ->comment('The user\'s preference for the content width. The default is 7xl.'); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table(config('users.eloquent.user.table', 'users'), function (Blueprint $table) { + $table->dropColumn('width_preference'); + }); + } +}; diff --git a/packages/core/database/migrations/2026_01_13_132458_2_create_user_traffic_table.php b/packages/core/database/migrations/2026_01_13_132458_2_create_user_traffic_table.php new file mode 100644 index 00000000..44cb05dd --- /dev/null +++ b/packages/core/database/migrations/2026_01_13_132458_2_create_user_traffic_table.php @@ -0,0 +1,38 @@ +id(); + $table->unsignedBigInteger('user_id')->nullable(); + + $table->string('method', 10); + $table->string('path'); + $table->text('full_url'); + $table->ipAddress('ip')->nullable(); + $table->text('user_agent')->nullable(); + $table->text('referer')->nullable(); + $table->string('route_name')->nullable(); + $table->string('route_action')->nullable(); + $table->json('route_parameters')->nullable(); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists(config('users.eloquent.user_traffic.table', 'user_traffic')); + } +}; diff --git a/packages/core/database/migrations/2026_01_13_132459_3_user_password_nullable.php b/packages/core/database/migrations/2026_01_13_132459_3_user_password_nullable.php new file mode 100644 index 00000000..7d7340ec --- /dev/null +++ b/packages/core/database/migrations/2026_01_13_132459_3_user_password_nullable.php @@ -0,0 +1,28 @@ +string('password')->nullable()->change(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table(config('users.eloquent.user.table', 'users'), function (Blueprint $table) { + $table->string('password')->nullable(false)->change(); + }); + } +}; diff --git a/packages/core/database/migrations/2026_01_13_132500_4_create_user_notification_preferences_table.php b/packages/core/database/migrations/2026_01_13_132500_4_create_user_notification_preferences_table.php new file mode 100644 index 00000000..a8de8e71 --- /dev/null +++ b/packages/core/database/migrations/2026_01_13_132500_4_create_user_notification_preferences_table.php @@ -0,0 +1,32 @@ +id(); + $table->unsignedBigInteger('user_id'); + + $table->string('navigation_type'); + $table->timestamps(); + + $table->unique(['user_id', 'navigation_type']); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists(config('users.eloquent.user_notification_preferences.table', 'user_notification_preferences')); + } +}; diff --git a/packages/core/database/migrations/2026_01_13_132501_5_drop_user_traffic_table.php b/packages/core/database/migrations/2026_01_13_132501_5_drop_user_traffic_table.php new file mode 100644 index 00000000..4a548c6d --- /dev/null +++ b/packages/core/database/migrations/2026_01_13_132501_5_drop_user_traffic_table.php @@ -0,0 +1,20 @@ +text('url')->change(); + }); + } + + public function down(): void + { + Schema::table(config('users.eloquent.user_login.table', 'user_logins'), function (Blueprint $table) { + $table->string('url', 255)->change(); + }); + } +}; diff --git a/packages/core/database/migrations/2026_01_13_132544_create_personal_access_tokens_table.php b/packages/core/database/migrations/2026_01_13_132544_create_personal_access_tokens_table.php new file mode 100644 index 00000000..40ff706e --- /dev/null +++ b/packages/core/database/migrations/2026_01_13_132544_create_personal_access_tokens_table.php @@ -0,0 +1,33 @@ +id(); + $table->morphs('tokenable'); + $table->text('name'); + $table->string('token', 64)->unique(); + $table->text('abilities')->nullable(); + $table->timestamp('last_used_at')->nullable(); + $table->timestamp('expires_at')->nullable()->index(); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('personal_access_tokens'); + } +}; diff --git a/packages/core/database/migrations/2026_01_13_133157_7_add_deleted_add_to_users_tabe.php b/packages/core/database/migrations/2026_01_13_133157_7_add_deleted_add_to_users_tabe.php new file mode 100644 index 00000000..1698f9e4 --- /dev/null +++ b/packages/core/database/migrations/2026_01_13_133157_7_add_deleted_add_to_users_tabe.php @@ -0,0 +1,26 @@ +getTable(), 'deleted_at')) { + $table->softDeletes(); + } + }); + } + + public function down(): void + { + Schema::table(config('users.eloquent.user.table', 'users'), function (Blueprint $table) { + if (Schema::hasColumn($table->getTable(), 'deleted_at')) { + $table->dropSoftDeletes(); + } + }); + } +}; diff --git a/packages/core/database/seeders/BackstageSeeder.php b/packages/core/database/seeders/BackstageSeeder.php index e23fc063..f66e65de 100644 --- a/packages/core/database/seeders/BackstageSeeder.php +++ b/packages/core/database/seeders/BackstageSeeder.php @@ -12,7 +12,6 @@ use Backstage\Models\Language; use Backstage\Models\Site; use Backstage\Models\Type; -use Backstage\Models\User; use Illuminate\Database\Seeder; use Illuminate\Support\Str; @@ -173,47 +172,5 @@ public function run(): void (string) Str::uuid() => ['type' => 'form', 'data' => ['slug' => 'contact']], ]), ]), 'values')->create(); - - User::factory([ - 'name' => 'Mark', - 'email' => 'mark@vk10.nl', - 'password' => 'mark@vk10.nl', - ])->create(); - - User::factory([ - 'name' => 'Rob', - 'email' => 'rob@vk10.nl', - 'password' => 'rob@vk10.nl', - ])->create(); - - User::factory([ - 'name' => 'Mathieu', - 'email' => 'mathieu@vk10.nl', - 'password' => 'mathieu@vk10.nl', - ])->create(); - - User::factory([ - 'name' => 'Bas', - 'email' => 'bas@vk10.nl', - 'password' => 'bas@vk10.nl', - ])->create(); - - User::factory([ - 'name' => 'Yoni', - 'email' => 'yoni@vk10.nl', - 'password' => 'yoni@vk10.nl', - ])->create(); - - User::factory([ - 'name' => 'Patrick', - 'email' => 'patrick@vk10.nl', - 'password' => 'patrick@vk10.nl', - ])->create(); - - User::factory([ - 'name' => 'Sandro', - 'email' => 'sandro@vk10.nl', - 'password' => 'sandro@vk10.nl', - ])->create(); } } diff --git a/packages/core/database/seeders/UserSeeder.php b/packages/core/database/seeders/UserSeeder.php index ade1b7c7..35442976 100644 --- a/packages/core/database/seeders/UserSeeder.php +++ b/packages/core/database/seeders/UserSeeder.php @@ -15,44 +15,44 @@ public function run(): void { User::factory()->state([ 'name' => 'Mark', - 'email' => 'mark@vk10.nl', - 'password' => 'mark@vk10.nl', + 'email' => 'mark@ux.nl', + 'password' => 'mark@ux.nl', ])->create(); User::factory([ - 'name' => 'Rob', - 'email' => 'rob@vk10.nl', - 'password' => 'rob@vk10.nl', + 'name' => 'Manoj', + 'email' => 'manoj@ux.nl', + 'password' => 'manoj@ux.nl', ])->create(); User::factory()->state([ 'name' => 'Mathieu', - 'email' => 'mathieu@vk10.nl', - 'password' => 'mathieu@vk10.nl', + 'email' => 'mathieu@ux.nl', + 'password' => 'mathieu@ux.nl', ])->create(); User::factory()->state([ 'name' => 'Bas', - 'email' => 'bas@vk10.nl', - 'password' => 'bas@vk10.nl', + 'email' => 'bas@ux.nl', + 'password' => 'bas@ux.nl', ])->create(); User::factory([ 'name' => 'Yoni', - 'email' => 'yoni@vk10.nl', - 'password' => 'yoni@vk10.nl', + 'email' => 'yoni@ux.nl', + 'password' => 'yoni@ux.nl', ])->create(); User::factory([ 'name' => 'Patrick', - 'email' => 'patrick@vk10.nl', - 'password' => 'patrick@vk10.nl', + 'email' => 'patrick@ux.nl', + 'password' => 'patrick@ux.nl', ])->create(); User::factory([ 'name' => 'Sandro', - 'email' => 'sandro@vk10.nl', - 'password' => 'sandro@vk10.nl', + 'email' => 'sandro@ux.nl', + 'password' => 'sandro@ux.nl', ])->create(); Site::default()->users()->attach(User::all()); diff --git a/packages/core/resources/views/components/page.blade.php b/packages/core/resources/views/components/page.blade.php index 745cbbde..da122ed7 100644 --- a/packages/core/resources/views/components/page.blade.php +++ b/packages/core/resources/views/components/page.blade.php @@ -5,7 +5,7 @@