diff --git a/app/Services/AvatarStorageService.php b/app/Services/AvatarStorageService.php
index 983b144b94..889293d668 100644
--- a/app/Services/AvatarStorageService.php
+++ b/app/Services/AvatarStorageService.php
@@ -28,11 +28,11 @@ class AvatarStorageService
$oldS3StorageId = $user->avatar_s3_storage_id;
$oldPath = $user->avatar_path;
- $user->update([
+ $user->fill([
'avatar_path' => $path,
'avatar_storage_type' => $storageType,
'avatar_s3_storage_id' => $s3StorageId,
- ]);
+ ])->touch();
if ($oldPath && ($oldStorageType !== $storageType || $oldS3StorageId !== $s3StorageId)) {
$this->disk($oldStorageType ?? 'local', $oldS3StorageId)->delete($oldPath);
diff --git a/app/Services/TeamAvatarStorageService.php b/app/Services/TeamAvatarStorageService.php
index ee66f8bff7..6ead014a29 100644
--- a/app/Services/TeamAvatarStorageService.php
+++ b/app/Services/TeamAvatarStorageService.php
@@ -28,7 +28,7 @@ class TeamAvatarStorageService extends AvatarStorageService
'avatar_path' => $path,
'avatar_storage_type' => $storageType,
'avatar_s3_storage_id' => $s3StorageId,
- ])->save();
+ ])->touch();
if ($oldPath && ($oldStorageType !== $storageType || $oldS3StorageId !== $s3StorageId)) {
$this->disk($oldStorageType ?? 'local', $oldS3StorageId)->delete($oldPath);
diff --git a/resources/views/livewire/server/index.blade.php b/resources/views/livewire/server/index.blade.php
index c9d0b150e4..cd7a3ba4ab 100644
--- a/resources/views/livewire/server/index.blade.php
+++ b/resources/views/livewire/server/index.blade.php
@@ -61,6 +61,7 @@
'name' => $server->name,
'description' => $server->description,
'href' => route('server.show', ['server_uuid' => $server->uuid]),
+ 'patchesHref' => route('server.security.patches', ['server_uuid' => $server->uuid]),
'status' => $status,
'statusType' => $statusType,
'resourceCount' => $server->definedResources()->count(),
@@ -91,6 +92,9 @@
.some(value => String(value || '').toLowerCase().includes(query))
);
},
+ openPatches(url) {
+ window.Livewire.navigate(url);
+ },
setViewMode(mode) {
this.viewMode = mode;
this.$wire.saveListView('servers', mode);
@@ -167,7 +171,10 @@
$cardPatchBadges = array_filter($serverRow['patchBadges'], fn ($badge) => $badge['type'] !== 'success');
@endphp
@if ($cardPatchBadges !== [])
-
@foreach ($cardPatchBadges as $badge)
@@ -222,7 +229,10 @@
@if ($showPatches)
-
+
and(Storage::disk('images')->size($user->avatar_path))->toBeLessThan(100_000);
})->skip(! extension_loaded('gd'), 'Requires the GD extension to generate and compress the test image.');
+it('changes the profile picture URL when an existing picture is replaced', function () {
+ Storage::fake('images');
+ $user = User::factory()->create();
+ $this->actingAs($user);
+ $upload = fn () => UploadedFile::fake()->createWithContent('avatar.jpg', file_get_contents(base_path('tests/Fixtures/project-icon.jpg')));
+
+ app(AvatarStorageService::class)->store($user, $upload());
+ $oldUrl = profile_avatar_url($user->fresh());
+
+ $this->travel(1)->minutes();
+ app(AvatarStorageService::class)->store($user->fresh(), $upload());
+
+ expect(profile_avatar_url($user->fresh()))->not->toBe($oldUrl);
+});
+
it('stores an already compressed browser JPEG without server image extensions', function () {
Storage::fake('images');
$user = User::factory()->create(['name' => 'Test User']);
diff --git a/tests/Feature/TeamAvatarTest.php b/tests/Feature/TeamAvatarTest.php
index f2c91172ae..1b91491acf 100644
--- a/tests/Feature/TeamAvatarTest.php
+++ b/tests/Feature/TeamAvatarTest.php
@@ -29,7 +29,7 @@ beforeEach(function () {
function giveTeamStoredAvatar(Team $team): string
{
$path = "team-avatars/{$team->id}/avatar.jpg";
- $team->forceFill(['avatar_path' => $path, 'avatar_storage_type' => 'local'])->save();
+ $team->forceFill(['avatar_path' => $path, 'avatar_storage_type' => 'local'])->saveQuietly();
Storage::disk('images')->put($path, 'avatar-content');
return $path;
@@ -53,6 +53,22 @@ it('stores a team avatar using the instance image storage setting', function ()
Storage::disk('images')->assertExists($this->team->avatar_path);
});
+it('changes the team avatar URL when an existing avatar is replaced', function () {
+ Storage::fake('images');
+ giveTeamStoredAvatar($this->team);
+ $oldUrl = team_avatar_url($this->team->fresh());
+ $upload = UploadedFile::fake()->createWithContent('team.jpg', file_get_contents(base_path('tests/Fixtures/project-icon.jpg')));
+
+ $this->travel(1)->minutes();
+
+ Livewire::test(Index::class)
+ ->set('avatar', $upload)
+ ->call('uploadAvatar')
+ ->assertHasNoErrors();
+
+ expect(team_avatar_url($this->team->fresh()))->not->toBe($oldUrl);
+});
+
it('does not let a team member change the team avatar', function () {
Storage::fake('images');
$path = giveTeamStoredAvatar($this->team);
@@ -123,7 +139,7 @@ it('deletes the avatar file when the team is deleted', function () {
it('shows team avatars in the team switcher', function () {
$otherTeam = Team::factory()->create(['name' => 'Zeta']);
$this->user->teams()->attach($otherTeam, ['role' => 'member']);
- $otherTeam->forceFill(['avatar_path' => "team-avatars/{$otherTeam->id}/avatar.jpg", 'avatar_storage_type' => 'local'])->save();
+ $otherTeam->forceFill(['avatar_path' => "team-avatars/{$otherTeam->id}/avatar.jpg", 'avatar_storage_type' => 'local'])->saveQuietly();
$this->user->load('teams');
Livewire::test(SwitchTeam::class)