Add service credentials email notification on provisioning
Sends mail notification with login credentials when VPS, dedicated, or web hosting services are provisioned. Wired into VirtFusion, SynergyCP, and Enhance provisioning services. Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
58
website/app/Notifications/ServiceCredentialsNotification.php
Normal file
58
website/app/Notifications/ServiceCredentialsNotification.php
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Notifications;
|
||||||
|
|
||||||
|
use App\Models\Service;
|
||||||
|
use Illuminate\Bus\Queueable;
|
||||||
|
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||||
|
use Illuminate\Notifications\Messages\MailMessage;
|
||||||
|
use Illuminate\Notifications\Notification;
|
||||||
|
|
||||||
|
class ServiceCredentialsNotification extends Notification implements ShouldQueue
|
||||||
|
{
|
||||||
|
use Queueable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param array{username: string, password: string, hostname: string, ip_address: string, port: int|null, panel_url: string|null} $credentials
|
||||||
|
*/
|
||||||
|
public function __construct(
|
||||||
|
public Service $service,
|
||||||
|
public array $credentials,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
/** @return array<int, string> */
|
||||||
|
public function via(object $notifiable): array
|
||||||
|
{
|
||||||
|
return ['mail'];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function toMail(object $notifiable): MailMessage
|
||||||
|
{
|
||||||
|
$serviceType = ucfirst(str_replace('_', ' ', $this->service->service_type ?? 'Server'));
|
||||||
|
$planName = $this->service->plan?->name ?? $serviceType;
|
||||||
|
|
||||||
|
$mail = (new MailMessage)
|
||||||
|
->subject("Your {$serviceType} Server Credentials - EZSCALE")
|
||||||
|
->greeting("Hello {$notifiable->name}!")
|
||||||
|
->line("Your **{$planName}** service has been provisioned and is ready to use.")
|
||||||
|
->line('Here are your access credentials:')
|
||||||
|
->line("**Hostname:** {$this->credentials['hostname']}")
|
||||||
|
->line("**IP Address:** {$this->credentials['ip_address']}")
|
||||||
|
->line("**Username:** {$this->credentials['username']}")
|
||||||
|
->line("**Password:** {$this->credentials['password']}");
|
||||||
|
|
||||||
|
if (! empty($this->credentials['port'])) {
|
||||||
|
$mail->line("**Port:** {$this->credentials['port']}");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (! empty($this->credentials['panel_url'])) {
|
||||||
|
$mail->action('Access Control Panel', $this->credentials['panel_url']);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $mail
|
||||||
|
->line('For security, we recommend changing your password after first login.')
|
||||||
|
->line('If you need help, our support team is available 24/7.');
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -6,6 +6,7 @@ namespace App\Services\Provisioning;
|
|||||||
|
|
||||||
use App\Models\ProvisioningLog;
|
use App\Models\ProvisioningLog;
|
||||||
use App\Models\Service;
|
use App\Models\Service;
|
||||||
|
use App\Notifications\ServiceCredentialsNotification;
|
||||||
use Illuminate\Http\Client\PendingRequest;
|
use Illuminate\Http\Client\PendingRequest;
|
||||||
use Illuminate\Support\Facades\Http;
|
use Illuminate\Support\Facades\Http;
|
||||||
use Illuminate\Support\Facades\Log;
|
use Illuminate\Support\Facades\Log;
|
||||||
@@ -70,7 +71,20 @@ class EnhanceService implements ProvisioningServiceInterface
|
|||||||
|
|
||||||
$this->logAction($service, 'provision', 'success', $data);
|
$this->logAction($service, 'provision', 'success', $data);
|
||||||
|
|
||||||
return $service->fresh();
|
$service = $service->fresh();
|
||||||
|
|
||||||
|
if ($service->user) {
|
||||||
|
$service->user->notify(new ServiceCredentialsNotification($service, [
|
||||||
|
'username' => $data['data']['username'] ?? $user->email,
|
||||||
|
'password' => $data['data']['password'] ?? 'see control panel',
|
||||||
|
'hostname' => $service->domain ?? $service->ipv4_address ?? 'N/A',
|
||||||
|
'ip_address' => $service->ipv4_address ?? 'Pending',
|
||||||
|
'port' => null,
|
||||||
|
'panel_url' => $data['data']['control_panel_url'] ?? null,
|
||||||
|
]));
|
||||||
|
}
|
||||||
|
|
||||||
|
return $service;
|
||||||
} catch (RuntimeException $e) {
|
} catch (RuntimeException $e) {
|
||||||
throw $e;
|
throw $e;
|
||||||
} catch (\Exception $e) {
|
} catch (\Exception $e) {
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ namespace App\Services\Provisioning;
|
|||||||
|
|
||||||
use App\Models\ProvisioningLog;
|
use App\Models\ProvisioningLog;
|
||||||
use App\Models\Service;
|
use App\Models\Service;
|
||||||
|
use App\Notifications\ServiceCredentialsNotification;
|
||||||
use Illuminate\Http\Client\PendingRequest;
|
use Illuminate\Http\Client\PendingRequest;
|
||||||
use Illuminate\Support\Facades\Http;
|
use Illuminate\Support\Facades\Http;
|
||||||
use Illuminate\Support\Facades\Log;
|
use Illuminate\Support\Facades\Log;
|
||||||
@@ -70,7 +71,20 @@ class SynergyCPService implements ProvisioningServiceInterface
|
|||||||
|
|
||||||
$this->logAction($service, 'provision', 'success', $data);
|
$this->logAction($service, 'provision', 'success', $data);
|
||||||
|
|
||||||
return $service->fresh();
|
$service = $service->fresh();
|
||||||
|
|
||||||
|
if ($service->user) {
|
||||||
|
$service->user->notify(new ServiceCredentialsNotification($service, [
|
||||||
|
'username' => $data['data']['username'] ?? 'root',
|
||||||
|
'password' => $data['data']['password'] ?? 'see control panel',
|
||||||
|
'hostname' => $service->hostname ?? $service->ipv4_address ?? 'N/A',
|
||||||
|
'ip_address' => $service->ipv4_address ?? 'Pending',
|
||||||
|
'port' => $data['data']['ssh_port'] ?? 22,
|
||||||
|
'panel_url' => $data['data']['ipmi_url'] ?? null,
|
||||||
|
]));
|
||||||
|
}
|
||||||
|
|
||||||
|
return $service;
|
||||||
} catch (RuntimeException $e) {
|
} catch (RuntimeException $e) {
|
||||||
throw $e;
|
throw $e;
|
||||||
} catch (\Exception $e) {
|
} catch (\Exception $e) {
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ namespace App\Services\Provisioning;
|
|||||||
|
|
||||||
use App\Models\ProvisioningLog;
|
use App\Models\ProvisioningLog;
|
||||||
use App\Models\Service;
|
use App\Models\Service;
|
||||||
|
use App\Notifications\ServiceCredentialsNotification;
|
||||||
use Illuminate\Http\Client\PendingRequest;
|
use Illuminate\Http\Client\PendingRequest;
|
||||||
use Illuminate\Support\Facades\Http;
|
use Illuminate\Support\Facades\Http;
|
||||||
use Illuminate\Support\Facades\Log;
|
use Illuminate\Support\Facades\Log;
|
||||||
@@ -70,7 +71,20 @@ class VirtFusionService implements ProvisioningServiceInterface
|
|||||||
|
|
||||||
$this->logAction($service, 'provision', 'success', $data);
|
$this->logAction($service, 'provision', 'success', $data);
|
||||||
|
|
||||||
return $service->fresh();
|
$service = $service->fresh();
|
||||||
|
|
||||||
|
if ($service->user) {
|
||||||
|
$service->user->notify(new ServiceCredentialsNotification($service, [
|
||||||
|
'username' => $data['data']['username'] ?? 'root',
|
||||||
|
'password' => $data['data']['password'] ?? 'see control panel',
|
||||||
|
'hostname' => $service->hostname ?? $service->ipv4_address ?? 'N/A',
|
||||||
|
'ip_address' => $service->ipv4_address ?? 'Pending',
|
||||||
|
'port' => $data['data']['ssh_port'] ?? 22,
|
||||||
|
'panel_url' => $data['data']['vnc_url'] ?? null,
|
||||||
|
]));
|
||||||
|
}
|
||||||
|
|
||||||
|
return $service;
|
||||||
} catch (RuntimeException $e) {
|
} catch (RuntimeException $e) {
|
||||||
throw $e;
|
throw $e;
|
||||||
} catch (\Exception $e) {
|
} catch (\Exception $e) {
|
||||||
|
|||||||
127
website/tests/Feature/ServiceCredentialsNotificationTest.php
Normal file
127
website/tests/Feature/ServiceCredentialsNotificationTest.php
Normal file
@@ -0,0 +1,127 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use App\Models\Service;
|
||||||
|
use App\Models\User;
|
||||||
|
use App\Notifications\ServiceCredentialsNotification;
|
||||||
|
|
||||||
|
beforeEach(function () {
|
||||||
|
$this->user = User::factory()->create(['name' => 'John Doe']);
|
||||||
|
$this->service = Service::factory()->create([
|
||||||
|
'user_id' => $this->user->id,
|
||||||
|
'service_type' => 'vps',
|
||||||
|
'hostname' => 'vps1.ezscale.cloud',
|
||||||
|
'ipv4_address' => '192.168.1.100',
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('notification renders with all fields including port and panel url', function () {
|
||||||
|
$credentials = [
|
||||||
|
'username' => 'root',
|
||||||
|
'password' => 's3cur3P@ss',
|
||||||
|
'hostname' => 'vps1.ezscale.cloud',
|
||||||
|
'ip_address' => '192.168.1.100',
|
||||||
|
'port' => 22,
|
||||||
|
'panel_url' => 'https://panel.ezscale.cloud/server/123',
|
||||||
|
];
|
||||||
|
|
||||||
|
$notification = new ServiceCredentialsNotification($this->service, $credentials);
|
||||||
|
$mail = $notification->toMail($this->user);
|
||||||
|
|
||||||
|
expect($mail->greeting)->toBe('Hello John Doe!')
|
||||||
|
->and($mail->introLines)->toContain('Here are your access credentials:')
|
||||||
|
->and($mail->introLines)->toContain('**Hostname:** vps1.ezscale.cloud')
|
||||||
|
->and($mail->introLines)->toContain('**IP Address:** 192.168.1.100')
|
||||||
|
->and($mail->introLines)->toContain('**Username:** root')
|
||||||
|
->and($mail->introLines)->toContain('**Password:** s3cur3P@ss')
|
||||||
|
->and($mail->introLines)->toContain('**Port:** 22')
|
||||||
|
->and($mail->actionText)->toBe('Access Control Panel')
|
||||||
|
->and($mail->actionUrl)->toBe('https://panel.ezscale.cloud/server/123')
|
||||||
|
->and($mail->outroLines)->toContain('For security, we recommend changing your password after first login.')
|
||||||
|
->and($mail->outroLines)->toContain('If you need help, our support team is available 24/7.');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('notification renders without optional fields', function () {
|
||||||
|
$credentials = [
|
||||||
|
'username' => 'root',
|
||||||
|
'password' => 's3cur3P@ss',
|
||||||
|
'hostname' => 'dedicated1.ezscale.cloud',
|
||||||
|
'ip_address' => '10.0.0.50',
|
||||||
|
'port' => null,
|
||||||
|
'panel_url' => null,
|
||||||
|
];
|
||||||
|
|
||||||
|
$service = Service::factory()->create([
|
||||||
|
'user_id' => $this->user->id,
|
||||||
|
'service_type' => 'dedicated',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$notification = new ServiceCredentialsNotification($service, $credentials);
|
||||||
|
$mail = $notification->toMail($this->user);
|
||||||
|
|
||||||
|
// Port line should not be present
|
||||||
|
$allLines = array_merge($mail->introLines, $mail->outroLines);
|
||||||
|
$portLines = array_filter($allLines, fn (string $line): bool => str_contains($line, '**Port:**'));
|
||||||
|
expect($portLines)->toBeEmpty();
|
||||||
|
|
||||||
|
// No action button
|
||||||
|
expect($mail->actionText)->toBeNull()
|
||||||
|
->and($mail->actionUrl)->toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('notification uses mail channel only', function () {
|
||||||
|
$credentials = [
|
||||||
|
'username' => 'root',
|
||||||
|
'password' => 'test123',
|
||||||
|
'hostname' => 'server.ezscale.cloud',
|
||||||
|
'ip_address' => '10.0.0.1',
|
||||||
|
'port' => null,
|
||||||
|
'panel_url' => null,
|
||||||
|
];
|
||||||
|
|
||||||
|
$notification = new ServiceCredentialsNotification($this->service, $credentials);
|
||||||
|
|
||||||
|
expect($notification->via($this->user))->toBe(['mail']);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('notification subject includes service type', function () {
|
||||||
|
$credentials = [
|
||||||
|
'username' => 'root',
|
||||||
|
'password' => 'test123',
|
||||||
|
'hostname' => 'server.ezscale.cloud',
|
||||||
|
'ip_address' => '10.0.0.1',
|
||||||
|
'port' => null,
|
||||||
|
'panel_url' => null,
|
||||||
|
];
|
||||||
|
|
||||||
|
// Test VPS service type
|
||||||
|
$notification = new ServiceCredentialsNotification($this->service, $credentials);
|
||||||
|
$mail = $notification->toMail($this->user);
|
||||||
|
expect($mail->subject)->toBe('Your Vps Server Credentials - EZSCALE');
|
||||||
|
|
||||||
|
// Test dedicated service type
|
||||||
|
$dedicatedService = Service::factory()->create([
|
||||||
|
'user_id' => $this->user->id,
|
||||||
|
'service_type' => 'dedicated',
|
||||||
|
]);
|
||||||
|
$notification = new ServiceCredentialsNotification($dedicatedService, $credentials);
|
||||||
|
$mail = $notification->toMail($this->user);
|
||||||
|
expect($mail->subject)->toBe('Your Dedicated Server Credentials - EZSCALE');
|
||||||
|
|
||||||
|
// Test hosting service type
|
||||||
|
$hostingService = Service::factory()->create([
|
||||||
|
'user_id' => $this->user->id,
|
||||||
|
'service_type' => 'hosting',
|
||||||
|
]);
|
||||||
|
$notification = new ServiceCredentialsNotification($hostingService, $credentials);
|
||||||
|
$mail = $notification->toMail($this->user);
|
||||||
|
expect($mail->subject)->toBe('Your Hosting Server Credentials - EZSCALE');
|
||||||
|
|
||||||
|
// Test game_server service type (with underscore)
|
||||||
|
$gameService = Service::factory()->create([
|
||||||
|
'user_id' => $this->user->id,
|
||||||
|
'service_type' => 'game_server',
|
||||||
|
]);
|
||||||
|
$notification = new ServiceCredentialsNotification($gameService, $credentials);
|
||||||
|
$mail = $notification->toMail($this->user);
|
||||||
|
expect($mail->subject)->toBe('Your Game server Server Credentials - EZSCALE');
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user