﻿CsvExport.php                                                                                       0000755                 00000001363 00000000000 0007161 0                                                                                                    ustar 00                                                                                                                                                                                                                                                       <?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");
    }
}
                                                                                                                                                                                                                                                                             DeleteExpiredCsvExports.php                                                                         0000755                 00000001672 00000000000 0012013 0                                                                                                    ustar 00                                                                                                                                                                                                                                                       <?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;
    }
}
                                                                      CsvExportReadyNotif.php                                                                             0000755                 00000003302 00000000000 0011141 0                                                                                                    ustar 00                                                                                                                                                                                                                                                       <?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),
        ]);
    }
}
                                                                                                                                                                                                                                                                                                                              BaseCsvExportJob.php                                                                                0000755                 00000004367 00000000000 0010416 0                                                                                                    ustar 00                                                                                                                                                                                                                                                       <?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()),
        );
    }
}
                                                                                                                                                                                                                                                                         CommonCsvExportController.php                                                                       0000755                 00000000632 00000000000 0012374 0                                                                                                    ustar 00                                                                                                                                                                                                                                                       <?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()));
    }
}
                                                                                                      BaseCsvExportController.php                                                                         0000755                 00000002362 00000000000 0012020 0                                                                                                    ustar 00                                                                                                                                                                                                                                                       <?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']);
    }
}
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                           