Laravel 12.x Shift (#1045)

* Convert route options to fluent methods

Laravel 8 adopts the tuple syntax for controller actions. Since the old options array is incompatible with this syntax, Shift converted them to use modern, fluent methods.

* Slim `lang` files

* Shift core files

* Validate via object directly within Controllers

* Use `Gate` facade for controller authorization

* Dispatch jobs directly

* Remove base controller inheritance

* Default config files

In an effort to make upgrading the constantly changing config files easier, Shift defaulted them and merged your true customizations - where ENV variables may not be used.

* Set new `ENV` variables

* Add new Laravel `composer run dev` script

* Add `storage/app/private` folder

* Bump Composer dependencies

* Convert `$casts` property to method

* Adopt Laravel type hints

* Shift cleanup

* Apply suggestions from code review

Co-authored-by: MartinOscar <40749467+rmartinoscar@users.noreply.github.com>

* Add old key as backup

* Update composer

* Remove extra line

* Update this

---------

Co-authored-by: Shift <shift@laravelshift.com>
Co-authored-by: MartinOscar <40749467+rmartinoscar@users.noreply.github.com>
This commit is contained in:
Lance Pioch 2025-03-03 14:41:00 -05:00 committed by GitHub
parent 839be53231
commit 82409f2fba
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
27 changed files with 860 additions and 671 deletions

2
.gitignore vendored
View File

@ -4,6 +4,7 @@
/public/hot /public/hot
/public/storage /public/storage
/storage/*.key /storage/*.key
/storage/pail
/storage/clockwork/* /storage/clockwork/*
/vendor /vendor
*.DS_Store* *.DS_Store*
@ -19,6 +20,7 @@ npm-debug.log
yarn-error.log yarn-error.log
/.fleet /.fleet
/.idea /.idea
/.nova
/.vscode /.vscode
public/assets/manifest.json public/assets/manifest.json

View File

@ -91,7 +91,7 @@ class EmailSettingsCommand extends Command
trans('command/messages.environment.mail.ask_smtp_password') trans('command/messages.environment.mail.ask_smtp_password')
); );
$this->variables['MAIL_ENCRYPTION'] = $this->option('encryption') ?? $this->choice( $this->variables['MAIL_SCHEME'] = $this->option('encryption') ?? $this->choice(
trans('command/messages.environment.mail.ask_encryption'), trans('command/messages.environment.mail.ask_encryption'),
['tls' => 'TLS', 'ssl' => 'SSL', '' => 'None'], ['tls' => 'TLS', 'ssl' => 'SSL', '' => 'None'],
config('mail.mailers.smtp.encryption', 'tls') config('mail.mailers.smtp.encryption', 'tls')

View File

@ -303,7 +303,7 @@ class Settings extends Page implements HasForms
'mail.mailers.smtp.port' => $get('MAIL_PORT'), 'mail.mailers.smtp.port' => $get('MAIL_PORT'),
'mail.mailers.smtp.username' => $get('MAIL_USERNAME'), 'mail.mailers.smtp.username' => $get('MAIL_USERNAME'),
'mail.mailers.smtp.password' => $get('MAIL_PASSWORD'), 'mail.mailers.smtp.password' => $get('MAIL_PASSWORD'),
'mail.mailers.smtp.encryption' => $get('MAIL_ENCRYPTION'), 'mail.mailers.smtp.encryption' => $get('MAIL_SCHEME'),
'mail.from.address' => $get('MAIL_FROM_ADDRESS'), 'mail.from.address' => $get('MAIL_FROM_ADDRESS'),
'mail.from.name' => $get('MAIL_FROM_NAME'), 'mail.from.name' => $get('MAIL_FROM_NAME'),
'services.mailgun.domain' => $get('MAILGUN_DOMAIN'), 'services.mailgun.domain' => $get('MAILGUN_DOMAIN'),
@ -366,7 +366,7 @@ class Settings extends Page implements HasForms
->password() ->password()
->revealable() ->revealable()
->default(env('MAIL_PASSWORD')), ->default(env('MAIL_PASSWORD')),
ToggleButtons::make('MAIL_ENCRYPTION') ToggleButtons::make('MAIL_SCHEME')
->label(trans('admin/setting.mail.smtp.encryption')) ->label(trans('admin/setting.mail.smtp.encryption'))
->inline() ->inline()
->options([ ->options([
@ -374,7 +374,7 @@ class Settings extends Page implements HasForms
'ssl' => trans('admin/setting.mail.smtp.ssl'), 'ssl' => trans('admin/setting.mail.smtp.ssl'),
'' => trans('admin/setting.mail.smtp.none'), '' => trans('admin/setting.mail.smtp.none'),
]) ])
->default(env('MAIL_ENCRYPTION', config('mail.mailers.smtp.encryption', 'tls'))) ->default(env('MAIL_SCHEME', config('mail.mailers.smtp.encryption', 'tls')))
->live() ->live()
->afterStateUpdated(function ($state, Set $set) { ->afterStateUpdated(function ($state, Set $set) {
$port = match ($state) { $port = match ($state) {

View File

@ -53,7 +53,7 @@ class SSHKeyController extends ClientApiController
*/ */
public function delete(ClientApiRequest $request): JsonResponse public function delete(ClientApiRequest $request): JsonResponse
{ {
$this->validate($request, ['fingerprint' => ['required', 'string']]); $request->validate(['fingerprint' => ['required', 'string']]);
$key = $request->user()->sshKeys() $key = $request->user()->sshKeys()
->where('fingerprint', $request->input('fingerprint')) ->where('fingerprint', $request->input('fingerprint'))

View File

@ -2,6 +2,7 @@
namespace App\Http\Controllers\Api\Client\Servers; namespace App\Http\Controllers\Api\Client\Servers;
use Illuminate\Support\Facades\Gate;
use App\Models\User; use App\Models\User;
use App\Models\Server; use App\Models\Server;
use App\Models\Permission; use App\Models\Permission;
@ -26,7 +27,7 @@ class ActivityLogController extends ClientApiController
*/ */
public function __invoke(ClientApiRequest $request, Server $server): array public function __invoke(ClientApiRequest $request, Server $server): array
{ {
$this->authorize(Permission::ACTION_ACTIVITY_READ, $server); Gate::authorize(Permission::ACTION_ACTIVITY_READ, $server);
$activity = QueryBuilder::for($server->activity()) $activity = QueryBuilder::for($server->activity())
->allowedSorts(['timestamp']) ->allowedSorts(['timestamp'])

View File

@ -67,7 +67,7 @@ class StartupController extends ClientApiController
$original = $variable->server_value; $original = $variable->server_value;
// Revalidate the variable value using the egg variable specific validation rules for it. // Revalidate the variable value using the egg variable specific validation rules for it.
$this->validate($request, ['value' => $variable->rules]); $request->validate(['value' => $variable->rules]);
ServerVariable::query()->updateOrCreate([ ServerVariable::query()->updateOrCreate([
'server_id' => $server->id, 'server_id' => $server->id,

View File

@ -2,14 +2,4 @@
namespace App\Http\Controllers; namespace App\Http\Controllers;
use Illuminate\Foundation\Bus\DispatchesJobs; abstract class Controller {}
use Illuminate\Routing\Controller as BaseController;
use Illuminate\Foundation\Validation\ValidatesRequests;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
abstract class Controller extends BaseController
{
use AuthorizesRequests;
use DispatchesJobs;
use ValidatesRequests;
}

View File

@ -8,7 +8,6 @@ use App\Models\Task;
use Illuminate\Queue\SerializesModels; use Illuminate\Queue\SerializesModels;
use Illuminate\Queue\InteractsWithQueue; use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\DispatchesJobs;
use App\Services\Backups\InitiateBackupService; use App\Services\Backups\InitiateBackupService;
use App\Repositories\Daemon\DaemonPowerRepository; use App\Repositories\Daemon\DaemonPowerRepository;
use App\Services\Files\DeleteFilesService; use App\Services\Files\DeleteFilesService;
@ -17,7 +16,6 @@ use Illuminate\Http\Client\ConnectionException;
class RunTaskJob extends Job implements ShouldQueue class RunTaskJob extends Job implements ShouldQueue
{ {
use DispatchesJobs;
use InteractsWithQueue; use InteractsWithQueue;
use SerializesModels; use SerializesModels;
@ -113,7 +111,7 @@ class RunTaskJob extends Job implements ShouldQueue
$nextTask->update(['is_queued' => true]); $nextTask->update(['is_queued' => true]);
$this->dispatch((new self($nextTask, $this->manualRun))->delay($nextTask->time_offset)); dispatch((new self($nextTask, $this->manualRun))->delay($nextTask->time_offset));
} }
/** /**

View File

@ -29,9 +29,12 @@ class RecoveryToken extends Model implements Validatable
'token' => 'required|string', 'token' => 'required|string',
]; ];
protected $casts = [ protected function casts(): array
'created_at' => 'immutable_datetime', {
]; return [
'created_at' => 'immutable_datetime',
];
}
public function user(): BelongsTo public function user(): BelongsTo
{ {

View File

@ -1,6 +1,7 @@
#!/usr/bin/env php #!/usr/bin/env php
<?php <?php
use Illuminate\Foundation\Application;
use Symfony\Component\Console\Input\ArgvInput; use Symfony\Component\Console\Input\ArgvInput;
define('LARAVEL_START', microtime(true)); define('LARAVEL_START', microtime(true));
@ -9,7 +10,9 @@ define('LARAVEL_START', microtime(true));
require __DIR__.'/vendor/autoload.php'; require __DIR__.'/vendor/autoload.php';
// Bootstrap Laravel and handle the command... // Bootstrap Laravel and handle the command...
$status = (require_once __DIR__.'/bootstrap/app.php') /** @var Application $app */
->handleCommand(new ArgvInput); $app = require_once __DIR__.'/bootstrap/app.php';
$status = $app->handleCommand(new ArgvInput);
exit($status); exit($status);

View File

@ -15,7 +15,7 @@ x-common:
# MAIL_FROM: "" # MAIL_FROM: ""
# MAIL_USERNAME: "" # MAIL_USERNAME: ""
# MAIL_PASSWORD: "" # MAIL_PASSWORD: ""
# MAIL_ENCRYPTION: "" # MAIL_SCHEME: ""
# #
# ------------------------------------------------------------------------------------------ # ------------------------------------------------------------------------------------------

View File

@ -15,14 +15,14 @@
"coderflex/filament-turnstile": "^2.2", "coderflex/filament-turnstile": "^2.2",
"dedoc/scramble": "^0.12.10", "dedoc/scramble": "^0.12.10",
"doctrine/dbal": "~3.6.0", "doctrine/dbal": "~3.6.0",
"filament/filament": "^3.2", "filament/filament": "^3.3",
"guzzlehttp/guzzle": "^7.8.1", "guzzlehttp/guzzle": "^7.8.1",
"laravel/framework": "^11.7", "laravel/framework": "^12.0",
"laravel/helpers": "^1.7", "laravel/helpers": "^1.7",
"laravel/sanctum": "^4.0.2", "laravel/sanctum": "^4.0.2",
"laravel/socialite": "^5.14", "laravel/socialite": "^5.17",
"laravel/tinker": "^2.9", "laravel/tinker": "^2.10.1",
"laravel/ui": "^4.5.1", "laravel/ui": "^4.6",
"lcobucci/jwt": "~4.3.0", "lcobucci/jwt": "~4.3.0",
"league/flysystem-aws-s3-v3": "~3.12.2", "league/flysystem-aws-s3-v3": "~3.12.2",
"league/flysystem-memory": "~3.10.3", "league/flysystem-memory": "~3.10.3",
@ -34,28 +34,29 @@
"socialiteproviders/authentik": "^5.2", "socialiteproviders/authentik": "^5.2",
"socialiteproviders/discord": "^4.2", "socialiteproviders/discord": "^4.2",
"socialiteproviders/steam": "^4.2", "socialiteproviders/steam": "^4.2",
"spatie/laravel-fractal": "^6.2", "spatie/laravel-fractal": "^6.3",
"spatie/laravel-health": "^1.30", "spatie/laravel-health": "^1.32",
"spatie/laravel-permission": "^6.9", "spatie/laravel-permission": "^6.12",
"spatie/laravel-query-builder": "^5.8.1", "spatie/laravel-query-builder": "^6.3",
"spatie/temporary-directory": "^2.2", "spatie/temporary-directory": "^2.2",
"symfony/http-client": "^7.1", "symfony/http-client": "^7.2",
"symfony/mailgun-mailer": "^7.1", "symfony/mailgun-mailer": "^7.2",
"symfony/postmark-mailer": "^7.0.7", "symfony/postmark-mailer": "^7.2",
"symfony/yaml": "^7.0.7", "symfony/yaml": "^7.2",
"webbingbrasil/filament-copyactions": "^3.0.1", "webbingbrasil/filament-copyactions": "^3.0.1",
"webmozart/assert": "~1.11.0" "webmozart/assert": "~1.11.0"
}, },
"require-dev": { "require-dev": {
"barryvdh/laravel-ide-helper": "^3.0", "barryvdh/laravel-ide-helper": "^3.5",
"fakerphp/faker": "^1.23.1", "fakerphp/faker": "^1.23.1",
"larastan/larastan": "^3.0", "larastan/larastan": "^3.1",
"laravel/pint": "^1.15.3", "laravel/pint": "^1.15.3",
"laravel/sail": "^1.29.1", "laravel/sail": "^1.41",
"mockery/mockery": "^1.6.11", "mockery/mockery": "^1.6.11",
"nunomaduro/collision": "^8.1.1", "nunomaduro/collision": "^8.6",
"pestphp/pest": "^3.7", "pestphp/pest": "^3.7",
"spatie/laravel-ignition": "^2.7" "spatie/laravel-ignition": "^2.9",
"laravel/pail": "^1.2.2"
}, },
"autoload": { "autoload": {
"files": [ "files": [
@ -84,6 +85,10 @@
], ],
"post-create-project-cmd": [ "post-create-project-cmd": [
"@php artisan key:generate --ansi" "@php artisan key:generate --ansi"
],
"dev": [
"Composer\\Config::disableProcessTimeout",
"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"
] ]
}, },
"config": { "config": {

1120
composer.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -1,5 +1,7 @@
<?php <?php
use Illuminate\Support\Str;
$database = env('DB_DATABASE', 'database.sqlite'); $database = env('DB_DATABASE', 'database.sqlite');
$datapasePath = database_path($database); $datapasePath = database_path($database);
@ -9,15 +11,42 @@ if (str_starts_with($database, '/') || $database === ':memory:') {
return [ return [
/*
|--------------------------------------------------------------------------
| Default Database Connection Name
|--------------------------------------------------------------------------
|
| Here you may specify which of the database connections below you wish
| to use as your default connection for database operations. This is
| the connection which will be utilized unless another connection
| is explicitly specified when you execute a query / statement.
|
*/
'default' => env('DB_CONNECTION', 'sqlite'), 'default' => env('DB_CONNECTION', 'sqlite'),
/*
|--------------------------------------------------------------------------
| Database Connections
|--------------------------------------------------------------------------
|
| Below are all of the database connections defined for your application.
| An example configuration is provided for each database system which
| is supported by Laravel. You're free to add / remove connections.
|
*/
'connections' => [ 'connections' => [
'sqlite' => [ 'sqlite' => [
'driver' => 'sqlite', 'driver' => 'sqlite',
'url' => env('DB_URL'), 'url' => env('DB_URL'),
'database' => $datapasePath, 'database' => $datapasePath,
'prefix' => '', 'prefix' => '',
'foreign_key_constraints' => env('DB_FOREIGN_KEYS', true), 'foreign_key_constraints' => env('DB_FOREIGN_KEYS', true),
'busy_timeout' => null,
'journal_mode' => null,
'synchronous' => null,
], ],
'mysql' => [ 'mysql' => [
@ -59,24 +88,57 @@ return [
PDO::MYSQL_ATTR_SSL_CA => env('MYSQL_ATTR_SSL_CA'), PDO::MYSQL_ATTR_SSL_CA => env('MYSQL_ATTR_SSL_CA'),
]) : [], ]) : [],
], ],
], ],
/*
|--------------------------------------------------------------------------
| Migration Repository Table
|--------------------------------------------------------------------------
|
| This table keeps track of all the migrations that have already run for
| your application. Using this information, we can determine which of
| the migrations on disk haven't actually been run on the database.
|
*/
'migrations' => [ 'migrations' => [
'table' => 'migrations', 'table' => 'migrations',
'update_date_on_publish' => false, // disable to preserve original behavior for existing applications 'update_date_on_publish' => false, // disable to preserve original behavior for existing applications
], ],
/*
|--------------------------------------------------------------------------
| Redis Databases
|--------------------------------------------------------------------------
|
| Redis is an open source, fast, and advanced key-value store that also
| provides a richer body of commands than a typical key-value system
| such as Memcached. You may define your connection settings here.
|
*/
'redis' => [ 'redis' => [
'client' => env('REDIS_CLIENT', 'predis'),
'client' => env('REDIS_CLIENT', 'phpredis'),
'options' => [
'cluster' => env('REDIS_CLUSTER', 'redis'),
'prefix' => env('REDIS_PREFIX', Str::slug(env('APP_NAME', 'laravel'), '_').'_database_'),
'persistent' => env('REDIS_PERSISTENT', false),
],
'default' => [ 'default' => [
'scheme' => env('REDIS_SCHEME', 'tcp'), 'scheme' => env('REDIS_SCHEME', 'tcp'),
'path' => env('REDIS_PATH', '/run/redis/redis.sock'), 'path' => env('REDIS_PATH', '/run/redis/redis.sock'),
'host' => env('REDIS_HOST', 'localhost'),
'url' => env('REDIS_URL'),
'host' => env('REDIS_HOST', '127.0.0.1'),
'username' => env('REDIS_USERNAME'), 'username' => env('REDIS_USERNAME'),
'password' => env('REDIS_PASSWORD'), 'password' => env('REDIS_PASSWORD'),
'port' => env('REDIS_PORT', 6379), 'port' => env('REDIS_PORT', '6379'),
'database' => env('REDIS_DATABASE', 0), 'database' => env('REDIS_DB', env('REDIS_DATABASE', '0')),
'context' => extension_loaded('redis') && env('REDIS_CLIENT') === 'phpredis' ? [ 'context' => extension_loaded('redis') && env('REDIS_CLIENT') === 'phpredis' ? [
'stream' => array_filter([ 'stream' => array_filter([
'verify_peer' => env('REDIS_VERIFY_PEER', true), 'verify_peer' => env('REDIS_VERIFY_PEER', true),
@ -106,6 +168,16 @@ return [
]), ]),
] : [], ] : [],
], ],
'cache' => [
'url' => env('REDIS_URL'),
'host' => env('REDIS_HOST', '127.0.0.1'),
'username' => env('REDIS_USERNAME'),
'password' => env('REDIS_PASSWORD'),
'port' => env('REDIS_PORT', '6379'),
'database' => env('REDIS_CACHE_DB', '1'),
],
], ],
]; ];

View File

@ -2,17 +2,118 @@
return [ return [
/*
|--------------------------------------------------------------------------
| Default Mailer
|--------------------------------------------------------------------------
|
| This option controls the default mailer that is used to send all email
| messages unless another mailer is explicitly specified when sending
| the message. All additional mailers can be configured within the
| "mailers" array. Examples of each type of mailer are provided.
|
*/
'default' => env('MAIL_MAILER', 'log'), 'default' => env('MAIL_MAILER', 'log'),
'from' => [ /*
'address' => env('MAIL_FROM_ADDRESS', 'no-reply@example.com'), |--------------------------------------------------------------------------
'name' => env('MAIL_FROM_NAME', 'Pelican Admin'), | Mailer Configurations
], |--------------------------------------------------------------------------
|
| Here you may configure all of the mailers used by your application plus
| their respective settings. Several examples have been configured for
| you and you are free to add your own as your application requires.
|
| Laravel supports a variety of mail "transport" drivers that can be used
| when delivering an email. You may specify which one you're using for
| your mailers below. You may also add additional mailers if needed.
|
| Supported: "smtp", "sendmail", "mailgun", "ses", "ses-v2",
| "postmark", "resend", "log", "array",
| "failover", "roundrobin"
|
*/
'mailers' => [ 'mailers' => [
'mailgun' => [ 'mailgun' => [
'transport' => 'mailgun', 'transport' => 'mailgun',
], ],
'smtp' => [
'transport' => 'smtp',
'scheme' => env('MAIL_SCHEME', env('MAIL_ENCRYPTION')),
'url' => env('MAIL_URL'),
'host' => env('MAIL_HOST', '127.0.0.1'),
'port' => env('MAIL_PORT', 2525),
'username' => env('MAIL_USERNAME'),
'password' => env('MAIL_PASSWORD'),
'timeout' => null,
'local_domain' => env('MAIL_EHLO_DOMAIN', parse_url(env('APP_URL', 'http://localhost'), PHP_URL_HOST)),
],
'ses' => [
'transport' => 'ses',
],
'postmark' => [
'transport' => 'postmark',
// 'message_stream_id' => env('POSTMARK_MESSAGE_STREAM_ID'),
// 'client' => [
// 'timeout' => 5,
// ],
],
'resend' => [
'transport' => 'resend',
],
'sendmail' => [
'transport' => 'sendmail',
'path' => env('MAIL_SENDMAIL_PATH', '/usr/sbin/sendmail -bs -i'),
],
'log' => [
'transport' => 'log',
'channel' => env('MAIL_LOG_CHANNEL'),
],
'array' => [
'transport' => 'array',
],
'failover' => [
'transport' => 'failover',
'mailers' => [
'smtp',
'log',
],
],
'roundrobin' => [
'transport' => 'roundrobin',
'mailers' => [
'ses',
'postmark',
],
],
],
/*
|--------------------------------------------------------------------------
| Global "From" Address
|--------------------------------------------------------------------------
|
| You may wish for all emails sent by your application to be sent from
| the same address. Here you may specify a name and address that is
| used globally for all emails that are sent by your application.
|
*/
'from' => [
'address' => env('MAIL_FROM_ADDRESS', 'hello@example.com'),
'name' => env('MAIL_FROM_NAME', 'Example'),
], ],
]; ];

View File

@ -1,20 +0,0 @@
<?php
return [
/*
|--------------------------------------------------------------------------
| Authentication Language Lines
|--------------------------------------------------------------------------
|
| The following language lines are used during authentication for various
| messages that we need to display to the user. You are free to modify
| these language lines according to your application's requirements.
|
*/
'failed' => 'These credentials do not match our records.',
'password' => 'The provided password is incorrect.',
'throttle' => 'Too many login attempts. Please try again in :seconds seconds.',
];

View File

@ -1,19 +0,0 @@
<?php
return [
/*
|--------------------------------------------------------------------------
| Pagination Language Lines
|--------------------------------------------------------------------------
|
| The following language lines are used by the paginator library to build
| the simple pagination links. You are free to change them to anything
| you want to customize your views to better match your application.
|
*/
'previous' => '&laquo; Previous',
'next' => 'Next &raquo;',
];

View File

@ -1,22 +0,0 @@
<?php
return [
/*
|--------------------------------------------------------------------------
| Password Reset Language Lines
|--------------------------------------------------------------------------
|
| The following language lines are the default lines which match reasons
| that are given by the password broker for a password update attempt
| outcome such as failure due to an invalid password / reset token.
|
*/
'reset' => 'Your password has been reset.',
'sent' => 'We have emailed your password reset link.',
'throttled' => 'Please wait before retrying.',
'token' => 'This password reset token is invalid.',
'user' => "We can't find a user with that email address.",
];

View File

@ -28,7 +28,7 @@ return [
'string' => 'The :attribute must be between :min and :max characters.', 'string' => 'The :attribute must be between :min and :max characters.',
'array' => 'The :attribute must have between :min and :max items.', 'array' => 'The :attribute must have between :min and :max items.',
], ],
'boolean' => 'The :attribute field must be true or false.',
'confirmed' => 'The :attribute confirmation does not match.', 'confirmed' => 'The :attribute confirmation does not match.',
'date' => 'The :attribute is not a valid date.', 'date' => 'The :attribute is not a valid date.',
'date_format' => 'The :attribute does not match the format :format.', 'date_format' => 'The :attribute does not match the format :format.',
@ -36,13 +36,13 @@ return [
'digits' => 'The :attribute must be :digits digits.', 'digits' => 'The :attribute must be :digits digits.',
'digits_between' => 'The :attribute must be between :min and :max digits.', 'digits_between' => 'The :attribute must be between :min and :max digits.',
'dimensions' => 'The :attribute has invalid image dimensions.', 'dimensions' => 'The :attribute has invalid image dimensions.',
'distinct' => 'The :attribute field has a duplicate value.',
'email' => 'The :attribute must be a valid email address.', 'email' => 'The :attribute must be a valid email address.',
'exists' => 'The selected :attribute is invalid.',
'file' => 'The :attribute must be a file.', 'file' => 'The :attribute must be a file.',
'filled' => 'The :attribute field is required.', 'filled' => 'The :attribute field is required.',
'image' => 'The :attribute must be an image.', 'image' => 'The :attribute must be an image.',
'in' => 'The selected :attribute is invalid.',
'in_array' => 'The :attribute field does not exist in :other.', 'in_array' => 'The :attribute field does not exist in :other.',
'integer' => 'The :attribute must be an integer.', 'integer' => 'The :attribute must be an integer.',
'ip' => 'The :attribute must be a valid IP address.', 'ip' => 'The :attribute must be a valid IP address.',
@ -61,17 +61,13 @@ return [
'string' => 'The :attribute must be at least :min characters.', 'string' => 'The :attribute must be at least :min characters.',
'array' => 'The :attribute must have at least :min items.', 'array' => 'The :attribute must have at least :min items.',
], ],
'not_in' => 'The selected :attribute is invalid.',
'numeric' => 'The :attribute must be a number.', 'numeric' => 'The :attribute must be a number.',
'present' => 'The :attribute field must be present.',
'regex' => 'The :attribute format is invalid.', 'regex' => 'The :attribute format is invalid.',
'required' => 'The :attribute field is required.',
'required_if' => 'The :attribute field is required when :other is :value.',
'required_unless' => 'The :attribute field is required unless :other is in :values.',
'required_with' => 'The :attribute field is required when :values is present.',
'required_with_all' => 'The :attribute field is required when :values is present.', 'required_with_all' => 'The :attribute field is required when :values is present.',
'required_without' => 'The :attribute field is required when :values is not present.',
'required_without_all' => 'The :attribute field is required when none of :values are present.',
'same' => 'The :attribute and :other must match.', 'same' => 'The :attribute and :other must match.',
'size' => [ 'size' => [
'numeric' => 'The :attribute must be :size.', 'numeric' => 'The :attribute must be :size.',
@ -81,8 +77,7 @@ return [
], ],
'string' => 'The :attribute must be a string.', 'string' => 'The :attribute must be a string.',
'timezone' => 'The :attribute must be a valid zone.', 'timezone' => 'The :attribute must be a valid zone.',
'unique' => 'The :attribute has already been taken.',
'uploaded' => 'The :attribute failed to upload.',
'url' => 'The :attribute format is invalid.', 'url' => 'The :attribute format is invalid.',
/* /*

View File

@ -9,12 +9,16 @@
RewriteCond %{HTTP:Authorization} . RewriteCond %{HTTP:Authorization} .
RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}] RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]
# Handle X-XSRF-Token Header
RewriteCond %{HTTP:x-xsrf-token} .
RewriteRule .* - [E=HTTP_X_XSRF_TOKEN:%{HTTP:X-XSRF-Token}]
# Redirect Trailing Slashes If Not A Folder... # Redirect Trailing Slashes If Not A Folder...
RewriteCond %{REQUEST_FILENAME} !-d RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_URI} (.+)/$ RewriteCond %{REQUEST_URI} (.+)/$
RewriteRule ^ %1 [L,R=301] RewriteRule ^ %1 [L,R=301]
# Handle Front Controller... # Send Requests To Front Controller...
RewriteCond %{REQUEST_FILENAME} !-d RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_FILENAME} !-f RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^ index.php [L] RewriteRule ^ index.php [L]

View File

@ -1,5 +1,6 @@
<?php <?php
use Illuminate\Foundation\Application;
use Illuminate\Http\Request; use Illuminate\Http\Request;
define('LARAVEL_START', microtime(true)); define('LARAVEL_START', microtime(true));
@ -13,5 +14,7 @@ if (file_exists($maintenance = __DIR__.'/../storage/framework/maintenance.php'))
require __DIR__.'/../vendor/autoload.php'; require __DIR__.'/../vendor/autoload.php';
// Bootstrap Laravel and handle the request... // Bootstrap Laravel and handle the request...
(require_once __DIR__.'/../bootstrap/app.php') /** @var Application $app */
->handleRequest(Request::capture()); $app = require_once __DIR__.'/../bootstrap/app.php';
$app->handleRequest(Request::capture());

View File

@ -111,7 +111,7 @@ Route::prefix('/eggs')->group(function () {
| Endpoint: /api/application/database-hosts | Endpoint: /api/application/database-hosts
| |
*/ */
Route::group(['prefix' => '/database-hosts'], function () { Route::prefix('/database-hosts')->group(function () {
Route::get('/', [Application\DatabaseHosts\DatabaseHostController::class, 'index'])->name('api.application.databasehosts'); Route::get('/', [Application\DatabaseHosts\DatabaseHostController::class, 'index'])->name('api.application.databasehosts');
Route::get('/{database_host:id}', [Application\DatabaseHosts\DatabaseHostController::class, 'view'])->name('api.application.databasehosts.view'); Route::get('/{database_host:id}', [Application\DatabaseHosts\DatabaseHostController::class, 'view'])->name('api.application.databasehosts.view');
@ -153,7 +153,7 @@ Route::prefix('mounts')->group(function () {
| Endpoint: /api/application/roles | Endpoint: /api/application/roles
| |
*/ */
Route::group(['prefix' => '/roles'], function () { Route::prefix('/roles')->group(function () {
Route::get('/', [Application\Roles\RoleController::class, 'index'])->name('api.application.roles'); Route::get('/', [Application\Roles\RoleController::class, 'index'])->name('api.application.roles');
Route::get('/{role:id}', [Application\Roles\RoleController::class, 'view'])->name('api.application.roles.view'); Route::get('/{role:id}', [Application\Roles\RoleController::class, 'view'])->name('api.application.roles.view');

View File

@ -3,7 +3,7 @@
use Dedoc\Scramble\Scramble; use Dedoc\Scramble\Scramble;
use Illuminate\Support\Facades\Route; use Illuminate\Support\Facades\Route;
Route::group(['prefix' => 'api'], function () { Route::prefix('api')->group(function () {
Scramble::registerUiRoute(path: 'application', api: 'application'); Scramble::registerUiRoute(path: 'application', api: 'application');
Scramble::registerJsonSpecificationRoute(path: 'application.json', api: 'application'); Scramble::registerJsonSpecificationRoute(path: 'application.json', api: 'application');

View File

@ -1,4 +1,5 @@
* *
!private/
!.gitignore !.gitignore
!services/* !services/*
packs/**/* packs/**/*

2
storage/app/private/.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
*
!.gitignore

View File

@ -30,7 +30,7 @@ class DispatchWebhooksTest extends TestCase
Queue::assertPushed(ProcessWebhook::class); Queue::assertPushed(ProcessWebhook::class);
} }
public function test_sends_multiple_webhooks() public function test_sends_multiple_webhooks(): void
{ {
WebhookConfiguration::factory(2) WebhookConfiguration::factory(2)
->create(['events' => ['eloquent.created: '.Server::class]]); ->create(['events' => ['eloquent.created: '.Server::class]]);
@ -40,7 +40,7 @@ class DispatchWebhooksTest extends TestCase
Queue::assertPushed(ProcessWebhook::class, 2); Queue::assertPushed(ProcessWebhook::class, 2);
} }
public function test_it_sends_no_webhooks() public function test_it_sends_no_webhooks(): void
{ {
WebhookConfiguration::factory()->create(); WebhookConfiguration::factory()->create();
@ -49,7 +49,7 @@ class DispatchWebhooksTest extends TestCase
Queue::assertNothingPushed(); Queue::assertNothingPushed();
} }
public function test_it_sends_some_webhooks() public function test_it_sends_some_webhooks(): void
{ {
WebhookConfiguration::factory(2) WebhookConfiguration::factory(2)
->sequence( ->sequence(
@ -62,7 +62,7 @@ class DispatchWebhooksTest extends TestCase
Queue::assertPushed(ProcessWebhook::class, 1); Queue::assertPushed(ProcessWebhook::class, 1);
} }
public function test_it_does_not_call_removed_events() public function test_it_does_not_call_removed_events(): void
{ {
$webhookConfig = WebhookConfiguration::factory()->create([ $webhookConfig = WebhookConfiguration::factory()->create([
'events' => ['eloquent.created: '.Server::class], 'events' => ['eloquent.created: '.Server::class],
@ -75,7 +75,7 @@ class DispatchWebhooksTest extends TestCase
Queue::assertNothingPushed(); Queue::assertNothingPushed();
} }
public function test_it_does_not_call_deleted_webhooks() public function test_it_does_not_call_deleted_webhooks(): void
{ {
$webhookConfig = WebhookConfiguration::factory()->create([ $webhookConfig = WebhookConfiguration::factory()->create([
'events' => ['eloquent.created: '.Server::class], 'events' => ['eloquent.created: '.Server::class],

View File

@ -77,7 +77,7 @@ class ProcessWebhooksTest extends TestCase
}); });
} }
public function test_sends_multiple_webhooks() public function test_sends_multiple_webhooks(): void
{ {
[$webhook1, $webhook2] = WebhookConfiguration::factory(2) [$webhook1, $webhook2] = WebhookConfiguration::factory(2)
->create(['events' => [$eventName = 'eloquent.created: '.Server::class]]); ->create(['events' => [$eventName = 'eloquent.created: '.Server::class]]);
@ -98,7 +98,7 @@ class ProcessWebhooksTest extends TestCase
Http::assertSent(fn (Request $request) => $webhook2->endpoint === $request->url()); Http::assertSent(fn (Request $request) => $webhook2->endpoint === $request->url());
} }
public function test_it_sends_no_webhooks() public function test_it_sends_no_webhooks(): void
{ {
Http::fake(); Http::fake();
@ -109,7 +109,7 @@ class ProcessWebhooksTest extends TestCase
Http::assertSentCount(0); Http::assertSentCount(0);
} }
public function test_it_sends_some_webhooks() public function test_it_sends_some_webhooks(): void
{ {
[$webhook1, $webhook2] = WebhookConfiguration::factory(2) [$webhook1, $webhook2] = WebhookConfiguration::factory(2)
->sequence( ->sequence(
@ -129,7 +129,7 @@ class ProcessWebhooksTest extends TestCase
Http::assertNotSent(fn (Request $request) => $webhook2->endpoint === $request->url()); Http::assertNotSent(fn (Request $request) => $webhook2->endpoint === $request->url());
} }
public function test_it_records_when_a_webhook_is_sent() public function test_it_records_when_a_webhook_is_sent(): void
{ {
$webhookConfig = WebhookConfiguration::factory() $webhookConfig = WebhookConfiguration::factory()
->create(['events' => ['eloquent.created: '.Server::class]]); ->create(['events' => ['eloquent.created: '.Server::class]]);
@ -152,7 +152,7 @@ class ProcessWebhooksTest extends TestCase
]); ]);
} }
public function test_it_records_when_a_webhook_fails() public function test_it_records_when_a_webhook_fails(): void
{ {
$webhookConfig = WebhookConfiguration::factory()->create([ $webhookConfig = WebhookConfiguration::factory()->create([
'events' => ['eloquent.created: '.Server::class], 'events' => ['eloquent.created: '.Server::class],
@ -173,7 +173,7 @@ class ProcessWebhooksTest extends TestCase
]); ]);
} }
public function test_it_is_triggered_on_custom_events() public function test_it_is_triggered_on_custom_events(): void
{ {
$webhookConfig = WebhookConfiguration::factory()->create([ $webhookConfig = WebhookConfiguration::factory()->create([
'events' => [Installed::class], 'events' => [Installed::class],