News post → town crier + Discord announcement pipeline
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
This commit is contained in:
@@ -8,6 +8,7 @@ const uoLinkSocket = require('./utils/uoLinkSocket')
|
||||
const uoLinkClient = require('./utils/uoLinkClient')
|
||||
const uoLinkConfig = require('./model/uoLinkConfig/uoLinkConfig.model')
|
||||
const shardBroadcast = require('./utils/shardBroadcast')
|
||||
const announceWorker = require('./utils/announceWorker')
|
||||
const { ensureSchema, close } = require('./utils/db')
|
||||
const { seedDefaults, createInitialAdminFromEnv } = require('../db/seed')
|
||||
const settings = require('./model/settings/settings.model')
|
||||
@@ -92,6 +93,11 @@ async function start() {
|
||||
log.warn('uo-link socket failed to start (continuing)', { error: err.message })
|
||||
}
|
||||
|
||||
// Start the news-announcement dispatcher: a light in-process poller that pushes
|
||||
// published news posts to the in-game town crier + Discord with independent
|
||||
// retry per leg. No-op until a news post is actually published.
|
||||
announceWorker.start()
|
||||
|
||||
setupShutdown(server, internalServer)
|
||||
}
|
||||
|
||||
@@ -129,6 +135,7 @@ function setupShutdown(server, internalServer) {
|
||||
closing = true
|
||||
log.warn(`${signal} received — shutting down gracefully`)
|
||||
botScore.stopSweeper() // stop the bot-store cleanup interval
|
||||
announceWorker.stop() // stop the news-announcement dispatcher poller
|
||||
uoLinkSocket.stop() // close the uo-link WS ingest client
|
||||
shardBroadcast.closeAll() // end any open shard live-feed SSE streams
|
||||
server.close(() => log.info('http server closed'))
|
||||
|
||||
Reference in New Issue
Block a user