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/Services/Customer/SendingDomainService.php
<?php

namespace App\Services\Customer;

use App\Dto\Refactor\SendingDomain\SendingDomainSidebarStatsDto;
use App\Model\Customer;
use App\Model\SendingIdentity;
use App\SendingServers\Identities\IdentityKind;
use App\SendingServers\Identities\IdentityStatus;
use App\SendingServers\Identities\ManagementMode;
use App\Services\Plans\Quotas\QuotaKey;
use App\Services\Plans\Quotas\QuotasService;
use Illuminate\Contracts\Pagination\LengthAwarePaginator;
use Illuminate\Support\Facades\DB;

/**
 * Read aggregator for the customer-side rich-redesigned Sending Domains page.
 *
 * Post-refactor this service queries `sending_identities` directly (filter
 * `management_mode=LOCAL_DKIM`, `kind=domain`, `customer_id=local`).
 * The page is the only entry point for LOCAL_DKIM rows, so the scope is
 * unambiguous: customer-owned, server-less, Acelle-DKIM-signed domains.
 */
class SendingDomainService
{
    public function __construct(private QuotasService $quotas)
    {
    }

    /**
     * @return array{total:int,verified:int,unverified:int,dkim_signed:int}
     */
    public function dashboardStats(Customer $customer): array
    {
        $localId = $customer->local()->id;

        $base = $this->baseQuery($localId);
        $total = (clone $base)->count();
        $verified = (clone $base)->where('status', IdentityStatus::VERIFIED->value)->count();
        $unverified = max(0, $total - $verified);

        // "DKIM signed" = domain has DKIM signing happening from somewhere:
        //   - LOCAL_DKIM with signing_enabled=true (Acelle signs locally), OR
        //   - VENDOR_SYNC verified (vendor signs on its infrastructure)
        // MANUAL is excluded — relay's DKIM behavior is opaque to Acelle.
        $dkimSigned = (clone $base)
            ->where(function ($q) {
                $q->where(function ($qq) {
                    $qq->where('management_mode', ManagementMode::LOCAL_DKIM->value)
                        ->where('signing_enabled', 1);
                })->orWhere(function ($qq) {
                    $qq->where('management_mode', ManagementMode::VENDOR_SYNC->value)
                        ->where('status', IdentityStatus::VERIFIED->value);
                });
            })
            ->count();

        return [
            'total'       => $total,
            'verified'    => $verified,
            'unverified'  => $unverified,
            'dkim_signed' => $dkimSigned,
        ];
    }

    public function list(
        Customer $customer,
        ?string $keyword = null,
        ?string $status = null,
        string $sortBy = 'created_at',
        string $sortDir = 'desc',
        int $perPage = 25,
    ): LengthAwarePaginator {
        $query = $this->baseQuery($customer->local()->id);

        if ($keyword !== null && $keyword !== '') {
            $query->where('value', 'like', '%' . $keyword . '%');
        }

        if ($status === 'verified') {
            $query->where('status', IdentityStatus::VERIFIED->value);
        } elseif ($status === 'unverified') {
            $query->where('status', '!=', IdentityStatus::VERIFIED->value);
        }

        $allowedSorts = ['created_at', 'value', 'status'];
        // Backward compat: legacy callers passed 'name' for the value column
        $sortBy = $sortBy === 'name' ? 'value' : $sortBy;
        if (!in_array($sortBy, $allowedSorts, true)) {
            $sortBy = 'created_at';
        }
        $sortDir = strtolower($sortDir) === 'asc' ? 'asc' : 'desc';

        return $query->orderBy($sortBy, $sortDir)->paginate($perPage);
    }

    public function sidebarStats(Customer $customer): SendingDomainSidebarStatsDto
    {
        $stats = $this->dashboardStats($customer);

        $verifiedPct = $stats['total'] > 0
            ? (int) round(($stats['verified'] / $stats['total']) * 100)
            : 0;

        return new SendingDomainSidebarStatsDto(
            verificationDonut: [
                'verified'     => $stats['verified'],
                'unverified'   => $stats['unverified'],
                'verified_pct' => $verifiedPct,
            ],
            needsAttention: $this->needsAttention($customer),
            addActivity: $this->addActivity($customer),
            quota: $this->quotaUsage($customer, $stats['total']),
        );
    }

    public function customerCanVerify(Customer $customer): bool
    {
        return $customer->allowVerifyingOwnDomains();
    }

