Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 39 additions & 0 deletions delivery-kid/pinning-service/app/routes/coconut.py
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,23 @@ def _update_draft_finalize(staging_dir: Path, job: dict) -> None:
data["status"] = "finalized"
data["final_cid"] = cid
data["finalized_at"] = now
# Narrate each webhook-side step we actually did so the wiki
# diagnostics panel doesn't have a 2-minute gap between
# "transcoding-submitted" and "complete". The webhook handler
# itself only writes to draft.json once, so the multiple
# entries below are persisted in one save (we don't have a
# streaming mechanism back to the user's browser; the panel
# picks them all up on its next poll).
log.append({
"ts": now,
"stage": "webhook",
"message": "Coconut callback received — HLS ready",
})
log.append({
"ts": now,
"stage": "pin",
"message": f"HLS pinned to IPFS as {cid}",
})
log.append({
"ts": now,
"stage": "complete",
Expand Down Expand Up @@ -124,6 +141,28 @@ def _update_draft_finalize(staging_dir: Path, job: dict) -> None:
_asyncio.create_task(snapshot_diagnostics_for_dict_async(draft_id, data))
except RuntimeError:
logger.debug("[%s] No running loop; skipping diagnostics snapshot", job["id"])

# Update the ReleaseDraft:{id} wiki page YAML so the Blue Railroad
# Imports bot's find_cid_from_history picks up the new final_cid
# on its next cron run and creates the Release:{cid} page.
# Pre-V2 this edit was done by the browser-side JS when the SSE
# delivered a 'complete' event; the slow Coconut path closes the
# SSE long before completion, so we write it here instead.
if data.get("status") == "finalized" and data.get("final_cid"):
try:
import asyncio as _asyncio
from ..services.pickipedia_client import (
write_finalized_to_releasedraft_async,
)
_asyncio.create_task(write_finalized_to_releasedraft_async(
draft_id, data["final_cid"], data["finalized_at"],
))
except RuntimeError:
logger.debug("[%s] No running loop; skipping ReleaseDraft YAML update",
job["id"])
except Exception as e:
logger.warning("[%s] Failed to schedule ReleaseDraft YAML update: %s",
job["id"], e)
except Exception as e:
logger.error("[%s] Failed to update draft finalize state: %s", job["id"], e)

Expand Down
86 changes: 86 additions & 0 deletions delivery-kid/pinning-service/app/services/pickipedia_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -159,3 +159,89 @@ def snapshot_diagnostics_for_dict_async(draft_id: str, draft_data: dict):
"preview_log": draft_data.get("preview_log") or [],
}
return asyncio.to_thread(snapshot_diagnostics, draft_id, payload)


def write_finalized_to_releasedraft(
draft_id: str,
final_cid: str,
finalized_at: str,
) -> bool:
"""Edit the ``ReleaseDraft:{draft_id}`` wiki page YAML to mark it finalized.

Pre-V2-Coconut-migration, the browser-side JS handled this edit when
the finalize SSE delivered a ``complete`` event. For the slow Coconut
path that event never fires (the SSE closes after submission and the
user's browser doesn't see the eventual completion), so we write from
the webhook side instead.

The edit:
- Loads the current YAML
- Sets ``status: finalized``, ``final_cid: <cid>``, ``finalized_at: <iso>``
- Saves with summary ``Finalized: pinned to IPFS as <cid>``

The summary line is what the Blue Railroad Imports bot's
``find_cid_from_history`` greps for to trigger Release page creation
on its next cron run.

Returns True if the edit succeeded, False if creds are missing or the
write failed. Never raises.
"""
site = _get_site()
if site is None:
return False

try:
import yaml as _yaml
except ImportError:
logger.error("pickipedia_client: PyYAML not installed; cannot edit ReleaseDraft YAML")
return False

title = f"ReleaseDraft:{draft_id}"
try:
page = site.pages[title]
if not page.exists:
logger.warning("pickipedia_client: %s does not exist; cannot mark finalized", title)
return False

existing_text = page.text()
try:
data = _yaml.safe_load(existing_text) or {}
except _yaml.YAMLError as e:
logger.error("pickipedia_client: failed to parse %s YAML: %s", title, e)
return False

if not isinstance(data, dict):
logger.error("pickipedia_client: %s YAML is not a mapping; refusing to edit", title)
return False

data["status"] = "finalized"
data["final_cid"] = final_cid
data["finalized_at"] = finalized_at

new_text = _yaml.safe_dump(data, default_flow_style=False, sort_keys=False)
summary = f"Finalized: pinned to IPFS as {final_cid}"

if new_text.strip() == existing_text.strip():
return True

page.save(new_text, summary=summary)
logger.info("pickipedia_client: marked %s finalized (final_cid=%s)", title, final_cid)
return True
except Exception as e:
logger.error("pickipedia_client: failed to mark %s finalized: %s", title, e)
return False


async def write_finalized_to_releasedraft_async(
draft_id: str,
final_cid: str,
finalized_at: str,
):
"""Async wrapper for ``write_finalized_to_releasedraft``.

Runs the sync mwclient calls in a thread so the FastAPI event loop
isn't blocked while the wiki round-trip happens.
"""
return await asyncio.to_thread(
write_finalized_to_releasedraft, draft_id, final_cid, finalized_at,
)
4 changes: 4 additions & 0 deletions delivery-kid/pinning-service/requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -24,3 +24,7 @@ libtorrent>=2.0.0
# MediaWiki client — used by pickipedia_client to snapshot draft
# diagnostics to ReleaseDraft:{id}/diagnostics on terminal state.
mwclient>=0.10.1

# YAML parser — used by pickipedia_client to read/update the
# ReleaseDraft:{id} wiki page YAML when finalize completes.
PyYAML>=6.0