# Web Admin Readiness

Status: **preparation only** — no web admin UI/API exists yet. This
document describes how one will be added later without disrupting
the Telegram bot.

## Design principle

The Telegram admin experience (Phase 6 in-chat builder, **done**) and
a possible future web admin panel must be **two thin interfaces over
the same Service layer** — never two independent implementations of
business logic.

```
Telegram admin commands  --\
                            >---> Tfb\Services\* (TestService, CampaignService, ...) ---> Repositories ---> DB
Future web admin HTTP    --/
```

This is already how the codebase is structured (see
`docs/ARCHITECTURE.md`), so adding a web admin later is additive, not
a refactor. **Phase 6 reinforced this principle in practice**, not
just in theory: every `Admin*Controller` calls a
`Tfb\Services\*`/`TestValidationService`/`OptionScoringService` method
— none of them contain SQL, HTML, or business rules inline — so a
future web admin panel could call the exact same service methods a
Telegram admin command already calls.

## What's already in place for this (updated after Phase 7)

1. **`src/AdminHttp/`** — reserved, empty namespace (`Tfb\AdminHttp\`)
   for future web route handlers. See its `README.md`. Still
   untouched by Phase 6.
2. **Services are UI-agnostic.** Every `Tfb\Services\*` class takes/
   returns plain arrays or scalars — no Telegram types, no HTML — so
   a web controller can call them exactly like a Telegram command
   handler does. Phase 6 added several new UI-agnostic services a
   future web admin panel could call directly, with no changes:
   - `TestService` (now also `listAll()`, `countAll()`, `countActive()`, `updateTitle()`, `setThreshold()`, `unsetDefault()`)
   - `TestValidationService::validate($test): {blocking, warnings}` — pure read-only, ideal for a web "can this test go live?" indicator
   - `OptionScoringService::loadDraft()/increment()/decrement()/commit()` — the web UI would only need its own draft-storage mechanism (e.g. a web session) instead of `user_states.context_json`; the score math itself is already framework-agnostic
   - `FileService` (`listRecent()`, `countAll()`, `deleteMetadata()`) — file library listing/deletion
   - `AdminFileUploadService` is the **one** Phase 6 service that is Telegram-specific (it calls `TelegramClient::copyMessage()`) — a web admin panel would need its own upload-to-files-channel mechanism (e.g. upload via the Bot API's `sendDocument`/`sendVoice` to the files channel from the web backend, then call the same `FileService::storeMetadata()`)

   **Phase 7 added several more UI-agnostic services/repository methods a web admin panel could call directly, with zero changes:**
   - `UserService` (`countAll()`, `countNewLast24Hours()`, `listRecent()`, `search()`, `findById()`, plus the pre-existing `ban()`/`unban()`) — a web "کاربران" page needs nothing else.
   - `CampaignService`/`CampaignRepository` (`listAll()`, `countAll()`) — full CRUD was already UI-agnostic since Phase 4.
   - `AnalyticsEventRepository` (`countByEventNameFiltered()`, `countDistinctUsersByEventNameFiltered()`, `resultDistribution()`) and `TestSessionRepository` (`averageDurationSeconds()`, `countByStatus()`) — everything `AdminAnalyticsController`'s Telegram reports compute is already available as plain PHP method calls returning scalars/arrays; a web dashboard would just render the same numbers differently (charts instead of Persian text lines).
   - `BackupService` is **fully Telegram-agnostic** — `createBackup()`/`restoreFromFile()`/`listBackups()`/`resolveBackupPath()`/`validateBackupFile()` take/return plain arrays and touch only the filesystem + PDO. A web admin panel's backup page would only need its own file-upload widget (for restore) and a download link (for backup) — `AdminBackupController` is the **only** Telegram-specific layer (it additionally calls `TelegramClient::sendLocalDocument()` to deliver the file into a chat, and drives the two-step confirmation via `user_states` instead of a web form's own CSRF-protected POST).
   - `Tfb\Logging\ProblemsLog`/`LogReader` are also fully UI-agnostic — a web "لاگ‌ها" page could call `recentSummaries()`/`tailApp()`/`tailErrorsOnly()` directly; only `AdminLogsController::sendProblemsFile()` (which uses `TelegramClient::sendLocalDocument()`) is Telegram-specific, and a web equivalent would just offer the file as a direct HTTP download instead (guarded by the same auth the rest of the web panel would need).
   - The one genuinely Ops-specific gap for a future web panel: **health checks** (`AdminHealthController`) call `TelegramClient::getMe()`/`getWebhookInfo()`/`getChatMember()` directly — these are inherently Telegram-specific checks (by design, since they check the *bot's* Telegram-side state), so a web version would reuse the exact same `TelegramClient` calls, just rendered as an HTML page instead of a chat message.
3. **`Tfb\Security\Csrf`** already exists (built for the installer
   wizard) and is reusable as-is for web admin form POSTs.
4. **Config has room for a separate secret.** Nothing web-admin
   specific has been added to `config/app.example.php` yet (avoiding
   dead config keys before there's a consumer), but the pattern to
   follow when it's needed is a new top-level `admin_web` section,
   e.g.:
   ```php
   'admin_web' => [
       'enabled' => false,
       'session_secret' => '',   // generated at setup time, like bot.webhook_secret
   ],
   ```
   Never reuse `bot.webhook_secret` or `admins.ids` Telegram-id
   checks for web auth — a web session needs its own credential
   scheme (e.g. a login form + hashed password, or a signed link),
   not a Telegram user id.

## What a future phase must do to add it

1. Add route handlers under `src/AdminHttp/` (naming/style to match
   `src/Controllers/Admin/` conventions from Phase 6 — thin, delegate
   to Services, one controller per entity rather than a god-class).
2. Add a **separate** entrypoint, e.g. `public/admin.php` (or a path
   prefix handled inside `public/index.php` only if it can be done
   without adding any risk/complexity to webhook handling — prefer
   the separate file). This keeps `public/index.php`'s webhook logic
   untouched and independently testable.
3. Add real authentication (session-based login, not Telegram-id
   trust) — do not skip this to "move fast"; an unauthenticated admin
   HTTP surface would be a critical vulnerability.
4. Block the new entrypoint from being an open registration surface —
   follow the installer's pattern (rate limiting, CSRF, no secrets in
   query strings).
5. Reuse `Tfb\Support\AdminEditableSettings` and
   `Tfb\Support\AdminStates` naming/shape conventions where relevant
   (e.g. a web settings page could iterate the exact same
   `AdminEditableSettings::all()` list Telegram uses) rather than
   inventing a parallel settings-whitelist concept.
6. Update `docs/ARCHITECTURE.md`'s extension points section and
   `docs/AGENT_HANDOFF.md` once implemented.

## What must NOT happen

- The Telegram webhook pipeline (`Tfb\Bot\WebhookKernel`,
  `Tfb\Middleware\*`) must never import or depend on anything from
  `Tfb\AdminHttp\`. This still holds after Phase 7 — the entire
  Telegram admin builder AND Ops layer lives under
  `Tfb\Controllers\Admin\`, wired through `WebhookKernel`/`UpdateRouter`
  exactly like the student runtime, and never touches `src/AdminHttp/`.
- No shared session/cookie state between the Telegram bot and a web
  admin panel — they are different trust domains.
- No admin web feature may be implemented by duplicating business
  logic instead of calling the existing Service layer.

