Files
preregister/app/Jobs/SyncSubscriberToMailwizz.php
bert.hausmans 627edbbb83 fix: queue Mailwizz sync by subscriber id and skip stale payloads
Serialize only the subscriber primary key to avoid ModelNotFound on
unserialize when the row is gone. Guard handle() when subscriberId is
missing after old payload shapes.

Made-with: Cursor
2026-04-05 13:33:50 +02:00

195 lines
6.2 KiB
PHP
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
declare(strict_types=1);
namespace App\Jobs;
use App\Models\MailwizzConfig;
use App\Models\Subscriber;
use App\Services\MailwizzCheckboxlistTags;
use App\Services\MailwizzService;
use App\Services\MailwizzSubscriberFormPayload;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldBeUnique;
use Illuminate\Contracts\Queue\ShouldQueueAfterCommit;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Support\Facades\Log;
use RuntimeException;
use Throwable;
class SyncSubscriberToMailwizz implements ShouldBeUnique, ShouldQueueAfterCommit
{
use Dispatchable;
use InteractsWithQueue;
use Queueable;
/**
* Seconds before the unique lock expires if the worker dies before releasing it.
*/
public int $uniqueFor = 86400;
public int $tries = 3;
/**
* @var list<int>
*/
public array $backoff = [10, 30, 60];
/**
* Set in the constructor for new jobs. Remains null when an old queue payload (presubscriber-id refactor) is unserialized.
*/
public ?int $subscriberId = null;
/**
* @param Subscriber|int $subscriber Model is accepted when dispatching; only the id is serialized for the queue.
*/
public function __construct(Subscriber|int $subscriber)
{
$this->subscriberId = $subscriber instanceof Subscriber
? (int) $subscriber->getKey()
: $subscriber;
$this->onQueue('mailwizz');
}
public function uniqueId(): string
{
return $this->subscriberId !== null
? (string) $this->subscriberId
: 'stale-mailwizz-sync-payload';
}
public function handle(): void
{
if ($this->subscriberId === null) {
Log::notice('SyncSubscriberToMailwizz: skipped job with missing subscriber id (stale queue payload). Clear the queue or re-dispatch sync jobs.');
return;
}
try {
$this->runSync();
} catch (Throwable $e) {
Log::error('SyncSubscriberToMailwizz: integration failed; subscriber remains local (use admin resync if needed)', [
'subscriber_id' => $this->subscriberId,
'message' => $e->getMessage(),
]);
}
}
private function runSync(): void
{
$subscriber = Subscriber::query()
->with(['preregistrationPage.mailwizzConfig'])
->find($this->subscriberId);
if ($subscriber === null) {
return;
}
$page = $subscriber->preregistrationPage;
$config = $page?->mailwizzConfig;
if ($page === null || $config === null) {
return;
}
if (! $this->configIsComplete($config)) {
Log::warning('SyncSubscriberToMailwizz: incomplete Mailwizz config', [
'subscriber_id' => $subscriber->id,
'page_id' => $page->id,
]);
$this->fail(new RuntimeException('Incomplete Mailwizz configuration.'));
return;
}
$apiKey = $config->api_key;
if (! is_string($apiKey) || $apiKey === '') {
Log::warning('SyncSubscriberToMailwizz: missing API key', ['subscriber_id' => $subscriber->id]);
$this->fail(new RuntimeException('Mailwizz API key is missing.'));
return;
}
$service = new MailwizzService($apiKey);
$listUid = $config->list_uid;
$search = $service->searchSubscriber($listUid, $subscriber->email);
if ($search === null) {
$this->createInMailwizz($service, $subscriber, $config, $listUid);
} else {
$this->updateInMailwizz($service, $subscriber, $config, $listUid, $search['subscriber_uid']);
}
$subscriber->update([
'synced_to_mailwizz' => true,
'synced_at' => now(),
]);
}
public function failed(?Throwable $exception): void
{
Log::error('SyncSubscriberToMailwizz failed', [
'subscriber_id' => $this->subscriberId,
'message' => $exception?->getMessage(),
]);
}
private function configIsComplete(MailwizzConfig $config): bool
{
if ($config->list_uid === '' || $config->field_email === '' || $config->field_first_name === '' || $config->field_last_name === '') {
return false;
}
if ($config->tag_field === null || $config->tag_field === '' || $config->tag_value === null || $config->tag_value === '') {
return false;
}
return true;
}
private function createInMailwizz(
MailwizzService $service,
Subscriber $subscriber,
MailwizzConfig $config,
string $listUid
): void {
$page = $subscriber->preregistrationPage;
$data = MailwizzSubscriberFormPayload::baseFields($subscriber, $config, $page->isPhoneFieldEnabledForSubscribers());
$tagField = $config->tag_field;
$tagValue = $config->tag_value;
if ($tagField !== null && $tagField !== '' && $tagValue !== null && $tagValue !== '') {
$data[$tagField] = [$tagValue];
}
$service->createSubscriber($listUid, $data);
}
private function updateInMailwizz(
MailwizzService $service,
Subscriber $subscriber,
MailwizzConfig $config,
string $listUid,
string $subscriberUid
): void {
$page = $subscriber->preregistrationPage;
$data = MailwizzSubscriberFormPayload::baseFields($subscriber, $config, $page->isPhoneFieldEnabledForSubscribers());
$tagField = $config->tag_field;
$tagValue = $config->tag_value;
if ($tagField !== null && $tagField !== '' && $tagValue !== null && $tagValue !== '') {
$full = $service->getSubscriber($listUid, $subscriberUid);
if ($full === null) {
throw new RuntimeException('Mailwizz getSubscriber returned an empty payload.');
}
$existingCsv = MailwizzCheckboxlistTags::extractCsvFromSubscriberResponse($full, $tagField);
$merged = MailwizzCheckboxlistTags::mergeValueIntoCsv($existingCsv, $tagValue);
$data[$tagField] = $merged;
}
$service->updateSubscriber($listUid, $subscriberUid, $data);
}
}