HEX
Server: LiteSpeed
System: Linux s1049.use1.mysecurecloudhost.com 4.18.0-477.27.2.lve.el8.x86_64 #1 SMP Wed Oct 11 12:32:56 UTC 2023 x86_64
User: xedaptot (3356)
PHP: 8.3.31
Disabled: NONE
Upload Files
File: /home/xedaptot/ai.naniguide.com/app/Http/Controllers/Refactor/Admin/SendingServerController.php
<?php

namespace App\Http\Controllers\Refactor\Admin;

use App\Http\Controllers\Controller;
use App\Http\Requests\Admin\WarmupStrategy\AssignWarmupStrategyRequest;
use App\Library\Facades\Hook;
use App\Library\StringHelper;
use App\Model\SendingIdentity;
use App\Model\SendingServer;
use App\Model\WarmupStrategy;
use App\Services\WarmupStrategyService;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Gate;

use function App\Helpers\dkim_sign_with_default_domain;

class SendingServerController extends Controller
{
    public function __construct(protected WarmupStrategyService $warmupStrategyService)
    {
    }

    public function index(Request $request)
    {
        if (!$request->user()->admin->can('read', new SendingServer())) {
            return $this->notAuthorized();
        }

        $query = SendingServer::system();

        $stats = [
            'total' => (clone $query)->count(),
            'active' => (clone $query)->where('sending_servers.status', 'active')->count(),
            'inactive' => (clone $query)->where('sending_servers.status', 'inactive')->count(),
        ];

        // Type distribution for chart
        $typeDistribution = (clone $query)
            ->selectRaw('type, count(*) as count')
            ->groupBy('type')
            ->pluck('count', 'type')
            ->toArray();

        return view('refactor.pages.admin.sending-servers.index', compact('stats', 'typeDistribution'));
    }

    public function listing(Request $request)
    {
        if (!$request->user()->admin->can('read', new SendingServer())) {
            return response('Unauthorized', 403);
        }

        if (!$request->user()->admin->can('readAll', new SendingServer())) {
            $request->merge(['admin_id' => $request->user()->admin->id]);
        }

        $request->merge(['no_customer' => true]);

        $query = SendingServer::system()->search($request->keyword)->filter($request);

        // Type filter
        if ($request->type && $request->type !== 'all') {
            $query = $query->where('sending_servers.type', $request->type);
        }

        // Status filter
        if ($request->status && $request->status !== 'all') {
            $query = $query->where('sending_servers.status', $request->status);
        }

        $sortBy = $request->sort_by ?? 'sending_servers.created_at';
        $sortDir = $request->sort_direction ?? 'desc';
        $query = $query->orderBy($sortBy, $sortDir);

        $items = $query->paginate($request->per_page ?? 15);

        return view('refactor.pages.admin.sending-servers._list', compact('items'));
    }

    public function select(Request $request)
    {
        if (!$request->user()->admin->can('create', SendingServer::class)) {
            return $this->notAuthorized();
        }

        $more = $this->collectPluginPickerCards();

        return view('refactor.pages.admin.sending-servers.select', compact('more'));
    }

    /**
     * Build "Plugin Servers" picker cards from two sources:
     *  (1) merged `register_sending_server_driver` hook with picker metadata
     *      (name / description / icon_url) — preferred for new plugins.
     *  (2) legacy `register_sending_server` hook — kept for backward
     *      compatibility with old plugins that registered the picker card
     *      separately from the driver.
     *
     * @return list<array{name: string, description: string, icon_url: string, create_url: string}>
     */
    private function collectPluginPickerCards(): array
    {
        $cards = [];

        foreach (Hook::collect('register_sending_server_driver') as $meta) {
            if (!is_array($meta) || !isset($meta['type'], $meta['driver'])) continue;
            // Only show on picker if plugin shipped picker metadata.
            if (!isset($meta['name'])) continue;

            $cards[] = [
                'name'        => (string) $meta['name'],
                'description' => (string) ($meta['description'] ?? ''),
                'icon_url'    => (string) ($meta['icon_url'] ?? ''),
                'create_url'  => (string) ($meta['create_url']
                    ?? route('refactor.admin.sending-servers.create', ['type' => $meta['type']])),
            ];
        }

        foreach (Hook::collect('register_sending_server') as $legacy) {
            if (!is_array($legacy)) continue;
            $cards[] = [
                'name'        => (string) ($legacy['name'] ?? ''),
                'description' => (string) ($legacy['description'] ?? ''),
                'icon_url'    => (string) ($legacy['icon_url'] ?? ''),
                'create_url'  => (string) ($legacy['create_url'] ?? '#'),
            ];
        }

        return $cards;
    }

