Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: prepare AWS S3 upload for avatars #1747

Merged
merged 14 commits into from
Sep 2, 2018
97 changes: 97 additions & 0 deletions app/Console/Commands/OneTime/MoveAvatars.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
<?php

namespace App\Console\Commands\OneTime;

use App\Models\Contact\Contact;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\Storage;
use Illuminate\Console\ConfirmableTrait;
use Symfony\Component\Console\Output\OutputInterface;
use Illuminate\Contracts\Filesystem\FileNotFoundException;

class MoveAvatars extends Command
{
use ConfirmableTrait;

/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'monica:moveavatars {--force : Force the operation to run when in production.} {--dryrun : Simulate the execution but not write anything.}';

/**
* The console command description.
*
* @var string
*/
protected $description = 'Move avatars from their current storage to current default storage';

/**
* Execute the console command.
*
* @return mixed
*/
public function handle()
{
if (! $this->confirmToProceed()) {
return;
}

Contact::where('has_avatar', true)
->chunk(200, function ($contacts) {
foreach ($contacts as $contact) {
if ($contact->avatar_location == config('filesystems.default')) {
return;
}

try {
// move avatars to new location
$this->moveAvatarSize($contact);
$this->moveAvatarSize($contact, 110);
$this->moveAvatarSize($contact, 174);

if (! $this->option('dryrun')) {
$contact->deleteAvatars();

// Update location. The filename has not changed.
$contact->avatar_location = config('filesystems.default');
$contact->save();
}
} catch (FileNotFoundException $e) {
continue;
}
}
});
}

private function moveAvatarSize($contact, $size = null)
{
$filename = pathinfo($contact->avatar_file_name, PATHINFO_FILENAME);
$extension = pathinfo($contact->avatar_file_name, PATHINFO_EXTENSION);

$avatarFileName = 'avatars/'.$filename.'.'.$extension;
if (! is_null($size)) {
$avatarFileName = 'avatars/'.$filename.'_'.$size.'.'.$extension;
}

$storage = Storage::disk($contact->avatar_location);
if (! $storage->exists($avatarFileName)) {
if ($this->getOutput()->getVerbosity() >= OutputInterface::VERBOSITY_VERBOSE) {
$this->line('File not found: '.$avatarFileName);
}

return;
}
$avatarFile = $storage->get($avatarFileName);

$newStorage = Storage::disk(config('filesystems.default'));
if (! $this->option('dryrun')) {
$newStorage->put($avatarFileName, $avatarFile, 'public');
}

if ($this->getOutput()->getVerbosity() >= OutputInterface::VERBOSITY_VERBOSE) {
$this->line('Moved file '.$avatarFileName);
}
}
}
1 change: 1 addition & 0 deletions app/Console/Kernel.php
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ class Kernel extends ConsoleKernel
'App\Console\Commands\SetPremiumAccount',
'App\Console\Commands\Update',
'App\Console\Commands\MigrateDatabaseCollation',
'App\Console\Commands\OneTime\MoveAvatars',
'App\Console\Commands\Reminder\ProcessOldReminders',
];

Expand Down
12 changes: 12 additions & 0 deletions app/Exceptions/MissingParameterException.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<?php

namespace App\Exceptions;

use RuntimeException;

/**
* Exception MissingParameterException.
*/
class MissingParameterException extends RuntimeException
{
}
17 changes: 14 additions & 3 deletions app/Http/Controllers/ContactsController.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
use App\Helpers\SearchHelper;
use App\Models\Contact\Contact;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Storage;
use App\Models\Relationship\Relationship;
use Barryvdh\Debugbar\Facade as Debugbar;
use Illuminate\Support\Facades\Validator;
Expand Down Expand Up @@ -275,9 +276,19 @@ public function update(Request $request, Contact $contact)
$contact->nickname = $request->input('nickname', null);

if ($request->file('avatar') != '') {
if ($contact->has_avatar) {
try {
$contact->deleteAvatars();
} catch (\Exception $e) {
return back()
->withInput()
->withErrors(trans('app.error_save'));
}
}

$contact->has_avatar = true;
$contact->avatar_location = config('filesystems.default');
$contact->avatar_file_name = $request->avatar->store('avatars', config('filesystems.default'));
$contact->avatar_file_name = $request->avatar->storePublicly('avatars', $contact->avatar_location);
}

// Is the person deceased?
Expand All @@ -291,7 +302,7 @@ public function update(Request $request, Contact $contact)
$specialDate = $contact->setSpecialDate('deceased_date', $request->input('deceased_date_year'), $request->input('deceased_date_month'), $request->input('deceased_date_day'));

if ($request->input('addReminderDeceased') != '') {
$newReminder = $specialDate->setReminder('year', 1, trans('people.deceased_reminder_title', ['name' => $contact->first_name]));
$specialDate->setReminder('year', 1, trans('people.deceased_reminder_title', ['name' => $contact->first_name]));
}
}
}
Expand All @@ -315,7 +326,7 @@ public function update(Request $request, Contact $contact)
);

if ($request->input('addReminder') != '') {
$newReminder = $specialDate->setReminder('year', 1, trans('people.people_add_birthday_reminder', ['name' => $contact->first_name]));
$specialDate->setReminder('year', 1, trans('people.people_add_birthday_reminder', ['name' => $contact->first_name]));
}

break;
Expand Down
51 changes: 29 additions & 22 deletions app/Jobs/ResizeAvatars.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,12 @@

use Illuminate\Bus\Queueable;
use App\Models\Contact\Contact;
use Intervention\Image\Facades\Image;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Facades\Storage;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Intervention\Image\Facades\Image as Image;
use Illuminate\Contracts\Filesystem\FileNotFoundException;

