106 lines
3.8 KiB
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;
|
|
}
|
|
}
|