diff --git a/backend/src/main/java/com/storycove/service/EPUBImportService.java b/backend/src/main/java/com/storycove/service/EPUBImportService.java
index e9c34d0..4a0d508 100644
--- a/backend/src/main/java/com/storycove/service/EPUBImportService.java
+++ b/backend/src/main/java/com/storycove/service/EPUBImportService.java
@@ -26,7 +26,9 @@ import org.springframework.web.multipart.MultipartFile;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
+import java.util.HashMap;
import java.util.List;
+import java.util.Map;
import java.util.Optional;
@Service
@@ -41,15 +43,17 @@ public class EPUBImportService {
private final ReadingPositionRepository readingPositionRepository;
private final HtmlSanitizationService sanitizationService;
private final ImageService imageService;
-
+ private final LibraryService libraryService;
+
@Autowired
public EPUBImportService(StoryService storyService,
- AuthorService authorService,
+ AuthorService authorService,
SeriesService seriesService,
TagService tagService,
ReadingPositionRepository readingPositionRepository,
HtmlSanitizationService sanitizationService,
- ImageService imageService) {
+ ImageService imageService,
+ LibraryService libraryService) {
this.storyService = storyService;
this.authorService = authorService;
this.seriesService = seriesService;
@@ -57,6 +61,7 @@ public class EPUBImportService {
this.readingPositionRepository = readingPositionRepository;
this.sanitizationService = sanitizationService;
this.imageService = imageService;
+ this.libraryService = libraryService;
}
public EPUBImportResponse importEPUB(EPUBImportRequest request) {
@@ -81,22 +86,37 @@ public class EPUBImportService {
Story savedStory = storyService.create(story);
log.info("Story saved successfully with ID: {}", savedStory.getId());
- // Process embedded images if content contains any
- String originalContent = story.getContentHtml();
- if (originalContent != null && originalContent.contains("
byHref = new HashMap<>();
+ Map byFilename = new HashMap<>();
+ for (Resource resource : book.getResources().getAll()) {
+ if (resource.getMediaType() != null &&
+ resource.getMediaType().toString().startsWith("image/")) {
+ String href = resource.getHref();
+ if (href != null) {
+ byHref.put(href, resource);
+ String filename = href.contains("/") ? href.substring(href.lastIndexOf('/') + 1) : href;
+ byFilename.putIfAbsent(filename, resource);
+ }
+ }
+ }
+
+ if (byHref.isEmpty()) {
+ log.debug("No image resources found in EPUB for story: {}", storyId);
+ return htmlContent;
+ }
+
+ String currentLibraryId = libraryService.getCurrentLibraryId();
+ if (currentLibraryId == null || currentLibraryId.trim().isEmpty()) {
+ currentLibraryId = "default";
+ }
+
+ org.jsoup.nodes.Document doc = Jsoup.parse(htmlContent);
+ for (org.jsoup.nodes.Element img : doc.select("img[src]")) {
+ String src = img.attr("src");
+
+ // Skip already-resolved or external URLs
+ if (src.startsWith("http://") || src.startsWith("https://") ||
+ src.startsWith("data:") || src.startsWith("/api/")) {
+ continue;
+ }
+
+ Resource resource = resolveEpubResource(src, byHref, byFilename);
+ if (resource == null) {
+ log.warn("Could not find EPUB resource for image src: {}", src);
+ continue;
+ }
+
+ try {
+ byte[] imageData = resource.getData();
+ if (imageData == null || imageData.length == 0) {
+ log.warn("EPUB image resource has no data for src: {}", src);
+ continue;
+ }
+
+ String mediaType = resource.getMediaType() != null ?
+ resource.getMediaType().toString() : "image/jpeg";
+ String extension = getExtensionFromMediaType(mediaType);
+ String filename = "epub-img-" + System.currentTimeMillis() + "-" +
+ (int) (Math.random() * 100000) + "." + extension;
+
+ MultipartFile imageFile = new EPUBCoverMultipartFile(imageData, filename, mediaType);
+ String imagePath = imageService.uploadImage(imageFile, ImageService.ImageType.CONTENT);
+ String imageUrl = "/api/files/images/" + currentLibraryId + "/" + imagePath;
+
+ img.attr("src", imageUrl);
+ log.debug("Resolved EPUB image: {} -> {}", src, imageUrl);
+
+ } catch (Exception e) {
+ log.error("Failed to save EPUB image {}: {}", src, e.getMessage(), e);
+ }
+ }
+
+ return doc.body().html();
+ }
+
+ /**
+ * Tries to match a relative EPUB src path against the resource maps.
+ * Resolution order: exact href match → strip leading ../ segments → filename only.
+ */
+ private Resource resolveEpubResource(String src, Map byHref, Map byFilename) {
+ if (byHref.containsKey(src)) {
+ return byHref.get(src);
+ }
+
+ // Strip leading ../ and ./ navigation to get a plain relative path
+ String normalized = src;
+ while (normalized.startsWith("../")) {
+ normalized = normalized.substring(3);
+ }
+ if (normalized.startsWith("./")) {
+ normalized = normalized.substring(2);
+ }
+
+ if (byHref.containsKey(normalized)) {
+ return byHref.get(normalized);
+ }
+
+ // Fall back to filename-only match
+ String filename = normalized.contains("/") ?
+ normalized.substring(normalized.lastIndexOf('/') + 1) : normalized;
+ return byFilename.get(filename);
+ }
+
private String extractAndSaveCoverImage(Book book) {
try {
Resource coverResource = book.getCoverImage();
diff --git a/backend/src/test/java/com/storycove/service/EPUBImportServiceTest.java b/backend/src/test/java/com/storycove/service/EPUBImportServiceTest.java
index e60b1d6..2f020e8 100644
--- a/backend/src/test/java/com/storycove/service/EPUBImportServiceTest.java
+++ b/backend/src/test/java/com/storycove/service/EPUBImportServiceTest.java
@@ -53,6 +53,9 @@ class EPUBImportServiceTest {
@Mock
private ImageService imageService;
+ @Mock
+ private LibraryService libraryService;
+
@InjectMocks
private EPUBImportService epubImportService;