Skip to content

Commit

Permalink
feat: use uploadcare to upload and manage files (#616)
Browse files Browse the repository at this point in the history
  • Loading branch information
djaiss committed Mar 13, 2021
1 parent fce1e15 commit 9e04c87
Show file tree
Hide file tree
Showing 29 changed files with 687 additions and 319 deletions.
8 changes: 8 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -68,3 +68,11 @@ MAPBOX_USERNAME=
# CurrencyLayeer has a generous 250 free requests per month.
CURRENCY_LAYER_PLAN=free #values: free|premium
CURRENCY_LAYER_API_KEY=

# API key for uploading files
# We use Uploadcare (https://uploadcare.com) to upload and store all user
# generated files.
# Uploadcare is GDPR and all privacy laws complient.
# It also provides a generous free plan.
UPLOADCARE_PUBLIC_KEY=
UPLOADCARE_PRIVATE_KEY=
23 changes: 23 additions & 0 deletions app/Events/FileDeleted.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<?php

namespace App\Events;

use App\Models\Company\File;
use Illuminate\Queue\SerializesModels;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Broadcasting\InteractsWithSockets;

class FileDeleted
{
use Dispatchable, InteractsWithSockets, SerializesModels;

public File $file;

/**
* Create a new event instance.
*/
public function __construct(File $file)
{
$this->file = $file;
}
}
9 changes: 9 additions & 0 deletions app/Exceptions/EnvVariablesNotSetException.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<?php

namespace App\Exceptions;

use Exception;

class EnvVariablesNotSetException extends Exception
{
}
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ public function upload(): Response
{
return Inertia::render('Adminland/Employee/Import', [
'notifications' => NotificationHelper::getNotifications(InstanceHelper::getLoggedEmployee()),
'uploadcarePublicKey' => config('officelife.uploadcare_public_key'),
]);
}

Expand All @@ -62,13 +63,19 @@ public function store(Request $request, int $companyId): JsonResponse
$file = (new UploadFile)->execute([
'company_id' => $loggedCompany->id,
'author_id' => $loggedEmployee->id,
'file' => $request->file('csv'),
'uuid' => $request->input('uuid'),
'name' => $request->input('name'),
'original_url' => $request->input('original_url'),
'cdn_url' => $request->input('cdn_url'),
'mime_type' => $request->input('mime_type'),
'size' => $request->input('size'),
'type' => 'csv',
]);

$job = (new StoreEmployeesFromCSVInTemporaryTable)->execute([
'company_id' => $loggedCompany->id,
'author_id' => $loggedEmployee->id,
'path' => $file->path,
'file_id' => $file->id,
]);

return response()->json([
Expand Down
7 changes: 7 additions & 0 deletions app/Http/ViewHelpers/Adminland/AdminGeneralViewHelper.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,11 @@
namespace App\Http\ViewHelpers\Adminland;

use App\Helpers\DateHelper;
use App\Models\Company\File;
use App\Helpers\AvatarHelper;
use App\Models\Company\Company;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\DB;
use Money\Currencies\ISOCurrencies;

class AdminGeneralViewHelper
Expand Down Expand Up @@ -43,12 +45,17 @@ public static function information($company): ?array
// creation date of the account
$creationDate = DateHelper::formatShortDateWithTime($company->created_at);

// total file sizes
$totalSize = DB::table('files')->where('company_id', $company->id)
->sum('size');

return [
'id' => $company->id,
'name' => $name,
'administrators' => $administratorsCollection,
'creation_date' => $creationDate,
'currency' => $company->currency,
'total_size' => round($totalSize / 1000, 4),
];
}

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

namespace App\Jobs;

use Uploadcare\Api;
use App\Models\Company\File;
use Illuminate\Bus\Queueable;
use Uploadcare\Configuration;
use Illuminate\Queue\SerializesModels;
use Http\Client\Exception\HttpException;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Uploadcare\File\File as UploadcareFile;
use App\Exceptions\EnvVariablesNotSetException;
use Uploadcare\Interfaces\File\FileInfoInterface;
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;

class DeleteFileInUploadcare implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;

/**
* The file instance.
*
* @var File
*/
public File $file;

/**
* The file in Uploadcare instance.
*
* @var FileInfoInterface
*/
public FileInfoInterface $fileInUploadcare;

/**
* The API used to query Uploadcare.
*
* @var Api
*/
public Api $api;

/**
* Create a new job instance.
*
* @param File $file
*/
public function __construct(File $file)
{
$this->file = $file;
}

/**
* Execute the job.
*/
public function handle(): void
{
$this->checkAPIKeyPresence();
$this->getFileFromUploadcare();
$this->deleteFile();
}

private function checkAPIKeyPresence(): void
{
if (is_null(config('officelife.uploadcare_private_key'))) {
throw new EnvVariablesNotSetException();
}

if (is_null(config('officelife.uploadcare_public_key'))) {
throw new EnvVariablesNotSetException();
}
}

private function getFileFromUploadcare(): void
{
$configuration = Configuration::create(config('officelife.uploadcare_public_key'), config('officelife.uploadcare_private_key'));
$this->api = new Api($configuration);

try {
$this->fileInUploadcare = $this->api->file()->fileInfo($this->file->uuid);
} catch (HttpException $e) {
throw new BadRequestHttpException($e->getMessage());
}
}

private function deleteFile(): void
{
// if (! $this->fileInUploadcare instanceof UploadcareFile) {
$this->api->file()->deleteFile($this->fileInUploadcare);
// } else {
//$this->fileInUploadcare->delete();
// }
}
}
26 changes: 26 additions & 0 deletions app/Listeners/DeleteFileInStorage.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<?php

namespace App\Listeners;

use App\Events\FileDeleted;
use App\Jobs\DeleteFileInUploadcare;

class DeleteFileInStorage
{
/**
* Create the event listener.
*/
public function __construct()
{
}

/**
* Handle the event.
*
* @param FileDeleted $event
*/
public function handle(FileDeleted $event)
{
DeleteFileInUploadcare::dispatch($event->file);
}
}
10 changes: 10 additions & 0 deletions app/Models/Company/Employee.php
Original file line number Diff line number Diff line change
Expand Up @@ -539,6 +539,16 @@ public function timesheetsAsApprover()
return $this->hasMany(Timesheet::class, 'approver_id', 'id');
}

