- Added client_id field to StoreTenantRequest and UpdateTenantRequest for tenant management. - Updated TenantResource to include client_id in the response. - Created migration to establish clients table and link tenants to clients. - Migrated existing tenant integrations to client_integrations table. - Grouped specific tenants under a shared client (OnTicket) in a new migration. - Updated seeders to reflect new client structure and relationships. - Adjusted integration configurations to require client instead of tenant. - Added tests for client integration functionality and ensured existing tests reflect the new client structure.
176 lines
5.5 KiB
PHP
176 lines
5.5 KiB
PHP
<?php
|
|
|
|
namespace App\Domains\Integration\Services;
|
|
|
|
use App\Domains\Client\Models\Client;
|
|
use Exception;
|
|
use Illuminate\Contracts\Mail\Factory as MailFactory;
|
|
use Illuminate\Contracts\Mail\Mailer;
|
|
use Illuminate\Mail\Mailable;
|
|
use Illuminate\Mail\MailManager;
|
|
use Illuminate\Support\Facades\Blade;
|
|
use InvalidArgumentException;
|
|
|
|
class MailService extends BaseIntegrationService
|
|
{
|
|
private const REQUIRED_SMTP_FIELDS = [
|
|
'MAIL_HOST',
|
|
'MAIL_PORT',
|
|
'MAIL_USERNAME',
|
|
'MAIL_PASSWORD',
|
|
'MAIL_FROM_ADDRESS',
|
|
];
|
|
|
|
protected string $integrationCode = 'email';
|
|
|
|
private readonly MailFactory $mailFactory;
|
|
|
|
private ?Mailer $mailer = null;
|
|
|
|
private bool $usesClientMailer = false;
|
|
|
|
public function __construct(?MailFactory $mailFactory = null)
|
|
{
|
|
$this->mailFactory = $mailFactory ?? app(MailFactory::class);
|
|
}
|
|
|
|
public function forTenant(string $tenantCode): self
|
|
{
|
|
parent::forTenant($tenantCode);
|
|
|
|
if ($this->clientIntegration) {
|
|
$this->mailer = $this->resolveMailer();
|
|
$this->usesClientMailer = true;
|
|
} else {
|
|
$this->mailer = $this->mailFactory->mailer();
|
|
$this->usesClientMailer = false;
|
|
}
|
|
|
|
return $this;
|
|
}
|
|
|
|
public function forClient(Client|string $client): self
|
|
{
|
|
parent::forClient($client);
|
|
$this->tenant = $this->clientContext?->tenants()->first();
|
|
|
|
if ($this->clientIntegration) {
|
|
$this->mailer = $this->resolveMailer();
|
|
$this->usesClientMailer = true;
|
|
} else {
|
|
$this->mailer = $this->mailFactory->mailer();
|
|
$this->usesClientMailer = false;
|
|
}
|
|
|
|
return $this;
|
|
}
|
|
|
|
public function getHeaders(): array
|
|
{
|
|
return [];
|
|
}
|
|
|
|
public function send(string|array $recipient, string $subject, string $content): void
|
|
{
|
|
if (! $this->mailer || ! $this->tenant) {
|
|
throw new Exception('MailService no está configurado. Llamá a forTenant() o forClient() primero.');
|
|
}
|
|
|
|
$this->tenant->loadMissing(['headerLogo', 'footerLogo']);
|
|
|
|
$html = Blade::render(
|
|
<<<'BLADE'
|
|
<x-mail.branded-layout :tenant="$tenant" :header-logo-url="$headerLogoUrl" :footer-logo-url="$footerLogoUrl">
|
|
{!! $content !!}
|
|
</x-mail.branded-layout>
|
|
BLADE,
|
|
[
|
|
'tenant' => $this->tenant,
|
|
'headerLogoUrl' => $this->tenant->headerLogo?->getTemporaryUrl(1440),
|
|
'footerLogoUrl' => $this->tenant->footerLogo?->getTemporaryUrl(1440),
|
|
'content' => $content,
|
|
],
|
|
);
|
|
|
|
$mail = (new Mailable)
|
|
->subject($subject)
|
|
->html($html);
|
|
|
|
$this->mailer->to($recipient)->send($mail);
|
|
}
|
|
|
|
public function mailerName(): string
|
|
{
|
|
return $this->usesClientMailer
|
|
? 'client-smtp'
|
|
: (string) config('mail.default');
|
|
}
|
|
|
|
public function onSetup(): void
|
|
{
|
|
if (! $this->mailer || ! $this->clientContext) {
|
|
throw new Exception('MailService no está configurado. Llamá a forTenant() o forClient() primero.');
|
|
}
|
|
|
|
$recipient = $this->getIntegrationSetting('MAIL_FROM_ADDRESS');
|
|
|
|
if (! is_string($recipient) || $recipient === '') {
|
|
throw new InvalidArgumentException('Falta MAIL_FROM_ADDRESS en la configuración SMTP del cliente.');
|
|
}
|
|
|
|
$subject = 'Configuración de correo validada';
|
|
$content = '<h1 style="margin: 0 0 20px;">Configuración de correo validada</h1>'
|
|
.'<p>La integración SMTP de '.e($this->clientContext->name).' fue configurada correctamente.</p>'
|
|
.'<p style="color: #64748b; font-size: 13px;">Este mensaje fue enviado automáticamente para validar las credenciales de correo.</p>';
|
|
|
|
if ($this->tenant) {
|
|
$this->send($recipient, $subject, $content);
|
|
|
|
return;
|
|
}
|
|
|
|
$this->mailer->to($recipient)->send(
|
|
(new Mailable)->subject($subject)->html($content)
|
|
);
|
|
}
|
|
|
|
private function resolveMailer(): Mailer
|
|
{
|
|
$data = $this->clientIntegration?->integration_data;
|
|
|
|
if (! is_array($data)) {
|
|
throw new InvalidArgumentException('La configuración SMTP del cliente no es válida.');
|
|
}
|
|
|
|
foreach (self::REQUIRED_SMTP_FIELDS as $field) {
|
|
if (! array_key_exists($field, $data) || $data[$field] === null || $data[$field] === '') {
|
|
throw new InvalidArgumentException("Falta {$field} en la configuración SMTP del cliente.");
|
|
}
|
|
}
|
|
|
|
// MailFake implements MailFactory but cannot build transports.
|
|
if (! $this->mailFactory instanceof MailManager) {
|
|
return $this->mailFactory->mailer();
|
|
}
|
|
|
|
$mailer = $this->mailFactory->build([
|
|
'name' => 'client-smtp-'.$this->clientContext?->id,
|
|
'transport' => 'smtp',
|
|
'scheme' => $data['MAIL_SCHEME'] ?? null,
|
|
'host' => $data['MAIL_HOST'],
|
|
'port' => (int) $data['MAIL_PORT'],
|
|
'username' => $data['MAIL_USERNAME'],
|
|
'password' => $data['MAIL_PASSWORD'],
|
|
'timeout' => isset($data['MAIL_TIMEOUT']) ? (int) $data['MAIL_TIMEOUT'] : null,
|
|
'local_domain' => $data['MAIL_EHLO_DOMAIN'] ?? null,
|
|
]);
|
|
|
|
$mailer->alwaysFrom(
|
|
$data['MAIL_FROM_ADDRESS'],
|
|
$data['MAIL_FROM_NAME'] ?? $this->clientContext?->name,
|
|
);
|
|
|
|
return $mailer;
|
|
}
|
|
}
|