Phase 2b — durable Postgres queue + worker (gated on DATABASE_URL)
Some checks failed
Docker Build / build-and-push-image (push) Has been cancelled
Some checks failed
Docker Build / build-and-push-image (push) Has been cancelled
Adds the async dispatch infrastructure : - Postgres pool + embedded migration (CREATE TABLE/INDEX IF NOT EXISTS gateway_jobs). Auto-applied at boot. lib/pq driver (matches webapp convention). - queue.go : Enqueue (idempotent on UNIQUE(bot_slug, update_id) — handles Telegram redelivery), Pop with FOR UPDATE SKIP LOCKED, MarkDone, MarkFailed with exponential backoff (30s → 2m → 10m → 1h → dead at 5). - worker.go : goroutine that drains the queue, dispatches via the same Handler interface as sync, schedules retries on failure, notifies the user once when a job goes to dead. - BotConfig gains `async: bool`. Registry refuses bots with async=true if DATABASE_URL is unset (queue=nil). - Server : when bot.Async, the webhook ack is immediate ; the update payload is enqueued for the worker. When DATABASE_URL is unset (current default), queue/worker stay disabled and only sync handlers (echo, http, auth) work — no breaking change to the running cluster. Refs ~/.claude/plans/pour-les-notifications-on-inherited-seal.md § Phase 2.
This commit is contained in:
41
handlers.go
41
handlers.go
@@ -11,23 +11,30 @@ type Handler interface {
|
||||
}
|
||||
|
||||
type Bot struct {
|
||||
Slug string
|
||||
Token string
|
||||
Secret string
|
||||
RequireAuth bool
|
||||
Handler Handler
|
||||
Slug string
|
||||
Token string
|
||||
Secret string
|
||||
HandlerLabel string // raw 'handler:' value (echo|auth|http|…) — used by queue rows for diagnostics
|
||||
RequireAuth bool
|
||||
Async bool
|
||||
Handler Handler
|
||||
}
|
||||
|
||||
// HandlerType returns the textual handler label for queue rows / logs.
|
||||
func (b Bot) HandlerType() string { return b.HandlerLabel }
|
||||
|
||||
type Registry struct {
|
||||
bots map[string]Bot
|
||||
}
|
||||
|
||||
// NewRegistry builds the bot routing map from the parsed YAML config + the
|
||||
// per-bot secrets (already merged into BotConfig from env). It receives the
|
||||
// shared TelegramClient and Auth so per-handler instances can use them.
|
||||
// `auth` may be nil (no AUTH_SECRET set) ; in that case any bot configured
|
||||
// with `handler: auth` or `requireAuth: true` is a fatal config error.
|
||||
func NewRegistry(cfg *Config, tg *TelegramClient, auth *Auth) (*Registry, error) {
|
||||
// shared TelegramClient, Auth and Queue so per-handler instances can use
|
||||
// them. `auth` may be nil (no AUTH_SECRET set) ; in that case any bot
|
||||
// configured with `handler: auth` or `requireAuth: true` is a fatal config
|
||||
// error. `queue` may be nil (no DATABASE_URL set) ; in that case any bot
|
||||
// with `async: true` is a fatal config error.
|
||||
func NewRegistry(cfg *Config, tg *TelegramClient, auth *Auth, queue Queue) (*Registry, error) {
|
||||
if len(cfg.Bots) == 0 {
|
||||
return nil, fmt.Errorf("no bots configured")
|
||||
}
|
||||
@@ -64,6 +71,10 @@ func NewRegistry(cfg *Config, tg *TelegramClient, auth *Auth) (*Registry, error)
|
||||
return nil, fmt.Errorf("bot %s has requireAuth: true (default) but AUTH_SECRET is unset; either set AUTH_SECRET or add `requireAuth: false` to the bot config", slug)
|
||||
}
|
||||
|
||||
if b.Async && queue == nil {
|
||||
return nil, fmt.Errorf("bot %s has async: true but DATABASE_URL is unset (no queue available)", slug)
|
||||
}
|
||||
|
||||
var h Handler
|
||||
switch b.Handler {
|
||||
case "echo":
|
||||
@@ -89,11 +100,13 @@ func NewRegistry(cfg *Config, tg *TelegramClient, auth *Auth) (*Registry, error)
|
||||
}
|
||||
|
||||
bots[slug] = Bot{
|
||||
Slug: slug,
|
||||
Token: token,
|
||||
Secret: secret,
|
||||
RequireAuth: requireAuth,
|
||||
Handler: h,
|
||||
Slug: slug,
|
||||
Token: token,
|
||||
Secret: secret,
|
||||
HandlerLabel: b.Handler,
|
||||
RequireAuth: requireAuth,
|
||||
Async: b.Async,
|
||||
Handler: h,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user