/**
* Get the file records associated with the employee as the uploader.
*
* @return HasMany
*/
public function filesUploaded()
{
return $this->hasMany(File::class, 'uploader_employee_id', 'id');
}

/**
* Scope a query to only include unlocked users.
*
Expand Down
49 changes: 24 additions & 25 deletions app/Models/Company/File.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@

namespace App\Models\Company;

use App\Events\FileDeleted;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Facades\Storage;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Factories\HasFactory;

Expand All @@ -20,44 +20,43 @@ class File extends Model
*/
protected $fillable = [
'company_id',
'fileable_id',
'fileable_type',
'filename',
'hashed_filename',
'extension',
'size_in_kb',
'uuid',
'uploader_employee_id',
'uploader_name',
'original_url',
'cdn_url',
'name',
'mime_type',
'type',
'size',
];

/**
* Get the company record associated with the import job object.
* The event map for the model.
*
* @return BelongsTo
* @var array
*/
public function company()
{
return $this->belongsTo(Company::class);
}
protected $dispatchesEvents = [
'deleted' => FileDeleted::class,
];

/**
* Get the parent imageable model.
* Get the company record associated with the file object.
*
* @return BelongsTo
*/
public function fileable()
public function company()
{
return $this->morphTo();
return $this->belongsTo(Company::class);
}

/**
* Get the full path of the file.
* Get the employee record associated with the file object.
*
* @param mixed $value
* @return string
* @return BelongsTo
*/
public function getPathAttribute($value): string
public function uploader()
{
if (config('filesystems.default') == 'local') {
return storage_path('app').'/'.$this->hashed_filename;
} else {
return Storage::url($this->hashed_filename);
}
return $this->belongsTo(Employee::class, 'uploader_employee_id');
}
}
5 changes: 5 additions & 0 deletions app/Providers/EventServiceProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@

namespace App\Providers;

use App\Events\FileDeleted;
use Illuminate\Support\Facades\Event;
use App\Listeners\DeleteFileInStorage;
use Illuminate\Auth\Events\Registered;
use Illuminate\Auth\Listeners\SendEmailVerificationNotification;
use Illuminate\Foundation\Support\Providers\EventServiceProvider as ServiceProvider;
Expand All @@ -18,6 +20,9 @@ class EventServiceProvider extends ServiceProvider
Registered::class => [
SendEmailVerificationNotification::class,
],
FileDeleted::class => [
DeleteFileInStorage::class,
],
];

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

use Carbon\Carbon;
use ErrorException;
use App\Models\Company\File;
use App\Services\BaseService;
use App\Models\Company\Employee;
use App\Models\Company\ImportJob;
Expand All @@ -17,6 +18,8 @@ class StoreEmployeesFromCSVInTemporaryTable extends BaseService

private ImportJob $importJob;

private File $file;

/**
* Get the validation rules that apply to the service.
*
Expand All @@ -27,7 +30,7 @@ public function rules(): array
return [
'company_id' => 'required|integer|exists:companies,id',
'author_id' => 'required|integer|exists:employees,id',
'path' => 'required|string',
'file_id' => 'required|integer|exists:files,id',
];
}

Expand All @@ -54,6 +57,9 @@ private function validate(): void
->inCompany($this->data['company_id'])
->asAtLeastHR()
->canExecuteService();

$this->file = File::where('company_id', $this->data['company_id'])
->findOrFail($this->data['file_id']);
}

/**
Expand Down Expand Up @@ -86,7 +92,10 @@ private function import(): ImportJob

private function readFile(ImportJob $job): void
{
SimpleExcelReader::create($this->data['path'])
$client = new \GuzzleHttp\Client();
$client->get($this->file->cdn_url.urlencode($this->file->name), ['sink' => storage_path().'/app/'.$this->file->name]);

SimpleExcelReader::create(storage_path().'/app/'.$this->file->name)
->trimHeaderRow()
->headersToSnakeCase()
->getRows()
Expand Down

0 comments on commit 9e04c87

Please sign in to comment.