add photo delete logic to user update endpoint

This commit is contained in:
Gregor Vostrak
2026-05-26 13:23:31 +02:00
parent 5b756be058
commit 00ffabe108
3 changed files with 92 additions and 11 deletions

View File

@@ -49,18 +49,23 @@ class UserController extends Controller
throw new AuthorizationException;
}
if ($request->getPhoto() !== null) {
$photo = Base64File::decode($request->getPhoto());
assert($photo !== null);
$extension = Base64File::extension($photo['mime_type']);
assert($extension !== null);
$previousPhotoPath = $user->profile_photo_path;
$photoPath = 'profile-photos/'.Str::uuid().'.'.$extension;
if ($request->hasPhotoKey()) {
$photoDisk = (string) config('jetstream.profile_photo_disk', 'public');
$previousPhotoPath = $user->profile_photo_path;
$newPhoto = $request->getPhoto();
Storage::disk($photoDisk)->put($photoPath, $photo['data'], 'public');
$user->profile_photo_path = $photoPath;
if ($newPhoto === null) {
$user->profile_photo_path = null;
} else {
$decoded = Base64File::decode($newPhoto);
assert($decoded !== null);
$extension = Base64File::extension($decoded['mime_type']);
assert($extension !== null);
$photoPath = 'profile-photos/'.Str::uuid().'.'.$extension;
Storage::disk($photoDisk)->put($photoPath, $decoded['data'], 'public');
$user->profile_photo_path = $photoPath;
}
if ($previousPhotoPath !== null) {
Storage::disk($photoDisk)->delete($previousPhotoPath);

View File

@@ -81,8 +81,15 @@ class UserUpdateRequest extends BaseFormRequest
return $this->has('week_start') ? Weekday::from($this->input('week_start')) : null;
}
public function hasPhotoKey(): bool
{
return $this->has('photo');
}
public function getPhoto(): ?string
{
return $this->has('photo') ? (string) $this->input('photo') : null;
$value = $this->input('photo');
return is_string($value) ? $value : null;
}
}

View File

@@ -354,6 +354,75 @@ class UserEndpointTest extends ApiEndpointTestAbstract
$response->assertJsonValidationErrors(['photo']);
}
public function test_update_with_null_photo_deletes_photo_file_and_clears_profile_photo_path(): void
{
// Arrange
$data = $this->createUserWithPermission();
$photoDisk = (string) config('jetstream.profile_photo_disk', 'public');
$photoPath = 'profile-photos/existing.png';
Storage::fake($photoDisk);
Storage::disk($photoDisk)->put($photoPath, 'photo contents');
$data->user->profile_photo_path = $photoPath;
$data->user->save();
Passport::actingAs($data->user);
// Act
$response = $this->putJson(route('api.v1.users.update', $data->user->getKey()), [
'photo' => null,
]);
// Assert
$response->assertSuccessful();
$user = $data->user->fresh();
$this->assertNull($user->profile_photo_path);
Storage::disk($photoDisk)->assertMissing($photoPath);
}
public function test_update_with_null_photo_is_a_noop_when_user_has_no_photo(): void
{
// Arrange
$data = $this->createUserWithPermission();
$photoDisk = (string) config('jetstream.profile_photo_disk', 'public');
Storage::fake($photoDisk);
$data->user->profile_photo_path = null;
$data->user->save();
Passport::actingAs($data->user);
// Act
$response = $this->putJson(route('api.v1.users.update', $data->user->getKey()), [
'photo' => null,
]);
// Assert
$response->assertSuccessful();
$user = $data->user->fresh();
$this->assertNull($user->profile_photo_path);
}
public function test_update_without_photo_key_leaves_existing_photo_untouched(): void
{
// Arrange
$data = $this->createUserWithPermission();
$photoDisk = (string) config('jetstream.profile_photo_disk', 'public');
$photoPath = 'profile-photos/existing.png';
Storage::fake($photoDisk);
Storage::disk($photoDisk)->put($photoPath, 'photo contents');
$data->user->profile_photo_path = $photoPath;
$data->user->save();
Passport::actingAs($data->user);
// Act
$response = $this->putJson(route('api.v1.users.update', $data->user->getKey()), [
'name' => 'Just A Name Change',
]);
// Assert
$response->assertSuccessful();
$user = $data->user->fresh();
$this->assertSame($photoPath, $user->profile_photo_path);
Storage::disk($photoDisk)->assertExists($photoPath);
}
public function test_delete_fails_if_given_user_is_not_the_authenticated_user(): void
{
// Arrange