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