// Package asynqqueue provides Asynq-backed implementations of the // taskqueue.Producer and taskqueue.Consumer interfaces. // // Architecture: // - Producer: dual-writes — creates a PocketBase record for audit/UI, then // enqueues an Asynq job so the runner picks it up immediately (sub-ms). // - Consumer: thin wrapper used only for result write-back (FinishAudioTask, // FinishScrapeTask, FailTask). ClaimNext*/Heartbeat/Reap are no-ops because // Asynq owns those responsibilities. // - Handlers: asynq.HandlerFunc wrappers that decode job payloads and invoke // the existing runner logic (runScrapeTask / runAudioTask). // // Fallback: when REDIS_ADDR is empty the caller should use the plain // storage.Store (PocketBase-polling) implementation unchanged. package asynqqueue // Queue names — keep all jobs on the default queue for now. // Add separate queues (e.g. "audio", "scrape") later if you need priority. const QueueDefault = "default" // Task type constants used for Asynq routing. const ( TypeAudioGenerate = "audio:generate" TypeScrapeBook = "scrape:book" TypeScrapeCatalogue = "scrape:catalogue" ) // AudioPayload is the Asynq job payload for audio generation tasks. type AudioPayload struct { // PBTaskID is the PocketBase record ID created before enqueueing. // The handler uses it to write results back via Consumer.FinishAudioTask. PBTaskID string `json:"pb_task_id"` Slug string `json:"slug"` Chapter int `json:"chapter"` Voice string `json:"voice"` } // ScrapePayload is the Asynq job payload for scrape tasks. type ScrapePayload struct { // PBTaskID is the PocketBase record ID created before enqueueing. PBTaskID string `json:"pb_task_id"` Kind string `json:"kind"` // "catalogue", "book", or "book_range" TargetURL string `json:"target_url"` // empty for catalogue tasks FromChapter int `json:"from_chapter"` // 0 unless Kind=="book_range" ToChapter int `json:"to_chapter"` // 0 unless Kind=="book_range" }