CluPilotCloud/app/Services/Mail/MailLane.php

124 lines
4.1 KiB
PHP

<?php
namespace App\Services\Mail;
use App\Mail\CloudResumedMail;
use App\Mail\CloudSuspendedMail;
use App\Mail\ContactRequestMail;
use App\Mail\DormantAccountWarningMail;
use App\Mail\DunningNoticeMail;
use App\Mail\InvoiceMail;
use App\Mail\MaintenanceAnnouncementMail;
use App\Mail\MaintenanceCancelledMail;
use App\Mail\NewDeviceSignInMail;
use App\Mail\OperatorInvitationMail;
use App\Mail\OperatorMessageMail;
use App\Mail\OrderConfirmationMail;
use App\Mail\ResetPasswordMail;
use App\Mail\SecurityBlockMail;
use App\Mail\VerifyEmailMail;
use App\Support\Settings;
use RuntimeException;
/**
* In welcher Spur eine Mail fährt.
*
* Drei Spuren, und die Trennung läuft nicht zwischen Massenversand und
* Einzelmail, sondern zwischen dringend und nicht dringend. Eine
* Ausfallmeldung geht an alle UND eilt — sie bekommt deshalb ein höheres
* Kontingent statt gar keines.
*
* Die Vorgaben stehen hier, die Änderung des Betreibers darüber in den
* Einstellungen. Eine unbekannte Klasse fällt in die ruhige Spur: eine zu
* langsam verschickte Mail ist ein kleinerer Fehler als ein ungedrosselter
* Schub, den niemand vorhergesehen hat.
*/
final class MailLane
{
public const DIRECT = 'mail-direkt';
public const URGENT = 'mail-wichtig';
public const CALM = 'mail-ruhig';
/**
* Die acht, auf die gerade jemand wartet.
*
* Sie entstehen einzeln, weil ein einzelner Mensch geklickt hat, und
* können gar keinen Schub bilden. Drosseln nützt dort nichts und kostet
* einen Supportfall je verzögertem Kennwort — deshalb sind sie gesperrt
* und nicht nur voreingestellt.
*/
private const LOCKED = [
ResetPasswordMail::class,
VerifyEmailMail::class,
NewDeviceSignInMail::class,
SecurityBlockMail::class,
ContactRequestMail::class,
OrderConfirmationMail::class,
OperatorMessageMail::class,
// Die Einladung in die Konsole. Der Inhaber hat gerade geklickt und
// sagt dem Eingeladenen im selben Moment „schau in dein Postfach" —
// eine Einladung, die eine Viertelstunde in der ruhigen Spur liegt,
// ist ein Anruf.
OperatorInvitationMail::class,
];
/** @var array<class-string, string> */
private const DEFAULTS = [
MaintenanceAnnouncementMail::class => self::URGENT,
MaintenanceCancelledMail::class => self::URGENT,
CloudSuspendedMail::class => self::URGENT,
CloudResumedMail::class => self::URGENT,
InvoiceMail::class => self::CALM,
DunningNoticeMail::class => self::CALM,
DormantAccountWarningMail::class => self::CALM,
];
/** Die Schlange, in die diese Mailklasse gehört. */
public static function for(string $mailableClass): string
{
if (self::isLocked($mailableClass)) {
return self::DIRECT;
}
$stored = (array) Settings::get('mail.lanes', []);
$lane = $stored[$mailableClass] ?? self::DEFAULTS[$mailableClass] ?? self::CALM;
return in_array($lane, [self::DIRECT, self::URGENT, self::CALM], true) ? $lane : self::CALM;
}
public static function isLocked(string $mailableClass): bool
{
return in_array($mailableClass, self::LOCKED, true);
}
/** Verschiebt eine Mailklasse. Wirft, wenn sie gesperrt ist. */
public static function assign(string $mailableClass, string $lane): void
{
if (self::isLocked($mailableClass)) {
throw new RuntimeException(
"{$mailableClass} fährt immer direkt: darauf wartet gerade jemand."
);
}
$stored = (array) Settings::get('mail.lanes', []);
$stored[$mailableClass] = $lane;
Settings::set('mail.lanes', $stored);
}
/**
* Jede bekannte Mailklasse mit ihrer Spur, für die Konsole.
*
* @return array<class-string, string>
*/
public static function all(): array
{
$classes = array_merge(self::LOCKED, array_keys(self::DEFAULTS));
return collect($classes)
->mapWithKeys(fn (string $class) => [$class => self::for($class)])
->all();
}
}