From f6679d169479177a94cbe6181a50ebcfb39171ea Mon Sep 17 00:00:00 2001 From: Mikhail Date: Sun, 17 May 2026 19:16:26 +0300 Subject: [PATCH] Extract HTML5 operations controller --- .../api_server/html5_operations_controller.py | 94 +++++++++++++++++++ services/api-server/src/api_server/main.py | 71 +++++++------- 2 files changed, 129 insertions(+), 36 deletions(-) create mode 100644 services/api-server/src/api_server/html5_operations_controller.py diff --git a/services/api-server/src/api_server/html5_operations_controller.py b/services/api-server/src/api_server/html5_operations_controller.py new file mode 100644 index 0000000..08ca7d5 --- /dev/null +++ b/services/api-server/src/api_server/html5_operations_controller.py @@ -0,0 +1,94 @@ +from __future__ import annotations + +import time +from collections.abc import Callable, Iterable, Iterator + +from fastapi import HTTPException + +from api_server.html5_operations import ( + filter_html5_operation_jobs, + render_html5_operation_detail, + render_html5_operation_rows, + render_html5_operation_summary, + render_html5_operations, +) +from api_server.html5_sse import html5_sse_comment, html5_sse_if_changed + + +def html5_operations_page( + *, + jobs: Iterable[object], + project_id: str = "", + status: str = "", + kind: str = "", +) -> str: + return render_html5_operations( + _filtered_jobs(jobs, project_id=project_id, status=status, kind=kind), + project_id=project_id, + status=status, + kind=kind, + ) + + +def html5_operation_rows( + *, + jobs: Iterable[object], + project_id: str = "", + status: str = "", + kind: str = "", +) -> str: + return render_html5_operation_rows(_filtered_jobs(jobs, project_id=project_id, status=status, kind=kind)) + + +def html5_operation_detail(*, jobs_by_id: dict[str, object], job_id: str) -> str: + job = jobs_by_id.get(job_id) + if job is None: + raise HTTPException(status_code=404, detail=f"Unknown operation job: {job_id}") + return render_html5_operation_detail(job) + + +def html5_operation_summary( + *, + jobs: Iterable[object], + project_id: str = "", + status: str = "", + kind: str = "", +) -> str: + return render_html5_operation_summary(_filtered_jobs(jobs, project_id=project_id, status=status, kind=kind)) + + +def html5_operations_event_stream( + *, + jobs: Callable[[], Iterable[object]], + once: bool = False, + project_id: str = "", + status: str = "", + kind: str = "", +) -> Iterator[str]: + last_fragments: dict[str, str] = {} + while True: + yield html5_sse_comment("operations heartbeat") + filtered = _filtered_jobs(jobs(), project_id=project_id, status=status, kind=kind) + yield from html5_sse_if_changed( + last_fragments, + "operations-summary", + render_html5_operation_summary(filtered), + ) + yield from html5_sse_if_changed( + last_fragments, + "operations-jobs", + render_html5_operation_rows(filtered), + ) + if once: + break + time.sleep(3) + + +def _filtered_jobs( + jobs: Iterable[object], + *, + project_id: str = "", + status: str = "", + kind: str = "", +) -> list[object]: + return filter_html5_operation_jobs(jobs, project_id=project_id, status=status, kind=kind) diff --git a/services/api-server/src/api_server/main.py b/services/api-server/src/api_server/main.py index b32487c..752fad5 100644 --- a/services/api-server/src/api_server/main.py +++ b/services/api-server/src/api_server/main.py @@ -81,12 +81,14 @@ from api_server.html5_editor import ( render_html5_symbols, ) from api_server.html5_operations import ( - filter_html5_operation_jobs, latest_html5_import_job, - render_html5_operation_detail, - render_html5_operation_rows, - render_html5_operation_summary, - render_html5_operations, +) +from api_server.html5_operations_controller import ( + html5_operation_detail as _html5_operation_detail, + html5_operation_rows as _html5_operation_rows, + html5_operation_summary as _html5_operation_summary, + html5_operations_event_stream as _html5_operations_event_stream, + html5_operations_page as _html5_operations_page, ) from api_server.html5_setup import ( render_html5_import_check, @@ -1663,8 +1665,8 @@ async def html5_delete_project(project_id: str, request: Request) -> Response: @app.get("/html5/operations") async def html5_operations(project_id: str = "", status: str = "", kind: str = "") -> Response: return _html5_response( - render_html5_operations( - _html5_operation_jobs(project_id=project_id, status=status, kind=kind), + _html5_operations_page( + jobs=_operations.jobs.values(), project_id=project_id, status=status, kind=kind, @@ -1674,22 +1676,31 @@ async def html5_operations(project_id: str = "", status: str = "", kind: str = " @app.get("/html5/operations/jobs") async def html5_operation_jobs(project_id: str = "", status: str = "", kind: str = "") -> Response: - jobs = _html5_operation_jobs(project_id=project_id, status=status, kind=kind) - return _html5_response(render_html5_operation_rows(jobs)) + return _html5_response( + _html5_operation_rows( + jobs=_operations.jobs.values(), + project_id=project_id, + status=status, + kind=kind, + ) + ) @app.get("/html5/operations/jobs/{job_id}/detail") async def html5_operation_job_detail(job_id: str) -> Response: - job = _operations.jobs.get(job_id) - if job is None: - raise HTTPException(status_code=404, detail=f"Unknown operation job: {job_id}") - return _html5_response(render_html5_operation_detail(job)) + return _html5_response(_html5_operation_detail(jobs_by_id=_operations.jobs, job_id=job_id)) @app.get("/html5/operations/summary") async def html5_operation_summary(project_id: str = "", status: str = "", kind: str = "") -> Response: - jobs = _html5_operation_jobs(project_id=project_id, status=status, kind=kind) - return _html5_response(render_html5_operation_summary(jobs)) + return _html5_response( + _html5_operation_summary( + jobs=_operations.jobs.values(), + project_id=project_id, + status=status, + kind=kind, + ) + ) @app.get("/html5/operations/events") @@ -1699,18 +1710,15 @@ async def html5_operations_events( status: str = "", kind: str = "", ) -> StreamingResponse: - def stream_operations(): - last_fragments: dict[str, str] = {} - while True: - yield _html5_sse_comment("operations heartbeat") - jobs = _html5_operation_jobs(project_id=project_id, status=status, kind=kind) - yield from _html5_sse_if_changed(last_fragments, "operations-summary", render_html5_operation_summary(jobs)) - yield from _html5_sse_if_changed(last_fragments, "operations-jobs", render_html5_operation_rows(jobs)) - if once: - break - time.sleep(3) - - return _html5_sse_response(stream_operations()) + return _html5_sse_response( + _html5_operations_event_stream( + jobs=lambda: _operations.jobs.values(), + once=once, + project_id=project_id, + status=status, + kind=kind, + ) + ) @app.get("/html5/projects/{project_id}/editor") @@ -8333,15 +8341,6 @@ def _current_import_source(project_id: str) -> ImportSourceKind: return ImportSourceKind.XML_DUMP -def _html5_operation_jobs(project_id: str = "", status: str = "", kind: str = "") -> list[OperationJob]: - return filter_html5_operation_jobs( - _operations.jobs.values(), - project_id=project_id, - status=status, - kind=kind, - ) - - def _html5_latest_import_job(project_id: str) -> OperationJob | None: return latest_html5_import_job(_operations.jobs.values(), project_id)