106 lines
4.2 KiB
PHP
106 lines
4.2 KiB
PHP
<?php
|
|
|
|
namespace App\Services\Api;
|
|
|
|
use App\Models\ApiUsageLog;
|
|
use Illuminate\Database\Eloquent\Builder;
|
|
|
|
class ApiUsageReporter
|
|
{
|
|
/**
|
|
* @return array<string, mixed>
|
|
*/
|
|
public function report(?string $from = null, ?string $to = null, ?int $userId = null, ?int $statusCode = null, int $top = 10): array
|
|
{
|
|
$baseQuery = $this->baseQuery($from, $to, $userId, $statusCode);
|
|
|
|
$totalRequests = (clone $baseQuery)->count();
|
|
|
|
return [
|
|
'generated_at' => now()->toIso8601String(),
|
|
'filters' => [
|
|
'from' => $from,
|
|
'to' => $to,
|
|
'user_id' => $userId,
|
|
'status_code' => $statusCode,
|
|
'top' => $top,
|
|
],
|
|
'summary' => [
|
|
'total_requests' => $totalRequests,
|
|
'unique_users' => (clone $baseQuery)->whereNotNull('user_id')->distinct('user_id')->count('user_id'),
|
|
'unique_tokens' => (clone $baseQuery)->whereNotNull('personal_access_token_id')->distinct('personal_access_token_id')->count('personal_access_token_id'),
|
|
'successful_requests' => (clone $baseQuery)->whereBetween('status_code', [200, 299])->count(),
|
|
'client_error_requests' => (clone $baseQuery)->whereBetween('status_code', [400, 499])->count(),
|
|
'server_error_requests' => (clone $baseQuery)->whereBetween('status_code', [500, 599])->count(),
|
|
'average_duration_ms' => $totalRequests > 0 ? round((float) (clone $baseQuery)->avg('duration_ms'), 2) : 0.0,
|
|
],
|
|
'top_paths' => $this->topRows($baseQuery, 'path', $top),
|
|
'status_codes' => $this->topRows($baseQuery, 'status_code', $top),
|
|
'top_users' => $this->topRows($baseQuery, 'user_id', $top),
|
|
'top_tokens' => $this->topRows($baseQuery, 'personal_access_token_id', $top),
|
|
'recent_requests' => $this->recentRequests($baseQuery, $top),
|
|
];
|
|
}
|
|
|
|
private function baseQuery(?string $from, ?string $to, ?int $userId, ?int $statusCode): Builder
|
|
{
|
|
return ApiUsageLog::query()
|
|
->when($from !== null, fn (Builder $query) => $query->where('requested_at', '>=', $from))
|
|
->when($to !== null, fn (Builder $query) => $query->where('requested_at', '<=', $to))
|
|
->when($userId !== null, fn (Builder $query) => $query->where('user_id', $userId))
|
|
->when($statusCode !== null, fn (Builder $query) => $query->where('status_code', $statusCode));
|
|
}
|
|
|
|
/**
|
|
* @return list<array{value: mixed, requests: int}>
|
|
*/
|
|
private function topRows(Builder $baseQuery, string $column, int $limit): array
|
|
{
|
|
return (clone $baseQuery)
|
|
->selectRaw("{$column} as value, count(*) as requests")
|
|
->whereNotNull($column)
|
|
->groupBy($column)
|
|
->orderByDesc('requests')
|
|
->limit($limit)
|
|
->get()
|
|
->map(fn ($row): array => [
|
|
'value' => $row->value,
|
|
'requests' => (int) $row->requests,
|
|
])
|
|
->all();
|
|
}
|
|
|
|
/**
|
|
* @return list<array<string, mixed>>
|
|
*/
|
|
private function recentRequests(Builder $baseQuery, int $limit): array
|
|
{
|
|
return (clone $baseQuery)
|
|
->latest('requested_at')
|
|
->latest('id')
|
|
->limit($limit)
|
|
->get([
|
|
'id',
|
|
'user_id',
|
|
'personal_access_token_id',
|
|
'method',
|
|
'path',
|
|
'route_name',
|
|
'status_code',
|
|
'duration_ms',
|
|
'requested_at',
|
|
])
|
|
->map(fn (ApiUsageLog $log): array => [
|
|
'id' => $log->id,
|
|
'user_id' => $log->user_id,
|
|
'personal_access_token_id' => $log->personal_access_token_id,
|
|
'method' => $log->method,
|
|
'path' => $log->path,
|
|
'route_name' => $log->route_name,
|
|
'status_code' => $log->status_code,
|
|
'duration_ms' => $log->duration_ms,
|
|
'requested_at' => $log->requested_at?->toIso8601String(),
|
|
])
|
|
->all();
|
|
}
|
|
}
|