| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431 |
- <?php
- declare(strict_types=1);
- namespace App\ApiClient;
- use App\ApiClient\DTOs\DashboardStatsDto;
- use App\ApiClient\DTOs\IpDetailDto;
- use App\ApiClient\DTOs\IpListDto;
- use App\ApiClient\DTOs\UserDto;
- /**
- * Wraps the api's `/api/v1/admin/*` endpoints. Calls go out with the
- * service token plus `X-Acting-User-Id` from the current session — the
- * api uses that to resolve the impersonated user's role and enforce
- * RBAC.
- *
- * For most CRUD endpoints (manual_blocks, allowlist, policies,
- * reporters, consumers, tokens, categories) we return raw associative
- * arrays mirroring the api's JSON shape. Templates bind onto these
- * directly. The richer DTO pattern is reserved for endpoints whose
- * response shape benefits from a typed accessor (`UserDto`, the
- * IP-detail / dashboard payloads).
- *
- * Throws the typed `ApiException` subclasses on non-2xx; controllers
- * catch them to render validation messages or "API unreachable" states.
- */
- final class AdminClient
- {
- public function __construct(private readonly ApiClient $api)
- {
- }
- // ---- identity ----
- public function getMe(int $actingUserId): UserDto
- {
- $payload = $this->api->request('GET', '/api/v1/admin/me', [], $actingUserId);
- return UserDto::fromArray($payload);
- }
- // ---- IPs / dashboard (M09) ----
- /**
- * @param array<string, mixed> $filters
- */
- public function searchIps(int $actingUserId, array $filters, int $page = 1, int $pageSize = 25): IpListDto
- {
- $query = ['page' => $page, 'page_size' => $pageSize];
- foreach (['q', 'category', 'min_score', 'max_score', 'country', 'asn', 'status'] as $key) {
- if (isset($filters[$key]) && $filters[$key] !== '' && $filters[$key] !== null) {
- $query[$key] = $filters[$key];
- }
- }
- $payload = $this->api->request('GET', '/api/v1/admin/ips', ['query' => $query], $actingUserId);
- return IpListDto::fromArray($payload);
- }
- public function getIp(int $actingUserId, string $ip): IpDetailDto
- {
- $payload = $this->api->request('GET', '/api/v1/admin/ips/' . rawurlencode($ip), [], $actingUserId);
- return IpDetailDto::fromArray($payload);
- }
- public function getDashboardStats(int $actingUserId): DashboardStatsDto
- {
- $payload = $this->api->request('GET', '/api/v1/admin/stats/dashboard', [], $actingUserId);
- return DashboardStatsDto::fromArray($payload);
- }
- /**
- * @return list<array{code: string, count: int}>
- */
- public function listCountries(int $actingUserId): array
- {
- $payload = $this->api->request('GET', '/api/v1/admin/ips/countries', [], $actingUserId);
- $items = $payload['items'] ?? [];
- if (!is_array($items)) {
- return [];
- }
- $out = [];
- foreach ($items as $item) {
- if (!is_array($item)) {
- continue;
- }
- $out[] = [
- 'code' => (string) ($item['code'] ?? ''),
- 'count' => (int) ($item['count'] ?? 0),
- ];
- }
- return $out;
- }
- // ---- manual blocks (M10) ----
- /**
- * @return array<string, mixed>
- */
- public function listManualBlocks(int $actingUserId, ?string $kind = null): array
- {
- $query = ['limit' => 200];
- if ($kind !== null && $kind !== '') {
- $query['kind'] = $kind;
- }
- return $this->api->request('GET', '/api/v1/admin/manual-blocks', ['query' => $query], $actingUserId);
- }
- /**
- * @param array<string, mixed> $body
- * @return array<string, mixed>
- */
- public function createManualBlock(int $actingUserId, array $body): array
- {
- return $this->api->request('POST', '/api/v1/admin/manual-blocks', ['json' => $body], $actingUserId);
- }
- public function deleteManualBlock(int $actingUserId, int $id): void
- {
- $this->api->request('DELETE', '/api/v1/admin/manual-blocks/' . $id, [], $actingUserId);
- }
- // ---- allowlist (M10) ----
- /**
- * @return array<string, mixed>
- */
- public function listAllowlist(int $actingUserId, ?string $kind = null): array
- {
- $query = ['limit' => 200];
- if ($kind !== null && $kind !== '') {
- $query['kind'] = $kind;
- }
- return $this->api->request('GET', '/api/v1/admin/allowlist', ['query' => $query], $actingUserId);
- }
- /**
- * @param array<string, mixed> $body
- * @return array<string, mixed>
- */
- public function createAllowlist(int $actingUserId, array $body): array
- {
- return $this->api->request('POST', '/api/v1/admin/allowlist', ['json' => $body], $actingUserId);
- }
- public function deleteAllowlist(int $actingUserId, int $id): void
- {
- $this->api->request('DELETE', '/api/v1/admin/allowlist/' . $id, [], $actingUserId);
- }
- // ---- policies (M10) ----
- /**
- * @return array<string, mixed>
- */
- public function listPolicies(int $actingUserId): array
- {
- return $this->api->request('GET', '/api/v1/admin/policies', [], $actingUserId);
- }
- /**
- * @return array<string, mixed>
- */
- public function getPolicy(int $actingUserId, int $id): array
- {
- return $this->api->request('GET', '/api/v1/admin/policies/' . $id, [], $actingUserId);
- }
- /**
- * @param array<string, mixed> $body
- * @return array<string, mixed>
- */
- public function createPolicy(int $actingUserId, array $body): array
- {
- return $this->api->request('POST', '/api/v1/admin/policies', ['json' => $body], $actingUserId);
- }
- /**
- * @param array<string, mixed> $body
- * @return array<string, mixed>
- */
- public function updatePolicy(int $actingUserId, int $id, array $body): array
- {
- return $this->api->request('PATCH', '/api/v1/admin/policies/' . $id, ['json' => $body], $actingUserId);
- }
- public function deletePolicy(int $actingUserId, int $id): void
- {
- $this->api->request('DELETE', '/api/v1/admin/policies/' . $id, [], $actingUserId);
- }
- /**
- * @return array<string, mixed>
- */
- public function previewPolicy(int $actingUserId, int $id): array
- {
- return $this->api->request('GET', '/api/v1/admin/policies/' . $id . '/preview', [], $actingUserId);
- }
- // ---- reporters (M10) ----
- /**
- * @return array<string, mixed>
- */
- public function listReporters(int $actingUserId): array
- {
- return $this->api->request('GET', '/api/v1/admin/reporters', ['query' => ['limit' => 200]], $actingUserId);
- }
- /**
- * @return array<string, mixed>
- */
- public function getReporter(int $actingUserId, int $id): array
- {
- return $this->api->request('GET', '/api/v1/admin/reporters/' . $id, [], $actingUserId);
- }
- /**
- * @param array<string, mixed> $body
- * @return array<string, mixed>
- */
- public function createReporter(int $actingUserId, array $body): array
- {
- return $this->api->request('POST', '/api/v1/admin/reporters', ['json' => $body], $actingUserId);
- }
- /**
- * @param array<string, mixed> $body
- * @return array<string, mixed>
- */
- public function updateReporter(int $actingUserId, int $id, array $body): array
- {
- return $this->api->request('PATCH', '/api/v1/admin/reporters/' . $id, ['json' => $body], $actingUserId);
- }
- public function deleteReporter(int $actingUserId, int $id): void
- {
- $this->api->request('DELETE', '/api/v1/admin/reporters/' . $id, [], $actingUserId);
- }
- // ---- consumers (M10) ----
- /**
- * @return array<string, mixed>
- */
- public function listConsumers(int $actingUserId): array
- {
- return $this->api->request('GET', '/api/v1/admin/consumers', ['query' => ['limit' => 200]], $actingUserId);
- }
- /**
- * @return array<string, mixed>
- */
- public function getConsumer(int $actingUserId, int $id): array
- {
- return $this->api->request('GET', '/api/v1/admin/consumers/' . $id, [], $actingUserId);
- }
- /**
- * @param array<string, mixed> $body
- * @return array<string, mixed>
- */
- public function createConsumer(int $actingUserId, array $body): array
- {
- return $this->api->request('POST', '/api/v1/admin/consumers', ['json' => $body], $actingUserId);
- }
- /**
- * @param array<string, mixed> $body
- * @return array<string, mixed>
- */
- public function updateConsumer(int $actingUserId, int $id, array $body): array
- {
- return $this->api->request('PATCH', '/api/v1/admin/consumers/' . $id, ['json' => $body], $actingUserId);
- }
- public function deleteConsumer(int $actingUserId, int $id): void
- {
- $this->api->request('DELETE', '/api/v1/admin/consumers/' . $id, [], $actingUserId);
- }
- // ---- tokens (M10) ----
- /**
- * @return array<string, mixed>
- */
- public function listTokens(int $actingUserId): array
- {
- return $this->api->request('GET', '/api/v1/admin/tokens', ['query' => ['limit' => 200]], $actingUserId);
- }
- /**
- * @param array<string, mixed> $body
- * @return array<string, mixed>
- */
- public function createToken(int $actingUserId, array $body): array
- {
- return $this->api->request('POST', '/api/v1/admin/tokens', ['json' => $body], $actingUserId);
- }
- public function deleteToken(int $actingUserId, int $id): void
- {
- $this->api->request('DELETE', '/api/v1/admin/tokens/' . $id, [], $actingUserId);
- }
- // ---- categories (M10) ----
- /**
- * @return array<string, mixed>
- */
- public function listCategories(int $actingUserId): array
- {
- return $this->api->request('GET', '/api/v1/admin/categories', [], $actingUserId);
- }
- /**
- * @return array<string, mixed>
- */
- public function getCategory(int $actingUserId, int $id): array
- {
- return $this->api->request('GET', '/api/v1/admin/categories/' . $id, [], $actingUserId);
- }
- /**
- * @param array<string, mixed> $body
- * @return array<string, mixed>
- */
- public function createCategory(int $actingUserId, array $body): array
- {
- return $this->api->request('POST', '/api/v1/admin/categories', ['json' => $body], $actingUserId);
- }
- /**
- * @param array<string, mixed> $body
- * @return array<string, mixed>
- */
- public function updateCategory(int $actingUserId, int $id, array $body): array
- {
- return $this->api->request('PATCH', '/api/v1/admin/categories/' . $id, ['json' => $body], $actingUserId);
- }
- public function deleteCategory(int $actingUserId, int $id): void
- {
- $this->api->request('DELETE', '/api/v1/admin/categories/' . $id, [], $actingUserId);
- }
- // ---- audit / settings (M12) ----
- /**
- * @param array<string, mixed> $filters
- * @return array<string, mixed>
- */
- public function listAuditLog(int $actingUserId, array $filters, int $page = 1, int $pageSize = 50): array
- {
- $query = ['page' => $page, 'page_size' => $pageSize];
- foreach (['actor_kind', 'actor_id', 'action', 'entity_type', 'entity_id', 'from', 'to'] as $key) {
- if (isset($filters[$key]) && $filters[$key] !== '' && $filters[$key] !== null) {
- $query[$key] = $filters[$key];
- }
- }
- return $this->api->request('GET', '/api/v1/admin/audit-log', ['query' => $query], $actingUserId);
- }
- /**
- * @return array<string, mixed>
- */
- public function getJobsStatus(int $actingUserId): array
- {
- return $this->api->request('GET', '/api/v1/admin/jobs/status', [], $actingUserId);
- }
- /**
- * @param array<string, mixed> $params
- * @return array<string, mixed>
- */
- public function triggerJob(int $actingUserId, string $name, array $params = []): array
- {
- return $this->api->request(
- 'POST',
- '/api/v1/admin/jobs/trigger/' . rawurlencode($name),
- $params === [] ? [] : ['json' => $params],
- $actingUserId,
- );
- }
- /**
- * @return array<string, mixed>
- */
- public function getConfig(int $actingUserId): array
- {
- return $this->api->request('GET', '/api/v1/admin/config', [], $actingUserId);
- }
- /**
- * Wipe operational data on the api side. The API requires
- * `confirm: "PURGE"` in the body — anything else returns 400.
- *
- * @return array<string, mixed>
- */
- public function purgeData(int $actingUserId): array
- {
- return $this->api->request(
- 'POST',
- '/api/v1/admin/maintenance/purge',
- ['json' => ['confirm' => 'PURGE']],
- $actingUserId,
- );
- }
- /**
- * Load the demo dataset. Returns 409 if data is already seeded.
- *
- * @return array<string, mixed>
- */
- public function seedDemo(int $actingUserId): array
- {
- return $this->api->request(
- 'POST',
- '/api/v1/admin/maintenance/seed-demo',
- [],
- $actingUserId,
- );
- }
- }
|