    public function create(Request $request)
    {
        if (!$request->user()->admin->can('create', SendingServer::class)) {
            return $this->notAuthorized();
        }

        $server = SendingServer::newOfType($request->type);
        $server->status = 'active';
        $server->uid = '0';
        $server->quota_value = '1000';
        $server->quota_base = '1';
        $server->quota_unit = 'hour';
        $server->fill($request->old());
        $server->name = SendingServer::resolveTypeLabel($request->type);

        return view('refactor.pages.admin.sending-servers.create', [
            'server' => $server,
        ]);
    }

    public function store(Request $request)
    {
        if (!$request->user()->admin->can('create', SendingServer::class)) {
            return $this->notAuthorized();
        }

        list($validator, $server) = SendingServer::createFromArray(array_merge($request->all(), [
            'admin_id' => $request->user()->admin->id,
        ]));

        if ($validator->fails()) {
            if ($server->isExtended()) {
                return redirect()->back()->withErrors($validator)->withInput();
            }
            return redirect()->route('refactor.admin.sending-servers.create', $server->type)
                    ->withErrors($validator)->withInput();
        }

        $request->session()->flash('alert-success', trans('refactor/admin_sending_servers.flash.created'));

        if ($server->isExtended()) {
            return redirect($server->getEditUrl());
        }
        return redirect()->route('refactor.admin.sending-servers.edit', $server->uid);
    }

    public function edit(Request $request, $uid)
    {
        $server = SendingServer::findByUid($uid);
        if (!$server) {
            abort(404);
        }

        if (!$request->user()->admin->can('update', $server)) {
            return $this->notAuthorized();
        }

        dispatch(new \App\Jobs\UpdateSendingServerJob($server));

        if ($request->old() && empty($request->old()['bounce_handler_id'])) {
            $server->bounce_handler_id = null;
        }
        if ($request->old() && empty($request->old()['feedback_loop_handler_id'])) {
            $server->feedback_loop_handler_id = null;
        }

        $server->fill($request->old());

        $notices = [];

        try {
            $driver = $server->driver();
            $testResult = $driver->test();
            if (!$testResult->ok) {
                throw new \RuntimeException($testResult->error ?? 'Unknown test failure');
            }
            if ($driver instanceof \App\SendingServers\Capabilities\SupportsIdentitySync) {
                app(\App\SendingServers\Identities\IdentitySyncService::class)->sync($server);
            }
            $server->setDefaultFromEmailAddress();
        } catch (\Throwable $ex) {
            $server->disable();
            $notices[] = [
                'title' => trans('messages.sending_server.connect_failed'),
                'message' => $ex->getMessage(),
            ];
        }

        $identities = collect();
        try {
            $identities = $server->identities()->orderBy('value')->get();
        } catch (\Throwable $ex) {
            $notices[] = [
                'title'   => trans('messages.sending_server.identities_list_failed'),
                'message' => $ex->getMessage(),
            ];
        }

        if (isset($request->old()['options'])) {
            $server->options = json_encode($request->old()['options']);
        }

        $bigNotices = Hook::collect('generate_big_notice_for_sending_server', [$server]);

        $emailit = ($server->getConfig('host') == 'smtp.emailit.com');
        $webhook = $emailit ? action('DeliveryController@notify', ['stype' => 'emailit']) : null;

        return view('refactor.pages.admin.sending-servers.edit', [
            'server'     => $server,
            'bigNotices' => $bigNotices,
            'notices'    => $notices,
            'identities' => $identities,
            'emailit'    => $emailit,
            'webhook'    => $webhook,
        ]);
    }

    /**
     * Save the Connection tab — driver-specific credentials (host, smtp_*, api_key, etc).
     * Validates via the driver's $cols rules + a live connection probe.
     */
    public function connectionSave(Request $request, $uid)
    {
        $server = SendingServer::findByUid($uid);
        if (!$server) {
            abort(404);
        }

        if (!$request->user()->admin->can('update', $server)) {
            return $this->notAuthorized();
        }

        $server->fill($request->all());

        $validator = $server->validConnection($request->all());

        if ($validator->fails()) {
            if ($server->isExtended()) {
                return redirect($server->getEditUrl())->withErrors($validator)->withInput();
            }
            return redirect()->route('refactor.admin.sending-servers.edit', ['uid' => $server->uid, 'section' => 'connection'])
                    ->withErrors($validator)->withInput();
        }

        $server->save();

        $request->session()->flash('alert-success', trans('refactor/admin_sending_servers.flash.updated'));
        if ($server->isExtended()) {
            return redirect($server->getEditUrl());
        }
        return redirect()->route('refactor.admin.sending-servers.edit', ['uid' => $server->uid, 'section' => 'connection']);
    }

