diff --git a/services/api-server/src/api_server/html5_setup_controller.py b/services/api-server/src/api_server/html5_setup_controller.py
new file mode 100644
index 0000000..656db7c
--- /dev/null
+++ b/services/api-server/src/api_server/html5_setup_controller.py
@@ -0,0 +1,163 @@
+from __future__ import annotations
+
+import asyncio
+from collections.abc import AsyncIterator, Callable, Iterable
+from typing import Any
+
+from fastapi import HTTPException
+
+from api_server.html5_forms import form_value
+from api_server.html5_setup import (
+ render_html5_import_check,
+ render_html5_import_job,
+ render_html5_project_setup,
+ render_html5_settings_panel,
+ render_html5_setup_summary,
+)
+from api_server.html5_sse import html5_sse_comment, html5_sse_if_changed
+
+
+def html5_setup_page(
+ *,
+ project_id: str,
+ projects: Iterable[object],
+ setup: object,
+) -> str:
+ return render_html5_project_setup(project_id=project_id, projects=projects, setup=setup)
+
+
+def html5_setup_summary(*, project_id: str, setup: object) -> str:
+ return render_html5_setup_summary(project_id, setup)
+
+
+async def html5_setup_event_stream(
+ *,
+ project_id: str,
+ setup_response: Callable[[str], object],
+ latest_import_job: Callable[[str], object | None],
+ once: bool = False,
+) -> AsyncIterator[str]:
+ last_fragments: dict[str, str] = {}
+ while True:
+ yield html5_sse_comment(f"setup {project_id} heartbeat")
+ try:
+ setup = setup_response(project_id)
+ except HTTPException as error:
+ setup_error = f'
'
+ for event_text in html5_sse_if_changed(last_fragments, "setup-summary", setup_error):
+ yield event_text
+ if once:
+ break
+ await asyncio.sleep(2)
+ continue
+ for event_text in html5_sse_if_changed(last_fragments, "setup-summary", render_html5_setup_summary(project_id, setup)):
+ yield event_text
+ for event_text in html5_sse_if_changed(
+ last_fragments,
+ "setup-import-job",
+ render_html5_import_job(project_id, latest_import_job(project_id)),
+ ):
+ yield event_text
+ if once:
+ break
+ await asyncio.sleep(2)
+
+
+async def html5_setup_source(
+ *,
+ project_id: str,
+ form: dict[str, list[str]],
+ import_source_kind: Callable[[str], Any],
+ setup_response: Callable[[str], object],
+ save_settings: Callable[[str, object], Any],
+) -> str:
+ source = import_source_kind(form_value(form, "source") or "XML_DUMP")
+ current = setup_response(project_id)
+ settings = current.settings.model_copy(update={"structure_source": source})
+ setup = await save_settings(project_id, settings)
+ return render_html5_setup_summary(project_id, setup)
+
+
+async def html5_setup_settings(
+ *,
+ project_id: str,
+ form: dict[str, list[str]],
+ setup_response: Callable[[str], object],
+ save_settings: Callable[[str, object], Any],
+) -> str:
+ current = setup_response(project_id)
+ settings = current.settings.model_copy(
+ update={
+ "name": form_value(form, "name") or current.settings.name,
+ "platform_version": form_value(form, "platform_version"),
+ "compatibility_mode": form_value(form, "compatibility_mode"),
+ }
+ )
+ setup = await save_settings(project_id, settings)
+ return render_html5_settings_panel(project_id, setup, saved=True)
+
+
+def html5_setup_check(
+ *,
+ project_id: str,
+ form: dict[str, list[str]],
+ import_source_kind: Callable[[str], Any],
+ import_request: Callable[..., object],
+ current_import_source: Callable[[str], object],
+ import_check: Callable[[str, object, object], object],
+) -> str:
+ source = import_source_kind(form_value(form, "source") or current_import_source(project_id).value)
+ check = import_check(project_id, source, import_request(source=source))
+ return render_html5_import_check(project_id, check)
+
+
+def html5_setup_import(
+ *,
+ project_id: str,
+ form: dict[str, list[str]],
+ import_source_kind: Callable[[str], Any],
+ import_request: Callable[..., object],
+ current_import_source: Callable[[str], object],
+ execute_import: Callable[[str, object], object],
+ setup_response: Callable[[str], object],
+) -> str:
+ source = import_source_kind(form_value(form, "source") or current_import_source(project_id).value)
+ structure_only = form_value(form, "structure_only") in {"1", "true", "on", "yes"}
+ execute_import(project_id, import_request(source=source, structure_only=structure_only))
+ return render_html5_setup_summary(project_id, setup_response(project_id))
+
+
+async def html5_setup_import_job(
+ *,
+ project_id: str,
+ form: dict[str, list[str]],
+ import_source_kind: Callable[[str], Any],
+ import_request: Callable[..., object],
+ current_import_source: Callable[[str], object],
+ start_import_job: Callable[[str, object, object], Any],
+) -> str:
+ source = import_source_kind(form_value(form, "source") or current_import_source(project_id).value)
+ job = await start_import_job(project_id, source, import_request(source=source))
+ return render_html5_import_job(project_id, job)
+
+
+def html5_setup_job(
+ *,
+ project_id: str,
+ job_id: str,
+ jobs_by_id: dict[str, object],
+) -> str:
+ job = jobs_by_id.get(job_id)
+ if job is None or (getattr(job, "payload", {}) or {}).get("project_id") != project_id:
+ raise HTTPException(status_code=404, detail=f"Unknown import job: {job_id}")
+ return render_html5_import_job(project_id, job)
+
+
+async def html5_setup_reindex(
+ *,
+ project_id: str,
+ reindex: Callable[[str], Any],
+ setup_response: Callable[[str], object],
+) -> str:
+ await reindex(project_id)
+ return render_html5_setup_summary(project_id, setup_response(project_id))
diff --git a/services/api-server/src/api_server/main.py b/services/api-server/src/api_server/main.py
index 752fad5..7310727 100644
--- a/services/api-server/src/api_server/main.py
+++ b/services/api-server/src/api_server/main.py
@@ -90,12 +90,17 @@ from api_server.html5_operations_controller import (
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,
- render_html5_import_job,
- render_html5_project_setup,
- render_html5_settings_panel,
- render_html5_setup_summary,
+from api_server.html5_setup_controller import (
+ html5_setup_check as _html5_setup_check,
+ html5_setup_event_stream as _html5_setup_event_stream,
+ html5_setup_import as _html5_setup_import,
+ html5_setup_import_job as _html5_setup_import_job,
+ html5_setup_job as _html5_setup_job,
+ html5_setup_page as _html5_setup_page,
+ html5_setup_reindex as _html5_setup_reindex,
+ html5_setup_settings as _html5_setup_settings,
+ html5_setup_source as _html5_setup_source,
+ html5_setup_summary as _html5_setup_summary,
)
from impact_engine import object_impact, routine_impact
from incremental_indexer import rebuild_changed_file
@@ -2019,113 +2024,115 @@ async def html5_project_authoring_apply_metadata_object(project_id: str, request
@app.get("/html5/projects/{project_id}/setup")
async def html5_project_setup(project_id: str) -> Response:
- setup = _project_setup_response(project_id)
return _html5_response(
- render_html5_project_setup(project_id=project_id, projects=_project_summaries(), setup=setup),
+ _html5_setup_page(project_id=project_id, projects=_project_summaries(), setup=_project_setup_response(project_id)),
)
@app.get("/html5/projects/{project_id}/setup/summary")
async def html5_project_setup_summary(project_id: str) -> Response:
- setup = _project_setup_response(project_id)
- return _html5_response(render_html5_setup_summary(project_id, setup))
+ return _html5_response(_html5_setup_summary(project_id=project_id, setup=_project_setup_response(project_id)))
@app.get("/html5/projects/{project_id}/setup/events")
async def html5_project_setup_events(project_id: str, once: bool = False) -> StreamingResponse:
- async def stream_setup():
- last_fragments: dict[str, str] = {}
- while True:
- yield _html5_sse_comment(f"setup {project_id} heartbeat")
- try:
- setup = _project_setup_response(project_id)
- except HTTPException as error:
- setup_error = f''
- for event_text in _html5_sse_if_changed(last_fragments, "setup-summary", setup_error):
- yield event_text
- if once:
- break
- await asyncio.sleep(2)
- continue
- for event_text in _html5_sse_if_changed(last_fragments, "setup-summary", render_html5_setup_summary(project_id, setup)):
- yield event_text
- for event_text in _html5_sse_if_changed(
- last_fragments,
- "setup-import-job",
- render_html5_import_job(project_id, _html5_latest_import_job(project_id)),
- ):
- yield event_text
- if once:
- break
- await asyncio.sleep(2)
-
- return _html5_sse_response(stream_setup())
+ return _html5_sse_response(
+ _html5_setup_event_stream(
+ project_id=project_id,
+ setup_response=_project_setup_response,
+ latest_import_job=_html5_latest_import_job,
+ once=once,
+ )
+ )
@app.post("/html5/projects/{project_id}/setup/source")
async def html5_project_setup_source(project_id: str, request: Request) -> Response:
form = await _html5_form_data(request)
- source = ImportSourceKind(_form_value(form, "source") or ImportSourceKind.XML_DUMP.value)
- current = _project_setup_response(project_id)
- settings = current.settings.model_copy(update={"structure_source": source})
- setup = await save_project_settings(project_id, settings)
- return _html5_response(render_html5_setup_summary(project_id, setup))
+ return _html5_response(
+ await _html5_setup_source(
+ project_id=project_id,
+ form=form,
+ import_source_kind=ImportSourceKind,
+ setup_response=_project_setup_response,
+ save_settings=save_project_settings,
+ )
+ )
@app.post("/html5/projects/{project_id}/setup/settings")
async def html5_project_setup_settings(project_id: str, request: Request) -> Response:
form = await _html5_form_data(request)
- current = _project_setup_response(project_id)
- settings = current.settings.model_copy(
- update={
- "name": _form_value(form, "name") or current.settings.name,
- "platform_version": _form_value(form, "platform_version"),
- "compatibility_mode": _form_value(form, "compatibility_mode"),
- }
+ return _html5_response(
+ await _html5_setup_settings(
+ project_id=project_id,
+ form=form,
+ setup_response=_project_setup_response,
+ save_settings=save_project_settings,
+ )
)
- setup = await save_project_settings(project_id, settings)
- return _html5_response(render_html5_settings_panel(project_id, setup, saved=True))
@app.post("/html5/projects/{project_id}/setup/check")
async def html5_project_setup_check(project_id: str, request: Request) -> Response:
form = await _html5_form_data(request)
- source = ImportSourceKind(_form_value(form, "source") or _current_import_source(project_id).value)
- check = _import_check_response(project_id, source, ImportRequest(source=source))
- return _html5_response(render_html5_import_check(project_id, check))
+ return _html5_response(
+ _html5_setup_check(
+ project_id=project_id,
+ form=form,
+ import_source_kind=ImportSourceKind,
+ import_request=ImportRequest,
+ current_import_source=_current_import_source,
+ import_check=_import_check_response,
+ )
+ )
@app.post("/html5/projects/{project_id}/setup/import")
async def html5_project_setup_import(project_id: str, request: Request) -> Response:
form = await _html5_form_data(request)
- source = ImportSourceKind(_form_value(form, "source") or _current_import_source(project_id).value)
- structure_only = _form_value(form, "structure_only") in {"1", "true", "on", "yes"}
- _execute_import_project(project_id, ImportRequest(source=source, structure_only=structure_only))
- setup = _project_setup_response(project_id)
- return _html5_response(render_html5_setup_summary(project_id, setup))
+ return _html5_response(
+ _html5_setup_import(
+ project_id=project_id,
+ form=form,
+ import_source_kind=ImportSourceKind,
+ import_request=ImportRequest,
+ current_import_source=_current_import_source,
+ execute_import=_execute_import_project,
+ setup_response=_project_setup_response,
+ )
+ )
@app.post("/html5/projects/{project_id}/setup/import-job")
async def html5_project_setup_import_job(project_id: str, request: Request) -> Response:
form = await _html5_form_data(request)
- source = ImportSourceKind(_form_value(form, "source") or _current_import_source(project_id).value)
- job = await start_project_import_job(project_id, source, ImportRequest(source=source))
- return _html5_response(render_html5_import_job(project_id, job))
+ return _html5_response(
+ await _html5_setup_import_job(
+ project_id=project_id,
+ form=form,
+ import_source_kind=ImportSourceKind,
+ import_request=ImportRequest,
+ current_import_source=_current_import_source,
+ start_import_job=start_project_import_job,
+ )
+ )
@app.get("/html5/projects/{project_id}/setup/jobs/{job_id}")
async def html5_project_setup_job(project_id: str, job_id: str) -> Response:
- job = _operations.jobs.get(job_id)
- if job is None or job.payload.get("project_id") != project_id:
- raise HTTPException(status_code=404, detail=f"Unknown import job: {job_id}")
- return _html5_response(render_html5_import_job(project_id, job))
+ return _html5_response(_html5_setup_job(project_id=project_id, job_id=job_id, jobs_by_id=_operations.jobs))
@app.post("/html5/projects/{project_id}/setup/reindex")
async def html5_project_setup_reindex(project_id: str) -> Response:
- await reindex_project(project_id)
- setup = _project_setup_response(project_id)
- return _html5_response(render_html5_setup_summary(project_id, setup))
+ return _html5_response(
+ await _html5_setup_reindex(
+ project_id=project_id,
+ reindex=reindex_project,
+ setup_response=_project_setup_response,
+ )
+ )
@app.get("/version")