53
common/Csv/BaseCsvExportController.php
Executable file
53
common/Csv/BaseCsvExportController.php
Executable file
@@ -0,0 +1,53 @@
|
||||
<?php
|
||||
|
||||
namespace Common\Csv;
|
||||
|
||||
use Auth;
|
||||
use Carbon\Carbon;
|
||||
use Common\Core\BaseController;
|
||||
use Illuminate\Http\Request;
|
||||
use Storage;
|
||||
use Symfony\Component\HttpFoundation\StreamedResponse;
|
||||
|
||||
class BaseCsvExportController extends BaseController
|
||||
{
|
||||
public function __construct(protected Request $request)
|
||||
{
|
||||
$this->middleware('auth');
|
||||
}
|
||||
|
||||
public function download(CsvExport $csvExport): StreamedResponse
|
||||
{
|
||||
if (
|
||||
!Auth::user()->hasPermission('admin') &&
|
||||
$csvExport->user_id !== Auth::id()
|
||||
) {
|
||||
abort(403);
|
||||
}
|
||||
|
||||
return Storage::download(
|
||||
$csvExport->filePath(),
|
||||
$csvExport->download_name,
|
||||
);
|
||||
}
|
||||
|
||||
protected function exportUsing(BaseCsvExportJob $exportJob)
|
||||
{
|
||||
$csvExport = CsvExport::where(
|
||||
'cache_name',
|
||||
$exportJob->cacheName(),
|
||||
)->first();
|
||||
|
||||
if (
|
||||
$csvExport &&
|
||||
$csvExport->created_at->greaterThan(Carbon::now()->addMinutes(-30))
|
||||
) {
|
||||
return $this->success([
|
||||
'downloadPath' => $csvExport->downloadLink(),
|
||||
]);
|
||||
}
|
||||
|
||||
$this->dispatch($exportJob);
|
||||
return $this->success(['result' => 'jobQueued']);
|
||||
}
|
||||
}
|
||||
92
common/Csv/BaseCsvExportJob.php
Executable file
92
common/Csv/BaseCsvExportJob.php
Executable file
@@ -0,0 +1,92 @@
|
||||
<?php
|
||||
|
||||
namespace Common\Csv;
|
||||
|
||||
use App\Models\User;
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Foundation\Bus\Dispatchable;
|
||||
use Illuminate\Queue\InteractsWithQueue;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
use Str;
|
||||
|
||||
abstract class BaseCsvExportJob implements ShouldQueue
|
||||
{
|
||||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||
|
||||
/**
|
||||
* @var resource
|
||||
*/
|
||||
private $csvStream;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $headerKeys;
|
||||
|
||||
abstract protected function generateLines();
|
||||
abstract public function cacheName(): string;
|
||||
|
||||
public function handle()
|
||||
{
|
||||
$this->csvStream = fopen('php://temp', 'w');
|
||||
$cacheName = $this->cacheName();
|
||||
|
||||
CsvExport::where('cache_name', $cacheName)->delete();
|
||||
|
||||
$this->generateLines();
|
||||
|
||||
$csvExport = CsvExport::create([
|
||||
'cache_name' => $cacheName,
|
||||
'user_id' => $this->requesterId ?? null,
|
||||
'download_name' => "$cacheName.csv",
|
||||
'uuid' => Str::uuid(),
|
||||
]);
|
||||
$csvExport->storeFile($this->csvStream);
|
||||
fclose($this->csvStream);
|
||||
|
||||
$this->sendNotification($csvExport);
|
||||
}
|
||||
|
||||
protected function writeLineToCsv(array $data)
|
||||
{
|
||||
if (!$this->headerKeys) {
|
||||
$this->buildCsvHeader($data);
|
||||
}
|
||||
|
||||
$values = array_map(function ($value) {
|
||||
if ($value instanceof Carbon) {
|
||||
return $value->created_at->format('Y-m-d');
|
||||
}
|
||||
return $value;
|
||||
}, array_values($data));
|
||||
|
||||
fputcsv($this->csvStream, $values);
|
||||
}
|
||||
|
||||
protected function buildCsvHeader(array $lineData)
|
||||
{
|
||||
$this->headerKeys = array_map(function ($column) {
|
||||
return Str::title(str_replace('_', ' ', $column));
|
||||
}, array_keys($lineData));
|
||||
|
||||
fputcsv($this->csvStream, $this->headerKeys);
|
||||
}
|
||||
|
||||
protected function notificationName(): string
|
||||
{
|
||||
return $this->cacheName();
|
||||
}
|
||||
|
||||
protected function sendNotification(CsvExport $export)
|
||||
{
|
||||
if (!$this->requesterId) {
|
||||
return;
|
||||
}
|
||||
|
||||
User::find($this->requesterId)?->notify(
|
||||
new CsvExportReadyNotif($export, $this->notificationName()),
|
||||
);
|
||||
}
|
||||
}
|
||||
20
common/Csv/CommonCsvExportController.php
Executable file
20
common/Csv/CommonCsvExportController.php
Executable file
@@ -0,0 +1,20 @@
|
||||
<?php
|
||||
|
||||
namespace Common\Csv;
|
||||
|
||||
use Auth;
|
||||
use Common\Auth\Jobs\ExportRolesCsv;
|
||||
use Common\Auth\Jobs\ExportUsersCsv;
|
||||
|
||||
class CommonCsvExportController extends BaseCsvExportController
|
||||
{
|
||||
public function exportUsers()
|
||||
{
|
||||
return $this->exportUsing(new ExportUsersCsv(Auth::id()));
|
||||
}
|
||||
|
||||
public function exportRoles()
|
||||
{
|
||||
return $this->exportUsing(new ExportRolesCsv(Auth::id()));
|
||||
}
|
||||
}
|
||||
39
common/Csv/CsvExport.php
Executable file
39
common/Csv/CsvExport.php
Executable file
@@ -0,0 +1,39 @@
|
||||
<?php
|
||||
|
||||
namespace Common\Csv;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Storage;
|
||||
|
||||
class CsvExport extends Model
|
||||
{
|
||||
protected $guarded = ['id'];
|
||||
|
||||
protected $casts = [
|
||||
'id' => 'integer',
|
||||
'user_id' => 'integer',
|
||||
];
|
||||
|
||||
const MODEL_TYPE = 'csv_export';
|
||||
|
||||
public static function getModelTypeAttribute(): string
|
||||
{
|
||||
return self::MODEL_TYPE;
|
||||
}
|
||||
|
||||
public function storeFile($stream): bool
|
||||
{
|
||||
Storage::delete($this->filePath());
|
||||
return Storage::writeStream($this->filePath(), $stream);
|
||||
}
|
||||
|
||||
public function filePath(): string
|
||||
{
|
||||
return "exports/csv/{$this->uuid}.csv";
|
||||
}
|
||||
|
||||
public function downloadLink(): string
|
||||
{
|
||||
return url("csv/download/$this->id");
|
||||
}
|
||||
}
|
||||
68
common/Csv/CsvExportReadyNotif.php
Executable file
68
common/Csv/CsvExportReadyNotif.php
Executable file
@@ -0,0 +1,68 @@
|
||||
<?php
|
||||
|
||||
namespace Common\Csv;
|
||||
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Notifications\Messages\MailMessage;
|
||||
use Illuminate\Notifications\Notification;
|
||||
|
||||
class CsvExportReadyNotif extends Notification
|
||||
{
|
||||
use Queueable;
|
||||
|
||||
public function __construct(
|
||||
protected CsvExport $csvExport,
|
||||
protected string $exportName
|
||||
) {
|
||||
}
|
||||
|
||||
public function via($notifiable): array
|
||||
{
|
||||
return ['mail', 'database'];
|
||||
}
|
||||
|
||||
public function toMail($notifiable): MailMessage
|
||||
{
|
||||
return (new MailMessage())
|
||||
->line($this->primaryLine())
|
||||
->line(
|
||||
__(
|
||||
'This download link will only work if you are logged in as user who has requested the export and it will expire in one day.',
|
||||
),
|
||||
)
|
||||
->action('Download', $this->csvExport->downloadLink());
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the array representation of the notification.
|
||||
*
|
||||
* @param mixed $notifiable
|
||||
* @return array
|
||||
*/
|
||||
public function toArray($notifiable): array
|
||||
{
|
||||
return [
|
||||
'mainAction' => [
|
||||
'Label' => 'Download',
|
||||
'action' => $this->csvExport->downloadLink(),
|
||||
],
|
||||
'lines' => [
|
||||
[
|
||||
'content' => $this->primaryLine(),
|
||||
],
|
||||
[
|
||||
'content' => __(
|
||||
'This download link will expire in one day.',
|
||||
),
|
||||
],
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
protected function primaryLine(): string
|
||||
{
|
||||
return __('“:name“ CSV export is ready to download.', [
|
||||
'name' => ucfirst($this->exportName),
|
||||
]);
|
||||
}
|
||||
}
|
||||
36
common/Csv/DeleteExpiredCsvExports.php
Executable file
36
common/Csv/DeleteExpiredCsvExports.php
Executable file
@@ -0,0 +1,36 @@
|
||||
<?php
|
||||
|
||||
namespace Common\Csv;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Support\Collection;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
|
||||
class DeleteExpiredCsvExports extends Command
|
||||
{
|
||||
protected $signature = 'csvExports:delete';
|
||||
protected $description = 'Deleted csv exports that are expired.';
|
||||
|
||||
public function handle(): int
|
||||
{
|
||||
$count = 0;
|
||||
|
||||
CsvExport::where(
|
||||
'created_at',
|
||||
'<',
|
||||
Carbon::now()->addDays(-1),
|
||||
)->chunkById(10, function (Collection $chunk) use ($count) {
|
||||
$count += $chunk->count();
|
||||
CsvExport::whereIn('id', $chunk->pluck('id'))->delete();
|
||||
$filePaths = $chunk->map(function (CsvExport $export) {
|
||||
return $export->filePath();
|
||||
});
|
||||
Storage::delete($filePaths);
|
||||
});
|
||||
|
||||
$this->info("Deleted $count expired csv exports");
|
||||
|
||||
return Command::SUCCESS;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user