    public function enable(Request $request)
    {
        $items = SendingServer::whereIn(
            'uid',
            is_array($request->uids) ? $request->uids : explode(',', $request->uids)
        );

        foreach ($items->get() as $item) {
            if ($request->user()->admin->can('enable', $item)) {
                $item->enable();
            }
        }

        echo trans('refactor/admin_sending_servers.flash.enabled');
    }

    public function disable(Request $request)
    {
        $items = SendingServer::whereIn(
            'uid',
            is_array($request->uids) ? $request->uids : explode(',', $request->uids)
        );

        foreach ($items->get() as $item) {
            if ($request->user()->admin->can('disable', $item)) {
                $item->disable();
            }
        }

        echo trans('refactor/admin_sending_servers.flash.disabled');
    }

    public function delete(Request $request)
    {
        $items = SendingServer::whereIn(
            'uid',
            is_array($request->uids) ? $request->uids : explode(',', $request->uids)
        );

        foreach ($items->get() as $item) {
            if ($request->user()->admin->can('delete', $item)) {
                $item->doDelete();
            }
        }

        echo trans('refactor/admin_sending_servers.flash.deleted');
    }

    public function testConnection(Request $request, $uid)
    {
        $server = SendingServer::findByUid($uid);
        if (!$server) {
            abort(404);
        }

        if (!$request->user()->admin->can('update', $server)) {
            return $this->notAuthorized();
        }

        // F4.1 (P11) — single source of truth for the probe shared with the
        // `sending.test_connection` AI tool. UI button + AI tool see byte-
        // identical health-check semantics.
        $result = app(\App\Services\SendingServerHealthService::class)->testConnection($server);
        if ($result['success']) {
            return response()->json([
                'status' => 'success',
                'message' => trans('messages.sending_server.test_success'),
            ]);
        }

        // Preserve legacy auto-disable side-effect on UI button path; the AI
        // tool path does NOT disable (probe is non-destructive there).
        $server->disable();
        return response()->json([
            'status' => 'error',
            'message' => $result['error_message'] ?? '',
        ], 400);
    }

    public function test(Request $request, $uid)
    {
        $server = SendingServer::findByUid($uid);
        if (!$server) {
            abort(404);
        }

        $server->fill($request->all());

        if (!$request->user()->admin->can('test', $server)) {
            return $this->notAuthorized();
        }

        if ($request->isMethod('post')) {
            $email = new \Symfony\Component\Mime\Email();
            $msgId = StringHelper::generateMessageId(StringHelper::getDomainFromEmail($request->from_email));
            $email->getHeaders()->addIdHeader('Message-ID', $msgId);
            $email->getHeaders()->addDateHeader('Date', new \DateTimeImmutable());
            $email->getHeaders()->addTextHeader('X-Acelle-Message-Id', $msgId);
            $email->subject($request->subject);
            $email->from($request->from_email);
            $email->to($request->to_email);
            $email->replyTo($request->from_email);
            $email->text($request->content);

            if (config('custom.sign_with_default_domain')) {
                $email = dkim_sign_with_default_domain($email);
            }

            try {
                $server->sendTestEmail($email);
            } catch (\Exception $ex) {
                return response()->json([
                    'status' => 'error',
                    'message' => $ex->getMessage(),
                ]);
            }

            return response()->json([
                'status' => 'success',
                'message' => trans('refactor/admin_sending_servers.test.success'),
            ]);
        }

        return view('refactor.pages.admin.sending-servers.test', compact('server'));
    }

    public function sendingLimit(Request $request)
    {
        if (!$request->uid) {
            $server = new SendingServer();
        } else {
            $server = SendingServer::findByUid($request->uid);
        }

        $server->fill($request->all());

        if ($server->quota_value == -1) {
            $server->quota_value = '1000';
            $server->quota_base = '1';
            $server->quota_unit = 'hour';
            $server->setOption('sending_limit', '1000_per_hour');
        }

        if ($request->isMethod('post')) {
            return view('refactor.pages.admin.sending-servers._partials._sending_limit', [
                'quotaValue' => $request->quota_value,
                'quotaBase' => $request->quota_base,
                'quotaUnit' => $request->quota_unit,
                'server' => $server,
            ]);
        }

        return view('refactor.pages.admin.sending-servers._partials.sending_limit', compact('server'));
    }

