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);
+ });
+});