bugfixes, and logging cleanup

This commit is contained in:
Stefan Hardegger
2025-09-21 14:51:21 +02:00
parent 58bb7f8229
commit 0101c0ca2c
11 changed files with 271 additions and 120 deletions

View File

@@ -2,6 +2,8 @@ package com.storycove.controller;
import com.storycove.service.ImageService;
import com.storycove.service.LibraryService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.io.FileSystemResource;
import org.springframework.core.io.Resource;
import org.springframework.http.HttpHeaders;
@@ -21,6 +23,7 @@ import java.util.Map;
@RestController
@RequestMapping("/api/files")
public class FileController {
private static final Logger log = LoggerFactory.getLogger(FileController.class);
private final ImageService imageService;
private final LibraryService libraryService;
@@ -32,7 +35,7 @@ public class FileController {
private String getCurrentLibraryId() {
String libraryId = libraryService.getCurrentLibraryId();
System.out.println("FileController - Current Library ID: " + libraryId);
log.debug("FileController - Current Library ID: {}", libraryId);
return libraryId != null ? libraryId : "default";
}
@@ -48,7 +51,7 @@ public class FileController {
String imageUrl = "/api/files/images/" + currentLibraryId + "/" + imagePath;
response.put("url", imageUrl);
System.out.println("Upload response - path: " + imagePath + ", url: " + imageUrl);
log.debug("Upload response - path: {}, url: {}", imagePath, imageUrl);
return ResponseEntity.ok(response);
} catch (IllegalArgumentException e) {

View File

@@ -16,6 +16,8 @@ import nl.siegmann.epublib.epub.EpubReader;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@@ -30,6 +32,7 @@ import java.util.Optional;
@Service
@Transactional
public class EPUBImportService {
private static final Logger log = LoggerFactory.getLogger(EPUBImportService.class);
private final StoryService storyService;
private final AuthorService authorService;
@@ -87,12 +90,12 @@ public class EPUBImportService {
savedStory = storyService.update(savedStory.getId(), savedStory);
// Log the image processing results
System.out.println("EPUB Import - Image processing completed for story " + savedStory.getId() +
". Downloaded " + imageResult.getDownloadedImages().size() + " images.");
log.debug("EPUB Import - Image processing completed for story {}. Downloaded {} images.",
savedStory.getId(), imageResult.getDownloadedImages().size());
if (imageResult.hasWarnings()) {
System.out.println("EPUB Import - Image processing warnings: " +
String.join(", ", imageResult.getWarnings()));
log.debug("EPUB Import - Image processing warnings: {}",
String.join(", ", imageResult.getWarnings()));
}
}
} catch (Exception e) {
@@ -282,7 +285,7 @@ public class EPUBImportService {
if (language != null && !language.trim().isEmpty()) {
// Store as metadata in story description if needed
// For now, we'll just log it for potential future use
System.out.println("EPUB Language: " + language);
log.debug("EPUB Language: {}", language);
}
// Extract publisher information
@@ -290,14 +293,14 @@ public class EPUBImportService {
if (publishers != null && !publishers.isEmpty()) {
String publisher = publishers.get(0);
// Could append to description or store separately in future
System.out.println("EPUB Publisher: " + publisher);
log.debug("EPUB Publisher: {}", publisher);
}
// Extract publication date
List<nl.siegmann.epublib.domain.Date> dates = metadata.getDates();
if (dates != null && !dates.isEmpty()) {
for (nl.siegmann.epublib.domain.Date date : dates) {
System.out.println("EPUB Date (" + date.getEvent() + "): " + date.getValue());
log.debug("EPUB Date ({}): {}", date.getEvent(), date.getValue());
}
}
@@ -305,7 +308,7 @@ public class EPUBImportService {
List<nl.siegmann.epublib.domain.Identifier> identifiers = metadata.getIdentifiers();
if (identifiers != null && !identifiers.isEmpty()) {
for (nl.siegmann.epublib.domain.Identifier identifier : identifiers) {
System.out.println("EPUB Identifier (" + identifier.getScheme() + "): " + identifier.getValue());
log.debug("EPUB Identifier ({}): {}", identifier.getScheme(), identifier.getValue());
}
}
}

View File

@@ -248,14 +248,14 @@ public class ImageService {
* Process HTML content and download all referenced images, replacing URLs with local paths
*/
public ContentImageProcessingResult processContentImages(String htmlContent, UUID storyId) {
logger.info("Processing content images for story: {}, content length: {}", storyId,
logger.debug("Processing content images for story: {}, content length: {}", storyId,
htmlContent != null ? htmlContent.length() : 0);
List<String> warnings = new ArrayList<>();
List<String> downloadedImages = new ArrayList<>();
if (htmlContent == null || htmlContent.trim().isEmpty()) {
logger.info("No content to process for story: {}", storyId);
logger.debug("No content to process for story: {}", storyId);
return new ContentImageProcessingResult(htmlContent, warnings, downloadedImages);
}
@@ -273,18 +273,18 @@ public class ImageService {
String imageUrl = matcher.group(1);
imageCount++;
logger.info("Found image #{}: {} in tag: {}", imageCount, imageUrl, fullImgTag);
logger.debug("Found image #{}: {} in tag: {}", imageCount, imageUrl, fullImgTag);
try {
// Skip if it's already a local path or data URL
if (imageUrl.startsWith("/") || imageUrl.startsWith("data:")) {
logger.info("Skipping local/data URL: {}", imageUrl);
logger.debug("Skipping local/data URL: {}", imageUrl);
matcher.appendReplacement(processedContent, Matcher.quoteReplacement(fullImgTag));
continue;
}
externalImageCount++;
logger.info("Processing external image #{}: {}", externalImageCount, imageUrl);
logger.debug("Processing external image #{}: {}", externalImageCount, imageUrl);
// Download and store the image
String localPath = downloadImageFromUrl(imageUrl, storyId);
@@ -292,7 +292,7 @@ public class ImageService {
// Generate local URL
String localUrl = getLocalImageUrl(storyId, localPath);
logger.info("Downloaded image: {} -> {}", imageUrl, localUrl);
logger.debug("Downloaded image: {} -> {}", imageUrl, localUrl);
// Replace the src attribute with the local path - handle both single and double quotes
String newImgTag = fullImgTag
@@ -305,7 +305,7 @@ public class ImageService {
newImgTag = fullImgTag.replaceAll("src\\s*=\\s*[\"']?" + Pattern.quote(imageUrl) + "[\"']?", "src=\"" + localUrl + "\"");
}
logger.info("Replaced img tag: {} -> {}", fullImgTag, newImgTag);
logger.debug("Replaced img tag: {} -> {}", fullImgTag, newImgTag);
matcher.appendReplacement(processedContent, Matcher.quoteReplacement(newImgTag));
} catch (Exception e) {
@@ -388,7 +388,7 @@ public class ImageService {
return "/api/files/images/default/" + imagePath;
}
String localUrl = "/api/files/images/" + currentLibraryId + "/" + imagePath;
logger.info("Generated local image URL: {} for story: {}", localUrl, storyId);
logger.debug("Generated local image URL: {} for story: {}", localUrl, storyId);
return localUrl;
}
@@ -437,20 +437,20 @@ public class ImageService {
int foldersToDelete = 0;
// Step 1: Collect all image references from all story content
logger.info("Scanning all story content for image references...");
logger.debug("Scanning all story content for image references...");
referencedImages = collectAllImageReferences();
logger.info("Found {} unique image references in story content", referencedImages.size());
logger.debug("Found {} unique image references in story content", referencedImages.size());
try {
// Step 2: Scan the content images directory
Path contentImagesDir = Paths.get(getUploadDir(), ImageType.CONTENT.getDirectory());
if (!Files.exists(contentImagesDir)) {
logger.info("Content images directory does not exist: {}", contentImagesDir);
logger.debug("Content images directory does not exist: {}", contentImagesDir);
return new ContentImageCleanupResult(orphanedImages, 0, 0, referencedImages.size(), errors, dryRun);
}
logger.info("Scanning content images directory: {}", contentImagesDir);
logger.debug("Scanning content images directory: {}", contentImagesDir);
// Walk through all story directories
Files.walk(contentImagesDir, 2)
@@ -465,7 +465,7 @@ public class ImageService {
boolean storyExists = storyService.findByIdOptional(UUID.fromString(storyId)).isPresent();
if (!storyExists) {
logger.info("Found orphaned story directory (story deleted): {}", storyId);
logger.debug("Found orphaned story directory (story deleted): {}", storyId);
// Mark entire directory for deletion
try {
Files.walk(storyDir)
@@ -535,7 +535,7 @@ public class ImageService {
// Step 3: Delete orphaned files if not dry run
if (!dryRun && !orphanedImages.isEmpty()) {
logger.info("Deleting {} orphaned images...", orphanedImages.size());
logger.debug("Deleting {} orphaned images...", orphanedImages.size());
Set<Path> directoriesToCheck = new HashSet<>();
@@ -557,7 +557,7 @@ public class ImageService {
try {
if (Files.exists(dir) && isDirEmpty(dir)) {
Files.delete(dir);
logger.info("Deleted empty story directory: {}", dir);
logger.debug("Deleted empty story directory: {}", dir);
}
} catch (IOException e) {
errors.add("Failed to delete empty directory " + dir + ": " + e.getMessage());

View File

@@ -144,9 +144,9 @@ public class LibraryService implements ApplicationContextAware {
String previousLibraryId = currentLibraryId;
if (libraryId.equals(currentLibraryId) && forceReindex) {
logger.info("Forcing reindex for current library: {} ({})", library.getName(), libraryId);
logger.debug("Forcing reindex for current library: {} ({})", library.getName(), libraryId);
} else {
logger.info("Switching to library: {} ({})", library.getName(), libraryId);
logger.debug("Switching to library: {} ({})", library.getName(), libraryId);
}
// Close current resources
@@ -155,14 +155,14 @@ public class LibraryService implements ApplicationContextAware {
// Set new active library (datasource routing handled by SmartRoutingDataSource)
currentLibraryId = libraryId;
// OpenSearch indexes are global - no per-library initialization needed
logger.info("Library switched to OpenSearch mode for library: {}", libraryId);
logger.debug("Library switched to OpenSearch mode for library: {}", libraryId);
logger.info("Successfully switched to library: {}", library.getName());
// Perform complete reindex AFTER library switch is fully complete
// This ensures database routing is properly established
if (forceReindex || !libraryId.equals(previousLibraryId)) {
logger.info("Starting post-switch OpenSearch reindex for library: {}", libraryId);
logger.debug("Starting post-switch OpenSearch reindex for library: {}", libraryId);
// Run reindex asynchronously to avoid blocking authentication response
// and allow time for database routing to fully stabilize
@@ -171,7 +171,7 @@ public class LibraryService implements ApplicationContextAware {
try {
// Give routing time to stabilize
Thread.sleep(500);
logger.info("Starting async OpenSearch reindex for library: {}", finalLibraryId);
logger.debug("Starting async OpenSearch reindex for library: {}", finalLibraryId);
SearchServiceAdapter searchService = applicationContext.getBean(SearchServiceAdapter.class);
// Get all stories and authors for reindexing
@@ -342,10 +342,10 @@ public class LibraryService implements ApplicationContextAware {
library.setInitialized((Boolean) data.getOrDefault("initialized", false));
libraries.put(id, library);
logger.info("Loaded library: {} ({})", library.getName(), id);
logger.debug("Loaded library: {} ({})", library.getName(), id);
}
} else {
logger.info("No libraries configuration file found, will create default");
logger.debug("No libraries configuration file found, will create default");
}
} catch (IOException e) {
logger.error("Failed to load libraries configuration", e);
@@ -411,7 +411,7 @@ public class LibraryService implements ApplicationContextAware {
String json = objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(config);
Files.writeString(Paths.get(LIBRARIES_CONFIG_PATH), json);
logger.info("Saved libraries configuration");
logger.debug("Saved libraries configuration");
} catch (IOException e) {
logger.error("Failed to save libraries configuration", e);
}
@@ -419,7 +419,7 @@ public class LibraryService implements ApplicationContextAware {
private DataSource createDataSource(String dbName) {
String url = baseDbUrl.replaceAll("/[^/]*$", "/" + dbName);
logger.info("Creating DataSource for: {}", url);
logger.debug("Creating DataSource for: {}", url);
// First, ensure the database exists
ensureDatabaseExists(dbName);
@@ -459,7 +459,7 @@ public class LibraryService implements ApplicationContextAware {
preparedStatement.setString(1, dbName);
try (var resultSet = preparedStatement.executeQuery()) {
if (resultSet.next()) {
logger.info("Database {} already exists", dbName);
logger.debug("Database {} already exists", dbName);
return; // Database exists, nothing to do
}
}
@@ -488,7 +488,7 @@ public class LibraryService implements ApplicationContextAware {
}
private void initializeNewDatabaseSchema(String dbName) {
logger.info("Initializing schema for new database: {}", dbName);
logger.debug("Initializing schema for new database: {}", dbName);
// Create a temporary DataSource for the new database to initialize schema
String newDbUrl = baseDbUrl.replaceAll("/[^/]*$", "/" + dbName);
@@ -505,7 +505,7 @@ public class LibraryService implements ApplicationContextAware {
// Use Hibernate to create the schema
// This mimics what Spring Boot does during startup
createSchemaUsingHibernate(tempDataSource);
logger.info("Schema initialized for database: {}", dbName);
logger.debug("Schema initialized for database: {}", dbName);
} catch (Exception e) {
logger.error("Failed to initialize schema for database {}: {}", dbName, e.getMessage());
@@ -520,7 +520,7 @@ public class LibraryService implements ApplicationContextAware {
}
try {
logger.info("Initializing resources for new library: {}", library.getName());
logger.debug("Initializing resources for new library: {}", library.getName());
// 1. Create image directory structure
initializeImageDirectories(library);
@@ -528,7 +528,7 @@ public class LibraryService implements ApplicationContextAware {
// 2. OpenSearch indexes are global and managed automatically
// No per-library initialization needed for OpenSearch
logger.info("Successfully initialized resources for library: {}", library.getName());
logger.debug("Successfully initialized resources for library: {}", library.getName());
} catch (Exception e) {
logger.error("Failed to initialize resources for library {}: {}", libraryId, e.getMessage());
@@ -544,16 +544,16 @@ public class LibraryService implements ApplicationContextAware {
if (!java.nio.file.Files.exists(libraryImagePath)) {
java.nio.file.Files.createDirectories(libraryImagePath);
logger.info("Created image directory: {}", imagePath);
logger.debug("Created image directory: {}", imagePath);
// Create subdirectories for different image types
java.nio.file.Files.createDirectories(libraryImagePath.resolve("stories"));
java.nio.file.Files.createDirectories(libraryImagePath.resolve("authors"));
java.nio.file.Files.createDirectories(libraryImagePath.resolve("collections"));
logger.info("Created image subdirectories for library: {}", library.getId());
logger.debug("Created image subdirectories for library: {}", library.getId());
} else {
logger.info("Image directory already exists: {}", imagePath);
logger.debug("Image directory already exists: {}", imagePath);
}
} catch (Exception e) {
@@ -749,7 +749,7 @@ public class LibraryService implements ApplicationContextAware {
statement.executeUpdate(sql);
}
logger.info("Successfully created all database tables and constraints");
logger.debug("Successfully created all database tables and constraints");
} catch (SQLException e) {
logger.error("Failed to create database schema", e);

View File

@@ -55,15 +55,15 @@ public class OpenSearchService {
@PostConstruct
public void initializeIndices() {
if (!isAvailable()) {
logger.info("OpenSearch client not available - skipping index initialization");
logger.debug("OpenSearch client not available - skipping index initialization");
return;
}
try {
logger.info("Initializing OpenSearch indices...");
logger.debug("Initializing OpenSearch indices...");
createStoriesIndex();
createAuthorsIndex();
logger.info("OpenSearch indices initialized successfully");
logger.debug("OpenSearch indices initialized successfully");
} catch (IOException e) {
logger.error("Failed to initialize OpenSearch indices", e);
}
@@ -80,7 +80,7 @@ public class OpenSearchService {
return;
}
logger.info("Creating stories index: {}", indexName);
logger.debug("Creating stories index: {}", indexName);
// Create index settings programmatically
IndexSettings indexSettings = IndexSettings.of(is -> is
@@ -125,7 +125,7 @@ public class OpenSearchService {
);
openSearchClient.indices().create(request);
logger.info("Created stories index successfully: {}", indexName);
logger.debug("Created stories index successfully: {}", indexName);
}
private void createAuthorsIndex() throws IOException {
@@ -135,7 +135,7 @@ public class OpenSearchService {
return;
}
logger.info("Creating authors index: {}", indexName);
logger.debug("Creating authors index: {}", indexName);
IndexSettings indexSettings = IndexSettings.of(is -> is
.numberOfShards(properties.getIndices().getDefaultShards())
@@ -159,7 +159,7 @@ public class OpenSearchService {
);
openSearchClient.indices().create(request);
logger.info("Created authors index successfully: {}", indexName);
logger.debug("Created authors index successfully: {}", indexName);
}
private boolean indexExists(String indexName) throws IOException {
@@ -293,7 +293,7 @@ public class OpenSearchService {
return;
}
logger.info("Bulk indexing {} stories", stories.size());
logger.debug("Bulk indexing {} stories", stories.size());
BulkRequest.Builder bulkBuilder = new BulkRequest.Builder();
@@ -319,7 +319,7 @@ public class OpenSearchService {
});
}
logger.info("Successfully bulk indexed {} stories", stories.size());
logger.debug("Successfully bulk indexed {} stories", stories.size());
}
public void bulkIndexAuthors(List<Author> authors) throws IOException {
@@ -333,7 +333,7 @@ public class OpenSearchService {
return;
}
logger.info("Bulk indexing {} authors", authors.size());
logger.debug("Bulk indexing {} authors", authors.size());
BulkRequest.Builder bulkBuilder = new BulkRequest.Builder();
@@ -359,7 +359,7 @@ public class OpenSearchService {
});
}
logger.info("Successfully bulk indexed {} authors", authors.size());
logger.debug("Successfully bulk indexed {} authors", authors.size());
}
// ===============================
@@ -380,27 +380,27 @@ public class OpenSearchService {
Integer minTagCount, Boolean popularOnly,
Boolean hiddenGemsOnly) {
try {
logger.info("OPENSEARCH SEARCH DEBUG:");
logger.info(" Query: '{}'", query);
logger.info(" Tags: {}", tags);
logger.info(" Author: '{}'", author);
logger.info(" Series: '{}'", series);
logger.info(" SortBy: '{}'", sortBy);
logger.info(" SortOrder: '{}'", sortOrder);
logger.info(" Page: {}, Size: {}", page, size);
logger.info(" FacetBy: {}", facetBy);
logger.info(" Advanced filters: createdAfter='{}', createdBefore='{}', lastReadAfter='{}', lastReadBefore='{}'",
logger.debug("OPENSEARCH SEARCH DEBUG:");
logger.debug(" Query: '{}'", query);
logger.debug(" Tags: {}", tags);
logger.debug(" Author: '{}'", author);
logger.debug(" Series: '{}'", series);
logger.debug(" SortBy: '{}'", sortBy);
logger.debug(" SortOrder: '{}'", sortOrder);
logger.debug(" Page: {}, Size: {}", page, size);
logger.debug(" FacetBy: {}", facetBy);
logger.debug(" Advanced filters: createdAfter='{}', createdBefore='{}', lastReadAfter='{}', lastReadBefore='{}'",
createdAfter, createdBefore, lastReadAfter, lastReadBefore);
logger.info(" Boolean filters: unratedOnly={}, readingStatus='{}', hasReadingProgress={}, hasCoverImage={}",
logger.debug(" Boolean filters: unratedOnly={}, readingStatus='{}', hasReadingProgress={}, hasCoverImage={}",
unratedOnly, readingStatus, hasReadingProgress, hasCoverImage);
logger.info(" Other filters: sourceDomain='{}', seriesFilter='{}', minTagCount={}, popularOnly={}, hiddenGemsOnly={}",
logger.debug(" Other filters: sourceDomain='{}', seriesFilter='{}', minTagCount={}, popularOnly={}, hiddenGemsOnly={}",
sourceDomain, seriesFilter, minTagCount, popularOnly, hiddenGemsOnly);
// Check index document count
try {
var countRequest = CountRequest.of(cr -> cr.index(getStoriesIndex()));
var countResponse = openSearchClient.count(countRequest);
logger.info(" Stories index document count: {}", countResponse.count());
logger.debug(" Stories index document count: {}", countResponse.count());
// Test a simple search without sorting to see if we get results
if (countResponse.count() > 0) {
@@ -410,7 +410,7 @@ public class OpenSearchService {
.query(q -> q.matchAll(ma -> ma))
);
var testResponse = openSearchClient.search(testSearch, StorySearchDto.class);
logger.info(" Test search without sorting: totalHits={}, hits.size()={}",
logger.debug(" Test search without sorting: totalHits={}, hits.size()={}",
testResponse.hits().total() != null ? testResponse.hits().total().value() : 0,
testResponse.hits().hits().size());
}
@@ -431,10 +431,10 @@ public class OpenSearchService {
String trimmedQuery = query.trim();
// Handle wildcard queries
if ("*".equals(trimmedQuery) || "**".equals(trimmedQuery)) {
logger.info(" Using matchAll query for wildcard: '{}'", trimmedQuery);
logger.debug(" Using matchAll query for wildcard: '{}'", trimmedQuery);
boolBuilder.must(m -> m.matchAll(ma -> ma));
} else {
logger.info(" Using multiMatch query for: '{}'", trimmedQuery);
logger.debug(" Using multiMatch query for: '{}'", trimmedQuery);
boolBuilder.must(m -> m
.multiMatch(mm -> mm
.query(trimmedQuery)
@@ -444,13 +444,13 @@ public class OpenSearchService {
);
}
} else {
logger.info(" Using matchAll query for empty query");
logger.debug(" Using matchAll query for empty query");
boolBuilder.must(m -> m.matchAll(ma -> ma));
}
// Add filters
if (tags != null && !tags.isEmpty()) {
logger.info(" Adding tags filter: {}", tags);
logger.debug(" Adding tags filter: {}", tags);
boolBuilder.filter(f -> f
.terms(t -> t
.field("tagNames")
@@ -460,7 +460,7 @@ public class OpenSearchService {
}
if (author != null && !author.trim().isEmpty() && !"null".equalsIgnoreCase(author.trim())) {
logger.info(" Adding author filter: '{}'", author.trim());
logger.debug(" Adding author filter: '{}'", author.trim());
boolBuilder.filter(f -> f
.term(t -> t
.field("authorName")
@@ -472,7 +472,7 @@ public class OpenSearchService {
// Series filtering is now handled by advanced seriesFilter parameter
if (minWordCount != null || maxWordCount != null) {
logger.info(" Adding word count filter: min={}, max={}", minWordCount, maxWordCount);
logger.debug(" Adding word count filter: min={}, max={}", minWordCount, maxWordCount);
boolBuilder.filter(f -> f
.range(r -> {
var rangeBuilder = r.field("wordCount");
@@ -488,7 +488,7 @@ public class OpenSearchService {
}
if (minRating != null) {
logger.info(" Adding rating filter: min={}", minRating);
logger.debug(" Adding rating filter: min={}", minRating);
boolBuilder.filter(f -> f
.range(r -> r
.field("rating")
@@ -498,7 +498,7 @@ public class OpenSearchService {
}
if (isRead != null) {
logger.info(" Adding isRead filter: {}", isRead);
logger.debug(" Adding isRead filter: {}", isRead);
boolBuilder.filter(f -> f
.term(t -> t
.field("isRead")
@@ -508,13 +508,13 @@ public class OpenSearchService {
}
if (isFavorite != null) {
logger.info(" isFavorite filter requested: {} (FIELD NOT IMPLEMENTED - IGNORING)", isFavorite);
logger.debug(" isFavorite filter requested: {} (FIELD NOT IMPLEMENTED - IGNORING)", isFavorite);
// isFavorite field is not implemented in Story entity or StorySearchDto, so ignore this filter
}
// Advanced date filters
if (createdAfter != null && !createdAfter.trim().isEmpty() && !"null".equalsIgnoreCase(createdAfter.trim())) {
logger.info(" Adding createdAfter filter: '{}'", createdAfter.trim());
logger.debug(" Adding createdAfter filter: '{}'", createdAfter.trim());
boolBuilder.filter(f -> f
.range(r -> r
.field("createdAt")
@@ -524,7 +524,7 @@ public class OpenSearchService {
}
if (createdBefore != null && !createdBefore.trim().isEmpty() && !"null".equalsIgnoreCase(createdBefore.trim())) {
logger.info(" Adding createdBefore filter: '{}'", createdBefore.trim());
logger.debug(" Adding createdBefore filter: '{}'", createdBefore.trim());
boolBuilder.filter(f -> f
.range(r -> r
.field("createdAt")
@@ -534,7 +534,7 @@ public class OpenSearchService {
}
if (lastReadAfter != null && !lastReadAfter.trim().isEmpty() && !"null".equalsIgnoreCase(lastReadAfter.trim())) {
logger.info(" Adding lastReadAfter filter: '{}'", lastReadAfter.trim());
logger.debug(" Adding lastReadAfter filter: '{}'", lastReadAfter.trim());
boolBuilder.filter(f -> f
.range(r -> r
.field("lastReadAt")
@@ -544,7 +544,7 @@ public class OpenSearchService {
}
if (lastReadBefore != null && !lastReadBefore.trim().isEmpty() && !"null".equalsIgnoreCase(lastReadBefore.trim())) {
logger.info(" Adding lastReadBefore filter: '{}'", lastReadBefore.trim());
logger.debug(" Adding lastReadBefore filter: '{}'", lastReadBefore.trim());
boolBuilder.filter(f -> f
.range(r -> r
.field("lastReadAt")
@@ -555,7 +555,7 @@ public class OpenSearchService {
// Advanced boolean filters
if (unratedOnly != null && unratedOnly) {
logger.info(" Adding unratedOnly filter");
logger.debug(" Adding unratedOnly filter");
boolBuilder.filter(f -> f
.bool(b -> b
.should(s -> s.term(t -> t.field("rating").value(FieldValue.of(0))))
@@ -565,7 +565,7 @@ public class OpenSearchService {
}
if (hasReadingProgress != null) {
logger.info(" Adding hasReadingProgress filter: {}", hasReadingProgress);
logger.debug(" Adding hasReadingProgress filter: {}", hasReadingProgress);
if (hasReadingProgress) {
boolBuilder.filter(f -> f
.range(r -> r
@@ -584,7 +584,7 @@ public class OpenSearchService {
}
if (hasCoverImage != null) {
logger.info(" Adding hasCoverImage filter: {}", hasCoverImage);
logger.debug(" Adding hasCoverImage filter: {}", hasCoverImage);
if (hasCoverImage) {
boolBuilder.filter(f -> f
.exists(e -> e.field("coverPath"))
@@ -599,7 +599,7 @@ public class OpenSearchService {
}
if (sourceDomain != null && !sourceDomain.trim().isEmpty() && !"null".equalsIgnoreCase(sourceDomain.trim())) {
logger.info(" Adding sourceDomain filter: '{}'", sourceDomain.trim());
logger.debug(" Adding sourceDomain filter: '{}'", sourceDomain.trim());
boolBuilder.filter(f -> f
.term(t -> t
.field("sourceDomain")
@@ -610,10 +610,10 @@ public class OpenSearchService {
// Reading status filter logic
if (readingStatus != null && !readingStatus.trim().isEmpty() && !"null".equalsIgnoreCase(readingStatus.trim()) && !"all".equalsIgnoreCase(readingStatus.trim())) {
logger.info(" Adding readingStatus filter: '{}'", readingStatus.trim());
logger.debug(" Adding readingStatus filter: '{}'", readingStatus.trim());
if ("unread".equalsIgnoreCase(readingStatus.trim())) {
// Simplified unread test: just check isRead = false
logger.info(" Applying simplified unread filter: isRead = false");
logger.debug(" Applying simplified unread filter: isRead = false");
boolBuilder.filter(f -> f
.term(t -> t.field("isRead").value(FieldValue.of(false)))
);
@@ -635,10 +635,10 @@ public class OpenSearchService {
// Series filter (separate from seriesFilter parameter which is handled above)
if (seriesFilter != null && !seriesFilter.trim().isEmpty() && !"null".equalsIgnoreCase(seriesFilter.trim())) {
logger.info(" Adding advanced seriesFilter: '{}'", seriesFilter.trim());
logger.debug(" Adding advanced seriesFilter: '{}'", seriesFilter.trim());
if ("standalone".equalsIgnoreCase(seriesFilter.trim())) {
// Stories without series: seriesName field doesn't exist or is null
logger.info(" Applying standalone filter: seriesName field must not exist");
logger.debug(" Applying standalone filter: seriesName field must not exist");
boolBuilder.filter(f -> f
.bool(b -> b
.mustNot(mn -> mn.exists(e -> e.field("seriesName")))
@@ -646,7 +646,7 @@ public class OpenSearchService {
);
} else if ("series".equalsIgnoreCase(seriesFilter.trim())) {
// Stories with series: seriesName field exists and has a value
logger.info(" Applying series filter: seriesName field must exist");
logger.debug(" Applying series filter: seriesName field must exist");
boolBuilder.filter(f -> f
.exists(e -> e.field("seriesName"))
);