From 1a2bf580cd4fe0f2d78aa8599d98d130a4ed5c48 Mon Sep 17 00:00:00 2001 From: root Date: Thu, 9 Apr 2026 15:55:06 +0500 Subject: [PATCH] =?UTF-8?q?v2.6.51:=20fix=20PDF=20import=20=E2=80=94=20rai?= =?UTF-8?q?se=20UI=20body=20limit,=20wire=20real=20analyze?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - docker-compose.yml: BODY_SIZE_LIMIT=52428800 (50MB) on UI service — adapter-node was rejecting PDFs >512KB with 'Content-length exceeds limit' - storage/import.go: add AnalyzeFile() public function using real PDF/EPUB parsers - handlers_import.go: analyzeImportFile() now calls storage.AnalyzeFile() instead of the file-size stub, so chapter count is accurate in the preview --- backend/internal/backend/handlers_import.go | 17 +++++----- backend/internal/storage/import.go | 36 ++++++++++++++++++++- docker-compose.yml | 2 ++ 3 files changed, 46 insertions(+), 9 deletions(-) diff --git a/backend/internal/backend/handlers_import.go b/backend/internal/backend/handlers_import.go index 2c4e276..624168d 100644 --- a/backend/internal/backend/handlers_import.go +++ b/backend/internal/backend/handlers_import.go @@ -127,16 +127,17 @@ func (s *Server) handleAdminImport(w http.ResponseWriter, r *http.Request) { }) } -// analyzeImportFile does a quick scan of the file to count chapters. -// This is a placeholder - real implementation would parse PDF/EPUB properly. +// analyzeImportFile parses the file to count chapters and extract preview lines. func analyzeImportFile(data []byte, fileType string) *importPreview { - // TODO: Implement actual PDF/EPUB parsing to count chapters - // For now, estimate based on file size - preview := &importPreview{ - Chapters: estimateChapters(data, fileType), - FirstLines: []string{}, + count, firstLines, err := storage.AnalyzeFile(data, fileType) + if err != nil || count == 0 { + // Fall back to rough size estimate so the UI still shows something + count = estimateChapters(data, fileType) + } + return &importPreview{ + Chapters: count, + FirstLines: firstLines, } - return preview } func estimateChapters(data []byte, fileType string) int { diff --git a/backend/internal/storage/import.go b/backend/internal/storage/import.go index cefe1b6..73244ce 100644 --- a/backend/internal/storage/import.go +++ b/backend/internal/storage/import.go @@ -54,7 +54,41 @@ func (i *importer) Import(ctx context.Context, objectKey, fileType string) ([]bo return parseEPUB(data) } -// ── PDF parsing ─────────────────────────────────────────────────────────────── +// AnalyzeFile parses the given PDF or EPUB data and returns the detected +// chapter count and up to 3 preview lines (first non-empty line of each of +// the first 3 chapters). It is used by the analyze-only endpoint so users +// can preview chapter count before committing the import. +func AnalyzeFile(data []byte, fileType string) (chapterCount int, firstLines []string, err error) { + var chapters []bookstore.Chapter + switch fileType { + case "pdf": + chapters, err = parsePDF(data) + case "epub": + chapters, err = parseEPUB(data) + default: + return 0, nil, fmt.Errorf("unsupported file type: %s", fileType) + } + if err != nil { + return 0, nil, err + } + chapterCount = len(chapters) + for i, ch := range chapters { + if i >= 3 { + break + } + line := strings.TrimSpace(ch.Content) + if nl := strings.Index(line, "\n"); nl > 0 { + line = line[:nl] + } + if len(line) > 120 { + line = line[:120] + "…" + } + firstLines = append(firstLines, line) + } + return chapterCount, firstLines, nil +} + + func parsePDF(data []byte) ([]bookstore.Chapter, error) { r, err := pdf.NewReader(bytes.NewReader(data), int64(len(data))) diff --git a/docker-compose.yml b/docker-compose.yml index 96cb47a..59f451c 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -307,6 +307,8 @@ services: # OpenTelemetry tracing OTEL_EXPORTER_OTLP_ENDPOINT: "${OTEL_EXPORTER_OTLP_ENDPOINT}" OTEL_SERVICE_NAME: "ui" + # Allow large PDF/EPUB uploads (adapter-node default is 512KB) + BODY_SIZE_LIMIT: "52428800" # OAuth2 providers GOOGLE_CLIENT_ID: "${GOOGLE_CLIENT_ID}" GOOGLE_CLIENT_SECRET: "${GOOGLE_CLIENT_SECRET}"