File: /home/xedaptot/ai.naniguide.com/app/Services/Customer/SendingServerService.php
<?php
namespace App\Services\Customer;
use App\Dto\Refactor\SendingServer\SendingServerSidebarStatsDto;
use App\Model\Customer;
use App\Model\SendingServer;
use App\SendingServers\DriverRegistry;
use Illuminate\Contracts\Pagination\LengthAwarePaginator;
/**
* Read aggregator for the customer-side rich-redesigned "Sending Servers" page.
* Owned scope: stats meta, filtered list, sidebar widgets (vendor mix donut,
* combined throughput, warmup status, service catalog).
*/
class SendingServerService
{
/**
* @return array{total:int,active:int,inactive:int,warmup:int,vendors:int}
*/
public function headerMeta(Customer $customer): array
{
$base = SendingServer::query()->where('customer_id', $customer->id);
$total = (clone $base)->count();
$active = (clone $base)->where('status', SendingServer::STATUS_ACTIVE)->count();
$inactive = max(0, $total - $active);
$warmup = (clone $base)->where('warmup_enabled', 1)->count();
$vendors = (clone $base)->distinct('type')->count('type');
return [
'total' => $total,
'active' => $active,
'inactive' => $inactive,
'warmup' => $warmup,
'vendors' => $vendors,
];
}
public function list(
Customer $customer,
?string $keyword = null,
?string $status = null,
?string $type = null,
string $sortBy = 'created_at',
string $sortDir = 'desc',
int $perPage = 25,
): LengthAwarePaginator {
$query = SendingServer::query()->where('customer_id', $customer->id);
if ($keyword !== null && $keyword !== '') {
$query->where('name', 'like', '%' . $keyword . '%');
}
if ($status === 'active') {
$query->where('status', SendingServer::STATUS_ACTIVE);
} elseif ($status === 'inactive') {
$query->where(function ($q) {
$q->where('status', '!=', SendingServer::STATUS_ACTIVE)
->orWhereNull('status');
});
}
if ($type !== null && $type !== '') {
$query->where('type', $type);
}
$allowedSorts = ['created_at', 'name', 'type', 'status'];
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): SendingServerSidebarStatsDto
{
return new SendingServerSidebarStatsDto(
headerMeta: $this->headerMeta($customer),
vendorMix: $this->vendorMix($customer),
throughput: $this->throughput($customer),
warmupStatus: $this->warmupStatus($customer),
coverage: $this->coverage($customer),
);
}
/** @return array{slices:list<array{key:string,name:string,count:int}>,total:int} */
private function vendorMix(Customer $customer): array
{
$rows = \Illuminate\Support\Facades\DB::table('sending_servers')
->where('customer_id', $customer->id)
->selectRaw('type, COUNT(*) as cnt')
->groupBy('type')
->get();
$slices = [];
$total = 0;
foreach ($rows as $row) {
$type = (string) $row->type;
$cnt = (int) $row->cnt;
$slices[] = [
'key' => $type,
'name' => $this->vendorLabel($type),
'count' => $cnt,
];
$total += $cnt;
}
usort($slices, fn ($a, $b) => $b['count'] <=> $a['count']);
return ['slices' => $slices, 'total' => $total];
}
/** @return array{total_per_hour:int,formatted:string,by_vendor:list<array{key:string,name:string,per_hour:int}>} */
private function throughput(Customer $customer): array
{
$servers = SendingServer::query()
->where('customer_id', $customer->id)
->where('status', SendingServer::STATUS_ACTIVE)
->get(['type', 'quota_value', 'quota_base', 'quota_unit']);
$byVendor = [];
$total = 0;
foreach ($servers as $server) {
$value = (int) ($server->quota_value ?? 0);
$base = max(1, (int) ($server->quota_base ?? 1));
$unit = (string) ($server->quota_unit ?? 'hour');
if ($value <= 0) {
continue;
}
$perBase = $value / $base;
$perHour = match ($unit) {
'minute' => (int) round($perBase * 60),
'hour' => (int) round($perBase),
'day' => (int) round($perBase / 24),
default => 0,
};
if ($perHour <= 0) {
continue;
}
$key = (string) $server->type;
if (! isset($byVendor[$key])) {
$byVendor[$key] = ['key' => $key, 'name' => $this->vendorLabel($key), 'per_hour' => 0];
}
$byVendor[$key]['per_hour'] += $perHour;
$total += $perHour;
}
$byVendor = array_values($byVendor);
usort($byVendor, fn ($a, $b) => $b['per_hour'] <=> $a['per_hour']);
return [
'total_per_hour' => $total,
'formatted' => $total > 0 ? number_format($total) . '/hour' : '—',
'by_vendor' => $byVendor,
];
}
/** @return array{enabled:int,disabled:int,started:int} */
private function warmupStatus(Customer $customer): array
{
$base = SendingServer::query()->where('customer_id', $customer->id);
$enabled = (clone $base)->where('warmup_enabled', 1)->count();
$started = (clone $base)->where('warmup_enabled', 1)->whereNotNull('warmup_started_at')->count();
$disabled = max(0, (clone $base)->count() - $enabled);
return [
'enabled' => $enabled,
'disabled' => $disabled,
'started' => $started,
];
}
/** @return array{used_vendor_keys:list<string>,catalog:list<array{key:string,name:string,connected:bool,count:int}>} */
private function coverage(Customer $customer): array
{
$rows = \Illuminate\Support\Facades\DB::table('sending_servers')
->where('customer_id', $customer->id)
->selectRaw('type, COUNT(*) as cnt')
->groupBy('type')
->pluck('cnt', 'type')
->all();
$catalog = [];
$usedKeys = [];
try {
$allTypes = array_keys(DriverRegistry::all() ?: []);
} catch (\Throwable $e) {
$allTypes = array_keys($rows);
}
// Merge in any used types that are not in the registry (defensive).
foreach (array_keys($rows) as $t) {
if (! in_array($t, $allTypes, true)) {
$allTypes[] = $t;
}
}
foreach ($allTypes as $type) {
$catalog[] = [
'key' => $type,
'name' => $this->vendorLabel($type),
'connected' => isset($rows[$type]),
'count' => (int) ($rows[$type] ?? 0),
];
if (isset($rows[$type])) {
$usedKeys[] = $type;
}
}
usort($catalog, function ($a, $b) {
if ($a['connected'] !== $b['connected']) {
return $a['connected'] ? -1 : 1;
}
return strcasecmp($a['name'], $b['name']);
});
return [
'used_vendor_keys' => $usedKeys,
'catalog' => $catalog,
];
}
private function vendorLabel(string $type): string
{
$key = 'refactor/sending.servers.type.' . $type;
$label = trans($key);
if ($label === $key) {
return \Illuminate\Support\Str::title(str_replace(['-', '_'], ' ', $type ?: '—'));
}
return (string) $label;
}
}