Files
portal-api/app/Mail/IpAllocationMail.php
T
baodev0 0b08689ea4 feat: harden admin auth and ticket workflows
- add rotating admin refresh tokens and password gates\n- expand ticket galleries, exports, and client resources\n- improve IP allocation auditing and dashboard data\n- enforce encrypted client access tokens on public routes
2026-10-16 17:00:55 +07:00

115 lines
4.3 KiB
PHP

<?php
namespace App\Mail;
use App\Mail\Concerns\AttachesStoredFiles;
use App\Models\IpPool;
use App\Support\EmailTemplateShell;
use Carbon\Carbon;
use Illuminate\Mail\Mailables\Envelope;
use Illuminate\Bus\Queueable;
class IpAllocationMail extends TemplateMailable
{
use Queueable, AttachesStoredFiles;
public const ALLOCATED = 'ip_allocated';
public const REVOKED = 'ip_revoked';
private const CELL = "padding: 13px 14px; font-size: 15px; text-align: center; font-family: 'Times New Roman', Times, serif; color: #2b2b2b;";
private const HEAD_CELL = "padding: 11px 14px; color: #c2410c; font-size: 13px; font-weight: bold; letter-spacing: .4px; border-bottom: 1px solid #f6d2b8; background: #fff5ee; text-align: center; font-family: 'Times New Roman', Times, serif;";
/**
* @param array<int, array{ip_address: string, cidr: ?int, gateway: ?string, service_name: ?string}> $ips
*/
public function __construct(
public readonly string $type,
public readonly string $clientName,
public readonly array $ips,
public readonly string $date,
public readonly ?string $note = null,
public readonly ?string $subjectOverride = null,
public readonly array $storedFiles = [],
) {}
/**
* @param iterable<int, IpPool> $pools
* @return array<int, array{ip_address: string, cidr: ?int, gateway: ?string, service_name: ?string}>
*/
public static function ipsFrom(iterable $pools): array
{
return collect($pools)
->map(fn (IpPool $pool) => $pool->only(['ip_address', 'cidr', 'gateway', 'service_name']))
->values()
->all();
}
public function attachments(): array
{
return $this->storedFileAttachments($this->storedFiles);
}
public function envelope(): Envelope
{
$envelope = parent::envelope();
if (filled($this->subjectOverride)) {
$envelope->subject = trim($this->subjectOverride);
}
return $envelope;
}
protected function scenario(): string
{
return $this->type;
}
protected function variables(): array
{
$carbon = Carbon::parse($this->date);
$date = $carbon->format('d/m/Y');
return [
'client_name' => $this->clientName,
'ip_count' => (string) count($this->ips),
'ip_rows' => collect($this->ips)->map(fn (array $ip) => EmailTemplateShell::row(
e($ip['ip_address']),
e(implode(' · ', array_filter([
$ip['cidr'] ? '/' . $ip['cidr'] : null,
$ip['gateway'] ? 'Gateway ' . $ip['gateway'] : null,
])) ?: '—'),
))->implode(''),
'ip_table' => $this->ipTable($date),
'issued_on' => "ngày {$carbon->day} tháng {$carbon->month} năm {$carbon->year}",
'allocated_at' => $date,
'revoked_at' => $date,
'note_block' => $this->note ? EmailTemplateShell::note(nl2br(e($this->note)), 'Ghi chú:') : '',
];
}
private function ipTable(string $date): string
{
$last = count($this->ips) - 1;
$rows = collect($this->ips)->values()->map(function (array $ip, int $index) use ($date, $last) {
$border = $index === $last ? '' : ' border-bottom: 1px solid #f6d2b8;';
$cell = fn (string $value, string $extra = '') => '<td style="' . self::CELL . $border . $extra . '">' . $value . '</td>';
return '<tr>'
. $cell((string) ($index + 1), ' width: 60px;')
. $cell(e($ip['ip_address']), " font-family: Consolas, 'Courier New', monospace; font-weight: bold; color: #c2410c;")
. $cell(e($ip['service_name'] ?: '—'))
. $cell($date)
. '</tr>';
})->implode('');
$head = collect(['STT', 'IP Address', 'Dịch vụ', 'Ngày cấp phát'])
->map(fn (string $title) => '<td style="' . self::HEAD_CELL . '">' . $title . '</td>')
->implode('');
return '<table style="' . EmailTemplateShell::TABLE_STYLE . '" role="presentation" width="100%" cellspacing="0" cellpadding="0"><tbody>'
. '<tr>' . $head . '</tr>' . $rows
. '</tbody></table>';
}
}