feat(dump): track async dump jobs in a status table

This commit is contained in:
2026-09-14 22:00:26 +02:00
parent 44e7648e06
commit f982c3c3da
2 changed files with 124 additions and 0 deletions
@@ -0,0 +1,35 @@
<?php declare(strict_types=1);
namespace AeonDumpManager\Migration;
use Doctrine\DBAL\Connection;
use Shopware\Core\Framework\Migration\MigrationStep;
class Migration1789413583CreateDumpJobTable extends MigrationStep
{
public function getCreationTimestamp(): int
{
return 1789413583;
}
public function update(Connection $connection): void
{
$connection->executeStatement(<<<'SQL'
CREATE TABLE IF NOT EXISTS `aeon_dump_manager_job` (
`id` BINARY(16) NOT NULL,
`status` VARCHAR(20) NOT NULL,
`percent` SMALLINT UNSIGNED NULL,
`filename` VARCHAR(255) NULL,
`error_message` TEXT NULL,
`created_at` DATETIME(3) NOT NULL,
`updated_at` DATETIME(3) NULL,
PRIMARY KEY (`id`)
) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4;
SQL);
}
public function updateDestructive(Connection $connection): void
{
// nothing destructive to do
}
}
+89
View File
@@ -0,0 +1,89 @@
<?php declare(strict_types=1);
namespace AeonDumpManager\Service;
use Doctrine\DBAL\Connection;
use Shopware\Core\Framework\Uuid\Uuid;
/**
* Owns the aeon_dump_manager_job table. Shopware core has no generic
* async-job-status framework to hook into (confirmed against core's own
* Import/Export source) — this mirrors that pattern with a small dedicated table.
*/
class DumpJobStatusService
{
public function __construct(private readonly Connection $connection)
{
}
public function createPending(): string
{
$id = Uuid::randomBytes();
$this->connection->insert('aeon_dump_manager_job', [
'id' => $id,
'status' => 'pending',
'created_at' => (new \DateTimeImmutable())->format('Y-m-d H:i:s.v'),
]);
return Uuid::fromBytesToHex($id);
}
public function markRunning(string $jobId): void
{
$this->update($jobId, ['status' => 'running']);
}
public function updateProgress(string $jobId, int $percent): void
{
$this->update($jobId, ['percent' => min(99, max(0, $percent))]);
}
public function markDone(string $jobId, string $filename): void
{
$this->update($jobId, ['status' => 'done', 'percent' => 100, 'filename' => $filename]);
}
public function markFailed(string $jobId, string $errorMessage): void
{
$this->update($jobId, ['status' => 'failed', 'error_message' => $errorMessage]);
}
/**
* @return array{id: string, status: string, percent: ?int, filename: ?string, errorMessage: ?string}|null
*/
public function find(string $jobId): ?array
{
$row = $this->connection->fetchAssociative(
'SELECT LOWER(HEX(id)) AS id, status, percent, filename, error_message FROM aeon_dump_manager_job WHERE id = :id',
['id' => Uuid::fromHexToBytes($jobId)]
);
if ($row === false) {
return null;
}
return [
'id' => $row['id'],
'status' => $row['status'],
'percent' => $row['percent'] !== null ? (int) $row['percent'] : null,
'filename' => $row['filename'],
'errorMessage' => $row['error_message'],
];
}
public function purgeOlderThan(\DateTimeImmutable $threshold): void
{
$this->connection->executeStatement(
'DELETE FROM aeon_dump_manager_job WHERE created_at < :threshold',
['threshold' => $threshold->format('Y-m-d H:i:s.v')]
);
}
private function update(string $jobId, array $data): void
{
$data['updated_at'] = (new \DateTimeImmutable())->format('Y-m-d H:i:s.v');
$this->connection->update('aeon_dump_manager_job', $data, ['id' => Uuid::fromHexToBytes($jobId)]);
}
}