124 lines
4.0 KiB
PHP
124 lines
4.0 KiB
PHP
<?php
|
|
|
|
namespace App\Temporal\ExternalApiSync;
|
|
|
|
use Carbon\CarbonInterval;
|
|
use Temporal\Activity\ActivityOptions;
|
|
use Temporal\Common\RetryOptions;
|
|
use Temporal\Workflow;
|
|
|
|
class ExternalApiSyncWorkflow implements ExternalApiSyncWorkflowInterface
|
|
{
|
|
private string $status = 'pending';
|
|
private int $pagesFetched = 0;
|
|
private int $recordsSynced = 0;
|
|
private int $rateLimitHits = 0;
|
|
private int $retryCount = 0;
|
|
private string $currentCursor = '0';
|
|
private bool $isPaused = false;
|
|
|
|
/** @var ExternalApiSyncActivityInterface */
|
|
private $activityStub;
|
|
|
|
public function __construct()
|
|
{
|
|
$this->activityStub = Workflow::newActivityStub(
|
|
ExternalApiSyncActivityInterface::class,
|
|
ActivityOptions::new()
|
|
->withStartToCloseTimeout(CarbonInterval::minutes(5))
|
|
->withHeartbeatTimeout(CarbonInterval::seconds(30))
|
|
->withRetryOptions(
|
|
RetryOptions::new()
|
|
->withMaximumAttempts(5)
|
|
->withInitialInterval(CarbonInterval::seconds(2))
|
|
->withBackoffCoefficient(2.0)
|
|
->withMaximumInterval(CarbonInterval::seconds(30))
|
|
)
|
|
);
|
|
}
|
|
|
|
public function sync(string $apiEndpoint, array $simulationConfig = []): \Generator
|
|
{
|
|
try {
|
|
// Step 1: Refresh API token
|
|
$this->status = 'authenticating';
|
|
$token = yield $this->activityStub->refreshToken($simulationConfig);
|
|
|
|
// Step 2: Paginated fetch loop
|
|
$this->status = 'syncing';
|
|
$hasMore = true;
|
|
|
|
while ($hasMore) {
|
|
// Check for pause
|
|
if ($this->isPaused) {
|
|
$this->status = 'paused';
|
|
yield Workflow::await(fn () => !$this->isPaused);
|
|
$this->status = 'syncing';
|
|
}
|
|
|
|
// Fetch one page
|
|
$pageResult = yield $this->activityStub->fetchPage($this->currentCursor, $token, $simulationConfig);
|
|
$pageResult = (array) $pageResult;
|
|
|
|
$attempt = $pageResult['attempt'] ?? 1;
|
|
if ($attempt > 1) {
|
|
$this->retryCount += ($attempt - 1);
|
|
}
|
|
|
|
$this->pagesFetched++;
|
|
$this->currentCursor = $pageResult['nextCursor'];
|
|
$hasMore = $pageResult['hasMore'];
|
|
|
|
// Transform and store records
|
|
if (!empty($pageResult['records'])) {
|
|
$storeResult = yield $this->activityStub->transformAndStore($pageResult['records'], $simulationConfig);
|
|
$storeResult = (array) $storeResult;
|
|
|
|
$this->recordsSynced += $storeResult['stored'] ?? 0;
|
|
|
|
$storeAttempt = $storeResult['attempt'] ?? 1;
|
|
if ($storeAttempt > 1) {
|
|
$this->retryCount += ($storeAttempt - 1);
|
|
}
|
|
}
|
|
}
|
|
} catch (\Throwable $e) {
|
|
$this->status = 'failed';
|
|
throw $e;
|
|
}
|
|
|
|
$this->status = 'completed';
|
|
|
|
return [
|
|
'status' => 'completed',
|
|
'pagesFetched' => $this->pagesFetched,
|
|
'recordsSynced' => $this->recordsSynced,
|
|
'rateLimitHits' => $this->rateLimitHits,
|
|
'retryCount' => $this->retryCount,
|
|
];
|
|
}
|
|
|
|
public function pause(): void
|
|
{
|
|
$this->isPaused = true;
|
|
}
|
|
|
|
public function resume(): void
|
|
{
|
|
$this->isPaused = false;
|
|
}
|
|
|
|
public function getProgress(): array
|
|
{
|
|
return [
|
|
'status' => $this->status,
|
|
'pagesFetched' => $this->pagesFetched,
|
|
'recordsSynced' => $this->recordsSynced,
|
|
'rateLimitHits' => $this->rateLimitHits,
|
|
'retryCount' => $this->retryCount,
|
|
'currentCursor' => $this->currentCursor,
|
|
'isPaused' => $this->isPaused,
|
|
];
|
|
}
|
|
}
|