Skip to content

Commit

Permalink
Merge pull request #238 from pinkary-project/feat/upload-avatar
Browse files Browse the repository at this point in the history
Feat: Allow users to upload avatar
  • Loading branch information
nunomaduro authored Apr 19, 2024
2 parents db2c7ec + 39df318 commit 6d840b8
Show file tree
Hide file tree
Showing 32 changed files with 567 additions and 345 deletions.
4 changes: 2 additions & 2 deletions app/Http/Controllers/Auth/RegisteredUserController.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

namespace App\Http\Controllers\Auth;

use App\Jobs\DownloadUserAvatar;
use App\Jobs\UpdateUserAvatar;
use App\Models\User;
use App\Rules\Recaptcha;
use App\Rules\Username;
Expand Down Expand Up @@ -52,7 +52,7 @@ public function store(Request $request): RedirectResponse

Auth::login($user);

dispatch(new DownloadUserAvatar($user));
dispatch(new UpdateUserAvatar($user));

return redirect(route('profile.show', [
'username' => $user->username,
Expand Down
42 changes: 42 additions & 0 deletions app/Http/Controllers/Profile/AvatarController.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
<?php

declare(strict_types=1);

namespace App\Http\Controllers\Profile;

use App\Http\Requests\UpdateUserAvatarRequest;
use App\Jobs\UpdateUserAvatar;
use App\Models\User;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Http\UploadedFile;

final readonly class AvatarController
{
/**
* Handles the verified refresh.
*/
public function update(UpdateUserAvatarRequest $request): RedirectResponse
{
$user = type(request()->user())->as(User::class);

$file = type($request->file('avatar'))->as(UploadedFile::class);
UpdateUserAvatar::dispatchSync($user, $file->getRealPath());

return to_route('profile.edit')
->with('flash-message', 'Avatar updated.');
}

/**
* Delete the existing avatar.
*/
public function delete(Request $request): RedirectResponse
{
$user = type($request->user())->as(User::class);

UpdateUserAvatar::dispatchSync($user);

return to_route('profile.edit')
->with('flash-message', 'Avatar deleted.');
}
}
6 changes: 4 additions & 2 deletions app/Http/Controllers/ProfileController.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@
namespace App\Http\Controllers;

use App\Http\Requests\ProfileUpdateRequest;
use App\Jobs\DownloadUserAvatar;
use App\Jobs\IncrementViews;
use App\Jobs\UpdateUserAvatar;
use App\Models\User;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
Expand Down Expand Up @@ -51,7 +51,9 @@ public function update(ProfileUpdateRequest $request): RedirectResponse

$user->save();

dispatch(new DownloadUserAvatar($user));
if (! $user->is_uploaded_avatar) {
UpdateUserAvatar::dispatch($user);
}

session()->flash('flash-message', 'Profile updated.');

Expand Down
25 changes: 25 additions & 0 deletions app/Http/Requests/UpdateUserAvatarRequest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<?php

declare(strict_types=1);

namespace App\Http\Requests;

use Illuminate\Contracts\Validation\ValidationRule;
use Illuminate\Contracts\Validation\ValidatorAwareRule;
use Illuminate\Foundation\Http\FormRequest;
use Stringable;

final class UpdateUserAvatarRequest extends FormRequest
{
/**
* Get the validation rules that apply to the request.
*
* @return array<string, array<int, ValidatorAwareRule|ValidationRule|Stringable|string>>
*/
public function rules(): array
{
return [
'avatar' => ['required', 'image', 'mimes:jpg,jpeg,png', 'max:2048'],
];
}
}
78 changes: 0 additions & 78 deletions app/Jobs/DownloadUserAvatar.php

This file was deleted.

2 changes: 1 addition & 1 deletion app/Jobs/IncrementViews.php
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ final class IncrementViews implements ShouldQueue
*
* @param Collection<array-key, Question>|Collection<array-key, User> $viewables
*/
public function __construct(protected Collection $viewables, protected int|string $id)
public function __construct(private Collection $viewables, private int|string $id)
{
//
}
Expand Down
99 changes: 99 additions & 0 deletions app/Jobs/UpdateUserAvatar.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
<?php

declare(strict_types=1);

namespace App\Jobs;

use App\Models\User;
use App\Services\Avatar;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Facades\File;
use Illuminate\Support\Facades\Storage;
use Intervention\Image\Drivers;
use Intervention\Image\ImageManager;
use Throwable;

final class UpdateUserAvatar implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;

/**
* Create a new job instance.
*/
public function __construct(private User $user, private ?string $file = null)
{
//
}

/**
* Execute the job.
*/
public function handle(): void
{
$disk = Storage::disk('public');

if ($this->user->avatar) {
if ($disk->exists(str_replace('storage/', '', $this->user->avatar))) {
$disk->delete(str_replace('storage/', '', $this->user->avatar));
}
}

$file = $this->file !== null ? $this->file : (new Avatar($this->user->email))->url();

$contents = (string) file_get_contents($file);

$avatar = 'avatars/'.hash('sha256', random_int(0, PHP_INT_MAX).'@'.$this->user->id).'.png';

Storage::disk('public')->put($avatar, $contents, 'public');

$this->resizer()->read($disk->path($avatar))
->resize(200, 200)
->save();

$this->user->update([
'avatar' => "storage/$avatar",
'avatar_updated_at' => now(),
'is_uploaded_avatar' => $this->file !== null,
]);

$this->ensureFileIsDeleted();
}

/**
* Handle a job failure.
*/
public function failed(?Throwable $exception): void
{
$this->ensureFileIsDeleted();

$this->user->update([
'avatar' => null,
'avatar_updated_at' => null,
'is_uploaded_avatar' => false,
]);
}

/**
* Ensure the file is deleted.
*/
private function ensureFileIsDeleted(): void
{
if ($this->file !== null) {
File::delete($this->file);
}
}

/**
* Creates a new image resizer.
*/
private function resizer(): ImageManager
{
return new ImageManager(
new Drivers\Gd\Driver(),
);
}
}
6 changes: 4 additions & 2 deletions app/Livewire/Links/Create.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

namespace App\Livewire\Links;

use App\Jobs\DownloadUserAvatar;
use App\Jobs\UpdateUserAvatar;
use App\Models\User;
use Illuminate\Http\Request;
use Illuminate\Support\Str;
Expand Down Expand Up @@ -53,7 +53,9 @@ public function store(Request $request): void

$user->links()->create($validated);

dispatch(new DownloadUserAvatar($user));
if (! $user->is_uploaded_avatar) {
dispatch(new UpdateUserAvatar($user));
}

$this->description = '';
$this->url = '';
Expand Down
38 changes: 5 additions & 33 deletions app/Livewire/Links/Index.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

namespace App\Livewire\Links;

use App\Jobs\DownloadUserAvatar;
use App\Jobs\UpdateUserAvatar;
use App\Models\Link;
use App\Models\User;
use Illuminate\Auth\Access\AuthorizationException;
Expand Down Expand Up @@ -42,24 +42,6 @@ public function click(int $linkId): void
Cache::put($cacheKey, true, now()->addDay());
}

/**
* Reset the user's avatar.
*/
public function resetAvatar(): void
{
$user = type(auth()->user())->as(User::class);

if (! $this->canResetAvatar($user)) {
$this->dispatch('notification.created', message: 'You have to wait 24 hours before resetting the avatar again.');

return;
}

dispatch_sync(new DownloadUserAvatar($user));

$this->dispatch('notification.created', message: 'Avatar reset.');
}

/**
* Store the new order of the links.
*
Expand Down Expand Up @@ -93,10 +75,12 @@ public function destroy(int $linkId): void

$this->authorize('delete', $link);

dispatch(new DownloadUserAvatar($user));

$link->delete();

if (! $user->is_uploaded_avatar) {
dispatch(new UpdateUserAvatar($user));
}

$this->dispatch('notification.created', message: 'Link deleted.');
}

Expand All @@ -120,7 +104,6 @@ public function render(): View

return view('livewire.links.index', [
'user' => $user,
'canResetAvatar' => $this->canResetAvatar($user),
'questionsReceivedCount' => $user->questionsReceived()
->where('is_reported', false)
->where('is_ignored', false)
Expand All @@ -134,15 +117,4 @@ public function render(): View
})->values(),
]);
}

/**
* Determine if the user can reset the avatar.
*/
private function canResetAvatar(User $user): bool
{
return auth()->id() === $this->userId && (
$user->avatar_updated_at === null
|| $user->avatar_updated_at->diffInHours(now()) > 24
);
}
}
Loading

0 comments on commit 6d840b8

Please sign in to comment.