News post → town crier + Discord announcement pipeline #52

Merged
whitlocktech merged 2 commits from feature/news-announce-pipeline into main 2026-07-11 21:58:34 +00:00
Member

What

Replaces the old fire-and-forget, Discord-only announce-on-publish with a retry-safe, two-leg announcement pipeline. When a post transitions into published news (a false→true publish while in news, or a category change into news while already published), the site enqueues an announce_jobs row with two independent delivery legs:

  • Town crier — sidecar POST /towncrier via uoLinkClient, stable id post-<id> so a retry replaces rather than duplicates the in-game entry.
  • Discord — bot POST /internal/announce via botInternalClient (the bot remains the single source of truth for the #news channel).

A light in-process poller (utils/announceWorker) sweeps the table every ANNOUNCE_POLL_MS and dispatches each due leg with its own exponential backoff (30s → 2h, 6 attempts). Transient failures (503/504/network) retry; data/config failures fail fast (400 over-cap, 401/409). Publishing never blocks on the sidecar or Discord — enqueue is local DB only. The parent status is a done/partial/failed rollup of the two legs; posts.announced_at is stamped once both deliver.

Admin visibility

GET /admin/posts/:id/announce + a per-leg Retry button, surfaced in the PostEditor for news posts (useful after fixing the sidecar/news-channel without re-publishing). The old manual /admin/uo-link/towncrier form is untouched.

Data model

  • New announce_jobs table (two-leg state machine, idempotent-migration friendly).
  • posts.announced_at + posts.announce_job_id (added via the existing idempotent ALTER … IF NOT EXISTS boot migration).

Testing

  • Pure decisions (text build/caps, classification, backoff, rollup) live in announceJobs.logic and are unit-tested — server/test/announceJobs.test.js (10 tests). Full server suite: 152/152 green.
  • Live smoke test performed: DB + site + Rust sidecar + ServUO shard brought up locally; published a news post → worker dispatched → entry landed in the shard's global town-crier list → an in-game crier announced it. Confirmed the Discord leg backs off correctly when the bot is down.

Config

New env (documented in server/.env.example + README): ANNOUNCE_POLL_MS (default 15000), TOWNCRIER_DURATION_SEC (default 3600, ≤ 86400).

🤖 Generated with Claude Code

https://claude.ai/code/session_0114TpmrNW4wNXsHq5CR72jQ

## What Replaces the old fire-and-forget, Discord-only announce-on-publish with a **retry-safe, two-leg announcement pipeline**. When a post transitions into *published news* (a `false→true` publish while in news, or a category change into news while already published), the site enqueues an `announce_jobs` row with two **independent** delivery legs: - **Town crier** — sidecar `POST /towncrier` via `uoLinkClient`, stable id `post-<id>` so a retry *replaces* rather than duplicates the in-game entry. - **Discord** — bot `POST /internal/announce` via `botInternalClient` (the bot remains the single source of truth for the `#news` channel). A light in-process poller (`utils/announceWorker`) sweeps the table every `ANNOUNCE_POLL_MS` and dispatches each due leg with its own exponential backoff (30s → 2h, 6 attempts). Transient failures (503/504/network) retry; data/config failures fail fast (400 over-cap, 401/409). **Publishing never blocks** on the sidecar or Discord — enqueue is local DB only. The parent `status` is a `done`/`partial`/`failed` rollup of the two legs; `posts.announced_at` is stamped once both deliver. ## Admin visibility `GET /admin/posts/:id/announce` + a per-leg **Retry** button, surfaced in the PostEditor for news posts (useful after fixing the sidecar/news-channel without re-publishing). The old manual `/admin/uo-link/towncrier` form is untouched. ## Data model - New `announce_jobs` table (two-leg state machine, idempotent-migration friendly). - `posts.announced_at` + `posts.announce_job_id` (added via the existing idempotent `ALTER … IF NOT EXISTS` boot migration). ## Testing - Pure decisions (text build/caps, classification, backoff, rollup) live in `announceJobs.logic` and are unit-tested — `server/test/announceJobs.test.js` (10 tests). Full server suite: 152/152 green. - **Live smoke test performed**: DB + site + Rust sidecar + ServUO shard brought up locally; published a news post → worker dispatched → entry landed in the shard's global town-crier list → an in-game crier announced it. Confirmed the Discord leg backs off correctly when the bot is down. ## Config New env (documented in `server/.env.example` + README): `ANNOUNCE_POLL_MS` (default 15000), `TOWNCRIER_DURATION_SEC` (default 3600, ≤ 86400). 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_0114TpmrNW4wNXsHq5CR72jQ
wtclaude added 1 commit 2026-07-11 21:55:30 +00:00
Replace the fire-and-forget Discord-only announce on publish with a
retry-safe, two-leg pipeline. When a post transitions into published-news
(false→true publish while in news, or category→news while published), an
announce_jobs row is enqueued with two INDEPENDENT delivery legs:

  • town crier — sidecar POST /towncrier via uoLinkClient (stable id
    `post-<id>` so a retry replaces rather than duplicates)
  • discord    — bot POST /internal/announce via botInternalClient
    (single source of truth for the #news channel stays in the bot)

An in-process poller (utils/announceWorker) sweeps the table every
ANNOUNCE_POLL_MS and dispatches each due leg with its own exponential
backoff (30s→2h, 6 attempts). A leg is retried on transient failures
(503/504/network) and failed fast on data/config errors (400 over-cap,
401/409). Publishing never blocks on the sidecar or Discord — enqueue is
local DB only. Parent `status` is a done/partial/failed rollup of the two
legs; posts.announced_at is stamped once both deliver.

Admin visibility: GET /admin/posts/:id/announce + a per-leg Retry
(POST .../announce/retry) surfaced in the PostEditor for news posts.

Pure decisions (text build/caps, classification, backoff, rollup) live in
announceJobs.logic and are unit-tested (server/test/announceJobs.test.js,
10 tests). The old manual /admin/uo-link/towncrier form is untouched.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0114TpmrNW4wNXsHq5CR72jQ
whitlocktech approved these changes 2026-07-11 21:57:41 +00:00
whitlocktech added 1 commit 2026-07-11 21:57:56 +00:00
whitlocktech merged commit 99649727f3 into main 2026-07-11 21:58:34 +00:00
whitlocktech deleted branch feature/news-announce-pipeline 2026-07-11 21:58:35 +00:00
Sign in to join this conversation.
No description provided.