- TenantMailConfigController: 목록, 편집, 저장, SMTP 테스트 API - TenantMailConfig, MailLog 모델 추가 - SmtpConnectionTester: SMTP 연결 테스트 서비스 (에러 코드, 트러블슈팅) - TenantMailService: 테넌트 설정 기반 메일 발송 (쿼터, Fallback) - config/mail-presets.php: Gmail/Naver/MS365 등 8개 SMTP 프리셋 - Blade 뷰: 테넌트 목록 현황 + 설정 폼 (프리셋 자동 채움, 연결 테스트) - 라우트 추가: /system/tenant-mail/*
251 lines
8.8 KiB
PHP
251 lines
8.8 KiB
PHP
<?php
|
|
|
|
namespace App\Http\Controllers\System;
|
|
|
|
use App\Http\Controllers\Controller;
|
|
use App\Models\Tenants\MailLog;
|
|
use App\Models\Tenants\Tenant;
|
|
use App\Models\Tenants\TenantMailConfig;
|
|
use App\Services\Mail\SmtpConnectionTester;
|
|
use App\Services\Mail\TenantMailService;
|
|
use Illuminate\Http\JsonResponse;
|
|
use Illuminate\Http\Request;
|
|
use Illuminate\Http\Response;
|
|
use Illuminate\View\View;
|
|
|
|
class TenantMailConfigController extends Controller
|
|
{
|
|
/**
|
|
* 전체 테넌트 메일 설정 현황
|
|
*/
|
|
public function index(Request $request): View|Response
|
|
{
|
|
if ($request->header('HX-Request')) {
|
|
return response('', 200)->header('HX-Redirect', route('system.tenant-mail.index'));
|
|
}
|
|
|
|
$tenants = Tenant::orderBy('company_name')
|
|
->get()
|
|
->map(function ($tenant) {
|
|
$config = TenantMailConfig::withoutGlobalScopes()
|
|
->where('tenant_id', $tenant->id)
|
|
->first();
|
|
|
|
$todayCount = MailLog::withoutGlobalScopes()
|
|
->where('tenant_id', $tenant->id)
|
|
->whereDate('created_at', today())
|
|
->whereIn('status', ['queued', 'sent'])
|
|
->count();
|
|
|
|
return (object) [
|
|
'tenant' => $tenant,
|
|
'config' => $config,
|
|
'today_count' => $todayCount,
|
|
];
|
|
});
|
|
|
|
return view('system.tenant-mail.index', compact('tenants'));
|
|
}
|
|
|
|
/**
|
|
* 테넌트 메일 설정 편집 폼
|
|
*/
|
|
public function edit(Request $request, int $tenantId): View|Response
|
|
{
|
|
if ($request->header('HX-Request')) {
|
|
return response('', 200)->header('HX-Redirect', route('system.tenant-mail.edit', $tenantId));
|
|
}
|
|
|
|
$tenant = Tenant::findOrFail($tenantId);
|
|
|
|
$config = TenantMailConfig::withoutGlobalScopes()
|
|
->where('tenant_id', $tenantId)
|
|
->first();
|
|
|
|
$presets = config('mail-presets', []);
|
|
|
|
$mailService = app(TenantMailService::class);
|
|
$todayCount = $mailService->getTodayCount($tenantId);
|
|
$monthCount = $mailService->getMonthCount($tenantId);
|
|
|
|
return view('system.tenant-mail.edit', compact(
|
|
'tenant',
|
|
'config',
|
|
'presets',
|
|
'todayCount',
|
|
'monthCount'
|
|
));
|
|
}
|
|
|
|
/**
|
|
* 메일 설정 저장
|
|
*/
|
|
public function update(Request $request, int $tenantId): JsonResponse
|
|
{
|
|
$tenant = Tenant::findOrFail($tenantId);
|
|
|
|
$validated = $request->validate([
|
|
'provider' => 'required|string|in:platform,smtp',
|
|
'from_name' => 'required|string|max:255',
|
|
'from_address' => 'required|email|max:255',
|
|
'reply_to' => 'nullable|email|max:255',
|
|
'daily_limit' => 'required|integer|min:1|max:99999',
|
|
'is_active' => 'boolean',
|
|
// SMTP 설정
|
|
'preset' => 'nullable|string',
|
|
'smtp_host' => 'required_if:provider,smtp|nullable|string|max:255',
|
|
'smtp_port' => 'required_if:provider,smtp|nullable|integer|min:1|max:65535',
|
|
'smtp_encryption' => 'required_if:provider,smtp|nullable|string|in:tls,ssl',
|
|
'smtp_username' => 'required_if:provider,smtp|nullable|string|max:255',
|
|
'smtp_password' => 'nullable|string|max:255',
|
|
// 브랜딩
|
|
'branding_company_name' => 'nullable|string|max:255',
|
|
'branding_primary_color' => 'nullable|string|max:7',
|
|
'branding_company_address' => 'nullable|string|max:500',
|
|
'branding_company_phone' => 'nullable|string|max:50',
|
|
'branding_footer_text' => 'nullable|string|max:500',
|
|
]);
|
|
|
|
$config = TenantMailConfig::withoutGlobalScopes()
|
|
->where('tenant_id', $tenantId)
|
|
->first();
|
|
|
|
$data = [
|
|
'tenant_id' => $tenantId,
|
|
'provider' => $validated['provider'],
|
|
'from_name' => $validated['from_name'],
|
|
'from_address' => $validated['from_address'],
|
|
'reply_to' => $validated['reply_to'] ?? null,
|
|
'daily_limit' => $validated['daily_limit'],
|
|
'is_active' => $validated['is_active'] ?? true,
|
|
];
|
|
|
|
// Options 구성
|
|
$options = $config?->options ?? [];
|
|
|
|
// SMTP 설정
|
|
if ($validated['provider'] === 'smtp') {
|
|
$options['preset'] = $validated['preset'] ?? 'custom';
|
|
$options['smtp'] = [
|
|
'host' => $validated['smtp_host'],
|
|
'port' => $validated['smtp_port'],
|
|
'encryption' => $validated['smtp_encryption'],
|
|
'username' => $validated['smtp_username'],
|
|
];
|
|
|
|
// 비밀번호: 새로 입력된 경우에만 업데이트
|
|
if (! empty($validated['smtp_password'])) {
|
|
$options['smtp']['password'] = encrypt($validated['smtp_password']);
|
|
} elseif (isset($config?->options['smtp']['password'])) {
|
|
$options['smtp']['password'] = $config->options['smtp']['password'];
|
|
}
|
|
} else {
|
|
// platform 모드에서는 SMTP 설정 제거
|
|
unset($options['smtp'], $options['preset']);
|
|
}
|
|
|
|
// 브랜딩
|
|
$options['branding'] = array_filter([
|
|
'company_name' => $validated['branding_company_name'] ?? null,
|
|
'primary_color' => $validated['branding_primary_color'] ?? '#1a56db',
|
|
'company_address' => $validated['branding_company_address'] ?? null,
|
|
'company_phone' => $validated['branding_company_phone'] ?? null,
|
|
'footer_text' => $validated['branding_footer_text'] ?? 'SAM 시스템에서 발송된 메일입니다.',
|
|
]);
|
|
|
|
$data['options'] = $options;
|
|
$data['updated_by'] = auth()->id();
|
|
|
|
if ($config) {
|
|
$config->update($data);
|
|
} else {
|
|
$data['created_by'] = auth()->id();
|
|
$config = TenantMailConfig::create($data);
|
|
}
|
|
|
|
return response()->json([
|
|
'ok' => true,
|
|
'message' => '메일 설정이 저장되었습니다.',
|
|
'data' => $config->fresh(),
|
|
]);
|
|
}
|
|
|
|
/**
|
|
* SMTP 연결 테스트
|
|
*/
|
|
public function test(Request $request, int $tenantId): JsonResponse
|
|
{
|
|
$validated = $request->validate([
|
|
'host' => 'required|string',
|
|
'port' => 'required|integer',
|
|
'encryption' => 'required|string|in:tls,ssl',
|
|
'username' => 'required|string',
|
|
'password' => 'required|string',
|
|
'preset' => 'nullable|string',
|
|
'send_test_mail' => 'boolean',
|
|
]);
|
|
|
|
$tester = new SmtpConnectionTester;
|
|
$result = $tester->testViaLaravel(
|
|
host: $validated['host'],
|
|
port: $validated['port'],
|
|
encryption: $validated['encryption'],
|
|
username: $validated['username'],
|
|
password: $validated['password'],
|
|
testRecipient: ($validated['send_test_mail'] ?? false) ? $validated['username'] : null
|
|
);
|
|
|
|
// 테스트 결과를 config에 기록
|
|
$config = TenantMailConfig::withoutGlobalScopes()
|
|
->where('tenant_id', $tenantId)
|
|
->first();
|
|
|
|
if ($config) {
|
|
$config->setOption('connection_test', [
|
|
'last_tested_at' => now()->toIso8601String(),
|
|
'last_result' => $result['success'] ? 'success' : 'failed',
|
|
'response_time_ms' => $result['response_time_ms'],
|
|
'server_banner' => $result['server_banner'] ?? null,
|
|
'tested_by' => auth()->user()?->email,
|
|
]);
|
|
$config->save();
|
|
}
|
|
|
|
if ($result['success']) {
|
|
return response()->json([
|
|
'ok' => true,
|
|
'message' => $result['message'],
|
|
'data' => [
|
|
'response_time_ms' => $result['response_time_ms'],
|
|
'server_banner' => $result['server_banner'],
|
|
'test_mail_sent' => $result['test_mail_sent'] ?? false,
|
|
],
|
|
]);
|
|
}
|
|
|
|
return response()->json([
|
|
'ok' => false,
|
|
'message' => $result['message'],
|
|
'data' => [
|
|
'error_code' => $result['error_code'],
|
|
'troubleshoot' => SmtpConnectionTester::getTroubleshoot(
|
|
$result['error_code'] ?? 'UNKNOWN',
|
|
$validated['preset'] ?? null
|
|
),
|
|
'response_time_ms' => $result['response_time_ms'],
|
|
],
|
|
]);
|
|
}
|
|
|
|
/**
|
|
* SMTP 프리셋 목록
|
|
*/
|
|
public function presets(): JsonResponse
|
|
{
|
|
return response()->json([
|
|
'ok' => true,
|
|
'data' => config('mail-presets', []),
|
|
]);
|
|
}
|
|
}
|