    /**
     * Save the Configuration tab — name, default_from_email, bounce/FBL handlers,
     * sending_limit + quota_*. Identity options are handled by identitySave() so
     * this method's scope is strictly the Configuration form's fields.
     */
    public function configurationSave(Request $request, $uid)
    {
        $server = SendingServer::findByUid($uid);
        if (!$server) {
            abort(404);
        }

        if (!$request->user()->admin->can('update', $server)) {
            return $this->notAuthorized();
        }

        $this->validate($request, [
            'name'                     => 'required',
            'default_from_email'       => 'nullable|email',
            'bounce_handler_id'        => 'nullable',
            'feedback_loop_handler_id' => 'nullable',
        ]);

        $server->name = $request->name;
        $server->default_from_email = $request->default_from_email;
        $server->bounce_handler_id = !empty($request->bounce_handler_id) ? $request->bounce_handler_id : null;
        $server->feedback_loop_handler_id = !empty($request->feedback_loop_handler_id) ? $request->feedback_loop_handler_id : null;

        // sending_limit → quota_value/unit/base columns + persist as option for "current"/"custom" passthrough.
        $sendingLimit = $request->input('options.sending_limit');
        if ($sendingLimit !== null && $sendingLimit !== '') {
            $server->setOption('sending_limit', $sendingLimit);
            if ($sendingLimit !== 'custom' && $sendingLimit !== 'current') {
                $limits = SendingServer::sendingLimitValues()[$sendingLimit] ?? null;
                if ($limits) {
                    $server->quota_value = $limits['quota_value'];
                    $server->quota_unit  = $limits['quota_unit'];
                    $server->quota_base  = $limits['quota_base'];
                }
            }
        }

        $server->save();

        $request->session()->flash('alert-success', trans('refactor/admin_sending_servers.flash.updated'));
        if ($server->isExtended()) {
            return redirect($server->getEditUrl());
        }
        return redirect()->route('refactor.admin.sending-servers.edit', ['uid' => $server->uid, 'section' => 'config']);
    }

    /**
     * Save the Sender Identity tab — verification toggles + per-identity enabled
     * flag (which domains/emails on the server are advertised to customers).
     * Validates only identity-scoped options; never touches name/handlers/quota.
     */
    public function identitySave(Request $request, $uid)
    {
        $server = SendingServer::findByUid($uid);
        if (!$server) {
            abort(404);
        }

        if (!$request->user()->admin->can('update', $server)) {
            return $this->notAuthorized();
        }

        $this->validate($request, [
            'options.allow_unverified_from_email'        => 'nullable|in:yes,no',
            'options.allow_verify_domain_against_acelle' => 'nullable|in:yes,no',
            'options.allow_verify_email_against_acelle'  => 'nullable|in:yes,no',
            'options.emails'                             => 'array',
            'options.emails.*'                           => 'string',
            'options.domains'                            => 'array',
            'options.domains.*'                          => 'string',
        ]);

        $options = $request->input('options', []);

        // Identity-scoped option keys only — prevent any other field from leaking through.
        $identityKeys = [
            'allow_unverified_from_email',
            'allow_verify_domain_against_acelle',
            'allow_verify_email_against_acelle',
        ];
        $identityOpts = array_intersect_key($options, array_flip($identityKeys));
        if (!empty($identityOpts)) {
            $server->setOptions($identityOpts);
        }

        $server->bulkUpdateShared($options);
        $server->save();

        $request->session()->flash('alert-success', trans('refactor/admin_sending_servers.flash.updated'));
        if ($server->isExtended()) {
            return redirect($server->getEditUrl());
        }
        return redirect()->route('refactor.admin.sending-servers.edit', ['uid' => $server->uid, 'section' => 'identity']);
    }


    public function awsRegionHost(Request $request)
    {
        if ($request->uid) {
            $server = SendingServer::findByUid($request->uid);
        } else {
            $server = new SendingServer();
        }

        foreach (SendingServer::awsRegionSelectOptions() as $option) {
            if (isset($option['host']) && $option['value'] == $request->aws_region) {
                $server->setConfig('host', $option['host']);
            }
        }

        return view('refactor.pages.admin.sending-servers._partials._aws_region_host', compact('server'));
    }

