guard($request)) { return $guard; } try { $data = (new ArchiveAdminService())->list( trim((string) $request->get('query', '')), (int) $request->get('page', 1), (int) $request->get('page_size', 20), ); } catch (Throwable $exception) { return $this->error(500, 'Archive list lookup failed.', ['archives' => $exception->getMessage()]); } return $this->ok('Archive list loaded.', $data); } public function archive(Request $request, string $archiveUid): Response { if ($guard = $this->guard($request)) { return $guard; } try { $archive = (new ArchiveAdminService())->detail($archiveUid); } catch (Throwable $exception) { return $this->error(500, 'Archive lookup failed.', ['archive' => $exception->getMessage()]); } if ($archive === null) { return $this->error(404, 'Archive not found.', ['archive_uid' => $archiveUid], 404); } return $this->ok('Archive loaded.', $archive); } public function updateArchive(Request $request, string $archiveUid): Response { if ($guard = $this->guard($request)) { return $guard; } try { $archive = (new ArchiveAdminService())->update($archiveUid, $this->payload($request)); } catch (JsonException $exception) { return $this->error(400, 'Invalid JSON body.', ['body' => $exception->getMessage()], 400); } catch (InvalidArgumentException $exception) { return $this->error(422, 'Archive update validation failed.', ['archive' => $exception->getMessage()], 422); } catch (Throwable $exception) { return $this->error(500, 'Archive update failed.', ['archive' => $exception->getMessage()]); } if ($archive === null) { return $this->error(404, 'Archive not found.', ['archive_uid' => $archiveUid], 404); } return $this->ok('Archive updated.', $archive); } public function deleteArchive(Request $request, string $archiveUid): Response { if ($guard = $this->guard($request)) { return $guard; } try { $deleted = (new ArchiveAdminService())->delete($archiveUid); } catch (Throwable $exception) { return $this->error(500, 'Archive delete failed.', ['archive' => $exception->getMessage()]); } if (!$deleted) { return $this->error(404, 'Archive not found.', ['archive_uid' => $archiveUid], 404); } return $this->ok('Archive deleted.', ['archive_uid' => $archiveUid]); } public function openSearchStatus(Request $request): Response { if ($guard = $this->guard($request)) { return $guard; } try { $status = (new OpenSearchAdminService())->status(); } catch (Throwable $exception) { return $this->error(500, 'OpenSearch status lookup failed.', ['opensearch' => $exception->getMessage()]); } return $this->ok('OpenSearch status loaded.', $status); } public function openSearchDocuments(Request $request): Response { if ($guard = $this->guard($request)) { return $guard; } try { $documents = (new OpenSearchAdminService())->documents( trim((string) $request->get('query', '')), (int) $request->get('size', 20), ); } catch (Throwable $exception) { return $this->error(500, 'OpenSearch document lookup failed.', ['opensearch' => $exception->getMessage()]); } return $this->ok('OpenSearch documents loaded.', $documents); } public function users(Request $request): Response { if ($guard = $this->guard($request)) { return $guard; } try { $users = (new AdminUserRepository())->listAll(); } catch (Throwable $exception) { return $this->error(500, 'Admin users lookup failed.', ['users' => $exception->getMessage()]); } return $this->ok('Admin users loaded.', ['items' => array_map(fn (array $user): array => $this->sanitizeUser($user), $users)]); } public function createUser(Request $request): Response { if ($guard = $this->guard($request)) { return $guard; } try { $payload = $this->payload($request); $username = trim((string) ($payload['username'] ?? '')); $password = trim((string) ($payload['password'] ?? '')); $displayName = trim((string) ($payload['display_name'] ?? '')); if ($username === '' || $password === '') { throw new InvalidArgumentException('username and password are required.'); } $repository = new AdminUserRepository(); if ($repository->findAnyByUsername($username)) { throw new InvalidArgumentException('username already exists.'); } $user = $repository->create($username, $password, $displayName !== '' ? $displayName : null); } catch (JsonException $exception) { return $this->error(400, 'Invalid JSON body.', ['body' => $exception->getMessage()], 400); } catch (InvalidArgumentException $exception) { return $this->error(422, 'Admin user creation validation failed.', ['user' => $exception->getMessage()], 422); } catch (Throwable $exception) { return $this->error(500, 'Admin user creation failed.', ['user' => $exception->getMessage()]); } return $this->ok('Admin user created.', ['user' => $this->sanitizeUser($user)]); } public function updateUser(Request $request, int $id): Response { if ($guard = $this->guard($request)) { return $guard; } try { $payload = $this->payload($request); $repository = new AdminUserRepository(); if ($repository->findAnyById($id) === null) { return $this->error(404, 'Admin user not found.', ['id' => $id], 404); } $updates = []; if (array_key_exists('display_name', $payload)) { $updates['display_name'] = $payload['display_name']; } if (array_key_exists('password', $payload)) { $updates['password'] = $payload['password']; } if (array_key_exists('is_active', $payload)) { $updates['is_active'] = (bool) $payload['is_active']; } $user = $repository->updateUser($id, $updates); } catch (JsonException $exception) { return $this->error(400, 'Invalid JSON body.', ['body' => $exception->getMessage()], 400); } catch (Throwable $exception) { return $this->error(500, 'Admin user update failed.', ['user' => $exception->getMessage()]); } return $this->ok('Admin user updated.', ['user' => $this->sanitizeUser($user ?? [])]); } public function docs(Request $request): Response { if ($guard = $this->guard($request)) { return $guard; } try { $docs = (new AdminDocService())->list(); } catch (Throwable $exception) { return $this->error(500, 'API docs lookup failed.', ['docs' => $exception->getMessage()]); } return $this->ok('API docs loaded.', ['items' => $docs]); } public function doc(Request $request, string $name): Response { if ($guard = $this->guard($request)) { return $guard; } try { $doc = (new AdminDocService())->read($name); } catch (Throwable $exception) { return $this->error(404, 'API doc not found.', ['doc' => $exception->getMessage()], 404); } return $this->ok('API doc loaded.', $doc); } public function scripts(Request $request): Response { if ($guard = $this->guard($request)) { return $guard; } return $this->ok('Maintenance scripts loaded.', ['items' => (new MaintenanceScriptService())->list()]); } public function script(Request $request, string $name): Response { if ($guard = $this->guard($request)) { return $guard; } try { $script = (new MaintenanceScriptService())->describe($name); } catch (Throwable $exception) { return $this->error(404, 'Maintenance script not found.', ['script' => $exception->getMessage()], 404); } return $this->ok('Maintenance script loaded.', $script); } public function runScript(Request $request): Response { if ($guard = $this->guard($request)) { return $guard; } try { $payload = $this->payload($request); $scriptName = trim((string) ($payload['script_name'] ?? '')); $args = $payload['args'] ?? []; if ($scriptName === '') { throw new InvalidArgumentException('script_name is required.'); } if (!is_array($args)) { throw new InvalidArgumentException('args must be an array.'); } $result = (new MaintenanceScriptService())->run($scriptName, $args); } catch (JsonException $exception) { return $this->error(400, 'Invalid JSON body.', ['body' => $exception->getMessage()], 400); } catch (InvalidArgumentException $exception) { return $this->error(422, 'Script execution validation failed.', ['script' => $exception->getMessage()], 422); } catch (Throwable $exception) { return $this->error(500, 'Script execution failed.', ['script' => $exception->getMessage()]); } return $this->ok('Maintenance script finished.', $result); } private function guard(Request $request): ?Response { return (new AdminAuthService())->current($request) === null ? $this->error(401, 'Admin session not found.', [], 401) : null; } /** * @throws JsonException */ private function payload(Request $request): array { $rawBody = trim($request->rawBody()); if ($rawBody === '') { return $request->post(); } $payload = json_decode($rawBody, true, 512, JSON_THROW_ON_ERROR); return is_array($payload) ? $payload : []; } private function ok(string $message, array $data): Response { return $this->jsonResponse([ 'code' => 0, 'message' => $message, 'data' => $data, ], 200); } private function error(int $code, string $message, array $errors = [], int $status = 500): Response { return $this->jsonResponse([ 'code' => $code, 'message' => $message, 'errors' => $errors, ], $status); } private function sanitizeUser(array $user): array { unset($user['password_hash']); return $user; } private function jsonResponse(array $data, int $status): Response { return response( json_encode($data, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | JSON_THROW_ON_ERROR), $status, ['Content-Type' => 'application/json'] ); } }