class ResizeAvatars implements ShouldQueue
Expand All @@ -35,27 +35,34 @@ public function __construct(Contact $contact)
*/
public function handle()
{
if ($this->contact->has_avatar) {
try {
$avatar_file = Storage::disk($this->contact->avatar_location)->get($this->contact->avatar_file_name);
$avatar_path = Storage::disk($this->contact->avatar_location)->url($this->contact->avatar_file_name);
$avatar_filename_without_extension = pathinfo($avatar_path, PATHINFO_FILENAME);
$avatar_extension = pathinfo($avatar_path, PATHINFO_EXTENSION);
} catch (FileNotFoundException $e) {
return;
}

$size = 110;
$avatar_cropped_path = 'avatars/'.$avatar_filename_without_extension.'_'.$size.'.'.$avatar_extension;
$avatar = Image::make($avatar_file);
$avatar->fit($size);
Storage::disk($this->contact->avatar_location)->put($avatar_cropped_path, $avatar->stream()->__toString());

$size = 174;
$avatar_cropped_path = 'avatars/'.$avatar_filename_without_extension.'_'.$size.'.'.$avatar_extension;
$avatar = Image::make($avatar_file);
$avatar->fit($size);
Storage::disk($this->contact->avatar_location)->put($avatar_cropped_path, $avatar->stream()->__toString());
if (! $this->contact->has_avatar) {
return;
}

$storage = Storage::disk($this->contact->avatar_location);
if (! $storage->exists($this->contact->avatar_file_name)) {
return;
}

try {
$avatarFile = $storage->get($this->contact->avatar_file_name);
$filename = pathinfo($this->contact->avatar_file_name, PATHINFO_FILENAME);
$extension = pathinfo($this->contact->avatar_file_name, PATHINFO_EXTENSION);
} catch (FileNotFoundException $e) {
return;
}

$this->resize($avatarFile, $filename, $extension, $storage, 110);
$this->resize($avatarFile, $filename, $extension, $storage, 174);
}

private function resize($avatarFile, $filename, $extension, $storage, $size)
{
$avatarFileName = 'avatars/'.$filename.'_'.$size.'.'.$extension;

$avatar = Image::make($avatarFile);
$avatar->fit($size);

$storage->put($avatarFileName, (string) $avatar->stream(), 'public');
}
}
53 changes: 48 additions & 5 deletions app/Models/Contact/Contact.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,12 @@
use App\Models\ModelBindingHasher as Model;
use App\Models\Relationship\RelationshipType;
use App\Http\Resources\Tag\Tag as TagResource;
use Illuminate\Contracts\Filesystem\Filesystem;
use Illuminate\Database\Eloquent\Relations\HasOne;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\ModelNotFoundException;
use Illuminate\Contracts\Filesystem\FileNotFoundException;
use App\Http\Resources\Address\AddressShort as AddressShortResource;
use App\Http\Resources\Contact\ContactShort as ContactShortResource;
use App\Http\Resources\ContactField\ContactField as ContactFieldResource;
Expand Down Expand Up @@ -957,12 +959,53 @@ public function getAvatarURL($size = 110)
return $this->avatar_external_url;
}

$original_avatar_url = Storage::disk($this->avatar_location)->url($this->avatar_file_name);
$avatar_filename = pathinfo($original_avatar_url, PATHINFO_FILENAME);
$avatar_extension = pathinfo($original_avatar_url, PATHINFO_EXTENSION);
$resized_avatar = 'avatars/'.$avatar_filename.'_'.$size.'.'.$avatar_extension;
$originalAvatarUrl = $this->avatar_file_name;
$avatarFilename = pathinfo($originalAvatarUrl, PATHINFO_FILENAME);
$avatarExtension = pathinfo($originalAvatarUrl, PATHINFO_EXTENSION);
$resizedAvatar = 'avatars/'.$avatarFilename.'_'.$size.'.'.$avatarExtension;

return asset(Storage::disk($this->avatar_location)->url($resized_avatar));
return asset(Storage::disk($this->avatar_location)->url($resizedAvatar));
}

/**
* Delete avatars files.
* This does not touch avatar_location or avatar_file_name properties of the contact.
*/
public function deleteAvatars()
{
if (! $this->has_avatar || $this->avatar_location == 'external') {
return;
}

$storage = Storage::disk($this->avatar_location);
$this->deleteAvatarSize($storage);
$this->deleteAvatarSize($storage, 110);
$this->deleteAvatarSize($storage, 174);
}

/**
* Delete avatar file for one size.
*
* @param Filesystem $storage
* @param int $size
*/
private function deleteAvatarSize(Filesystem $storage, int $size = null)
{
$avatarFileName = $this->avatar_file_name;

if (! is_null($size)) {
$filename = pathinfo($avatarFileName, PATHINFO_FILENAME);
$extension = pathinfo($avatarFileName, PATHINFO_EXTENSION);
$avatarFileName = 'avatars/'.$filename.'_'.$size.'.'.$extension;
}

try {
if ($storage->exists($avatarFileName)) {
$storage->delete($avatarFileName);
}
} catch (FileNotFoundException $e) {
return;
}
}

/**
Expand Down
1 change: 1 addition & 0 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
"laravel/socialite": "^3.0",
"laravel/tinker": "^1.0",
"league/flysystem-aws-s3-v3": "~1.0",
"league/flysystem-cached-adapter": "^1.0",
"mariuzzo/laravel-js-localization": "^1.4",
"matriphe/iso-639": "^1.0",
"moneyphp/money": "^3.1",
Expand Down
Loading