From 2f5ca24da09b4cbc03dc430586dff95059685879 Mon Sep 17 00:00:00 2001 From: HolgerHatGarKeineNode Date: Wed, 11 Feb 2026 21:10:09 +0100 Subject: [PATCH] =?UTF-8?q?=F0=9F=94=A5=20Remove=20Laravel=20Sail,=20Docke?= =?UTF-8?q?r,=20and=20related=20setup,=20migrate=20to=20simplified=20local?= =?UTF-8?q?=20development=20environment?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - **Removed:** `docker-compose.yml`, Sail-specific Dockerfiles, and related scripts for PHP 8.3 setup. - **Updated:** Documentation to reflect a shift from Docker to a direct PHP-based local development workflow. - **Removed:** `laravel/sail` dependency from `composer.lock`. - **Implemented:** `#[Locked]` Livewire attribute across components for read-only properties. - **Added:** Feature tests to ensure locked properties cannot be tampered with. --- .junie/guidelines.md | 45 ++---- .junie/mcp/mcp.json | 4 +- .mcp.json | 2 +- AGENTS.md | 45 ++---- CLAUDE.md | 45 ++---- README.md | 34 +++-- app/Livewire/Forms/ServiceForm.php | 4 +- boost.json | 3 +- composer.json | 3 +- composer.lock | 65 +-------- docker-compose.yml | 82 ----------- docker/8.3/Dockerfile | 75 ---------- docker/8.3/php.ini | 5 - docker/8.3/start-container | 26 ---- docker/8.3/supervisord.conf | 14 -- docker/pgsql/create-testing-database.sql | 2 - opencode.json | 2 +- phpunit.xml | 2 +- .../courses/create-edit-events.blade.php | 2 + .../views/livewire/courses/edit.blade.php | 8 +- .../views/livewire/lecturers/edit.blade.php | 8 +- .../meetups/create-edit-events.blade.php | 2 + .../views/livewire/meetups/edit.blade.php | 8 +- .../views/livewire/services/create.blade.php | 3 + .../Feature/LivewireLockedPropertiesTest.php | 132 ++++++++++++++++++ 25 files changed, 232 insertions(+), 389 deletions(-) delete mode 100644 docker-compose.yml delete mode 100644 docker/8.3/Dockerfile delete mode 100644 docker/8.3/php.ini delete mode 100644 docker/8.3/start-container delete mode 100644 docker/8.3/supervisord.conf delete mode 100644 docker/pgsql/create-testing-database.sql create mode 100644 tests/Feature/LivewireLockedPropertiesTest.php diff --git a/.junie/guidelines.md b/.junie/guidelines.md index 51b64d1..4fec71c 100644 --- a/.junie/guidelines.md +++ b/.junie/guidelines.md @@ -67,7 +67,7 @@ The Laravel Boost guidelines are specifically curated by Laravel maintainers for ## Foundational Context This application is a Laravel application and its main Laravel ecosystems package & versions are below. You are an expert with them all. Ensure you abide by these specific packages & versions. -- php - 8.3.29 +- php - 8.4.17 - laravel/framework (LARAVEL) - v12 - laravel/horizon (HORIZON) - v5 - laravel/nightwatch (NIGHTWATCH) - v1 @@ -78,7 +78,6 @@ This application is a Laravel application and its main Laravel ecosystems packag - livewire/livewire (LIVEWIRE) - v4 - laravel/mcp (MCP) - v0 - laravel/pint (PINT) - v1 -- laravel/sail (SAIL) - v1 - pestphp/pest (PEST) - v4 - phpunit/phpunit (PHPUNIT) - v12 - tailwindcss (TAILWINDCSS) - v4 @@ -96,7 +95,7 @@ This application is a Laravel application and its main Laravel ecosystems packag - Do not change the application's dependencies without approval. ## Frontend Bundling -- If the user doesn't see a frontend change reflected in the UI, it could mean they need to run `vendor/bin/sail yarn run build`, `vendor/bin/sail yarn run dev`, or `vendor/bin/sail composer run dev`. Ask them. +- If the user doesn't see a frontend change reflected in the UI, it could mean they need to run `yarn run build`, `yarn run dev`, or `composer run dev`. Ask them. ## Replies - Be concise in your explanations - focus on what's important rather than explaining obvious details. @@ -171,33 +170,19 @@ protected function isAccessible(User $user, ?string $path = null): bool ## Enums - Typically, keys in an Enum should be TitleCase. For example: `FavoritePerson`, `BestLake`, `Monthly`. -=== sail rules === - -## Laravel Sail - -- This project runs inside Laravel Sail's Docker containers. You MUST execute all commands through Sail. -- Start services using `vendor/bin/sail up -d` and stop them with `vendor/bin/sail stop`. -- Open the application in the browser by running `vendor/bin/sail open`. -- Always prefix PHP, Artisan, Composer, and Node commands with `vendor/bin/sail`. Examples: - - Run Artisan Commands: `vendor/bin/sail artisan migrate` - - Install Composer packages: `vendor/bin/sail composer install` - - Execute Node commands: `vendor/bin/sail yarn run dev` - - Execute PHP scripts: `vendor/bin/sail php [script]` -- View all available Sail commands by running `vendor/bin/sail` without arguments. - === tests rules === ## Test Enforcement - Every change must be programmatically tested. Write a new test or update an existing test, then run the affected tests to make sure they pass. -- Run the minimum number of tests needed to ensure code quality and speed. Use `vendor/bin/sail artisan test --compact` with a specific filename or filter. +- Run the minimum number of tests needed to ensure code quality and speed. Use `php artisan test --compact` with a specific filename or filter. === laravel/core rules === ## Do Things the Laravel Way -- Use `vendor/bin/sail artisan make:` commands to create new files (i.e. migrations, controllers, models, etc.). You can list available Artisan commands using the `list-artisan-commands` tool. -- If you're creating a generic PHP class, use `vendor/bin/sail artisan make:class`. +- Use `php artisan make:` commands to create new files (i.e. migrations, controllers, models, etc.). You can list available Artisan commands using the `list-artisan-commands` tool. +- If you're creating a generic PHP class, use `php artisan make:class`. - Pass `--no-interaction` to all Artisan commands to ensure they work without user input. You should also pass the correct `--options` to ensure correct behavior. ### Database @@ -208,7 +193,7 @@ protected function isAccessible(User $user, ?string $path = null): bool - Use Laravel's query builder for very complex database operations. ### Model Creation -- When creating new models, create useful factories and seeders for them too. Ask the user if they need any other things, using `list-artisan-commands` to check the available options to `vendor/bin/sail artisan make:model`. +- When creating new models, create useful factories and seeders for them too. Ask the user if they need any other things, using `list-artisan-commands` to check the available options to `php artisan make:model`. ### APIs & Eloquent Resources - For APIs, default to using Eloquent API Resources and API versioning unless existing API routes do not, then you should follow existing application convention. @@ -232,10 +217,10 @@ protected function isAccessible(User $user, ?string $path = null): bool ### Testing - When creating models for tests, use the factories for the models. Check if the factory has custom states that can be used before manually setting up the model. - Faker: Use methods such as `$this->faker->word()` or `fake()->randomDigit()`. Follow existing conventions whether to use `$this->faker` or `fake()`. -- When creating tests, make use of `vendor/bin/sail artisan make:test [options] {name}` to create a feature test, and pass `--unit` to create a unit test. Most tests should be feature tests. +- When creating tests, make use of `php artisan make:test [options] {name}` to create a feature test, and pass `--unit` to create a unit test. Most tests should be feature tests. ### Vite Error -- If you receive an "Illuminate\Foundation\ViteException: Unable to locate file in Vite manifest" error, you can run `vendor/bin/sail yarn run build` or ask the user to run `vendor/bin/sail yarn run dev` or `vendor/bin/sail composer run dev`. +- If you receive an "Illuminate\Foundation\ViteException: Unable to locate file in Vite manifest" error, you can run `yarn run build` or ask the user to run `yarn run dev` or `composer run dev`. === laravel/v12 rules === @@ -286,7 +271,7 @@ accordion, autocomplete, avatar, badge, brand, breadcrumbs, button, calendar, ca ## Livewire - Use the `search-docs` tool to find exact version-specific documentation for how to write Livewire and Livewire tests. -- Use the `vendor/bin/sail artisan make:livewire [Posts\CreatePost]` Artisan command to create new components. +- Use the `php artisan make:livewire [Posts\CreatePost]` Artisan command to create new components. - State should live on the server, with the UI reflecting it. - All Livewire requests hit the Laravel backend; they're like regular HTTP requests. Always validate form data and run authorization checks in Livewire actions. @@ -330,8 +315,8 @@ accordion, autocomplete, avatar, badge, brand, breadcrumbs, button, calendar, ca ## Laravel Pint Code Formatter -- You must run `vendor/bin/sail bin pint --dirty` before finalizing changes to ensure your code matches the project's expected style. -- Do not run `vendor/bin/sail bin pint --test`, simply run `vendor/bin/sail bin pint` to fix any formatting issues. +- You must run `vendor/bin/pint --dirty` before finalizing changes to ensure your code matches the project's expected style. +- Do not run `vendor/bin/pint --test`, simply run `vendor/bin/pint` to fix any formatting issues. === pest/core rules === @@ -340,7 +325,7 @@ accordion, autocomplete, avatar, badge, brand, breadcrumbs, button, calendar, ca - If you need to verify a feature is working, write or update a Unit / Feature test. ### Pest Tests -- All tests must be written using Pest. Use `vendor/bin/sail artisan make:test --pest {name}`. +- All tests must be written using Pest. Use `php artisan make:test --pest {name}`. - You must not remove any tests or test files from the tests directory without approval. These are not temporary or helper files - these are core to the application. - Tests should test all of the happy paths, failure paths, and weird paths. - Tests live in the `tests/Feature` and `tests/Unit` directories. @@ -353,9 +338,9 @@ it('is true', function () { ### Running Tests - Run the minimal number of tests using an appropriate filter before finalizing code edits. -- To run all tests: `vendor/bin/sail artisan test --compact`. -- To run all tests in a file: `vendor/bin/sail artisan test --compact tests/Feature/ExampleTest.php`. -- To filter on a particular test name: `vendor/bin/sail artisan test --compact --filter=testName` (recommended after making a change to a related file). +- To run all tests: `php artisan test --compact`. +- To run all tests in a file: `php artisan test --compact tests/Feature/ExampleTest.php`. +- To filter on a particular test name: `php artisan test --compact --filter=testName` (recommended after making a change to a related file). - When the tests relating to your changes are passing, ask the user if they would like to run the entire test suite to ensure everything is still passing. ### Pest Assertions diff --git a/.junie/mcp/mcp.json b/.junie/mcp/mcp.json index 04eba29..406fbdc 100644 --- a/.junie/mcp/mcp.json +++ b/.junie/mcp/mcp.json @@ -1,9 +1,9 @@ { "mcpServers": { "laravel-boost": { - "command": "vendor/bin/sail", + "command": "/usr/bin/php", "args": [ - "artisan", + "/var/home/user/Code/einundzwanzig-app/artisan", "boost:mcp" ] } diff --git a/.mcp.json b/.mcp.json index 04eba29..8c6715a 100644 --- a/.mcp.json +++ b/.mcp.json @@ -1,7 +1,7 @@ { "mcpServers": { "laravel-boost": { - "command": "vendor/bin/sail", + "command": "php", "args": [ "artisan", "boost:mcp" diff --git a/AGENTS.md b/AGENTS.md index a7b568e..69f01da 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -8,7 +8,7 @@ The Laravel Boost guidelines are specifically curated by Laravel maintainers for ## Foundational Context This application is a Laravel application and its main Laravel ecosystems package & versions are below. You are an expert with them all. Ensure you abide by these specific packages & versions. -- php - 8.3.29 +- php - 8.4.17 - laravel/framework (LARAVEL) - v12 - laravel/horizon (HORIZON) - v5 - laravel/nightwatch (NIGHTWATCH) - v1 @@ -19,7 +19,6 @@ This application is a Laravel application and its main Laravel ecosystems packag - livewire/livewire (LIVEWIRE) - v4 - laravel/mcp (MCP) - v0 - laravel/pint (PINT) - v1 -- laravel/sail (SAIL) - v1 - pestphp/pest (PEST) - v4 - phpunit/phpunit (PHPUNIT) - v12 - tailwindcss (TAILWINDCSS) - v4 @@ -37,7 +36,7 @@ This application is a Laravel application and its main Laravel ecosystems packag - Do not change the application's dependencies without approval. ## Frontend Bundling -- If the user doesn't see a frontend change reflected in the UI, it could mean they need to run `vendor/bin/sail yarn run build`, `vendor/bin/sail yarn run dev`, or `vendor/bin/sail composer run dev`. Ask them. +- If the user doesn't see a frontend change reflected in the UI, it could mean they need to run `yarn run build`, `yarn run dev`, or `composer run dev`. Ask them. ## Replies - Be concise in your explanations - focus on what's important rather than explaining obvious details. @@ -112,33 +111,19 @@ protected function isAccessible(User $user, ?string $path = null): bool ## Enums - Typically, keys in an Enum should be TitleCase. For example: `FavoritePerson`, `BestLake`, `Monthly`. -=== sail rules === - -## Laravel Sail - -- This project runs inside Laravel Sail's Docker containers. You MUST execute all commands through Sail. -- Start services using `vendor/bin/sail up -d` and stop them with `vendor/bin/sail stop`. -- Open the application in the browser by running `vendor/bin/sail open`. -- Always prefix PHP, Artisan, Composer, and Node commands with `vendor/bin/sail`. Examples: - - Run Artisan Commands: `vendor/bin/sail artisan migrate` - - Install Composer packages: `vendor/bin/sail composer install` - - Execute Node commands: `vendor/bin/sail yarn run dev` - - Execute PHP scripts: `vendor/bin/sail php [script]` -- View all available Sail commands by running `vendor/bin/sail` without arguments. - === tests rules === ## Test Enforcement - Every change must be programmatically tested. Write a new test or update an existing test, then run the affected tests to make sure they pass. -- Run the minimum number of tests needed to ensure code quality and speed. Use `vendor/bin/sail artisan test --compact` with a specific filename or filter. +- Run the minimum number of tests needed to ensure code quality and speed. Use `php artisan test --compact` with a specific filename or filter. === laravel/core rules === ## Do Things the Laravel Way -- Use `vendor/bin/sail artisan make:` commands to create new files (i.e. migrations, controllers, models, etc.). You can list available Artisan commands using the `list-artisan-commands` tool. -- If you're creating a generic PHP class, use `vendor/bin/sail artisan make:class`. +- Use `php artisan make:` commands to create new files (i.e. migrations, controllers, models, etc.). You can list available Artisan commands using the `list-artisan-commands` tool. +- If you're creating a generic PHP class, use `php artisan make:class`. - Pass `--no-interaction` to all Artisan commands to ensure they work without user input. You should also pass the correct `--options` to ensure correct behavior. ### Database @@ -149,7 +134,7 @@ protected function isAccessible(User $user, ?string $path = null): bool - Use Laravel's query builder for very complex database operations. ### Model Creation -- When creating new models, create useful factories and seeders for them too. Ask the user if they need any other things, using `list-artisan-commands` to check the available options to `vendor/bin/sail artisan make:model`. +- When creating new models, create useful factories and seeders for them too. Ask the user if they need any other things, using `list-artisan-commands` to check the available options to `php artisan make:model`. ### APIs & Eloquent Resources - For APIs, default to using Eloquent API Resources and API versioning unless existing API routes do not, then you should follow existing application convention. @@ -173,10 +158,10 @@ protected function isAccessible(User $user, ?string $path = null): bool ### Testing - When creating models for tests, use the factories for the models. Check if the factory has custom states that can be used before manually setting up the model. - Faker: Use methods such as `$this->faker->word()` or `fake()->randomDigit()`. Follow existing conventions whether to use `$this->faker` or `fake()`. -- When creating tests, make use of `vendor/bin/sail artisan make:test [options] {name}` to create a feature test, and pass `--unit` to create a unit test. Most tests should be feature tests. +- When creating tests, make use of `php artisan make:test [options] {name}` to create a feature test, and pass `--unit` to create a unit test. Most tests should be feature tests. ### Vite Error -- If you receive an "Illuminate\Foundation\ViteException: Unable to locate file in Vite manifest" error, you can run `vendor/bin/sail yarn run build` or ask the user to run `vendor/bin/sail yarn run dev` or `vendor/bin/sail composer run dev`. +- If you receive an "Illuminate\Foundation\ViteException: Unable to locate file in Vite manifest" error, you can run `yarn run build` or ask the user to run `yarn run dev` or `composer run dev`. === laravel/v12 rules === @@ -227,7 +212,7 @@ accordion, autocomplete, avatar, badge, brand, breadcrumbs, button, calendar, ca ## Livewire - Use the `search-docs` tool to find exact version-specific documentation for how to write Livewire and Livewire tests. -- Use the `vendor/bin/sail artisan make:livewire [Posts\CreatePost]` Artisan command to create new components. +- Use the `php artisan make:livewire [Posts\CreatePost]` Artisan command to create new components. - State should live on the server, with the UI reflecting it. - All Livewire requests hit the Laravel backend; they're like regular HTTP requests. Always validate form data and run authorization checks in Livewire actions. @@ -271,8 +256,8 @@ accordion, autocomplete, avatar, badge, brand, breadcrumbs, button, calendar, ca ## Laravel Pint Code Formatter -- You must run `vendor/bin/sail bin pint --dirty` before finalizing changes to ensure your code matches the project's expected style. -- Do not run `vendor/bin/sail bin pint --test`, simply run `vendor/bin/sail bin pint` to fix any formatting issues. +- You must run `vendor/bin/pint --dirty` before finalizing changes to ensure your code matches the project's expected style. +- Do not run `vendor/bin/pint --test`, simply run `vendor/bin/pint` to fix any formatting issues. === pest/core rules === @@ -281,7 +266,7 @@ accordion, autocomplete, avatar, badge, brand, breadcrumbs, button, calendar, ca - If you need to verify a feature is working, write or update a Unit / Feature test. ### Pest Tests -- All tests must be written using Pest. Use `vendor/bin/sail artisan make:test --pest {name}`. +- All tests must be written using Pest. Use `php artisan make:test --pest {name}`. - You must not remove any tests or test files from the tests directory without approval. These are not temporary or helper files - these are core to the application. - Tests should test all of the happy paths, failure paths, and weird paths. - Tests live in the `tests/Feature` and `tests/Unit` directories. @@ -294,9 +279,9 @@ it('is true', function () { ### Running Tests - Run the minimal number of tests using an appropriate filter before finalizing code edits. -- To run all tests: `vendor/bin/sail artisan test --compact`. -- To run all tests in a file: `vendor/bin/sail artisan test --compact tests/Feature/ExampleTest.php`. -- To filter on a particular test name: `vendor/bin/sail artisan test --compact --filter=testName` (recommended after making a change to a related file). +- To run all tests: `php artisan test --compact`. +- To run all tests in a file: `php artisan test --compact tests/Feature/ExampleTest.php`. +- To filter on a particular test name: `php artisan test --compact --filter=testName` (recommended after making a change to a related file). - When the tests relating to your changes are passing, ask the user if they would like to run the entire test suite to ensure everything is still passing. ### Pest Assertions diff --git a/CLAUDE.md b/CLAUDE.md index a7b568e..69f01da 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -8,7 +8,7 @@ The Laravel Boost guidelines are specifically curated by Laravel maintainers for ## Foundational Context This application is a Laravel application and its main Laravel ecosystems package & versions are below. You are an expert with them all. Ensure you abide by these specific packages & versions. -- php - 8.3.29 +- php - 8.4.17 - laravel/framework (LARAVEL) - v12 - laravel/horizon (HORIZON) - v5 - laravel/nightwatch (NIGHTWATCH) - v1 @@ -19,7 +19,6 @@ This application is a Laravel application and its main Laravel ecosystems packag - livewire/livewire (LIVEWIRE) - v4 - laravel/mcp (MCP) - v0 - laravel/pint (PINT) - v1 -- laravel/sail (SAIL) - v1 - pestphp/pest (PEST) - v4 - phpunit/phpunit (PHPUNIT) - v12 - tailwindcss (TAILWINDCSS) - v4 @@ -37,7 +36,7 @@ This application is a Laravel application and its main Laravel ecosystems packag - Do not change the application's dependencies without approval. ## Frontend Bundling -- If the user doesn't see a frontend change reflected in the UI, it could mean they need to run `vendor/bin/sail yarn run build`, `vendor/bin/sail yarn run dev`, or `vendor/bin/sail composer run dev`. Ask them. +- If the user doesn't see a frontend change reflected in the UI, it could mean they need to run `yarn run build`, `yarn run dev`, or `composer run dev`. Ask them. ## Replies - Be concise in your explanations - focus on what's important rather than explaining obvious details. @@ -112,33 +111,19 @@ protected function isAccessible(User $user, ?string $path = null): bool ## Enums - Typically, keys in an Enum should be TitleCase. For example: `FavoritePerson`, `BestLake`, `Monthly`. -=== sail rules === - -## Laravel Sail - -- This project runs inside Laravel Sail's Docker containers. You MUST execute all commands through Sail. -- Start services using `vendor/bin/sail up -d` and stop them with `vendor/bin/sail stop`. -- Open the application in the browser by running `vendor/bin/sail open`. -- Always prefix PHP, Artisan, Composer, and Node commands with `vendor/bin/sail`. Examples: - - Run Artisan Commands: `vendor/bin/sail artisan migrate` - - Install Composer packages: `vendor/bin/sail composer install` - - Execute Node commands: `vendor/bin/sail yarn run dev` - - Execute PHP scripts: `vendor/bin/sail php [script]` -- View all available Sail commands by running `vendor/bin/sail` without arguments. - === tests rules === ## Test Enforcement - Every change must be programmatically tested. Write a new test or update an existing test, then run the affected tests to make sure they pass. -- Run the minimum number of tests needed to ensure code quality and speed. Use `vendor/bin/sail artisan test --compact` with a specific filename or filter. +- Run the minimum number of tests needed to ensure code quality and speed. Use `php artisan test --compact` with a specific filename or filter. === laravel/core rules === ## Do Things the Laravel Way -- Use `vendor/bin/sail artisan make:` commands to create new files (i.e. migrations, controllers, models, etc.). You can list available Artisan commands using the `list-artisan-commands` tool. -- If you're creating a generic PHP class, use `vendor/bin/sail artisan make:class`. +- Use `php artisan make:` commands to create new files (i.e. migrations, controllers, models, etc.). You can list available Artisan commands using the `list-artisan-commands` tool. +- If you're creating a generic PHP class, use `php artisan make:class`. - Pass `--no-interaction` to all Artisan commands to ensure they work without user input. You should also pass the correct `--options` to ensure correct behavior. ### Database @@ -149,7 +134,7 @@ protected function isAccessible(User $user, ?string $path = null): bool - Use Laravel's query builder for very complex database operations. ### Model Creation -- When creating new models, create useful factories and seeders for them too. Ask the user if they need any other things, using `list-artisan-commands` to check the available options to `vendor/bin/sail artisan make:model`. +- When creating new models, create useful factories and seeders for them too. Ask the user if they need any other things, using `list-artisan-commands` to check the available options to `php artisan make:model`. ### APIs & Eloquent Resources - For APIs, default to using Eloquent API Resources and API versioning unless existing API routes do not, then you should follow existing application convention. @@ -173,10 +158,10 @@ protected function isAccessible(User $user, ?string $path = null): bool ### Testing - When creating models for tests, use the factories for the models. Check if the factory has custom states that can be used before manually setting up the model. - Faker: Use methods such as `$this->faker->word()` or `fake()->randomDigit()`. Follow existing conventions whether to use `$this->faker` or `fake()`. -- When creating tests, make use of `vendor/bin/sail artisan make:test [options] {name}` to create a feature test, and pass `--unit` to create a unit test. Most tests should be feature tests. +- When creating tests, make use of `php artisan make:test [options] {name}` to create a feature test, and pass `--unit` to create a unit test. Most tests should be feature tests. ### Vite Error -- If you receive an "Illuminate\Foundation\ViteException: Unable to locate file in Vite manifest" error, you can run `vendor/bin/sail yarn run build` or ask the user to run `vendor/bin/sail yarn run dev` or `vendor/bin/sail composer run dev`. +- If you receive an "Illuminate\Foundation\ViteException: Unable to locate file in Vite manifest" error, you can run `yarn run build` or ask the user to run `yarn run dev` or `composer run dev`. === laravel/v12 rules === @@ -227,7 +212,7 @@ accordion, autocomplete, avatar, badge, brand, breadcrumbs, button, calendar, ca ## Livewire - Use the `search-docs` tool to find exact version-specific documentation for how to write Livewire and Livewire tests. -- Use the `vendor/bin/sail artisan make:livewire [Posts\CreatePost]` Artisan command to create new components. +- Use the `php artisan make:livewire [Posts\CreatePost]` Artisan command to create new components. - State should live on the server, with the UI reflecting it. - All Livewire requests hit the Laravel backend; they're like regular HTTP requests. Always validate form data and run authorization checks in Livewire actions. @@ -271,8 +256,8 @@ accordion, autocomplete, avatar, badge, brand, breadcrumbs, button, calendar, ca ## Laravel Pint Code Formatter -- You must run `vendor/bin/sail bin pint --dirty` before finalizing changes to ensure your code matches the project's expected style. -- Do not run `vendor/bin/sail bin pint --test`, simply run `vendor/bin/sail bin pint` to fix any formatting issues. +- You must run `vendor/bin/pint --dirty` before finalizing changes to ensure your code matches the project's expected style. +- Do not run `vendor/bin/pint --test`, simply run `vendor/bin/pint` to fix any formatting issues. === pest/core rules === @@ -281,7 +266,7 @@ accordion, autocomplete, avatar, badge, brand, breadcrumbs, button, calendar, ca - If you need to verify a feature is working, write or update a Unit / Feature test. ### Pest Tests -- All tests must be written using Pest. Use `vendor/bin/sail artisan make:test --pest {name}`. +- All tests must be written using Pest. Use `php artisan make:test --pest {name}`. - You must not remove any tests or test files from the tests directory without approval. These are not temporary or helper files - these are core to the application. - Tests should test all of the happy paths, failure paths, and weird paths. - Tests live in the `tests/Feature` and `tests/Unit` directories. @@ -294,9 +279,9 @@ it('is true', function () { ### Running Tests - Run the minimal number of tests using an appropriate filter before finalizing code edits. -- To run all tests: `vendor/bin/sail artisan test --compact`. -- To run all tests in a file: `vendor/bin/sail artisan test --compact tests/Feature/ExampleTest.php`. -- To filter on a particular test name: `vendor/bin/sail artisan test --compact --filter=testName` (recommended after making a change to a related file). +- To run all tests: `php artisan test --compact`. +- To run all tests in a file: `php artisan test --compact tests/Feature/ExampleTest.php`. +- To filter on a particular test name: `php artisan test --compact --filter=testName` (recommended after making a change to a related file). - When the tests relating to your changes are passing, ask the user if they would like to run the entire test suite to ensure everything is still passing. ### Pest Assertions diff --git a/README.md b/README.md index 05bc348..e304ddd 100644 --- a/README.md +++ b/README.md @@ -31,43 +31,41 @@ After setting up your CNAME, please notify the repository owner to refresh SSL c ## Development +### Prerequisites + +- PHP 8.3+ +- PostgreSQL (running locally or as a container) +- Redis (running locally or as a container) +- Node.js + Yarn + ### Installation ```cp .env.example .env``` -``` -docker run --rm \ - -u "$(id -u):$(id -g)" \ - -v $(pwd):/var/www/html \ - -w /var/www/html \ - laravelsail/php83-composer:latest \ - composer install --ignore-platform-reqs -``` +```composer install``` *(you need a valid Flux Pro license or send a message to [Nostr - The Ben](http://njump.me/npub1pt0kw36ue3w2g4haxq3wgm6a2fhtptmzsjlc2j2vphtcgle72qesgpjyc6))* -#### Start docker development containers - -```vendor/bin/sail up -d``` - ### Migrate and seed the database -```./vendor/bin/sail artisan migrate:fresh --seed``` +```php artisan migrate:fresh --seed``` ### Laravel storage link -```./vendor/bin/sail artisan storage:link``` +```php artisan storage:link``` #### Install node dependencies -```vendor/bin/sail yarn``` +```yarn``` -#### Start just in time compiler +#### Start development environment -```vendor/bin/sail yarn dev``` +```composer run dev``` + +This starts the PHP dev server, queue worker, Pail log viewer, and Vite concurrently. #### Update dependencies -```vendor/bin/sail yarn``` +```yarn``` ## Security Vulnerabilities diff --git a/app/Livewire/Forms/ServiceForm.php b/app/Livewire/Forms/ServiceForm.php index b507352..69618c1 100644 --- a/app/Livewire/Forms/ServiceForm.php +++ b/app/Livewire/Forms/ServiceForm.php @@ -4,11 +4,13 @@ namespace App\Livewire\Forms; use App\Enums\SelfHostedServiceType; use App\Models\SelfHostedService; +use Livewire\Attributes\Locked; use Livewire\Attributes\Validate; use Livewire\Form; class ServiceForm extends Form { + #[Locked] public ?SelfHostedService $service = null; #[Validate('required|string|max:255')] @@ -47,7 +49,7 @@ class ServiceForm extends Form 'name' => ['required', 'string', 'max:255'], 'type' => [ 'required', - 'in:' . collect(SelfHostedServiceType::cases())->map(fn($c) => $c->value)->implode(',') + 'in:'.collect(SelfHostedServiceType::cases())->map(fn ($c) => $c->value)->implode(','), ], 'intro' => ['nullable', 'string'], 'url_clearnet' => ['nullable', 'url', 'max:255'], diff --git a/boost.json b/boost.json index 7304ffe..9369faa 100644 --- a/boost.json +++ b/boost.json @@ -9,6 +9,5 @@ "opencode", "phpstorm" ], - "guidelines": [], - "sail": true + "guidelines": [] } diff --git a/composer.json b/composer.json index 01a1c65..f4ca2e7 100644 --- a/composer.json +++ b/composer.json @@ -48,7 +48,6 @@ "laravel/boost": "^1.8", "laravel/pail": "^1.2.2", "laravel/pint": "^1.18", - "laravel/sail": "^1.43", "mockery/mockery": "^1.6", "nunomaduro/collision": "^8.6", "pestphp/pest": "^4.3", @@ -89,7 +88,7 @@ ], "dev": [ "Composer\\Config::disableProcessTimeout", - "npx concurrently -c \"#c4b5fd,#fb7185,#fdba74\" \"php artisan queue:listen --tries=1\" \"php artisan pail --timeout=0\" \"npm run dev\" --names=server,queue,logs,vite --kill-others" + "npx concurrently -c \"#93c5fd,#c4b5fd,#fb7185,#fdba74\" \"php artisan serve\" \"php artisan queue:listen --tries=1\" \"php artisan pail --timeout=0\" \"npm run dev\" --names=server,queue,logs,vite --kill-others" ], "test": [ "@php artisan config:clear --ansi", diff --git a/composer.lock b/composer.lock index 6c9df3b..d298b0f 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "46e95f6250487f6e4f947386804ca205", + "content-hash": "ad826718b1e72bed521d15e12e5ac92f", "packages": [ { "name": "akuechler/laravel-geoly", @@ -12113,69 +12113,6 @@ }, "time": "2025-10-20T09:56:46+00:00" }, - { - "name": "laravel/sail", - "version": "v1.52.0", - "source": { - "type": "git", - "url": "https://github.com/laravel/sail.git", - "reference": "64ac7d8abb2dbcf2b76e61289451bae79066b0b3" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/laravel/sail/zipball/64ac7d8abb2dbcf2b76e61289451bae79066b0b3", - "reference": "64ac7d8abb2dbcf2b76e61289451bae79066b0b3", - "shasum": "" - }, - "require": { - "illuminate/console": "^9.52.16|^10.0|^11.0|^12.0", - "illuminate/contracts": "^9.52.16|^10.0|^11.0|^12.0", - "illuminate/support": "^9.52.16|^10.0|^11.0|^12.0", - "php": "^8.0", - "symfony/console": "^6.0|^7.0", - "symfony/yaml": "^6.0|^7.0" - }, - "require-dev": { - "orchestra/testbench": "^7.0|^8.0|^9.0|^10.0", - "phpstan/phpstan": "^2.0" - }, - "bin": [ - "bin/sail" - ], - "type": "library", - "extra": { - "laravel": { - "providers": [ - "Laravel\\Sail\\SailServiceProvider" - ] - } - }, - "autoload": { - "psr-4": { - "Laravel\\Sail\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Taylor Otwell", - "email": "taylor@laravel.com" - } - ], - "description": "Docker files for running a basic Laravel application.", - "keywords": [ - "docker", - "laravel" - ], - "support": { - "issues": "https://github.com/laravel/sail/issues", - "source": "https://github.com/laravel/sail" - }, - "time": "2026-01-01T02:46:03+00:00" - }, { "name": "mockery/mockery", "version": "1.6.12", diff --git a/docker-compose.yml b/docker-compose.yml deleted file mode 100644 index ba62c7e..0000000 --- a/docker-compose.yml +++ /dev/null @@ -1,82 +0,0 @@ -services: - laravel.test: - build: - context: './docker/8.3' - dockerfile: Dockerfile - args: - WWWGROUP: '${WWWGROUP}' - image: 'sail-8.3/einundzwanzig-app' - extra_hosts: - - 'host.docker.internal:host-gateway' - ports: - - '${APP_PORT:-80}:80' - - '${VITE_PORT:-5173}:${VITE_PORT:-5173}' - environment: - WWWUSER: '${WWWUSER}' - LARAVEL_SAIL: 1 - XDEBUG_MODE: '${SAIL_XDEBUG_MODE:-off}' - XDEBUG_CONFIG: '${SAIL_XDEBUG_CONFIG:-client_host=host.docker.internal}' - IGNITION_LOCAL_SITES_PATH: '${PWD}' - volumes: - - '.:/var/www/html' - networks: - - sail - depends_on: - - pgsql - - redis - - mailpit - pgsql: - image: 'postgres:17' - ports: - - '${FORWARD_DB_PORT:-5432}:5432' - environment: - PGPASSWORD: '${DB_PASSWORD:-secret}' - POSTGRES_DB: '${DB_DATABASE}' - POSTGRES_USER: '${DB_USERNAME}' - POSTGRES_PASSWORD: '${DB_PASSWORD:-secret}' - volumes: - - 'sail-pgsql:/var/lib/postgresql/data' - - './docker/pgsql/create-testing-database.sql:/docker-entrypoint-initdb.d/10-create-testing-database.sql' - networks: - - sail - healthcheck: - test: - - CMD - - pg_isready - - '-q' - - '-d' - - '${DB_DATABASE}' - - '-U' - - '${DB_USERNAME}' - retries: 3 - timeout: 5s - redis: - image: 'redis:alpine' - ports: - - '${FORWARD_REDIS_PORT:-6379}:6379' - volumes: - - 'sail-redis:/data' - networks: - - sail - healthcheck: - test: - - CMD - - redis-cli - - ping - retries: 3 - timeout: 5s - mailpit: - image: 'axllent/mailpit:latest' - ports: - - '${FORWARD_MAILPIT_PORT:-1025}:1025' - - '${FORWARD_MAILPIT_DASHBOARD_PORT:-8025}:8025' - networks: - - sail -networks: - sail: - driver: bridge -volumes: - sail-pgsql: - driver: local - sail-redis: - driver: local diff --git a/docker/8.3/Dockerfile b/docker/8.3/Dockerfile deleted file mode 100644 index d3f515b..0000000 --- a/docker/8.3/Dockerfile +++ /dev/null @@ -1,75 +0,0 @@ -FROM ubuntu:24.04 - -LABEL maintainer="Taylor Otwell" - -ARG WWWGROUP -ARG NODE_VERSION=24 -ARG MYSQL_CLIENT="mysql-client" -ARG POSTGRES_VERSION=18 - -WORKDIR /var/www/html - -ENV DEBIAN_FRONTEND=noninteractive -ENV TZ=UTC -ENV SUPERVISOR_PHP_COMMAND="/usr/bin/php -d variables_order=EGPCS /var/www/html/artisan serve --host=0.0.0.0 --port=80" -ENV SUPERVISOR_PHP_USER="sail" -ENV PLAYWRIGHT_BROWSERS_PATH=0 - -RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone - -RUN echo "Acquire::http::Pipeline-Depth 0;" > /etc/apt/apt.conf.d/99custom && \ - echo "Acquire::http::No-Cache true;" >> /etc/apt/apt.conf.d/99custom && \ - echo "Acquire::BrokenProxy true;" >> /etc/apt/apt.conf.d/99custom - -RUN apt-get update && apt-get upgrade -y \ - && mkdir -p /etc/apt/keyrings \ - && apt-get install -y gnupg gosu curl ca-certificates zip unzip git supervisor sqlite3 libcap2-bin libpng-dev python3 dnsutils librsvg2-bin fswatch ffmpeg nano \ - && curl -sS 'https://keyserver.ubuntu.com/pks/lookup?op=get&search=0xb8dc7e53946656efbce4c1dd71daeaab4ad4cab6' | gpg --dearmor | tee /etc/apt/keyrings/ppa_ondrej_php.gpg > /dev/null \ - && echo "deb [signed-by=/etc/apt/keyrings/ppa_ondrej_php.gpg] https://ppa.launchpadcontent.net/ondrej/php/ubuntu noble main" > /etc/apt/sources.list.d/ppa_ondrej_php.list \ - && apt-get update \ - && apt-get install -y php8.3-cli php8.3-dev \ - php8.3-pgsql php8.3-sqlite3 php8.3-gd \ - php8.3-curl php8.3-mongodb \ - php8.3-imap php8.3-mysql php8.3-mbstring \ - php8.3-xml php8.3-zip php8.3-bcmath php8.3-soap \ - php8.3-intl php8.3-readline \ - php8.3-ldap \ - php8.3-gmp \ - php8.3-msgpack php8.3-igbinary php8.3-redis \ - php8.3-memcached php8.3-pcov php8.3-imagick php8.3-xdebug php8.3-swoole \ - && curl -sLS https://getcomposer.org/installer | php -- --install-dir=/usr/bin/ --filename=composer \ - && curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key | gpg --dearmor -o /etc/apt/keyrings/nodesource.gpg \ - && echo "deb [signed-by=/etc/apt/keyrings/nodesource.gpg] https://deb.nodesource.com/node_$NODE_VERSION.x nodistro main" > /etc/apt/sources.list.d/nodesource.list \ - && apt-get update \ - && apt-get install -y nodejs \ - && npm install -g npm \ - && npm install -g pnpm \ - && npm install -g bun \ - && npx playwright install-deps \ - && curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | gpg --dearmor | tee /etc/apt/keyrings/yarn.gpg >/dev/null \ - && echo "deb [signed-by=/etc/apt/keyrings/yarn.gpg] https://dl.yarnpkg.com/debian/ stable main" > /etc/apt/sources.list.d/yarn.list \ - && curl -sS https://www.postgresql.org/media/keys/ACCC4CF8.asc | gpg --dearmor | tee /etc/apt/keyrings/pgdg.gpg >/dev/null \ - && echo "deb [signed-by=/etc/apt/keyrings/pgdg.gpg] http://apt.postgresql.org/pub/repos/apt noble-pgdg main" > /etc/apt/sources.list.d/pgdg.list \ - && apt-get update \ - && apt-get install -y yarn \ - && apt-get install -y $MYSQL_CLIENT \ - && apt-get install -y postgresql-client-$POSTGRES_VERSION \ - && apt-get -y autoremove \ - && apt-get clean \ - && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* - -RUN setcap "cap_net_bind_service=+ep" /usr/bin/php8.3 - -RUN userdel -r ubuntu -RUN groupadd --force -g $WWWGROUP sail -RUN useradd -ms /bin/bash --no-user-group -g $WWWGROUP -u 1337 sail -RUN git config --global --add safe.directory /var/www/html - -COPY start-container /usr/local/bin/start-container -COPY supervisord.conf /etc/supervisor/conf.d/supervisord.conf -COPY php.ini /etc/php/8.3/cli/conf.d/99-sail.ini -RUN chmod +x /usr/local/bin/start-container - -EXPOSE 80/tcp - -ENTRYPOINT ["start-container"] diff --git a/docker/8.3/php.ini b/docker/8.3/php.ini deleted file mode 100644 index 0d8ce9e..0000000 --- a/docker/8.3/php.ini +++ /dev/null @@ -1,5 +0,0 @@ -[PHP] -post_max_size = 100M -upload_max_filesize = 100M -variables_order = EGPCS -pcov.directory = . diff --git a/docker/8.3/start-container b/docker/8.3/start-container deleted file mode 100644 index 40c55df..0000000 --- a/docker/8.3/start-container +++ /dev/null @@ -1,26 +0,0 @@ -#!/usr/bin/env bash - -if [ "$SUPERVISOR_PHP_USER" != "root" ] && [ "$SUPERVISOR_PHP_USER" != "sail" ]; then - echo "You should set SUPERVISOR_PHP_USER to either 'sail' or 'root'." - exit 1 -fi - -if [ ! -z "$WWWUSER" ]; then - usermod -u $WWWUSER sail -fi - -if [ ! -d /.composer ]; then - mkdir /.composer -fi - -chmod -R ugo+rw /.composer - -if [ $# -gt 0 ]; then - if [ "$SUPERVISOR_PHP_USER" = "root" ]; then - exec "$@" - else - exec gosu $WWWUSER "$@" - fi -else - exec /usr/bin/supervisord -c /etc/supervisor/conf.d/supervisord.conf -fi diff --git a/docker/8.3/supervisord.conf b/docker/8.3/supervisord.conf deleted file mode 100644 index 656da8a..0000000 --- a/docker/8.3/supervisord.conf +++ /dev/null @@ -1,14 +0,0 @@ -[supervisord] -nodaemon=true -user=root -logfile=/var/log/supervisor/supervisord.log -pidfile=/var/run/supervisord.pid - -[program:php] -command=%(ENV_SUPERVISOR_PHP_COMMAND)s -user=%(ENV_SUPERVISOR_PHP_USER)s -environment=LARAVEL_SAIL="1" -stdout_logfile=/dev/stdout -stdout_logfile_maxbytes=0 -stderr_logfile=/dev/stderr -stderr_logfile_maxbytes=0 diff --git a/docker/pgsql/create-testing-database.sql b/docker/pgsql/create-testing-database.sql deleted file mode 100644 index d84dc07..0000000 --- a/docker/pgsql/create-testing-database.sql +++ /dev/null @@ -1,2 +0,0 @@ -SELECT 'CREATE DATABASE testing' -WHERE NOT EXISTS (SELECT FROM pg_database WHERE datname = 'testing')\gexec diff --git a/opencode.json b/opencode.json index 6f800ed..53e16f3 100644 --- a/opencode.json +++ b/opencode.json @@ -5,7 +5,7 @@ "type": "local", "enabled": true, "command": [ - "vendor/bin/sail", + "php", "artisan", "boost:mcp" ] diff --git a/phpunit.xml b/phpunit.xml index 6fe7020..bc02390 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -25,7 +25,7 @@ - + diff --git a/resources/views/livewire/courses/create-edit-events.blade.php b/resources/views/livewire/courses/create-edit-events.blade.php index 01f4bac..6c0b4da 100644 --- a/resources/views/livewire/courses/create-edit-events.blade.php +++ b/resources/views/livewire/courses/create-edit-events.blade.php @@ -6,6 +6,7 @@ use App\Models\Course; use App\Models\CourseEvent; use App\Models\Venue; use App\Traits\SeoTrait; +use Livewire\Attributes\Locked; use Livewire\Attributes\Validate; use Livewire\Component; @@ -17,6 +18,7 @@ class extends Component { public Course $course; public ?CourseEvent $event = null; + #[Locked] public $country = 'de'; public string $fromDate = ''; diff --git a/resources/views/livewire/courses/edit.blade.php b/resources/views/livewire/courses/edit.blade.php index 3af8ff5..6e88904 100644 --- a/resources/views/livewire/courses/edit.blade.php +++ b/resources/views/livewire/courses/edit.blade.php @@ -5,6 +5,7 @@ use App\Models\Course; use App\Models\Lecturer; use App\Traits\SeoTrait; use Illuminate\Validation\Rule; +use Livewire\Attributes\Locked; use Livewire\Attributes\Validate; use Livewire\Component; use Livewire\WithFileUploads; @@ -25,9 +26,14 @@ class extends Component { public ?int $lecturer_id = null; public ?string $description = null; - // System fields (read-only) + // System fields (read-only) - locked to prevent client-side tampering + #[Locked] public ?int $created_by = null; + + #[Locked] public ?string $created_at = null; + + #[Locked] public ?string $updated_at = null; public function mount(): void diff --git a/resources/views/livewire/lecturers/edit.blade.php b/resources/views/livewire/lecturers/edit.blade.php index dbc3948..f9fffbf 100644 --- a/resources/views/livewire/lecturers/edit.blade.php +++ b/resources/views/livewire/lecturers/edit.blade.php @@ -4,6 +4,7 @@ use App\Attributes\SeoDataAttribute; use App\Models\Lecturer; use App\Traits\SeoTrait; use Illuminate\Validation\Rule; +use Livewire\Attributes\Locked; use Livewire\Attributes\Validate; use Livewire\Component; use Livewire\WithFileUploads; @@ -34,9 +35,14 @@ class extends Component { public ?string $node_id = null; public ?string $paynym = null; - // System fields (read-only) + // System fields (read-only) - locked to prevent client-side tampering + #[Locked] public ?int $created_by = null; + + #[Locked] public ?string $created_at = null; + + #[Locked] public ?string $updated_at = null; public function mount(): void diff --git a/resources/views/livewire/meetups/create-edit-events.blade.php b/resources/views/livewire/meetups/create-edit-events.blade.php index a907084..8a14985 100644 --- a/resources/views/livewire/meetups/create-edit-events.blade.php +++ b/resources/views/livewire/meetups/create-edit-events.blade.php @@ -5,6 +5,7 @@ use App\Enums\RecurrenceType; use App\Models\Meetup; use App\Models\MeetupEvent; use App\Traits\SeoTrait; +use Livewire\Attributes\Locked; use Livewire\Attributes\Validate; use Livewire\Component; @@ -16,6 +17,7 @@ class extends Component { public Meetup $meetup; public ?MeetupEvent $event = null; + #[Locked] public $country = 'de'; public string $startDate = ''; diff --git a/resources/views/livewire/meetups/edit.blade.php b/resources/views/livewire/meetups/edit.blade.php index 493d4d1..a259290 100644 --- a/resources/views/livewire/meetups/edit.blade.php +++ b/resources/views/livewire/meetups/edit.blade.php @@ -6,6 +6,7 @@ use App\Models\Country; use App\Models\Meetup; use App\Traits\SeoTrait; use Illuminate\Validation\Rule; +use Livewire\Attributes\Locked; use Livewire\Attributes\Validate; use Livewire\Component; use Livewire\WithFileUploads; @@ -42,9 +43,14 @@ class extends Component { public ?string $github_data = null; public bool $visible_on_map = false; - // System fields (read-only) + // System fields (read-only) - locked to prevent client-side tampering + #[Locked] public ?int $created_by = null; + + #[Locked] public ?string $created_at = null; + + #[Locked] public ?string $updated_at = null; // New City Modal diff --git a/resources/views/livewire/services/create.blade.php b/resources/views/livewire/services/create.blade.php index 6398b5c..879a44e 100644 --- a/resources/views/livewire/services/create.blade.php +++ b/resources/views/livewire/services/create.blade.php @@ -4,6 +4,7 @@ use App\Attributes\SeoDataAttribute; use App\Enums\SelfHostedServiceType; use App\Livewire\Forms\ServiceForm; use App\Traits\SeoTrait; +use Livewire\Attributes\Locked; use Livewire\Attributes\Validate; use Livewire\Component; use Livewire\WithFileUploads; @@ -13,7 +14,9 @@ new class extends Component { use SeoTrait; + #[Locked] public string $country = 'de'; + public ServiceForm $form; public function mount(): void diff --git a/tests/Feature/LivewireLockedPropertiesTest.php b/tests/Feature/LivewireLockedPropertiesTest.php new file mode 100644 index 0000000..c8fbbab --- /dev/null +++ b/tests/Feature/LivewireLockedPropertiesTest.php @@ -0,0 +1,132 @@ +user = User::factory()->create(['timezone' => 'Europe/Berlin']); + $this->country = Country::factory()->create(); + $this->city = City::factory()->for($this->country)->create(); +}); + +describe('Meetup Edit Component', function () { + beforeEach(function () { + $this->meetup = Meetup::factory()->for($this->city)->create([ + 'created_by' => $this->user->id, + ]); + }); + + it('loads meetup edit page correctly with locked properties', function () { + Livewire::actingAs($this->user) + ->test('meetups.edit', ['meetup' => $this->meetup]) + ->assertSet('created_by', $this->meetup->created_by) + ->assertSet('created_at', $this->meetup->created_at->format('Y-m-d H:i:s')) + ->assertSet('updated_at', $this->meetup->updated_at->format('Y-m-d H:i:s')); + }); + + it('throws exception when tampering with locked created_by property', function () { + $this->expectException(CannotUpdateLockedPropertyException::class); + + Livewire::actingAs($this->user) + ->test('meetups.edit', ['meetup' => $this->meetup]) + ->set('created_by', 999); + }); + + it('throws exception when tampering with locked created_at property', function () { + $this->expectException(CannotUpdateLockedPropertyException::class); + + Livewire::actingAs($this->user) + ->test('meetups.edit', ['meetup' => $this->meetup]) + ->set('created_at', '2020-01-01 00:00:00'); + }); + + it('throws exception when tampering with locked updated_at property', function () { + $this->expectException(CannotUpdateLockedPropertyException::class); + + Livewire::actingAs($this->user) + ->test('meetups.edit', ['meetup' => $this->meetup]) + ->set('updated_at', '2020-01-01 00:00:00'); + }); + + it('can still update non-locked properties', function () { + Livewire::actingAs($this->user) + ->test('meetups.edit', ['meetup' => $this->meetup]) + ->set('name', 'Updated Meetup Name') + ->set('community', 'einundzwanzig') + ->call('updateMeetup') + ->assertHasNoErrors(); + + $this->meetup->refresh(); + expect($this->meetup->name)->toBe('Updated Meetup Name'); + }); +}); + +describe('Meetup Create-Edit Events Component', function () { + beforeEach(function () { + $this->meetup = Meetup::factory()->for($this->city)->create(); + }); + + it('has locked country property', function () { + Livewire::actingAs($this->user) + ->test('meetups.create-edit-events', ['meetup' => $this->meetup]) + ->assertSet('country', 'de'); + }); + + it('throws exception when tampering with locked country property', function () { + $this->expectException(CannotUpdateLockedPropertyException::class); + + Livewire::actingAs($this->user) + ->test('meetups.create-edit-events', ['meetup' => $this->meetup]) + ->set('country', 'us'); + }); +}); + +describe('Services Create Component', function () { + it('has locked country property', function () { + Livewire::actingAs($this->user) + ->test('services.create') + ->assertSet('country', 'de'); + }); + + it('throws exception when tampering with locked country property', function () { + $this->expectException(CannotUpdateLockedPropertyException::class); + + Livewire::actingAs($this->user) + ->test('services.create') + ->set('country', 'us'); + }); +}); + +describe('ServiceForm Locked Properties', function () { + beforeEach(function () { + // Create service with the current user as creator + $this->service = SelfHostedService::factory()->create([ + 'created_by' => $this->user->id, + 'anon' => false, + ]); + }); + + it('has locked service property in edit component', function () { + Livewire::actingAs($this->user) + ->test('services.edit', ['service' => $this->service]) + ->assertSet('form.service.id', $this->service->id); + }); + + it('throws exception when tampering with locked service model in form', function () { + $anotherService = SelfHostedService::factory()->create([ + 'created_by' => $this->user->id, + 'anon' => false, + ]); + + $this->expectException(CannotUpdateLockedPropertyException::class); + + Livewire::actingAs($this->user) + ->test('services.edit', ['service' => $this->service]) + ->set('form.service', $anotherService); + }); +});