    public function addDomain(Request $request, $uid)
    {
        $server = SendingServer::findByUid($uid);

        if ($request->isMethod('post')) {
            $value = strtolower(trim((string) $request->domain));
            $isEmail = checkEmail($value);
            $kind = $isEmail
                ? \App\SendingServers\Identities\IdentityKind::EMAIL
                : \App\SendingServers\Identities\IdentityKind::DOMAIN;

            $validator = \Validator::make(
                ['domain' => $value],
                ['domain' => $isEmail
                    ? ['required', 'email']
                    : ['required', 'regex:' . SendingIdentity::VALIDATION_REGEXP],
                ],
            );

            $exists = $server->identities()
                ->where('kind', $kind->value)
                ->where('value', $value)
                ->exists();
            if ($exists) {
                $validator->errors()->add('domain', trans('messages.sending_identity.exist_error'));
            }

            if ($validator->fails()) {
                return redirect()->route('refactor.admin.sending-servers.add-domain', $server->uid)
                    ->withErrors($validator)->withInput();
            }

            app(\App\SendingServers\Identities\IdentityCreationService::class)
                ->createByAdmin(
                    server: $server,
                    kind: $kind,
                    value: $value,
                    initialStatus: \App\SendingServers\Identities\IdentityStatus::VERIFIED,
                    shared: true,
                );

            $request->session()->flash('alert-success', trans('refactor/admin_sending_servers.flash.updated'));
            return;
        }

        return view('refactor.pages.admin.sending-servers.add_domain', compact('server'));
    }

    public function removeDomain(Request $request, $uid, $identity)
    {
        $server = SendingServer::findByUid($uid);
        $value = base64_decode($identity);

        $row = $server->identities()->where('value', $value)->first();
        if ($row === null) {
            return response()->json([
                'message' => trans('messages.sending_server.domain.removed'),
                'reload' => true,
            ]);
        }

        // Guard: VENDOR_SYNC rows can't be deleted from Acelle alone — the
        // next IdentitySyncService::sync would re-INSERT from vendor list.
        // Until a SupportsRemoteDomainDelete capability ships, refuse the
        // operation. Frontend already hides the trash for these rows; this
        // catch handles URL/API tinkering.
        if ($row->management_mode === \App\SendingServers\Identities\ManagementMode::VENDOR_SYNC) {
            return response()->json([
                'status'  => 'error',
                'message' => 'This identity is vendor-managed (VENDOR_SYNC). '
                    . 'Delete it at the vendor dashboard; the next sync will reflect the change.',
            ], 422);
        }

        $row->delete();

        return response()->json([
            'message' => trans('messages.sending_server.domain.removed'),
            'reload' => true,
        ]);
    }

    public function fromDropbox(Request $request, $uid)
    {
        $server = SendingServer::findByUid($uid);
        $droplist = app(\App\SendingServers\Identities\IdentityCatalog::class)
            ->droplistForServer($server, strtolower(trim($request->keyword)));
        return response()->json($droplist);
    }

    public function warmupStats(Request $request, $uid)
    {
        $server = SendingServer::findByUid($uid);
        abort_if(is_null($server), 404);
        abort_if(is_null($server->warmupStrategy), 404);

        return view('refactor.pages.admin.sending-servers.warmup_stats', compact('server'));
    }

    public function warmupReset(Request $request, $uid)
    {
        $server = SendingServer::findByUid($uid);
        abort_if(is_null($server), 404);

        $server->warmupUsages()->delete();
        $server->warmupLogs()->delete();
        $server->warmup_started_at = null;
        $server->warmup_enabled = false;
        $server->save();

        return response()->json([
            'message' => 'Warmup records have been reset.',
            'reload' => true,
        ]);
    }

    public function warmupSave(AssignWarmupStrategyRequest $request, $uid)
    {
        $server = SendingServer::findByUid($uid);
        abort_if(is_null($server), 404);

        $warmupStrategy = $request->filled('warmup_strategy_uid')
            ? WarmupStrategy::findByUid($request->warmup_strategy_uid)
            : null;

        $this->warmupStrategyService->assignToSendingServer($server, $warmupStrategy);

        return redirect()->route('refactor.admin.sending-servers.edit', $server->uid);
    }

