From fb69275cd7443d0221f30016e8c191a536ff256b Mon Sep 17 00:00:00 2001 From: Your Name Date: Sat, 29 Aug 2026 23:58:40 +0930 Subject: [PATCH 1/2] chore: target PHP 8.4 in psalm config Set phpVersion="8.4" so Psalm targets 8.4 explicitly instead of inferring 8.3 from composer.json's ^8.3|^8.4 constraint. --- psalm.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/psalm.xml b/psalm.xml index 6f8f24c49..ae3bbcb85 100644 --- a/psalm.xml +++ b/psalm.xml @@ -4,6 +4,7 @@ xmlns="https://getpsalm.org/schema/config" xsi:schemaLocation="https://getpsalm.org/schema/config vendor/vimeo/psalm/config.xsd" errorLevel="8" + phpVersion="8.4" findUnusedCode="false" ensureOverrideAttribute="false" errorBaseline="psalm-baseline.xml" From 878775cab95be5a29649f6e35139ffd72c4c08c6 Mon Sep 17 00:00:00 2001 From: Your Name Date: Sat, 29 Aug 2026 23:59:34 +0930 Subject: [PATCH 2/2] chore: resolve psalm issues in admin commands and auth - Add return type hints (void) and final class markers - Guard null returns from newestBackup() and putFileAs() in BackupToCloud - Type ask() default values as strings - Fix uses_left fallback condition for null/zero max_uses - Annotate AdminInvite::whereInviteCode and cast Str::uuid() to string - Ignore local redis-data and mysql-9-data dev directories --- .gitignore | 2 ++ app/Auth/BearerTokenResponse.php | 2 +- .../Commands/Admin/AdminInviteCommand.php | 14 ++++++++------ app/Console/Commands/Admin/BackupToCloud.php | 16 +++++++++++++++- app/Console/Commands/Admin/BannedEmailCheck.php | 6 ++---- .../Commands/Admin/CaptchaToggleCommand.php | 4 ++-- app/Models/AdminInvite.php | 6 +++++- 7 files changed, 35 insertions(+), 15 deletions(-) diff --git a/.gitignore b/.gitignore index 4abdbb6c8..6f7ac05c2 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,5 @@ +/redis-data +/mysql-9-data .DS_Store /.bash_history /.bash_profile diff --git a/app/Auth/BearerTokenResponse.php b/app/Auth/BearerTokenResponse.php index ce70150fe..d16e030f2 100644 --- a/app/Auth/BearerTokenResponse.php +++ b/app/Auth/BearerTokenResponse.php @@ -4,7 +4,7 @@ namespace App\Auth; use League\OAuth2\Server\Entities\AccessTokenEntityInterface; -class BearerTokenResponse extends \League\OAuth2\Server\ResponseTypes\BearerTokenResponse +final class BearerTokenResponse extends \League\OAuth2\Server\ResponseTypes\BearerTokenResponse { /** * Add custom fields to your Bearer Token response here, then override diff --git a/app/Console/Commands/Admin/AdminInviteCommand.php b/app/Console/Commands/Admin/AdminInviteCommand.php index 6f094032a..2d782bb74 100644 --- a/app/Console/Commands/Admin/AdminInviteCommand.php +++ b/app/Console/Commands/Admin/AdminInviteCommand.php @@ -8,7 +8,7 @@ use Illuminate\Console\Command; use Illuminate\Support\Facades\Mail; use Illuminate\Support\Facades\Validator; -class AdminInviteCommand extends Command +final class AdminInviteCommand extends Command { /** * The name and signature of the console command. @@ -62,7 +62,7 @@ class AdminInviteCommand extends Command }; } - protected function handleExpiredInvites() + protected function handleExpiredInvites(): void { AdminInvite::whereNotNull('expires_at')->where('expires_at', '<', now())->delete(); } @@ -81,7 +81,7 @@ class AdminInviteCommand extends Command $message = $this->ask('Invite Message (optional)', 'You\'ve been invited to join'); $this->info('Set maximum # of invite uses, use 0 for unlimited'); - $max_uses = $this->ask('Max uses', 1); + $max_uses = $this->ask('Max uses', '1'); $expires = match ($this->choice( 'Set an invite expiry date?', @@ -95,7 +95,7 @@ class AdminInviteCommand extends Command 'Yes - expire after 24 hours' => now()->addHours(24), 'No - invite never expires' => null, 'Custom - let me pick an expiry date' => now()->addDays( - (int) $this->ask('Custom expiry date in days', 14) + (int) $this->ask('Custom expiry date in days', '14') ), }; @@ -145,7 +145,9 @@ class AdminInviteCommand extends Command AdminInvite::all(['invite_code', 'max_uses', 'uses', 'expires_at'])->map(function ($invite) { return [ 'invite_code' => $invite->invite_code, - 'uses_left' => $invite->max_uses ? ($invite->max_uses - $invite->uses) : '∞', + 'uses_left' => $invite->max_uses !== null && $invite->max_uses > 0 + ? ($invite->max_uses - $invite->uses) + : '∞', 'expires_at' => $invite->expires_at ? $invite->expires_at->diffForHumans() : 'never', ]; })->toArray() @@ -156,7 +158,7 @@ class AdminInviteCommand extends Command protected function expire(): int { - $token = $this->anticipate('Enter invite code to expire', function ($val) { + $token = $this->anticipate('Enter invite code to expire', function (string $val) { return AdminInvite::query() ->where('invite_code', 'like', "%$val%") ->pluck('invite_code') diff --git a/app/Console/Commands/Admin/BackupToCloud.php b/app/Console/Commands/Admin/BackupToCloud.php index 60f6e89f6..c19910720 100644 --- a/app/Console/Commands/Admin/BackupToCloud.php +++ b/app/Console/Commands/Admin/BackupToCloud.php @@ -7,7 +7,7 @@ use Illuminate\Http\File; use Illuminate\Support\Facades\Storage; use Spatie\Backup\BackupDestination\BackupDestination; -class BackupToCloud extends Command +final class BackupToCloud extends Command { /** * The name and signature of the console command. @@ -58,12 +58,26 @@ class BackupToCloud extends Command } $newest = $backupDestination->newestBackup(); + + if ($newest === null) { + $this->error('No backup found to upload.'); + + return Command::FAILURE; + } + $name = $newest->path(); $parts = explode('/', $name); $fileName = array_pop($parts); $storagePath = 'backups'; $path = storage_path('app/'.$name); $file = $cloudDisk->putFileAs($storagePath, new File($path), $fileName, 'private'); + + if ($file === false) { + $this->error('Failed to upload the backup to cloud storage.'); + + return Command::FAILURE; + } + $this->info('Backup file successfully saved!'); $url = $cloudDisk->url($file); $this->table( diff --git a/app/Console/Commands/Admin/BannedEmailCheck.php b/app/Console/Commands/Admin/BannedEmailCheck.php index f0bec3db9..433f39ce6 100644 --- a/app/Console/Commands/Admin/BannedEmailCheck.php +++ b/app/Console/Commands/Admin/BannedEmailCheck.php @@ -6,7 +6,7 @@ use App\Models\User; use App\Services\EmailService; use Illuminate\Console\Command; -class BannedEmailCheck extends Command +final class BannedEmailCheck extends Command { /** * The name and signature of the console command. @@ -34,10 +34,8 @@ class BannedEmailCheck extends Command /** * Execute the console command. - * - * @return mixed */ - public function handle() + public function handle(): void { $users = User::whereNull('status')->get()->filter(function ($u) { return EmailService::isBanned($u->email) == true; diff --git a/app/Console/Commands/Admin/CaptchaToggleCommand.php b/app/Console/Commands/Admin/CaptchaToggleCommand.php index f9d574b54..b1a60a752 100644 --- a/app/Console/Commands/Admin/CaptchaToggleCommand.php +++ b/app/Console/Commands/Admin/CaptchaToggleCommand.php @@ -8,7 +8,7 @@ use Illuminate\Console\Command; use function Laravel\Prompts\confirm; use function Laravel\Prompts\info; -class CaptchaToggleCommand extends Command +final class CaptchaToggleCommand extends Command { /** * The name and signature of the console command. @@ -27,7 +27,7 @@ class CaptchaToggleCommand extends Command /** * Execute the console command. */ - public function handle() + public function handle(): void { $captchaEnabled = (bool) config_cache('captcha.enabled'); diff --git a/app/Models/AdminInvite.php b/app/Models/AdminInvite.php index f74a78fde..b65a08430 100644 --- a/app/Models/AdminInvite.php +++ b/app/Models/AdminInvite.php @@ -2,9 +2,13 @@ namespace App\Models; +use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Model; use Illuminate\Support\Str; +/** + * @method static Builder|AdminInvite whereInviteCode(string $value) + */ class AdminInvite extends Model { protected $guarded = []; @@ -20,7 +24,7 @@ class AdminInvite extends Model protected static function booted(): void { static::creating(function (AdminInvite $invite) { - $invite->invite_code = Str::uuid().Str::random(random_int(1, 6)); + $invite->invite_code = (string) Str::uuid().Str::random(random_int(1, 6)); }); }