- Add ImportTask/ImportResult types to domain.go - Add TypeImportBook to asynqqueue for task routing - Add CreateImportTask to producer and storage layers - Add ClaimNextImportTask/FinishImportTask to Consumer - Add import task handling to runner (polling + Asynq handler) - Add BookImporter interface to bookstore for PDF/EPUB parsing - Add backend API endpoints: POST/GET /api/admin/import - Add SvelteKit UI at /admin/import with task list - Add nav link in admin layout Note: PDF/EPUB parsing is a placeholder - needs external library integration.
57 lines
2.3 KiB
Go
57 lines
2.3 KiB
Go
// 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"
|
|
TypeImportBook = "import:book"
|
|
)
|
|
|
|
// 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"
|
|
}
|
|
|
|
// ImportPayload is the Asynq job payload for PDF/EPUB import tasks.
|
|
type ImportPayload struct {
|
|
PBTaskID string `json:"pb_task_id"`
|
|
Slug string `json:"slug"`
|
|
Title string `json:"title"`
|
|
FileType string `json:"file_type"` // "pdf" or "epub"
|
|
ObjectKey string `json:"object_key"` // MinIO path to uploaded file
|
|
}
|