    public function gmailOAuthSetup(Request $request, $uid)
    {
        $server = SendingServer::findByUid($uid);
        abort_if(is_null($server), 404);

        if (!$request->user()->admin->can('update', $server)) {
            return $this->notAuthorized();
        }

        return view('refactor.pages.admin.sending-servers.gmail-oauth-setup', [
            'server'      => $server,
            'callbackUrl' => route('refactor.admin.sending-servers.gmail-oauth-callback'),
            'setupError'  => session('gmail_setup_error'),
        ]);
    }

    public function gmailOAuthRedirect(Request $request, $uid)
    {
        $server = SendingServer::findByUid($uid);
        abort_if(is_null($server), 404);

        if (!$request->user()->admin->can('update', $server)) {
            return $this->notAuthorized();
        }

        $params = http_build_query([
            'client_id'     => $server->getConfig('api_key'),
            'redirect_uri'  => route('refactor.admin.sending-servers.gmail-oauth-callback'),
            'response_type' => 'code',
            'scope'         => 'https://mail.google.com/ https://www.googleapis.com/auth/userinfo.email',
            'access_type'   => 'offline',
            'prompt'        => 'consent',
            'state'         => $uid,
        ]);

        return redirect('https://accounts.google.com/o/oauth2/v2/auth?' . $params);
    }

    public function gmailOAuthCallback(Request $request)
    {
        $uid   = $request->query('state');
        $code  = $request->query('code');
        $error = $request->query('error');

        $server = SendingServer::findByUid($uid);
        abort_if(is_null($server), 404);

        if ($error === 'redirect_uri_mismatch' || $error === 'access_denied') {
            return redirect()->route('refactor.admin.sending-servers.gmail-oauth-setup', $server->uid)
                ->with('gmail_setup_error', $error);
        }

        if ($error || !$code) {
            return redirect()->route('refactor.admin.sending-servers.edit', $server->uid)
                ->with('alert-error', trans('messages.sending_servers.gmail_oauth.oauth_error', ['error' => $error ?? 'no_code']));
        }

        try {
            $client = new \GuzzleHttp\Client();

            $response = $client->post(\App\SendingServers\Drivers\Vendors\Gmail\GmailOAuthDriver::GOOGLE_TOKEN_URL, [
                'form_params' => [
                    'code'          => $code,
                    'client_id'     => $server->getConfig('api_key'),
                    'client_secret' => $server->getConfig('api_secret_key'),
                    'redirect_uri'  => route('refactor.admin.sending-servers.gmail-oauth-callback'),
                    'grant_type'    => 'authorization_code',
                ],
            ]);

            $tokens = json_decode((string) $response->getBody(), true);

            if (empty($tokens['access_token'])) {
                throw new \Exception('No access token in response: ' . json_encode($tokens));
            }

            $userResponse = $client->get(\App\SendingServers\Drivers\Vendors\Gmail\GmailOAuthDriver::GOOGLE_USERINFO_URL, [
                'headers' => ['Authorization' => 'Bearer ' . $tokens['access_token']],
            ]);
            $userInfo = json_decode((string) $userResponse->getBody(), true);
            $email    = $userInfo['email'] ?? null;

            $server->setConfigMany([
                'oauth_access_token'     => $tokens['access_token'],
                'oauth_refresh_token'    => $tokens['refresh_token'] ?? $server->getConfig('oauth_refresh_token'),
                'oauth_token_expires_at' => \Carbon\Carbon::now()->addSeconds($tokens['expires_in'] ?? 3600)->toIso8601String(),
                'smtp_username'          => $email,
            ]);
            $server->default_from_email     = $server->default_from_email ?: $email;
            $server->save();

            $testResult = $server->driver()->test();
            if (!$testResult->ok) {
                throw new \RuntimeException($testResult->error ?? 'Test failed');
            }
            $server->status = SendingServer::STATUS_ACTIVE;
            $server->save();

        } catch (\Throwable $e) {
            return redirect()->route('refactor.admin.sending-servers.edit', $server->uid)
                ->with('alert-error', trans('messages.sending_servers.gmail_oauth.oauth_failed') . ': ' . $e->getMessage());
        }

        return redirect()->route('refactor.admin.sending-servers.edit', $server->uid)
            ->with('alert-success', trans('messages.sending_servers.gmail_oauth.oauth_success', ['email' => $server->getConfig('smtp_username')]));
    }

    public function typeChart(Request $request)
    {
        $data = SendingServer::system()
            ->selectRaw('type, count(*) as count')
            ->groupBy('type')
            ->pluck('count', 'type')
            ->toArray();

        return response()->json($data);
    }
}