presseportale/app/Services/Api/ApiUsageReporter.php
Kevin Adametz 5b8bdf4182
Some checks are pending
linter / quality (push) Waiting to run
tests / ci (push) Waiting to run
12-05-2026 Frontend dev
2026-05-12 18:32:33 +02:00

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();
}
}