From ae08382b8155e22fb067fb2c8f6be1b6bb1cd60d Mon Sep 17 00:00:00 2001 From: root Date: Thu, 9 Apr 2026 19:05:11 +0500 Subject: [PATCH] fix(import): wire ImportFileStore to bypass Asynq type assertion; add pb-init collections - Add ImportFileStore interface to bookstore package - Add ImportFileStore field to backend.Dependencies - Wire ImportFileStore: store in cmd/backend/main.go - handlers_import.go: use s.deps.ImportFileStore.PutImportFile instead of broken s.deps.Producer.(*storage.Store) type assertion (fails when Asynq active) - pb-init-v3.sh: add import_tasks and notifications collection definitions --- backend/cmd/backend/main.go | 1 + backend/internal/backend/handlers_import.go | 5 ++-- backend/internal/backend/server.go | 3 ++ backend/internal/bookstore/bookstore.go | 7 +++++ scripts/pb-init-v3.sh | 33 +++++++++++++++++++++ 5 files changed, 46 insertions(+), 3 deletions(-) diff --git a/backend/cmd/backend/main.go b/backend/cmd/backend/main.go index c2b9290..9c19483 100644 --- a/backend/cmd/backend/main.go +++ b/backend/cmd/backend/main.go @@ -189,6 +189,7 @@ func run() error { ChapterImageStore: store, Producer: producer, TaskReader: store, + ImportFileStore: store, SearchIndex: searchIndex, Kokoro: kokoroClient, PocketTTS: pocketTTSClient, diff --git a/backend/internal/backend/handlers_import.go b/backend/internal/backend/handlers_import.go index 71255e1..aa84e29 100644 --- a/backend/internal/backend/handlers_import.go +++ b/backend/internal/backend/handlers_import.go @@ -98,12 +98,11 @@ func (s *Server) handleAdminImport(w http.ResponseWriter, r *http.Request) { // Upload to MinIO for actual import objectKey = fmt.Sprintf("imports/%d_%s", time.Now().Unix(), header.Filename) - store, ok := s.deps.Producer.(*storage.Store) - if !ok { + if s.deps.ImportFileStore == nil { jsonError(w, http.StatusInternalServerError, "storage not available") return } - if err := store.PutImportFile(r.Context(), objectKey, data); err != nil { + if err := s.deps.ImportFileStore.PutImportFile(r.Context(), objectKey, data); err != nil { jsonError(w, http.StatusInternalServerError, "upload file: "+err.Error()) return } diff --git a/backend/internal/backend/server.go b/backend/internal/backend/server.go index 2b1e81d..496a196 100644 --- a/backend/internal/backend/server.go +++ b/backend/internal/backend/server.go @@ -85,6 +85,9 @@ type Dependencies struct { // BookWriter writes book metadata and chapter refs to PocketBase. // Used by admin text-gen apply endpoints. BookWriter bookstore.BookWriter + // ImportFileStore uploads raw PDF/EPUB files to MinIO for the runner to process. + // Always wired to the concrete *storage.Store (not the Asynq wrapper). + ImportFileStore bookstore.ImportFileStore // AIJobStore tracks long-running AI generation jobs in PocketBase. // If nil, job persistence is disabled (jobs still run but are not recorded). AIJobStore bookstore.AIJobStore diff --git a/backend/internal/bookstore/bookstore.go b/backend/internal/bookstore/bookstore.go index d1e63bf..3d49bcc 100644 --- a/backend/internal/bookstore/bookstore.go +++ b/backend/internal/bookstore/bookstore.go @@ -215,3 +215,10 @@ type BookImporter interface { // Returns the extracted chapters or an error. Import(ctx context.Context, objectKey, fileType string) ([]Chapter, error) } + +// ImportFileStore uploads raw import files to object storage. +// Kept separate from BookImporter so the HTTP handler can upload the file +// without a concrete type assertion, regardless of which Producer is wired. +type ImportFileStore interface { + PutImportFile(ctx context.Context, objectKey string, data []byte) error +} diff --git a/scripts/pb-init-v3.sh b/scripts/pb-init-v3.sh index acf9871..308fd1d 100755 --- a/scripts/pb-init-v3.sh +++ b/scripts/pb-init-v3.sh @@ -299,6 +299,39 @@ create "translation_jobs" '{ {"name":"heartbeat_at", "type":"date"} ]}' +create "import_tasks" '{ + "name":"import_tasks","type":"base","fields":[ + {"name":"slug", "type":"text", "required":true}, + {"name":"title", "type":"text", "required":true}, + {"name":"file_name", "type":"text"}, + {"name":"file_type", "type":"text"}, + {"name":"object_key", "type":"text"}, + {"name":"author", "type":"text"}, + {"name":"cover_url", "type":"text"}, + {"name":"genres", "type":"text"}, + {"name":"summary", "type":"text"}, + {"name":"book_status", "type":"text"}, + {"name":"worker_id", "type":"text"}, + {"name":"initiator_user_id", "type":"text"}, + {"name":"status", "type":"text", "required":true}, + {"name":"chapters_done", "type":"number"}, + {"name":"chapters_total", "type":"number"}, + {"name":"error_message", "type":"text"}, + {"name":"started", "type":"date"}, + {"name":"finished", "type":"date"}, + {"name":"heartbeat_at", "type":"date"} + ]}' + +create "notifications" '{ + "name":"notifications","type":"base","fields":[ + {"name":"user_id", "type":"text","required":true}, + {"name":"title", "type":"text","required":true}, + {"name":"message", "type":"text"}, + {"name":"link", "type":"text"}, + {"name":"read", "type":"bool"}, + {"name":"created", "type":"date"} + ]}' + create "ai_jobs" '{ "name":"ai_jobs","type":"base","fields":[ {"name":"kind", "type":"text", "required":true},