feat: refine current match live feed
This commit is contained in:
@@ -354,12 +354,17 @@ def build_current_match_kill_feed_payload(
|
||||
*,
|
||||
server_slug: str,
|
||||
limit: int = 30,
|
||||
since_event_id: str | None = None,
|
||||
) -> dict[str, object]:
|
||||
"""Return normalized AdminLog kill rows for one trusted current-match page."""
|
||||
origin = get_trusted_public_scoreboard_origin(server_slug)
|
||||
if origin is None:
|
||||
raise ValueError("Unsupported current match server.")
|
||||
feed = list_current_match_kill_feed(server_key=origin.slug, limit=limit)
|
||||
feed = list_current_match_kill_feed(
|
||||
server_key=origin.slug,
|
||||
limit=limit,
|
||||
since_event_id=since_event_id,
|
||||
)
|
||||
return {
|
||||
"status": "ok",
|
||||
"data": {
|
||||
|
||||
@@ -337,11 +337,13 @@ def list_current_match_kill_feed(
|
||||
*,
|
||||
server_key: str,
|
||||
limit: int = 30,
|
||||
since_event_id: str | None = None,
|
||||
db_path: Path | None = None,
|
||||
now: datetime | None = None,
|
||||
) -> dict[str, object]:
|
||||
"""Return safe recent kill rows for one AdminLog server window."""
|
||||
resolved_path = initialize_rcon_admin_log_storage(db_path=db_path)
|
||||
since_row_id = _parse_current_match_event_row_id(since_event_id)
|
||||
if use_postgres_rcon_storage(explicit_sqlite_path=db_path):
|
||||
from .postgres_rcon_storage import connect_postgres_compat
|
||||
|
||||
@@ -377,10 +379,11 @@ def list_current_match_kill_feed(
|
||||
FROM rcon_admin_log_events
|
||||
WHERE (target_key = ? OR external_server_id = ?)
|
||||
AND event_type = 'kill'
|
||||
AND (? IS NULL OR id > ?)
|
||||
ORDER BY server_time DESC, id DESC
|
||||
LIMIT ?
|
||||
""",
|
||||
(server_key, server_key, limit),
|
||||
(server_key, server_key, since_row_id, since_row_id, limit),
|
||||
).fetchall()
|
||||
scope = "recent-admin-log-window"
|
||||
confidence = "partial"
|
||||
@@ -393,10 +396,11 @@ def list_current_match_kill_feed(
|
||||
WHERE (target_key = ? OR external_server_id = ?)
|
||||
AND event_type = 'kill'
|
||||
AND server_time >= ?
|
||||
AND (? IS NULL OR id > ?)
|
||||
ORDER BY server_time DESC, id DESC
|
||||
LIMIT ?
|
||||
""",
|
||||
(server_key, server_key, open_start_time, limit),
|
||||
(server_key, server_key, open_start_time, since_row_id, since_row_id, limit),
|
||||
).fetchall()
|
||||
scope = "open-admin-log-match-window"
|
||||
confidence = "admin-log-boundary"
|
||||
@@ -613,6 +617,17 @@ def _serialize_kill_feed_row(row: Mapping[str, object]) -> dict[str, object]:
|
||||
}
|
||||
|
||||
|
||||
def _parse_current_match_event_row_id(value: object) -> int | None:
|
||||
prefix, separator, row_id = str(value or "").rpartition(":")
|
||||
if separator != ":" or not prefix.startswith("rcon-admin-log:"):
|
||||
return None
|
||||
try:
|
||||
parsed = int(row_id)
|
||||
except ValueError:
|
||||
return None
|
||||
return parsed if parsed > 0 else None
|
||||
|
||||
|
||||
def _safe_event_field(value: object) -> str | None:
|
||||
normalized = str(value or "").strip()
|
||||
return normalized or None
|
||||
|
||||
@@ -76,7 +76,8 @@ def resolve_get_payload(path: str) -> tuple[HTTPStatus | None, dict[str, object]
|
||||
limit = _parse_limit(parsed.query)
|
||||
if limit is None:
|
||||
return HTTPStatus.BAD_REQUEST, build_error_payload("Invalid limit parameter")
|
||||
server_slug = parse_qs(parsed.query).get("server", [None])[0]
|
||||
params = parse_qs(parsed.query)
|
||||
server_slug = params.get("server", [None])[0]
|
||||
if not server_slug:
|
||||
return HTTPStatus.BAD_REQUEST, build_error_payload("Server parameter is required")
|
||||
if get_trusted_public_scoreboard_origin(server_slug) is None:
|
||||
@@ -84,6 +85,7 @@ def resolve_get_payload(path: str) -> tuple[HTTPStatus | None, dict[str, object]
|
||||
return HTTPStatus.OK, build_current_match_kill_feed_payload(
|
||||
server_slug=server_slug,
|
||||
limit=limit,
|
||||
since_event_id=params.get("since_event_id", [None])[0],
|
||||
)
|
||||
|
||||
if parsed.path == "/api/current-match/players":
|
||||
|
||||
@@ -368,3 +368,40 @@ def test_current_match_kill_feed_marks_fresh_recent_fallback_rows_partial(tmp_pa
|
||||
assert feed["stale_events_filtered"] == 0
|
||||
assert [item["killer_name"] for item in feed["items"]] == ["Fresh Killer"]
|
||||
gc.collect()
|
||||
|
||||
|
||||
def test_current_match_kill_feed_filters_rows_before_incremental_cursor(tmp_path):
|
||||
db_path = tmp_path / "admin_log.sqlite3"
|
||||
persist_rcon_admin_log_entries(
|
||||
target=TARGET,
|
||||
entries=[
|
||||
{
|
||||
"timestamp": "2026-05-21T10:00:00Z",
|
||||
"message": "[1:00 min (100)] MATCH START Mortain Warfare",
|
||||
},
|
||||
{
|
||||
"timestamp": "2026-05-21T10:01:00Z",
|
||||
"message": (
|
||||
"[2:00 min (120)] KILL: First Killer(Allies/steam-first) -> "
|
||||
"First Victim(Axis/steam-first-victim) with M1 GARAND"
|
||||
),
|
||||
},
|
||||
{
|
||||
"timestamp": "2026-05-21T10:02:00Z",
|
||||
"message": (
|
||||
"[3:00 min (140)] KILL: Next Killer(Axis/steam-next) -> "
|
||||
"Next Victim(Allies/steam-next-victim) with MP40"
|
||||
),
|
||||
},
|
||||
],
|
||||
db_path=db_path,
|
||||
)
|
||||
|
||||
feed = list_current_match_kill_feed(
|
||||
server_key="test-rcon-target",
|
||||
db_path=db_path,
|
||||
since_event_id="rcon-admin-log:test-rcon-target:2",
|
||||
)
|
||||
|
||||
assert [item["killer_name"] for item in feed["items"]] == ["Next Killer"]
|
||||
gc.collect()
|
||||
|
||||
Reference in New Issue
Block a user