From aa152684c973b1795f956ec01aa6dbb1c454b464 Mon Sep 17 00:00:00 2001 From: "Ronald A. Richardson" Date: Tue, 30 Jan 2024 20:08:19 +0800 Subject: [PATCH] WIP: upgraded to work with Laravel 10x --- composer.json | 30 +++---- ..._09_04_091906_create_failed_jobs_table.php | 4 +- ...0_18_080950_create_notifications_table.php | 4 +- ...column_to_personal_access_tokens_table.php | 32 +++++++ src/Mail/TestEmail.php | 32 ------- src/Mail/TestMail.php | 39 ++++++++ src/Mail/VerificationMail.php | 73 +++++++++++++++ src/Mail/VerifyEmail.php | 90 ------------------- src/Models/ApiCredential.php | 20 ++++- src/Models/Company.php | 8 +- src/Models/File.php | 22 ++++- src/Models/Invite.php | 4 +- src/Models/User.php | 23 +++++ src/Models/VerificationCode.php | 65 ++++++++++---- src/Models/WebhookEndpoint.php | 19 ++++ src/Providers/CoreServiceProvider.php | 4 + src/Support/TwoFactorAuth.php | 20 +++-- src/Support/Utils.php | 12 ++- src/Traits/HasPublicId.php | 4 +- views/mail/verification.blade.php | 20 +++++ 20 files changed, 338 insertions(+), 187 deletions(-) create mode 100644 migrations/2024_01_30_164300_add_expires_at_column_to_personal_access_tokens_table.php delete mode 100644 src/Mail/TestEmail.php create mode 100644 src/Mail/TestMail.php create mode 100644 src/Mail/VerificationMail.php delete mode 100644 src/Mail/VerifyEmail.php create mode 100644 views/mail/verification.blade.php diff --git a/composer.json b/composer.json index 1130050..d7efa33 100644 --- a/composer.json +++ b/composer.json @@ -19,36 +19,36 @@ ], "require": { "php": "^7.4|^8.0", + "fleetbase/laravel-mysql-spatial": "^1.0.1", + "illuminate/broadcasting": "^9.0|^10.0", + "illuminate/contracts": "^9.0|^10.0", + "illuminate/database": "^9.0|^10.0", + "illuminate/http": "^9.0|^10.0", + "illuminate/notifications": "^9.0|^10.0", + "illuminate/routing": "^9.0|^10.0", + "illuminate/support": "^9.0|^10.0", "aloha/twilio": "^5.0", "aws/aws-sdk-php-laravel": "^3.7", "giggsey/libphonenumber-for-php": "^8.13", - "grimzy/laravel-mysql-spatial": "^5.0", "guzzlehttp/guzzle": "^7.4", "hammerstone/fast-paginate": "^1.0", - "illuminate/broadcasting": "^7.0|^8.0|^9.0", - "illuminate/contracts": "^7.0|^8.0|^9.0", - "illuminate/database": "^7.0|^8.0|^9.0", - "illuminate/http": "^7.0|^8.0|^9.0", - "illuminate/notifications": "^7.0|^8.0|^9.0", - "illuminate/routing": "^7.0|^8.0|^9.0", - "illuminate/support": "^7.0|^8.0|^9.0", "jdorn/sql-formatter": "^1.2", - "laravel/sanctum": "^2.15", + "laravel/sanctum": "^3.2", "maatwebsite/excel": "^3.1", "phpoffice/phpspreadsheet": "^1.28", "phrity/websocket": "^1.7", "pragmarx/countries": "^0.8.2", "sentry/sentry-laravel": "*", - "spatie/laravel-activitylog": "^3.17", - "spatie/laravel-permission": "^5.5", - "spatie/laravel-responsecache": "^6.6", - "spatie/laravel-sluggable": "^2.6", - "vinkla/hashids": "^9.1", + "spatie/laravel-activitylog": "^4.7", + "spatie/laravel-permission": "^6.3", + "spatie/laravel-responsecache": "^7.4", + "spatie/laravel-sluggable": "^3.5", + "sqids/sqids": "^0.4.1", "xantios/mimey": "^2.2.0" }, "require-dev": { "friendsofphp/php-cs-fixer": "^3.34.1", - "nunomaduro/collision": "^5.11.0|^6.4.0", + "nunomaduro/collision": "^7.0", "pestphp/pest": "^1.22.6", "phpstan/phpstan": "^1.10.38", "symfony/var-dumper": "^5.4.29" diff --git a/migrations/2023_09_04_091906_create_failed_jobs_table.php b/migrations/2023_09_04_091906_create_failed_jobs_table.php index 6aa6d74..1719198 100644 --- a/migrations/2023_09_04_091906_create_failed_jobs_table.php +++ b/migrations/2023_09_04_091906_create_failed_jobs_table.php @@ -4,7 +4,7 @@ use Illuminate\Database\Schema\Blueprint; use Illuminate\Support\Facades\Schema; -class CreateFailedJobsTable extends Migration +return new class extends Migration { /** * Run the migrations. @@ -33,4 +33,4 @@ public function down() { Schema::dropIfExists('failed_jobs'); } -} +}; diff --git a/migrations/2023_10_18_080950_create_notifications_table.php b/migrations/2023_10_18_080950_create_notifications_table.php index 5f8c2dd..c3a255d 100644 --- a/migrations/2023_10_18_080950_create_notifications_table.php +++ b/migrations/2023_10_18_080950_create_notifications_table.php @@ -4,7 +4,7 @@ use Illuminate\Database\Schema\Blueprint; use Illuminate\Support\Facades\Schema; -class CreateNotificationsTable extends Migration +return new class extends Migration { /** * Run the migrations. @@ -32,4 +32,4 @@ public function down() { Schema::dropIfExists('notifications'); } -} +}; \ No newline at end of file diff --git a/migrations/2024_01_30_164300_add_expires_at_column_to_personal_access_tokens_table.php b/migrations/2024_01_30_164300_add_expires_at_column_to_personal_access_tokens_table.php new file mode 100644 index 0000000..fcde9da --- /dev/null +++ b/migrations/2024_01_30_164300_add_expires_at_column_to_personal_access_tokens_table.php @@ -0,0 +1,32 @@ +timestamp('expires_at')->nullable()->after('last_used_at'); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('personal_access_tokens', function (Blueprint $table) { + $table->dropColumn('expires_at'); + }); + } +}; \ No newline at end of file diff --git a/src/Mail/TestEmail.php b/src/Mail/TestEmail.php deleted file mode 100644 index edd936d..0000000 --- a/src/Mail/TestEmail.php +++ /dev/null @@ -1,32 +0,0 @@ -subject($subject) - ->html((new MailMessage()) - ->greeting($subject) - ->line('Hello! This is a test email from Fleetbase to confirm that your mail configuration works.') - ->render() - ); - } -} diff --git a/src/Mail/TestMail.php b/src/Mail/TestMail.php new file mode 100644 index 0000000..ae4366c --- /dev/null +++ b/src/Mail/TestMail.php @@ -0,0 +1,39 @@ +verificationCode = $verificationCode; + $this->content = $content; + } + + /** + * Get the message content definition. + * + * @return \Illuminate\Mail\Mailables\Envelope + */ + public function envelope(): Envelope + { + return new Envelope( + subject: $this->verificationCode->code . ' is your ' . config('app.name') . ' verification code', + ); + } + + /** + * Get the message content definition. + * + * @return \Illuminate\Mail\Mailables\Content + */ + public function content(): Content + { + return new Content( + html: 'fleetbase::mail.verification', + with: [ + 'appName' => config('app.name'), + 'currentHour' => now()->hour, + 'user' => $this->verificationCode->subject, + 'code' => $this->verificationCode->code, + 'content' => $this->content + ] + ); + } +} diff --git a/src/Mail/VerifyEmail.php b/src/Mail/VerifyEmail.php deleted file mode 100644 index 5a7a030..0000000 --- a/src/Mail/VerifyEmail.php +++ /dev/null @@ -1,90 +0,0 @@ -setVerificationCode($verificationCode); - $this->setSubject($subject); - $this->setEmailLines($lines); - $this->setGreeting($user); - } - - public function setVerificationCode($verificationCode): void - { - if ($verificationCode instanceof VerificationCode) { - $this->verifyCode = $verificationCode->code; - } else { - $this->verifyCode = $verificationCode; - } - } - - public function setSubject(?string $subject): void - { - if (is_string($subject) && !empty($subject)) { - $this->messageSubject = $subject; - } else { - $this->messageSubject = $this->verifyCode . ' is your ' . config('app.name') . ' verification code'; - } - } - - public function setEmailLines(array $lines = []): void - { - if (!empty($lines)) { - $this->lines = $lines; - } else { - $this->lines = [ - 'Welcome to ' . config('app.name') . ', use the code below to verify your email address and complete registration to ' . config('app.name') . '.', - new HtmlString('

Your verification code: ' . $this->verifyCode . '


'), - ]; - } - } - - public function setGreeting(Model $user): void - { - if ($user && isset($user->name)) { - $this->greeting = 'Hello, ' . $user->name . '!'; - } else { - $this->greeting = 'Hello!'; - } - } - - /** - * Build the message. - * - * @return $this - */ - public function build() - { - return $this - ->subject($this->messageSubject) - ->html((new MailMessage()) - ->greeting($this->greeting) - ->lines($this->lines) - ->render() - ); - } -} diff --git a/src/Models/ApiCredential.php b/src/Models/ApiCredential.php index eff758a..419b9a4 100644 --- a/src/Models/ApiCredential.php +++ b/src/Models/ApiCredential.php @@ -13,9 +13,8 @@ use Illuminate\Support\Carbon; use Illuminate\Support\Facades\Hash; use Spatie\Activitylog\Traits\LogsActivity; +use Spatie\Activitylog\LogOptions; use Spatie\Permission\Traits\HasPermissions; -use Vinkla\Hashids\Facades\Hashids; - class ApiCredential extends Model { use HasUuid; @@ -100,6 +99,20 @@ class ApiCredential extends Model */ public static array $skipTables = ['vehicles_data', 'permissions', 'roles', 'role_has_permissions', 'model_has_permissions', 'model_has_roles', 'model_has_policies']; + /** + * Get the activity log options for the model. + * + * @return \Spatie\Activitylog\LogOptions + */ + public function getActivitylogOptions(): LogOptions + { + return LogOptions::defaults() + ->logOnly([ + 'name' + ]) + ->logOnlyDirty(); + } + /** * @return \Illuminate\Database\Eloquent\Relations\BelongsTo */ @@ -174,7 +187,8 @@ public function setExpiresAtAttribute($expiresAt) */ public static function generateKeys($encode, $testKey = false) { - $key = Hashids::encode($encode); + $sqids = new \Sqids\Sqids(); + $key = $sqids->encode([$encode]); $hash = Hash::make($key); return [ diff --git a/src/Models/Company.php b/src/Models/Company.php index e56eaaf..f3661de 100644 --- a/src/Models/Company.php +++ b/src/Models/Company.php @@ -113,15 +113,9 @@ class Company extends Model */ protected $casts = [ 'options' => Json::class, + 'trial_ends_at' => 'datetime' ]; - /** - * The attributes that should be cast to dates. - * - * @var array - */ - protected $dates = ['trial_ends_at']; - /** * Properties which activity needs to be logged. * diff --git a/src/Models/File.php b/src/Models/File.php index 2e77613..49ff5be 100644 --- a/src/Models/File.php +++ b/src/Models/File.php @@ -14,9 +14,9 @@ use Illuminate\Support\Str; use Mimey\MimeTypes; use Spatie\Activitylog\Traits\LogsActivity; +use Spatie\Activitylog\LogOptions; use Spatie\Sluggable\HasSlug; use Spatie\Sluggable\SlugOptions; -use Vinkla\Hashids\Facades\Hashids; class File extends Model { @@ -109,6 +109,23 @@ public function getSlugOptions(): SlugOptions ->saveSlugsTo('slug'); } + /** + * Get the activity log options for the model. + * + * @return \Spatie\Activitylog\LogOptions + */ + public function getActivitylogOptions(): LogOptions + { + return LogOptions::defaults() + ->logOnly([ + 'subject_uuid', + 'bucket', + 'disk', + 'path' + ]) + ->logOnlyDirty(); + } + /** * Get the disk name for the file. * @@ -303,8 +320,9 @@ public static function randomFileNameFromRequest(Request $request, ?string $exte if ($request->hasFile('file')) { $extension = strtolower($file->getClientOriginalExtension()); $extension = Str::startsWith($extension, '.') ? $extension : '.' . $extension; + $sqids = new \Sqids\Sqids(); - return Hashids::encode(strlen($file->hashName()), time()) . $extension; + return $sqids->encode([strlen($file->hashName()), time()]) . $extension; } return static::randomFileName($extension); diff --git a/src/Models/Invite.php b/src/Models/Invite.php index 6f83fc8..1f73a5c 100644 --- a/src/Models/Invite.php +++ b/src/Models/Invite.php @@ -9,7 +9,6 @@ use Fleetbase\Traits\HasUuid; use Illuminate\Support\Carbon; use Illuminate\Support\Str; -use Vinkla\Hashids\Facades\Hashids; class Invite extends Model { @@ -98,7 +97,8 @@ public static function boot() parent::boot(); static::creating(function ($model) { // generate uri - $uri = lcfirst(Hashids::encode(time(), rand(), rand())); + $sqids = new \Sqids\Sqids(); + $uri = lcfirst($sqids->encode([time(), rand(), rand()])); $uri = substr($uri, 0, 12); $model->uri = $uri; diff --git a/src/Models/User.php b/src/Models/User.php index ebc373f..b1acec3 100644 --- a/src/Models/User.php +++ b/src/Models/User.php @@ -23,6 +23,7 @@ use Laravel\Sanctum\HasApiTokens; use Spatie\Activitylog\Traits\CausesActivity; use Spatie\Activitylog\Traits\LogsActivity; +use Spatie\Activitylog\LogOptions; use Spatie\Permission\Traits\HasRoles; use Spatie\Sluggable\HasSlug; use Spatie\Sluggable\SlugOptions; @@ -198,6 +199,28 @@ public function getSlugOptions(): SlugOptions ->saveSlugsTo('slug'); } + /** + * Get the activity log options for the model. + * + * @return \Spatie\Activitylog\LogOptions + */ + public function getActivitylogOptions(): LogOptions + { + return LogOptions::defaults() + ->logOnly([ + 'name', + 'username', + 'email', + 'phone', + 'date_of_birth', + 'timezone', + 'country', + 'avatar_uuid', + ]) + ->logOnlyDirty() + ->dontLogIfAttributesChangedOnly(['last_login']); + } + /** * The company this user belongs to. * diff --git a/src/Models/VerificationCode.php b/src/Models/VerificationCode.php index 0294b17..ad45016 100644 --- a/src/Models/VerificationCode.php +++ b/src/Models/VerificationCode.php @@ -4,7 +4,7 @@ use Aloha\Twilio\Support\Laravel\Facade as Twilio; use Fleetbase\Casts\Json; -use Fleetbase\Mail\VerifyEmail; +use Fleetbase\Mail\VerificationMail; use Fleetbase\Traits\Expirable; use Fleetbase\Traits\HasMetaAttributes; use Fleetbase\Traits\HasSubject; @@ -92,35 +92,64 @@ public static function generateFor($subject = null, $for = 'general_verification } /** static method to generate code for email verification */ - public static function generateEmailVerificationFor($subject, $for = 'email_verification', \Closure $messageCallback = null, \Closure $linesCallback = null, $meta = [], $expireAfter = null) + public static function generateEmailVerificationFor($subject, $for = 'email_verification', array $options = []) { - $verifyCode = static::generateFor($subject, $for, false); - $verifyCode->expires_at = $expireAfter === null ? Carbon::now()->addHour() : $expireAfter; - $verifyCode->meta = $meta; - $verifyCode->save(); - - $emailSubject = is_callable($messageCallback) ? $messageCallback($verifyCode) : null; - $emailLines = is_callable($linesCallback) ? $linesCallback($verifyCode) : []; + $expireAfter = data_get($options, 'expireAfter'); + $verificationCode = static::generateFor($subject, $for, false); + $verificationCode->expires_at = $expireAfter === null ? Carbon::now()->addHour() : $expireAfter; + $verificationCode->meta = data_get($options, 'meta', []); + $verificationCode->save(); if (isset($subject->email)) { - Mail::to($subject)->send(new VerifyEmail($verifyCode, $emailSubject, $emailLines, $subject)); + // See if subject option passed is callable + $mailableSubject = data_get($options, 'subject'); + if (is_callable($mailableSubject)) { + $options['subject'] = $mailableSubject($verificationCode); + } + + // See if content passed + $content = data_get($options, 'content'); + if (is_callable($content)) { + $content = $content($verificationCode); + } + + // Initialize the mailable definition + $mail = new VerificationMail($verificationCode, $content); + + // Apply any additional Mail facade parameters + $mailer = Mail::to($subject); + foreach ($options as $key => $value) { + if (method_exists($mailer, $key)) { + $mailer->$key($value); + } + } + + $mailer->send($mail); } - return $verifyCode; + return $verificationCode; } /** static method to generate code for phone verification */ - public static function generateSmsVerificationFor($subject, $for = 'phone_verification', \Closure $messageCallback = null, $meta = [], $expireAfter = null) + public static function generateSmsVerificationFor($subject, $for = 'phone_verification', array $options = [], \Closure $messageCallback = null, $meta = [], $expireAfter = null) { - $verifyCode = static::generateFor($subject, $for, false); - $verifyCode->expires_at = $expireAfter === null ? Carbon::now()->addHour() : $expireAfter; - $verifyCode->meta = $meta; - $verifyCode->save(); + $expireAfter = data_get($options, 'expireAfter'); + $verificationCode = static::generateFor($subject, $for, false); + $verificationCode->expires_at = $expireAfter === null ? Carbon::now()->addHour() : $expireAfter; + $verificationCode->meta = data_get($options, 'meta', []); + $verificationCode->save(); + + // Get message + $message = 'Your ' . config('app.name') . ' verification code is ' . $verificationCode->code; + $messageCallback = data_get($options, 'messageCallback'); + if (is_callable($messageCallback)) { + $message = $messageCallback($verificationCode); + } if ($subject->phone) { - Twilio::message($subject->phone, $messageCallback ? $messageCallback($verifyCode) : 'Your ' . config('app.name') . ' verification code is ' . $verifyCode->code); + Twilio::message($subject->phone, $message); } - return $verifyCode; + return $verificationCode; } } diff --git a/src/Models/WebhookEndpoint.php b/src/Models/WebhookEndpoint.php index c9335b5..fce4dde 100644 --- a/src/Models/WebhookEndpoint.php +++ b/src/Models/WebhookEndpoint.php @@ -7,6 +7,7 @@ use Fleetbase\Traits\HasUuid; use Fleetbase\Traits\Searchable; use Spatie\Activitylog\Traits\LogsActivity; +use Spatie\Activitylog\LogOptions; class WebhookEndpoint extends Model { @@ -81,6 +82,24 @@ class WebhookEndpoint extends Model */ protected static $logName = 'webhook_endpoint'; + /** + * Get the activity log options for the model. + * + * @return \Spatie\Activitylog\LogOptions + */ + public function getActivitylogOptions(): LogOptions + { + return LogOptions::defaults() + ->logOnly([ + 'url', + 'mode', + 'version', + 'description', + 'events' + ]) + ->logOnlyDirty(); + } + /** * @return \Illuminate\Database\Eloquent\Relations\BelongsTo */ diff --git a/src/Providers/CoreServiceProvider.php b/src/Providers/CoreServiceProvider.php index 14023ed..dbaef0a 100644 --- a/src/Providers/CoreServiceProvider.php +++ b/src/Providers/CoreServiceProvider.php @@ -76,12 +76,16 @@ public function boot() $this->__hotfixCommonmarkDeprecation(); $this->registerCommands(); + $this->scheduleCommands(function ($schedule) { + $schedule->command('cache:prune-stale-tags')->hourly(); + }); $this->registerObservers(); $this->registerExpansionsFrom(); $this->registerMiddleware(); $this->registerNotifications(); $this->loadRoutesFrom(__DIR__ . '/../routes.php'); $this->loadMigrationsFrom(__DIR__ . '/../../migrations'); + $this->loadViewsFrom(__DIR__ . '/../../views', 'fleetbase'); $this->mergeConfigFrom(__DIR__ . '/../../config/database.connections.php', 'database.connections'); $this->mergeConfigFrom(__DIR__ . '/../../config/database.redis.php', 'database.redis'); $this->mergeConfigFrom(__DIR__ . '/../../config/broadcasting.connections.php', 'broadcasting.connections'); diff --git a/src/Support/TwoFactorAuth.php b/src/Support/TwoFactorAuth.php index c6d11b8..471052c 100644 --- a/src/Support/TwoFactorAuth.php +++ b/src/Support/TwoFactorAuth.php @@ -258,7 +258,10 @@ public static function sendVerificationCode(User $user, int $expiresAfter = 61): } // create verification code - return VerificationCode::generateSmsVerificationFor($user, '2fa', $messageCallback, [], $expiresAfter); + return VerificationCode::generateSmsVerificationFor($user, '2fa', [ + 'messageCallback' => $messageCallback, + 'expiresAfter' => $expiresAfter + ]); } if ($method === 'email') { @@ -267,15 +270,14 @@ public static function sendVerificationCode(User $user, int $expiresAfter = 61): throw new \Exception('No email to send 2FA code to.'); } - // 2FA Message Lines - $linesCallback = function ($verificationCode) { - return [ - new HtmlString('

Your two-factor authentication code is: ' . $verificationCode->code . '

'), - ]; - }; - // create verification code - return VerificationCode::generateEmailVerificationFor($user, '2fa', $messageCallback, $linesCallback, [], $expiresAfter); + return VerificationCode::generateEmailVerificationFor($user, '2fa', [ + 'subject' => $messageCallback, + 'content' => function ($verificationCode) { + return '

Your two-factor authentication code is: ' . $verificationCode->code . '

'; + }, + 'expiresAfter' => $expiresAfter + ]); } throw new \Exception('Invalid 2FA method selected in settings.'); diff --git a/src/Support/Utils.php b/src/Support/Utils.php index b7b8e44..92b271f 100644 --- a/src/Support/Utils.php +++ b/src/Support/Utils.php @@ -1227,10 +1227,11 @@ public static function isBase64(string $data) */ public static function generatePublicId(string $type): string { - $hashid = lcfirst(\Vinkla\Hashids\Facades\Hashids::encode(time(), rand(), rand())); - $hashid = substr($hashid, 0, 7); + $sqids = new \Sqids\Sqids(); + $id = lcfirst($sqids->encode([time(), rand(), rand()])); + $id = substr($id, 0, 7); - return $type . '_' . $hashid; + return $type . '_' . $id; } public static function formatSeconds($seconds) @@ -2172,6 +2173,11 @@ public static function getDefaultMailFromAddress(?string $default = 'hello@fleet */ public static function addWwwToUrl($url) { + if ($url === null) { + // Handle the case where $url is null + return null; + } + // Check if the URL already starts with 'http://' or 'https://' if (strpos($url, 'http://') !== 0 && strpos($url, 'https://') !== 0) { // Check if the URL starts with 'www.' diff --git a/src/Traits/HasPublicId.php b/src/Traits/HasPublicId.php index 0aca462..b3d9c95 100644 --- a/src/Traits/HasPublicId.php +++ b/src/Traits/HasPublicId.php @@ -3,7 +3,6 @@ namespace Fleetbase\Traits; use Fleetbase\Support\Utils; -use Vinkla\Hashids\Facades\Hashids; trait HasPublicId { @@ -32,7 +31,8 @@ function ($model) { */ public static function getPublicId() { - $hashid = lcfirst(Hashids::encode(time(), rand(), rand())); + $sqids = new \Sqids\Sqids(); + $hashid = lcfirst($sqids->encode([time(), rand(), rand()])); $hashid = substr($hashid, 0, 7); return $hashid; diff --git a/views/mail/verification.blade.php b/views/mail/verification.blade.php new file mode 100644 index 0000000..83255d3 --- /dev/null +++ b/views/mail/verification.blade.php @@ -0,0 +1,20 @@ +@section('greeting') + @if($currentHour < 12) + Good Morning, {{ $user->name }}! + @elseif($currentHour < 18) + Good Afternoon, {{ $user->name }}! + @else + Good Evening, {{ $user->name }}! + @endif +@endsection + +@section('content') + @if($content) + {!! $content !!} + @else +

Welcome to {{ $appName }}, use the code below to verify your email address and complete registration to {{ $appName }}.

+
+

Your verification code: {{ $code }}

+
+ @endif +@endsection