Files
XC_VM/docs/adr/search-json-contract.md
T
Divarion_D 48298859fa feat(admin): serve global search as structured JSON, render cards client-side
Replace the server-side HTML builder in the `search` admin-ajax action with a
structured JSON contract: each result item now carries `{id, url, text, entity,
data}` and the client renders the card. Permission checks, status resolution and
category/server lookups stay server-side; only markup moves to the browser. This
unblocks the upcoming redesign, where the embedded HTML builder was in the way.

- SearchAjaxController::search() now emits structured items (per-entity `data`
  payloads for stream/movie/channel/radio/episode, series, user, line, mag,
  enigma) plus a `no_results` item for empty responses. Data-gathering is
  unchanged (byte-identical to the previous fuzzy full-text gather).
- New client renderer `assets/admin/js/search.js` (`renderSearchItem`), loaded
  before common.js; the Select2 quick-search `templateResult` now calls it
  (with a loading-state guard) instead of consuming a server `html` field.
- ADR docs/adr/search-json-contract.md documents the envelope, item shape,
  self-describing actions and per-entity payloads.

Note: this changes only the search render path, not search matching. Live
streams missing from results are a stale streams FULLTEXT index on the DB
(rebuild with `ALTER TABLE streams ENGINE=InnoDB`), unrelated to this change.
2026-08-28 21:50:05 +03:00

84 lines
3.9 KiB
Markdown

# Search JSON contract
Goal: the `search` endpoint returns **structured data**, not server-rendered HTML, so the new design (and any future client) renders cards itself. Permission checks, status resolution, category/server lookups stay server-side; only markup moves to the client.
Endpoint: `?action=search` ({@see \XcVm\Public\Controllers\Admin\Ajax\SearchAjaxController}). The client renderer is `src/Public/assets/admin/js/search.js` (`renderSearchItem(item)`), wired into the Select2 quick-search box in `common.js`.
## Envelope
```jsonc
{ "result": true, "total_count": 12, "items": [ Item, … ] }
```
## Item (common)
```jsonc
{
"id": "streams#512", // stable identity (kept for Select2)
"url": "stream_view?id=512",// primary navigation target
"text": "CNN HD", // plain label (kept for Select2 matching)
"entity": "stream", // stream|movie|channel|radio|episode|series|user|line|mag|enigma
"data": { … } // entity-specific payload (below)
}
```
Every `data.actions[]` entry is **self-describing** so the client needs no per-action logic:
```jsonc
{ "kind": "navigate", "target": "movie?id=512", "icon": "mdi-pencil", "title": "Edit" }
{ "kind": "api", "entity": "stream", "sub": "stop", "icon": "mdi-stop", "title": "Stop", "enabled": true }
{ "kind": "fingerprint", "id": 512, "context": "stream", "icon": "mdi-fingerprint", "enabled": true }
{ "kind": "credits", "id": 5, "icon": "mdi-coin", "title": "Add credits" }
```
`kind` → client call: `navigate(target)` · `searchAPI(entity,id,sub)` · `modalFingerprint(id,context)` · `addCredits(id)`. `enabled:false` renders a disabled button.
## data by entity
### stream / movie / channel / radio / episode
```jsonc
{
"layout": "live" | "vod",
"title": "CNN HD", "title_link": "stream_view?id=512" | null,
"category": "News (+2)",
"server": "EU-1 (+1)" | "",
"image": { "url": "cnn.png", "size": 96 }, // live 96 (stream_icon) / vod 512 (movie_image)
"badge": { "text": "STREAM", "variant": "success" },
"connections": 128, "connections_link": "live_connections?stream_id=512",
"status": { "kind": "uptime", "text": "01h 02m 03s" }
| { "kind": "progress", "percent": 42 }
| { "kind": "status", "code": 5, "label": "DIRECT", "variant": "purple" },
"rating": { "stars_full": 3, "half": true, "empty": 1, "year": "2021" } | null, // movies
"actions": [ Action, … ]
}
```
### series
```jsonc
{ "title": "Breaking Bad", "category": "Drama", "image": {"url":"cover.jpg","size":512},
"rating": {"stars_full":4,"half":false,"empty":1,"year":"2008"},
"badge": {"text":"TV SERIES","variant":"danger"}, "seasons": 5, "episodes": 62,
"actions": [ … ] }
```
### user
```jsonc
{ "username":"reseller1", "group":"Resellers", "owner":"admin", "is_reseller":true, "credits":1000,
"status":{"label":"Active","variant":"info"}, "users_count":12, "lines_count":340,
"badge":{"text":"USER","variant":"warning"}, "actions":[ … ] }
```
### line / mag / enigma
```jsonc
{ "title": "line123" | "AA:BB:CC:…", // username for line, mac for device
"device_type": "mag" | "enigma" | null,
"status": {"label":"Active"|"Banned"|"Disabled","variant":"info"|"danger"|"warning"},
"owner": "admin", "expires": "2025-01-01 00:00:00" | null,
"last_active": {"online":true,"stream_id":512,"stream_name":"CNN","online_for":"01h 00m 00s"}
| {"online":false,"date":"2024-12-01 10:00:00"|null},
"connections": 5, "flags": {"restreamer":false,"trial":true},
"badge": {"variant":"pink"}, "actions":[ … ] }
```
## Status codes (streams)
Server resolves `$rActualStatus` (-1…10) exactly as today; label + variant are derived from the existing `$rSearchStatusArray` constant, so they stay the single source of truth. Codes `1` (running → uptime) and `6` (created-channel encode → progress) are special-cased into the `uptime` / `progress` kinds.