feat(scheduler): dispatch event on job failure, notify admins via notifications module
Add ?ModuleEventDispatcher to SchedulerRunService (fail-open, nullable). Dispatch scheduler.job_failed event when a job fails with payload: job_id, job_key, label, error_code, error_message, trigger_type. New SchedulerJobFailedNotificationListener in notifications module creates in-app admin notifications for users with JOBS_MANAGE permission, scoped per tenant, with 30-min dedup per job_key. Also fixes audit module manifest: system_audit_purge job_key was mismatched (audit_system_purge vs system_audit_purge in DB). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,89 @@
|
||||
<?php
|
||||
|
||||
namespace MintyPHP\Module\Notifications\Listeners;
|
||||
|
||||
use MintyPHP\App\Module\Contracts\EventListener;
|
||||
use MintyPHP\Module\Notifications\Service\NotificationMessage;
|
||||
use MintyPHP\Module\Notifications\Service\NotificationService;
|
||||
use MintyPHP\Repository\User\UserReadRepositoryInterface;
|
||||
use MintyPHP\Repository\Tenant\UserTenantRepositoryInterface;
|
||||
use MintyPHP\Service\Access\PermissionService;
|
||||
|
||||
final class SchedulerJobFailedNotificationListener implements EventListener
|
||||
{
|
||||
public function __construct(
|
||||
private readonly NotificationService $notificationService,
|
||||
private readonly UserReadRepositoryInterface $userReadRepository,
|
||||
private readonly UserTenantRepositoryInterface $userTenantRepository
|
||||
) {
|
||||
}
|
||||
|
||||
public function handle(string $event, array $payload): void
|
||||
{
|
||||
$jobKey = trim((string) ($payload['job_key'] ?? ''));
|
||||
$jobId = (int) ($payload['job_id'] ?? 0);
|
||||
$label = trim((string) ($payload['label'] ?? ''));
|
||||
$errorCode = trim((string) ($payload['error_code'] ?? ''));
|
||||
|
||||
if ($jobKey === '' || $jobId <= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
$displayLabel = $label !== '' ? $label : $jobKey;
|
||||
|
||||
$adminUserIds = $this->userReadRepository->listPrivilegedUserIdsByPermissionKeys(
|
||||
[PermissionService::JOBS_MANAGE]
|
||||
);
|
||||
if ($adminUserIds === []) {
|
||||
return;
|
||||
}
|
||||
$adminSet = array_flip($adminUserIds);
|
||||
|
||||
$titleParams = [$displayLabel];
|
||||
$bodyParams = $errorCode !== '' ? [$errorCode] : [];
|
||||
|
||||
$message = NotificationMessage::localized(
|
||||
'scheduler.job_failed',
|
||||
'Scheduled job failed: %s',
|
||||
$titleParams,
|
||||
$errorCode !== '' ? 'Error: %s' : null,
|
||||
$bodyParams,
|
||||
'admin/scheduled-jobs',
|
||||
[
|
||||
'job_id' => $jobId,
|
||||
'job_key' => $jobKey,
|
||||
'error_code' => $errorCode,
|
||||
'dedupe_target' => 'job:' . $jobKey,
|
||||
]
|
||||
);
|
||||
|
||||
$tenantIds = $this->resolveAdminTenantIds($adminUserIds);
|
||||
|
||||
foreach ($tenantIds as $tenantId) {
|
||||
$this->notificationService->createForTenantAdminUsersFromMessage(
|
||||
$tenantId,
|
||||
$message,
|
||||
null,
|
||||
$adminSet
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Collect all unique tenant IDs across admin users.
|
||||
*
|
||||
* @param list<int> $userIds
|
||||
* @return list<int>
|
||||
*/
|
||||
private function resolveAdminTenantIds(array $userIds): array
|
||||
{
|
||||
$tenantIds = [];
|
||||
foreach ($userIds as $userId) {
|
||||
foreach ($this->userTenantRepository->listTenantIdsByUserId($userId) as $tenantId) {
|
||||
$tenantIds[$tenantId] = true;
|
||||
}
|
||||
}
|
||||
|
||||
return array_keys($tenantIds);
|
||||
}
|
||||
}
|
||||
@@ -9,6 +9,7 @@ use MintyPHP\Module\Notifications\Listeners\UserActivatedNotificationListener;
|
||||
use MintyPHP\Module\Notifications\Listeners\UserAssignmentChangedNotificationListener;
|
||||
use MintyPHP\Module\Notifications\Listeners\UserCreatedNotificationListener;
|
||||
use MintyPHP\Module\Notifications\Listeners\UserDeactivatedNotificationListener;
|
||||
use MintyPHP\Module\Notifications\Listeners\SchedulerJobFailedNotificationListener;
|
||||
use MintyPHP\Module\Notifications\Listeners\UserDeletedNotificationListener;
|
||||
use MintyPHP\Module\Notifications\Repository\NotificationRepository;
|
||||
use MintyPHP\Module\Notifications\Service\NotificationService;
|
||||
@@ -62,5 +63,11 @@ final class NotificationsContainerRegistrar implements ContainerRegistrar
|
||||
$c->get(NotificationService::class),
|
||||
$c->get(UserReadRepository::class)
|
||||
));
|
||||
|
||||
$container->set(SchedulerJobFailedNotificationListener::class, static fn (AppContainer $c): SchedulerJobFailedNotificationListener => new SchedulerJobFailedNotificationListener(
|
||||
$c->get(NotificationService::class),
|
||||
$c->get(UserReadRepository::class),
|
||||
$c->get(UserTenantRepository::class)
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@ class NotificationService
|
||||
'user.activated' => true,
|
||||
'user.deactivated' => true,
|
||||
'user.assignment_changed' => true,
|
||||
'scheduler.job_failed' => true,
|
||||
];
|
||||
|
||||
public function __construct(
|
||||
|
||||
Reference in New Issue
Block a user