diff --git a/app/Jobs/Campaigns/Export.php b/app/Jobs/Campaigns/Export.php index 749335b6bc..e70f1bb256 100644 --- a/app/Jobs/Campaigns/Export.php +++ b/app/Jobs/Campaigns/Export.php @@ -4,6 +4,7 @@ use App\Jobs\FileCleanup; use App\Models\Campaign; +use App\Models\CampaignExport; use App\Services\Campaign\ExportService; use App\User; use Exception; @@ -31,16 +32,19 @@ class Export implements ShouldQueue protected int $userId; + protected int $campaignExportId; + protected bool $assets; /** * CampaignExport constructor. */ - public function __construct(Campaign $campaign, User $user, bool $assets = false) + public function __construct(Campaign $campaign, User $user, CampaignExport $campaignExport, bool $assets = false) { $this->campaignId = $campaign->id; $this->userId = $user->id; $this->assets = $assets; + $this->campaignExportId = $campaignExport->id; } /** @@ -49,6 +53,12 @@ public function __construct(Campaign $campaign, User $user, bool $assets = false */ public function handle() { + $campaignExport = CampaignExport::find($this->campaignExportId); + if (!$campaignExport) { + return 0; + } + $campaignExport->update(['status' => CampaignExport::STATUS_RUNNING]); + /** @var Campaign|null $campaign */ $campaign = Campaign::find($this->campaignId); if (!$campaign) { @@ -69,12 +79,13 @@ public function handle() ->assets($this->assets) ->export(); + $campaignExport->update(['status' => CampaignExport::STATUS_FINISHED, 'size' => $service->filesize(), 'path' => $service->exportPath()]); + // Don't delete in "sync" mode as there is no delay. $queue = config('queue.default'); if ($queue !== 'sync') { FileCleanup::dispatch($service->exportPath())->delay(now()->addMinutes(60)); } - return 1; } @@ -83,6 +94,12 @@ public function handle() */ public function failed(Exception $exception) { + $campaignExport = CampaignExport::find($this->campaignExportId); + if (!$campaignExport) { + return; + } + $campaignExport->update(['status' => CampaignExport::STATUS_FAILED]); + // Set the campaign export date to null so that the user can try again. // If it failed once, trying again won't help, but this might motivate // them to report the error. diff --git a/app/Models/CampaignExport.php b/app/Models/CampaignExport.php new file mode 100644 index 0000000000..4928517a8d --- /dev/null +++ b/app/Models/CampaignExport.php @@ -0,0 +1,49 @@ +<?php + +namespace App\Models; + +use Illuminate\Database\Eloquent\Builder; +use Illuminate\Database\Eloquent\MassPrunable; +use Illuminate\Database\Eloquent\Model; + +/** + * Class CampaignExport + * @package App\Models + * + * @property int $id + * @property int $campaign_id + * @property int $created_by + * @property int $status + * @property string $path + * + */ +class CampaignExport extends Model +{ + use MassPrunable; + + public const TYPE_ENTITIES = 1; + public const TYPE_ASSETS = 2; + + public const STATUS_SCHEDULED = 1; + public const STATUS_RUNNING = 2; + public const STATUS_FINISHED = 3; + public const STATUS_FAILED = 4; + + public $fillable = [ + 'size', + 'type', + 'status', + 'campaign_id', + 'created_by', + 'path', + ]; + + /** + * Automatically prune old elements from the db + */ + public function prunable(): Builder + { + return static::where('updated_at', '<=', now()->subDays(90)); + } + +} diff --git a/app/Services/Campaign/ExportService.php b/app/Services/Campaign/ExportService.php index d6f304c3c7..cb6a1d2bce 100644 --- a/app/Services/Campaign/ExportService.php +++ b/app/Services/Campaign/ExportService.php @@ -5,6 +5,7 @@ use App\Facades\CampaignCache; use App\Jobs\Campaigns\Export; use App\Models\Image; +use App\Models\CampaignExport; use App\Notifications\Header; use App\Traits\CampaignAware; use App\Traits\UserAware; @@ -29,6 +30,7 @@ class ExportService protected bool $assets = false; protected int $files = 0; + protected int $filesize = 0; public function exportPath(): string { @@ -46,8 +48,23 @@ public function queue(): self $this->campaign->export_date = date('Y-m-d'); $this->campaign->saveQuietly(); - Export::dispatch($this->campaign, $this->user, false); - Export::dispatch($this->campaign, $this->user, true); + $entitiesExport = CampaignExport::create([ + 'campaign_id' => $this->campaign->id, + 'created_by' => $this->user->id, + 'type' => CampaignExport::TYPE_ENTITIES, + 'status' => CampaignExport::STATUS_SCHEDULED, + ]); + + Export::dispatch($this->campaign, $this->user, $entitiesExport, false); + + $assetExport = CampaignExport::create([ + 'campaign_id' => $this->campaign->id, + 'created_by' => $this->user->id, + 'type' => CampaignExport::TYPE_ASSETS, + 'status' => CampaignExport::STATUS_SCHEDULED, + ]); + + Export::dispatch($this->campaign, $this->user, $assetExport, true); return $this; } @@ -66,6 +83,11 @@ public function export(): self return $this; } + public function filesize(): int + { + return $this->filesize; + } + protected function prepare(): self { $saveFolder = storage_path() . '/exports/campaigns/'; @@ -212,6 +234,8 @@ protected function finish(): self $saveFolder = storage_path() . '/exports/campaigns/'; $this->archive->saveTo($saveFolder); + $this->filesize = (int) floor(filesize($this->path) / pow(1024, 2)); + } catch (Exception $e) { // The export might fail if the zip is too big. $this->files = 0; diff --git a/database/migrations/2023_10_25_173253_create_campaign_exports_table.php b/database/migrations/2023_10_25_173253_create_campaign_exports_table.php new file mode 100644 index 0000000000..ec839b30a1 --- /dev/null +++ b/database/migrations/2023_10_25_173253_create_campaign_exports_table.php @@ -0,0 +1,39 @@ +<?php + +use Illuminate\Database\Migrations\Migration; +use Illuminate\Database\Schema\Blueprint; +use Illuminate\Support\Facades\Schema; + +return new class () extends Migration { + /** + * Run the migrations. + */ + public function up(): void + { + Schema::create('campaign_exports', function (Blueprint $table) { + + $table->increments('id'); + $table->unsignedInteger('campaign_id'); + $table->unsignedInteger('created_by')->nullable(); + $table->integer('size'); + $table->tinyInteger('type'); + $table->tinyInteger('status'); + $table->string('path')->nullable(); + $table->timestamps(); + + $table->index(['status']); + + $table->foreign('campaign_id')->references('id')->on('campaigns')->onDelete('cascade'); + $table->foreign('created_by')->references('id')->on('users')->onDelete('set null'); + + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('campaign_exports'); + } +};