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
This commit is contained in:
@@ -189,6 +189,7 @@ func run() error {
|
|||||||
ChapterImageStore: store,
|
ChapterImageStore: store,
|
||||||
Producer: producer,
|
Producer: producer,
|
||||||
TaskReader: store,
|
TaskReader: store,
|
||||||
|
ImportFileStore: store,
|
||||||
SearchIndex: searchIndex,
|
SearchIndex: searchIndex,
|
||||||
Kokoro: kokoroClient,
|
Kokoro: kokoroClient,
|
||||||
PocketTTS: pocketTTSClient,
|
PocketTTS: pocketTTSClient,
|
||||||
|
|||||||
@@ -98,12 +98,11 @@ func (s *Server) handleAdminImport(w http.ResponseWriter, r *http.Request) {
|
|||||||
|
|
||||||
// Upload to MinIO for actual import
|
// Upload to MinIO for actual import
|
||||||
objectKey = fmt.Sprintf("imports/%d_%s", time.Now().Unix(), header.Filename)
|
objectKey = fmt.Sprintf("imports/%d_%s", time.Now().Unix(), header.Filename)
|
||||||
store, ok := s.deps.Producer.(*storage.Store)
|
if s.deps.ImportFileStore == nil {
|
||||||
if !ok {
|
|
||||||
jsonError(w, http.StatusInternalServerError, "storage not available")
|
jsonError(w, http.StatusInternalServerError, "storage not available")
|
||||||
return
|
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())
|
jsonError(w, http.StatusInternalServerError, "upload file: "+err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -85,6 +85,9 @@ type Dependencies struct {
|
|||||||
// BookWriter writes book metadata and chapter refs to PocketBase.
|
// BookWriter writes book metadata and chapter refs to PocketBase.
|
||||||
// Used by admin text-gen apply endpoints.
|
// Used by admin text-gen apply endpoints.
|
||||||
BookWriter bookstore.BookWriter
|
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.
|
// AIJobStore tracks long-running AI generation jobs in PocketBase.
|
||||||
// If nil, job persistence is disabled (jobs still run but are not recorded).
|
// If nil, job persistence is disabled (jobs still run but are not recorded).
|
||||||
AIJobStore bookstore.AIJobStore
|
AIJobStore bookstore.AIJobStore
|
||||||
|
|||||||
@@ -215,3 +215,10 @@ type BookImporter interface {
|
|||||||
// Returns the extracted chapters or an error.
|
// Returns the extracted chapters or an error.
|
||||||
Import(ctx context.Context, objectKey, fileType string) ([]Chapter, 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
|
||||||
|
}
|
||||||
|
|||||||
@@ -299,6 +299,39 @@ create "translation_jobs" '{
|
|||||||
{"name":"heartbeat_at", "type":"date"}
|
{"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" '{
|
create "ai_jobs" '{
|
||||||
"name":"ai_jobs","type":"base","fields":[
|
"name":"ai_jobs","type":"base","fields":[
|
||||||
{"name":"kind", "type":"text", "required":true},
|
{"name":"kind", "type":"text", "required":true},
|
||||||
|
|||||||
Reference in New Issue
Block a user