    /** @return array{count:int,oldest_days:int,first_uid:?string} */
    private function needsAttention(Customer $customer): array
    {
        $base = $this->baseQuery($customer->local()->id)
            ->where('status', '!=', IdentityStatus::VERIFIED->value);

        $count = (clone $base)->count();
        if ($count === 0) {
            return ['count' => 0, 'oldest_days' => 0, 'first_uid' => null];
        }

        $oldest = (clone $base)->orderBy('created_at', 'asc')->first();
        $days = ($oldest && $oldest->created_at)
            ? max(0, (int) $oldest->created_at->diffInDays(now()))
            : 0;

        return [
            'count'       => $count,
            'oldest_days' => $days,
            'first_uid'   => $oldest?->uid,
        ];
    }

    /**
     * @return array{added_30d:int,added_prev_30d:int,delta_pct:?float,daily:list<array{date:string,count:int}>}
     */
    private function addActivity(Customer $customer): array
    {
        $localId = $customer->local()->id;
        $now = now();
        $start30 = $now->copy()->subDays(30);
        $startPrev = $now->copy()->subDays(60);

        $added30 = $this->baseQuery($localId)
            ->where('created_at', '>=', $start30)
            ->count();
        $addedPrev = $this->baseQuery($localId)
            ->whereBetween('created_at', [$startPrev, $start30])
            ->count();

        $deltaPct = null;
        if ($addedPrev > 0) {
            $deltaPct = round((($added30 - $addedPrev) / $addedPrev) * 100, 1);
        } elseif ($added30 > 0) {
            $deltaPct = 100.0;
        }

        $tablePrefix = DB::getTablePrefix();
        $rows = DB::table('sending_identities')
            ->where('customer_id', $localId)
            ->where('management_mode', ManagementMode::LOCAL_DKIM->value)
            ->where('kind', IdentityKind::DOMAIN->value)
            ->where('created_at', '>=', $start30)
            ->select([
                DB::raw('DATE(' . $tablePrefix . 'sending_identities.created_at) as day'),
                DB::raw('COUNT(*) as c'),
            ])
            ->groupBy('day')
            ->get()
            ->keyBy('day');

        $daily = [];
        for ($i = 0; $i < 30; $i++) {
            $date = $now->copy()->subDays(29 - $i)->format('Y-m-d');
            $daily[] = [
                'date'  => $date,
                'count' => isset($rows[$date]) ? (int) $rows[$date]->c : 0,
            ];
        }

        return [
            'added_30d'      => $added30,
            'added_prev_30d' => $addedPrev,
            'delta_pct'      => $deltaPct,
            'daily'          => $daily,
        ];
    }

    /** @return array{used:int,max:int|null,pct:?int,unlimited:bool,not_on_plan:bool} */
    private function quotaUsage(Customer $customer, int $used): array
    {
        // limit() returns int|null|false (false = plan doesn't grant, null = unlimited, int = cap)
        $limit = $this->quotas->limit($customer, QuotaKey::MAX_SENDING_DOMAINS);

        if ($limit === false) {
            // Plan does not grant MAX_SENDING_DOMAINS at all → widget renders "not on plan" state.
            return ['used' => $used, 'max' => null, 'pct' => null, 'unlimited' => false, 'not_on_plan' => true];
        }

        if ($limit === null) {
            return ['used' => $used, 'max' => null, 'pct' => null, 'unlimited' => true, 'not_on_plan' => false];
        }

        if ($limit > 0) {
            $pct = min(100, (int) round(($used / $limit) * 100));
        } else {
            $pct = $used > 0 ? 100 : 0;
        }

        return [
            'used'        => $used,
            'max'         => $limit,
            'pct'         => $pct,
            'unlimited'   => false,
            'not_on_plan' => false,
        ];
    }

    /**
     * All customer-owned domain identities, across every management mode.
     * The page renders a mode badge per row so customers can tell apart:
     *   - LOCAL_DKIM  : server-less, Acelle DKIM-signed
     *   - VENDOR_SYNC : pushed to a vendor (SES / SendGrid / etc.)
     *   - MANUAL      : declared on a server with no vendor sync API
     */
    private function baseQuery(int $customerId): \Illuminate\Database\Eloquent\Builder
    {
        return SendingIdentity::query()
            ->where('customer_id', $customerId)
            ->where('kind', IdentityKind::DOMAIN->value);
    }
}