TenantAtlas/app/Services/Alerts/AlertDestinationTestMessageService.php

116 lines
3.7 KiB
PHP

<?php
declare(strict_types=1);
namespace App\Services\Alerts;
use App\Jobs\Alerts\DeliverAlertsJob;
use App\Models\AlertDelivery;
use App\Models\AlertDestination;
use App\Models\User;
use App\Services\Audit\WorkspaceAuditLogger;
use App\Support\Audit\AuditActionId;
use Illuminate\Auth\Access\AuthorizationException;
class AlertDestinationTestMessageService
{
private const int RATE_LIMIT_SECONDS = 60;
public function __construct(
private WorkspaceAuditLogger $auditLogger,
) {}
/**
* Send a test message for the given alert destination.
*
* @return array{success: bool, message: string, delivery_id: int|null}
*/
public function sendTest(AlertDestination $destination, User $actor): array
{
if (! $actor->can('update', $destination)) {
throw new AuthorizationException('You do not have permission to send test messages for this destination.');
}
if (! $destination->is_enabled) {
return [
'success' => false,
'message' => 'This destination is currently disabled. Enable it before sending a test message.',
'delivery_id' => null,
];
}
if ($this->isRateLimited($destination)) {
return [
'success' => false,
'message' => 'A test message was sent recently. Please wait before trying again.',
'delivery_id' => null,
];
}
$delivery = $this->createTestDelivery($destination);
$this->auditLog($destination, $actor);
DeliverAlertsJob::dispatch((int) $destination->workspace_id);
return [
'success' => true,
'message' => 'Test message queued for delivery.',
'delivery_id' => (int) $delivery->getKey(),
];
}
private function isRateLimited(AlertDestination $destination): bool
{
return AlertDelivery::query()
->where('workspace_id', (int) $destination->workspace_id)
->where('alert_destination_id', (int) $destination->getKey())
->where('event_type', AlertDelivery::EVENT_TYPE_TEST)
->where('created_at', '>=', now()->subSeconds(self::RATE_LIMIT_SECONDS))
->exists();
}
private function createTestDelivery(AlertDestination $destination): AlertDelivery
{
return AlertDelivery::create([
'workspace_id' => (int) $destination->workspace_id,
'tenant_id' => null,
'alert_rule_id' => null,
'alert_destination_id' => (int) $destination->getKey(),
'event_type' => AlertDelivery::EVENT_TYPE_TEST,
'status' => AlertDelivery::STATUS_QUEUED,
'severity' => null,
'fingerprint_hash' => 'test:'.(int) $destination->getKey(),
'attempt_count' => 0,
'payload' => [
'title' => 'Test alert',
'body' => 'This is a test delivery for destination verification.',
],
]);
}
private function auditLog(AlertDestination $destination, User $actor): void
{
$workspace = $destination->workspace;
if ($workspace === null) {
return;
}
$this->auditLogger->log(
workspace: $workspace,
action: AuditActionId::AlertDestinationTestRequested->value,
context: [
'metadata' => [
'alert_destination_id' => (int) $destination->getKey(),
'name' => (string) $destination->name,
'type' => (string) $destination->type,
],
],
actor: $actor,
resourceType: 'alert_destination',
resourceId: (string) $destination->getKey(),
);
}
}