CluPilotCloud/app/Notifications/CloudReady.php

106 lines
3.8 KiB
PHP

<?php
namespace App\Notifications;
use App\Mail\Concerns\SendsFromMailbox;
use App\Models\Datacenter;
use App\Models\Instance;
use App\Services\Mail\MailPurpose;
use App\Support\ProvisioningSettings;
use Illuminate\Bus\Queueable;
use Illuminate\Notifications\Messages\MailMessage;
use Illuminate\Notifications\Notification;
/**
* Announces that provisioning has finished. Carries NO credential.
*
* The initial admin password used to travel here in clear text — crossing
* third-party mail servers and then sitting in an inbox forever, the weakest
* link in the chain. It now lives on the instance instead
* (Instance::$admin_password, encrypted — see CompleteProvisioning) until the
* customer acknowledges it on their dashboard, at which point it is deleted
* for good. This mail only says the cloud is ready and where to find them.
*
* Sent SYNCHRONOUSLY from CompleteProvisioning (not queued): the step only
* records the `credentials_sent` breadcrumb AFTER the send returns, so a mail
* failure re-runs the step instead of losing the breadcrumb in a failed queue
* job. Delivery is at-least-once — a crash in the tiny window after send but
* before the breadcrumb can re-send a duplicate welcome email, which is
* preferable to the customer never being told their cloud is ready.
*/
class CloudReady extends Notification
{
use Queueable, SendsFromMailbox;
public function __construct(
public Instance $instance,
) {}
/** @return array<int, string> */
public function via(object $notifiable): array
{
return ['mail'];
}
public function toMail(object $notifiable): MailMessage
{
$url = 'https://'.$this->instance->subdomain.'.'.ProvisioningSettings::dnsZone();
[$from, $replyTo] = $this->mailboxAddresses(MailPurpose::PROVISIONING);
$message = (new MailMessage)->mailer('cp_'.MailPurpose::PROVISIONING);
if ($from !== null) {
$message->from($from->address, $from->name);
}
if ($replyTo !== null) {
$message->replyTo($replyTo->address, $replyTo->name);
}
// ->view() rather than the notification's markdown builder: the default
// arrives with the framework's logo, button and footer, and this is the
// mail that greets somebody on the day their product starts working.
// Kept as a Notification rather than turned into a Mailable, because
// CompleteProvisioning depends on it being sent synchronously and on
// what happens after the send returns.
return $message
->subject(__('provisioning.mail.ready_subject'))
->view('mail.cloud-ready', [
'name' => (string) ($notifiable->name ?? ''),
'url' => $url,
'plan' => $this->instance->plan,
'storage' => __('provisioning.mail.value_storage', ['gb' => $this->instance->quota_gb]),
'location' => $this->location(),
'dashboardUrl' => route('dashboard'),
]);
}
/**
* The datacenter in words, falling back to the code.
*
* Read through the host, because an instance knows which machine it is on
* and a machine knows where it stands. A missing name is not worth a second
* query or an empty row — the code is what the customer chose at checkout
* and will recognise.
*/
private function location(): string
{
$code = $this->instance->host?->datacenter;
if (! is_string($code) || $code === '') {
return '—';
}
$datacenter = Datacenter::query()->where('code', $code)->first();
if ($datacenter === null) {
return $code;
}
return $datacenter->facility
? $datacenter->name.' · '.$datacenter->facility
: $datacenter->name;
}
}