Files
sam-manage/app/Http/Controllers/System/TenantMailConfigController.php
김보곤 a0ba7fc13f feat: [email] 테넌트 이메일 설정 관리 기능 추가
- TenantMailConfigController: 목록, 편집, 저장, SMTP 테스트 API
- TenantMailConfig, MailLog 모델 추가
- SmtpConnectionTester: SMTP 연결 테스트 서비스 (에러 코드, 트러블슈팅)
- TenantMailService: 테넌트 설정 기반 메일 발송 (쿼터, Fallback)
- config/mail-presets.php: Gmail/Naver/MS365 등 8개 SMTP 프리셋
- Blade 뷰: 테넌트 목록 현황 + 설정 폼 (프리셋 자동 채움, 연결 테스트)
- 라우트 추가: /system/tenant-mail/*
2026-03-12 07:42:17 +09:00

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', []),
]);
}
}