diff --git a/app/Models/User.php b/app/Models/User.php index 2e45c08..3b4dde5 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -2,7 +2,7 @@ namespace App\Models; -// use Illuminate\Contracts\Auth\MustVerifyEmail; +use Illuminate\Contracts\Auth\MustVerifyEmail; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Foundation\Auth\User as Authenticatable; use Illuminate\Notifications\Notifiable; @@ -10,7 +10,7 @@ use Laravel\Fortify\TwoFactorAuthenticatable; use Laravel\Jetstream\HasProfilePhoto; use Laravel\Sanctum\HasApiTokens; -class User extends Authenticatable +class User extends Authenticatable implements MustVerifyEmail { use HasApiTokens; use HasFactory; diff --git a/composer.json b/composer.json index 822c6c3..0a8292d 100644 --- a/composer.json +++ b/composer.json @@ -5,18 +5,18 @@ "keywords": ["laravel", "framework"], "license": "MIT", "require": { - "php": "^8.2", + "php": "^8.3", "laravel/framework": "^11.0", "laravel/jetstream": "^5.1", + "laravel/nova": "^4.0", + "laravel/pulse": "^1.1", "laravel/sanctum": "^4.0", "laravel/tinker": "^2.9", - "laravel/nova": "^4.0", "livewire/livewire": "^3.0" }, "require-dev": { "fakerphp/faker": "^1.23", "laravel/pint": "^1.13", - "laravel/sail": "^1.26", "mockery/mockery": "^1.6", "nunomaduro/collision": "^8.0", "phpunit/phpunit": "^11.0.1", diff --git a/composer.lock b/composer.lock index fef2217..ede6585 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": "ebb2c95ee3eb1d6576337e4eeec6cd7b", + "content-hash": "9eff4d5ab142ca28af7182cfab7b1316", "packages": [ { "name": "bacon/bacon-qr-code", @@ -695,6 +695,61 @@ ], "time": "2024-02-05T11:56:58+00:00" }, + { + "name": "doctrine/sql-formatter", + "version": "1.4.0", + "source": { + "type": "git", + "url": "https://github.com/doctrine/sql-formatter.git", + "reference": "d1ac84aef745c69ea034929eb6d65a6908b675cc" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/sql-formatter/zipball/d1ac84aef745c69ea034929eb6d65a6908b675cc", + "reference": "d1ac84aef745c69ea034929eb6d65a6908b675cc", + "shasum": "" + }, + "require": { + "php": "^8.1" + }, + "require-dev": { + "doctrine/coding-standard": "^12", + "phpstan/phpstan": "^1.10", + "phpunit/phpunit": "^10.5", + "vimeo/psalm": "^5.24" + }, + "bin": [ + "bin/sql-formatter" + ], + "type": "library", + "autoload": { + "psr-4": { + "Doctrine\\SqlFormatter\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jeremy Dorn", + "email": "jeremy@jeremydorn.com", + "homepage": "https://jeremydorn.com/" + } + ], + "description": "a PHP SQL highlighting library", + "homepage": "https://github.com/doctrine/sql-formatter/", + "keywords": [ + "highlight", + "sql" + ], + "support": { + "issues": "https://github.com/doctrine/sql-formatter/issues", + "source": "https://github.com/doctrine/sql-formatter/tree/1.4.0" + }, + "time": "2024-05-08T08:12:09+00:00" + }, { "name": "dragonmantank/cron-expression", "version": "v3.3.3", @@ -1963,6 +2018,93 @@ }, "time": "2024-04-30T12:46:16+00:00" }, + { + "name": "laravel/pulse", + "version": "v1.1.0", + "source": { + "type": "git", + "url": "https://github.com/laravel/pulse.git", + "reference": "16746346a6cb2cd12d9e93db206aaf2b9d792b21" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laravel/pulse/zipball/16746346a6cb2cd12d9e93db206aaf2b9d792b21", + "reference": "16746346a6cb2cd12d9e93db206aaf2b9d792b21", + "shasum": "" + }, + "require": { + "doctrine/sql-formatter": "^1.2", + "guzzlehttp/promises": "^1.0|^2.0", + "illuminate/auth": "^10.48.4|^11.0.8", + "illuminate/cache": "^10.48.4|^11.0.8", + "illuminate/config": "^10.48.4|^11.0.8", + "illuminate/console": "^10.48.4|^11.0.8", + "illuminate/contracts": "^10.48.4|^11.0.8", + "illuminate/database": "^10.48.4|^11.0.8", + "illuminate/events": "^10.48.4|^11.0.8", + "illuminate/http": "^10.48.4|^11.0.8", + "illuminate/queue": "^10.48.4|^11.0.8", + "illuminate/redis": "^10.48.4|^11.0.8", + "illuminate/routing": "^10.48.4|^11.0.8", + "illuminate/support": "^10.48.4|^11.0.8", + "illuminate/view": "^10.48.4|^11.0.8", + "livewire/livewire": "^3.4.9", + "nesbot/carbon": "^2.67|^3.0", + "php": "^8.1", + "symfony/console": "^6.0|^7.0" + }, + "conflict": { + "nunomaduro/collision": "<7.7.0" + }, + "require-dev": { + "guzzlehttp/guzzle": "^7.7", + "mockery/mockery": "^1.0", + "orchestra/testbench": "^8.23.1|^9.0", + "pestphp/pest": "^2.0", + "pestphp/pest-plugin-laravel": "^2.2", + "phpstan/phpstan": "^1.11", + "predis/predis": "^1.0|^2.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.x-dev" + }, + "laravel": { + "providers": [ + "Laravel\\Pulse\\PulseServiceProvider" + ], + "aliases": { + "Pulse": "Laravel\\Pulse\\Facades\\Pulse" + } + } + }, + "autoload": { + "psr-4": { + "Laravel\\Pulse\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylor@laravel.com" + } + ], + "description": "Laravel Pulse is a real-time application performance monitoring tool and dashboard for your Laravel application.", + "homepage": "https://github.com/laravel/pulse", + "keywords": [ + "laravel" + ], + "support": { + "issues": "https://github.com/laravel/pulse/issues", + "source": "https://github.com/laravel/pulse" + }, + "time": "2024-05-06T17:11:35+00:00" + }, { "name": "laravel/sanctum", "version": "v4.0.2", @@ -7277,69 +7419,6 @@ }, "time": "2024-04-30T15:02:26+00:00" }, - { - "name": "laravel/sail", - "version": "v1.29.1", - "source": { - "type": "git", - "url": "https://github.com/laravel/sail.git", - "reference": "8be4a31150eab3b46af11a2e7b2c4632eefaad7e" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/laravel/sail/zipball/8be4a31150eab3b46af11a2e7b2c4632eefaad7e", - "reference": "8be4a31150eab3b46af11a2e7b2c4632eefaad7e", - "shasum": "" - }, - "require": { - "illuminate/console": "^9.52.16|^10.0|^11.0", - "illuminate/contracts": "^9.52.16|^10.0|^11.0", - "illuminate/support": "^9.52.16|^10.0|^11.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", - "phpstan/phpstan": "^1.10" - }, - "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": "2024-03-20T20:09:31+00:00" - }, { "name": "mockery/mockery", "version": "1.6.11", @@ -9350,77 +9429,6 @@ ], "time": "2024-05-02T13:42:49+00:00" }, - { - "name": "symfony/yaml", - "version": "v7.0.7", - "source": { - "type": "git", - "url": "https://github.com/symfony/yaml.git", - "reference": "0d3916ae69ea28b59d94b60c4f2b50f4e25adb5c" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/yaml/zipball/0d3916ae69ea28b59d94b60c4f2b50f4e25adb5c", - "reference": "0d3916ae69ea28b59d94b60c4f2b50f4e25adb5c", - "shasum": "" - }, - "require": { - "php": ">=8.2", - "symfony/polyfill-ctype": "^1.8" - }, - "conflict": { - "symfony/console": "<6.4" - }, - "require-dev": { - "symfony/console": "^6.4|^7.0" - }, - "bin": [ - "Resources/bin/yaml-lint" - ], - "type": "library", - "autoload": { - "psr-4": { - "Symfony\\Component\\Yaml\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Loads and dumps YAML files", - "homepage": "https://symfony.com", - "support": { - "source": "https://github.com/symfony/yaml/tree/v7.0.7" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2024-04-28T11:44:19+00:00" - }, { "name": "theseer/tokenizer", "version": "1.2.3", @@ -9478,7 +9486,7 @@ "prefer-stable": true, "prefer-lowest": false, "platform": { - "php": "^8.2" + "php": "^8.3" }, "platform-dev": [], "plugin-api-version": "2.6.0" diff --git a/config/fortify.php b/config/fortify.php index 726d83b..0551d1d 100644 --- a/config/fortify.php +++ b/config/fortify.php @@ -146,7 +146,7 @@ return [ 'features' => [ Features::registration(), Features::resetPasswords(), - // Features::emailVerification(), + Features::emailVerification(), Features::updateProfileInformation(), Features::updatePasswords(), Features::twoFactorAuthentication([ diff --git a/config/jetstream.php b/config/jetstream.php index d5e5f11..e4b3b20 100644 --- a/config/jetstream.php +++ b/config/jetstream.php @@ -58,10 +58,9 @@ return [ */ 'features' => [ - // Features::termsAndPrivacyPolicy(), - // Features::profilePhotos(), - // Features::api(), - // Features::teams(['invitations' => true]), + Features::termsAndPrivacyPolicy(), + Features::profilePhotos(), + Features::api(), Features::accountDeletion(), ], diff --git a/config/pulse.php b/config/pulse.php new file mode 100644 index 0000000..e417066 --- /dev/null +++ b/config/pulse.php @@ -0,0 +1,232 @@ + env('PULSE_DOMAIN'), + + /* + |-------------------------------------------------------------------------- + | Pulse Path + |-------------------------------------------------------------------------- + | + | This is the path which the Pulse dashboard will be accessible from. Feel + | free to change this path to anything you'd like. Note that this won't + | affect the path of the internal API that is never exposed to users. + | + */ + + 'path' => env('PULSE_PATH', 'pulse'), + + /* + |-------------------------------------------------------------------------- + | Pulse Master Switch + |-------------------------------------------------------------------------- + | + | This configuration option may be used to completely disable all Pulse + | data recorders regardless of their individual configurations. This + | provides a single option to quickly disable all Pulse recording. + | + */ + + 'enabled' => env('PULSE_ENABLED', true), + + /* + |-------------------------------------------------------------------------- + | Pulse Storage Driver + |-------------------------------------------------------------------------- + | + | This configuration option determines which storage driver will be used + | while storing entries from Pulse's recorders. In addition, you also + | may provide any options to configure the selected storage driver. + | + */ + + 'storage' => [ + 'driver' => env('PULSE_STORAGE_DRIVER', 'database'), + + 'database' => [ + 'connection' => env('PULSE_DB_CONNECTION', null), + 'chunk' => 1000, + ], + ], + + /* + |-------------------------------------------------------------------------- + | Pulse Ingest Driver + |-------------------------------------------------------------------------- + | + | This configuration options determines the ingest driver that will be used + | to capture entries from Pulse's recorders. Ingest drivers are great to + | free up your request workers quickly by offloading the data storage. + | + */ + + 'ingest' => [ + 'driver' => env('PULSE_INGEST_DRIVER', 'storage'), + + 'buffer' => env('PULSE_INGEST_BUFFER', 5_000), + + 'trim' => [ + 'lottery' => [1, 1_000], + 'keep' => '7 days', + ], + + 'redis' => [ + 'connection' => env('PULSE_REDIS_CONNECTION'), + 'chunk' => 1000, + ], + ], + + /* + |-------------------------------------------------------------------------- + | Pulse Cache Driver + |-------------------------------------------------------------------------- + | + | This configuration option determines the cache driver that will be used + | for various tasks, including caching dashboard results, establishing + | locks for events that should only occur on one server and signals. + | + */ + + 'cache' => env('PULSE_CACHE_DRIVER'), + + /* + |-------------------------------------------------------------------------- + | Pulse Route Middleware + |-------------------------------------------------------------------------- + | + | These middleware will be assigned to every Pulse route, giving you the + | chance to add your own middleware to this list or change any of the + | existing middleware. Of course, reasonable defaults are provided. + | + */ + + 'middleware' => [ + 'web', + Authorize::class, + ], + + /* + |-------------------------------------------------------------------------- + | Pulse Recorders + |-------------------------------------------------------------------------- + | + | The following array lists the "recorders" that will be registered with + | Pulse, along with their configuration. Recorders gather application + | event data from requests and tasks to pass to your ingest driver. + | + */ + + 'recorders' => [ + Recorders\CacheInteractions::class => [ + 'enabled' => env('PULSE_CACHE_INTERACTIONS_ENABLED', true), + 'sample_rate' => env('PULSE_CACHE_INTERACTIONS_SAMPLE_RATE', 1), + 'ignore' => [ + ...Pulse::defaultVendorCacheKeys(), + ], + 'groups' => [ + '/^job-exceptions:.*/' => 'job-exceptions:*', + // '/:\d+/' => ':*', + ], + ], + + Recorders\Exceptions::class => [ + 'enabled' => env('PULSE_EXCEPTIONS_ENABLED', true), + 'sample_rate' => env('PULSE_EXCEPTIONS_SAMPLE_RATE', 1), + 'location' => env('PULSE_EXCEPTIONS_LOCATION', true), + 'ignore' => [ + // '/^Package\\\\Exceptions\\\\/', + ], + ], + + Recorders\Queues::class => [ + 'enabled' => env('PULSE_QUEUES_ENABLED', true), + 'sample_rate' => env('PULSE_QUEUES_SAMPLE_RATE', 1), + 'ignore' => [ + // '/^Package\\\\Jobs\\\\/', + ], + ], + + Recorders\Servers::class => [ + 'server_name' => env('PULSE_SERVER_NAME', gethostname()), + 'directories' => explode(':', env('PULSE_SERVER_DIRECTORIES', '/')), + ], + + Recorders\SlowJobs::class => [ + 'enabled' => env('PULSE_SLOW_JOBS_ENABLED', true), + 'sample_rate' => env('PULSE_SLOW_JOBS_SAMPLE_RATE', 1), + 'threshold' => env('PULSE_SLOW_JOBS_THRESHOLD', 1000), + 'ignore' => [ + // '/^Package\\\\Jobs\\\\/', + ], + ], + + Recorders\SlowOutgoingRequests::class => [ + 'enabled' => env('PULSE_SLOW_OUTGOING_REQUESTS_ENABLED', true), + 'sample_rate' => env('PULSE_SLOW_OUTGOING_REQUESTS_SAMPLE_RATE', 1), + 'threshold' => env('PULSE_SLOW_OUTGOING_REQUESTS_THRESHOLD', 1000), + 'ignore' => [ + // '#^http://127\.0\.0\.1:13714#', // Inertia SSR... + ], + 'groups' => [ + // '#^https://api\.github\.com/repos/.*$#' => 'api.github.com/repos/*', + // '#^https?://([^/]*).*$#' => '\1', + // '#/\d+#' => '/*', + ], + ], + + Recorders\SlowQueries::class => [ + 'enabled' => env('PULSE_SLOW_QUERIES_ENABLED', true), + 'sample_rate' => env('PULSE_SLOW_QUERIES_SAMPLE_RATE', 1), + 'threshold' => env('PULSE_SLOW_QUERIES_THRESHOLD', 1000), + 'location' => env('PULSE_SLOW_QUERIES_LOCATION', true), + 'max_query_length' => env('PULSE_SLOW_QUERIES_MAX_QUERY_LENGTH', null), + 'ignore' => [ + '/(["`])pulse_[\w]+?\1/', // Pulse tables... + '/(["`])telescope_[\w]+?\1/', // Telescope tables... + ], + ], + + Recorders\SlowRequests::class => [ + 'enabled' => env('PULSE_SLOW_REQUESTS_ENABLED', true), + 'sample_rate' => env('PULSE_SLOW_REQUESTS_SAMPLE_RATE', 1), + 'threshold' => env('PULSE_SLOW_REQUESTS_THRESHOLD', 1000), + 'ignore' => [ + '#^/'.env('PULSE_PATH', 'pulse').'$#', // Pulse dashboard... + '#^/telescope#', // Telescope dashboard... + ], + ], + + Recorders\UserJobs::class => [ + 'enabled' => env('PULSE_USER_JOBS_ENABLED', true), + 'sample_rate' => env('PULSE_USER_JOBS_SAMPLE_RATE', 1), + 'ignore' => [ + // '/^Package\\\\Jobs\\\\/', + ], + ], + + Recorders\UserRequests::class => [ + 'enabled' => env('PULSE_USER_REQUESTS_ENABLED', true), + 'sample_rate' => env('PULSE_USER_REQUESTS_SAMPLE_RATE', 1), + 'ignore' => [ + '#^/'.env('PULSE_PATH', 'pulse').'$#', // Pulse dashboard... + '#^/telescope#', // Telescope dashboard... + ], + ], + ], +]; diff --git a/database/migrations/2024_05_14_040126_create_pulse_tables.php b/database/migrations/2024_05_14_040126_create_pulse_tables.php new file mode 100644 index 0000000..5d194e2 --- /dev/null +++ b/database/migrations/2024_05_14_040126_create_pulse_tables.php @@ -0,0 +1,84 @@ +shouldRun()) { + return; + } + + Schema::create('pulse_values', function (Blueprint $table) { + $table->id(); + $table->unsignedInteger('timestamp'); + $table->string('type'); + $table->mediumText('key'); + match ($this->driver()) { + 'mariadb', 'mysql' => $table->char('key_hash', 16)->charset('binary')->virtualAs('unhex(md5(`key`))'), + 'pgsql' => $table->uuid('key_hash')->storedAs('md5("key")::uuid'), + 'sqlite' => $table->string('key_hash'), + }; + $table->mediumText('value'); + + $table->index('timestamp'); // For trimming... + $table->index('type'); // For fast lookups and purging... + $table->unique(['type', 'key_hash']); // For data integrity and upserts... + }); + + Schema::create('pulse_entries', function (Blueprint $table) { + $table->id(); + $table->unsignedInteger('timestamp'); + $table->string('type'); + $table->mediumText('key'); + match ($this->driver()) { + 'mariadb', 'mysql' => $table->char('key_hash', 16)->charset('binary')->virtualAs('unhex(md5(`key`))'), + 'pgsql' => $table->uuid('key_hash')->storedAs('md5("key")::uuid'), + 'sqlite' => $table->string('key_hash'), + }; + $table->bigInteger('value')->nullable(); + + $table->index('timestamp'); // For trimming... + $table->index('type'); // For purging... + $table->index('key_hash'); // For mapping... + $table->index(['timestamp', 'type', 'key_hash', 'value']); // For aggregate queries... + }); + + Schema::create('pulse_aggregates', function (Blueprint $table) { + $table->id(); + $table->unsignedInteger('bucket'); + $table->unsignedMediumInteger('period'); + $table->string('type'); + $table->mediumText('key'); + match ($this->driver()) { + 'mariadb', 'mysql' => $table->char('key_hash', 16)->charset('binary')->virtualAs('unhex(md5(`key`))'), + 'pgsql' => $table->uuid('key_hash')->storedAs('md5("key")::uuid'), + 'sqlite' => $table->string('key_hash'), + }; + $table->string('aggregate'); + $table->decimal('value', 20, 2); + $table->unsignedInteger('count')->nullable(); + + $table->unique(['bucket', 'period', 'type', 'aggregate', 'key_hash']); // Force "on duplicate update"... + $table->index(['period', 'bucket']); // For trimming... + $table->index('type'); // For purging... + $table->index(['period', 'type', 'aggregate', 'bucket']); // For aggregate queries... + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('pulse_values'); + Schema::dropIfExists('pulse_entries'); + Schema::dropIfExists('pulse_aggregates'); + } +}; diff --git a/phpunit.xml b/phpunit.xml index 506b9a3..c09b5bc 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -22,8 +22,7 @@ - - + diff --git a/resources/views/vendor/pulse/dashboard.blade.php b/resources/views/vendor/pulse/dashboard.blade.php new file mode 100644 index 0000000..6a95bb1 --- /dev/null +++ b/resources/views/vendor/pulse/dashboard.blade.php @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + +