twigEngine; } protected function sessions(): SessionManager { return $this->sessionManager; } public function index(ServerRequestInterface $request, ResponseInterface $response): ResponseInterface { if (($redirect = $this->requireUser($request, $response)) !== null) { return $redirect; } $user = $this->sessions()->getUser(); if ($user === null) { return $response->withStatus(302)->withHeader('Location', '/login'); } if (!$this->userIs($user, 'admin')) { return $response->withStatus(303)->withHeader('Location', '/no-access'); } $config = null; $jobs = null; $error = null; try { $config = $this->admin->getConfig($user->userId); $jobs = $this->admin->getJobsStatus($user->userId); } catch (ApiAuthException) { return $response->withStatus(303)->withHeader('Location', '/no-access'); } catch (ApiException $e) { $error = 'API error: ' . $e->getMessage(); } return $this->twigEngine->render($response, 'pages/settings/index.twig', [ 'active_section' => 'settings', 'config' => $config, 'jobs' => $jobs, 'error' => $error, ]); } /** * @param array{name: string} $args */ public function trigger(ServerRequestInterface $request, ResponseInterface $response, array $args): ResponseInterface { if (($redirect = $this->requireUser($request, $response)) !== null) { return $redirect; } $user = $this->sessions()->getUser(); if ($user === null) { return $response->withStatus(302)->withHeader('Location', '/login'); } if (!$this->userIs($user, 'admin')) { return $response->withStatus(303)->withHeader('Location', '/no-access'); } $name = $args['name']; $body = $this->formBody($request); $params = []; if (isset($body['full'])) { $params['full'] = $this->formBool($body['full']); } if (isset($body['reenrich'])) { $params['reenrich'] = $this->formBool($body['reenrich']); } try { $result = $this->admin->triggerJob($user->userId, $name, $params); $status = (string) ($result['status'] ?? 'unknown'); $items = (int) ($result['items_processed'] ?? 0); $duration = (int) ($result['duration_ms'] ?? 0); $this->sessions()->flash( $status === 'success' ? 'success' : 'error', sprintf('%s — %s (items=%d, duration=%dms)', $name, $status, $items, $duration), ); } catch (ApiException $e) { $this->flashFromException($e); } return $response->withStatus(303)->withHeader('Location', '/app/settings'); } /** * Wipe operational data. The API requires the literal `PURGE` string; * we additionally require the user to have typed it in the form, both * to avoid drive-by clicks and to keep curl-style misuse loud. */ public function purge(ServerRequestInterface $request, ResponseInterface $response): ResponseInterface { if (($redirect = $this->requireUser($request, $response)) !== null) { return $redirect; } $user = $this->sessions()->getUser(); if ($user === null) { return $response->withStatus(302)->withHeader('Location', '/login'); } if (!$this->userIs($user, 'admin')) { return $response->withStatus(303)->withHeader('Location', '/no-access'); } $body = $this->formBody($request); $confirm = isset($body['confirm']) && is_string($body['confirm']) ? trim($body['confirm']) : ''; if ($confirm !== 'PURGE') { $this->sessions()->flash('error', 'Type PURGE exactly to confirm the wipe.'); return $response->withStatus(303)->withHeader('Location', '/app/settings'); } try { $result = $this->admin->purgeData($user->userId); $deleted = is_array($result['deleted'] ?? null) ? $result['deleted'] : []; $total = array_sum(array_map('intval', $deleted)); $this->sessions()->flash('success', sprintf( 'Database purged. %d rows deleted across %d tables.', $total, count($deleted), )); } catch (ApiException $e) { $this->flashFromException($e); } return $response->withStatus(303)->withHeader('Location', '/app/settings'); } /** * Load the demo dataset. Idempotent on the api side — repeats return * 409 which we surface as an info-level flash, not an error. */ public function seedDemo(ServerRequestInterface $request, ResponseInterface $response): ResponseInterface { if (($redirect = $this->requireUser($request, $response)) !== null) { return $redirect; } $user = $this->sessions()->getUser(); if ($user === null) { return $response->withStatus(302)->withHeader('Location', '/login'); } if (!$this->userIs($user, 'admin')) { return $response->withStatus(303)->withHeader('Location', '/no-access'); } try { $result = $this->admin->seedDemo($user->userId); $summary = is_array($result['summary'] ?? null) ? $result['summary'] : []; $this->sessions()->flash('success', sprintf( 'Demo data loaded — %d reporters, %d consumers, %d IPs, %d reports.', (int) ($summary['reporters'] ?? 0), (int) ($summary['consumers'] ?? 0), (int) ($summary['ips'] ?? 0), (int) ($summary['reports'] ?? 0), )); } catch (ApiException $e) { $this->flashFromException($e); } return $response->withStatus(303)->withHeader('Location', '/app/settings'); } }