feat(nostr): sweep republishes events flagged as pending

A flagged row recovers on its own instead of waiting for the next sale
that happens to land while the signer is healthy — or for an operator
who already knows to run /republish-all, which was the only recovery
path and requires knowing about drift that nothing reported.

Retrying from the DB rather than an in-memory queue means the retry
survives a restart, and it needs no theory about why the publish didn't
land: the sweep covers the signer outage of #35 and the silent skip of
#51 identically, along with causes nobody has hit yet.

Runs every 5 minutes, take-down branch mirroring the publish/delete
split the CRUD endpoints already use. Quiet by design — on a healthy
instance the query returns nothing and it logs nothing.

Refs #35
This commit is contained in:
Padreug 2026-09-26 23:45:47 +02:00
commit 643322131e
2 changed files with 69 additions and 0 deletions

20
crud.py
View file

@ -239,6 +239,26 @@ async def get_pending_events() -> list[Event]:
)
async def get_events_pending_republish() -> list[Event]:
"""Events whose relay copy may be behind this row.
`nostr_publish_pending` is set before every publish attempt and
cleared only on a confirmed success, so a row still flagged here
either failed to publish or never got the chance. Drives the
reconciliation sweep in `events_start`.
Ordered oldest-first so a backlog drains in the order it accrued.
"""
return await db.fetchall(
"""
SELECT * FROM events.events
WHERE nostr_publish_pending = TRUE
ORDER BY time ASC
""",
model=Event,
)
async def get_settings() -> EventsSettings:
"""Singleton settings row, seeded by m010."""
row = await db.fetchone("SELECT * FROM events.settings WHERE id = 1")