make series entries number double

This commit is contained in:
Stefan Hardegger
2026-07-20 11:27:58 +02:00
parent 665de12611
commit 362f6a9988
19 changed files with 65 additions and 63 deletions

View File

@@ -837,7 +837,7 @@ public class StoryController {
@RequestParam(required = false) String authorName, @RequestParam(required = false) String authorName,
@RequestParam(required = false) UUID seriesId, @RequestParam(required = false) UUID seriesId,
@RequestParam(required = false) String seriesName, @RequestParam(required = false) String seriesName,
@RequestParam(required = false) Integer seriesVolume, @RequestParam(required = false) Double seriesVolume,
@RequestParam(required = false) List<String> tags, @RequestParam(required = false) List<String> tags,
@RequestParam(defaultValue = "true") Boolean preserveReadingPosition, @RequestParam(defaultValue = "true") Boolean preserveReadingPosition,
@RequestParam(defaultValue = "false") Boolean overwriteExisting, @RequestParam(defaultValue = "false") Boolean overwriteExisting,
@@ -948,7 +948,7 @@ public class StoryController {
@RequestParam(required = false) String authorName, @RequestParam(required = false) String authorName,
@RequestParam(required = false) UUID seriesId, @RequestParam(required = false) UUID seriesId,
@RequestParam(required = false) String seriesName, @RequestParam(required = false) String seriesName,
@RequestParam(required = false) Integer seriesVolume, @RequestParam(required = false) Double seriesVolume,
@RequestParam(required = false) List<String> tags, @RequestParam(required = false) List<String> tags,
@RequestParam(defaultValue = "true") Boolean createMissingAuthor, @RequestParam(defaultValue = "true") Boolean createMissingAuthor,
@RequestParam(defaultValue = "true") Boolean createMissingSeries, @RequestParam(defaultValue = "true") Boolean createMissingSeries,
@@ -1082,7 +1082,7 @@ public class StoryController {
private String description; private String description;
private String contentHtml; private String contentHtml;
private String sourceUrl; private String sourceUrl;
private Integer volume; private Double volume;
private UUID authorId; private UUID authorId;
private String authorName; private String authorName;
private UUID seriesId; private UUID seriesId;
@@ -1100,8 +1100,8 @@ public class StoryController {
public void setContentHtml(String contentHtml) { this.contentHtml = contentHtml; } public void setContentHtml(String contentHtml) { this.contentHtml = contentHtml; }
public String getSourceUrl() { return sourceUrl; } public String getSourceUrl() { return sourceUrl; }
public void setSourceUrl(String sourceUrl) { this.sourceUrl = sourceUrl; } public void setSourceUrl(String sourceUrl) { this.sourceUrl = sourceUrl; }
public Integer getVolume() { return volume; } public Double getVolume() { return volume; }
public void setVolume(Integer volume) { this.volume = volume; } public void setVolume(Double volume) { this.volume = volume; }
public UUID getAuthorId() { return authorId; } public UUID getAuthorId() { return authorId; }
public void setAuthorId(UUID authorId) { this.authorId = authorId; } public void setAuthorId(UUID authorId) { this.authorId = authorId; }
public String getAuthorName() { return authorName; } public String getAuthorName() { return authorName; }
@@ -1120,7 +1120,7 @@ public class StoryController {
private String description; private String description;
private String contentHtml; private String contentHtml;
private String sourceUrl; private String sourceUrl;
private Integer volume; private Double volume;
private UUID authorId; private UUID authorId;
private String authorName; private String authorName;
private UUID seriesId; private UUID seriesId;
@@ -1138,8 +1138,8 @@ public class StoryController {
public void setContentHtml(String contentHtml) { this.contentHtml = contentHtml; } public void setContentHtml(String contentHtml) { this.contentHtml = contentHtml; }
public String getSourceUrl() { return sourceUrl; } public String getSourceUrl() { return sourceUrl; }
public void setSourceUrl(String sourceUrl) { this.sourceUrl = sourceUrl; } public void setSourceUrl(String sourceUrl) { this.sourceUrl = sourceUrl; }
public Integer getVolume() { return volume; } public Double getVolume() { return volume; }
public void setVolume(Integer volume) { this.volume = volume; } public void setVolume(Double volume) { this.volume = volume; }
public UUID getAuthorId() { return authorId; } public UUID getAuthorId() { return authorId; }
public void setAuthorId(UUID authorId) { this.authorId = authorId; } public void setAuthorId(UUID authorId) { this.authorId = authorId; }
public String getAuthorName() { return authorName; } public String getAuthorName() { return authorName; }

View File

@@ -19,7 +19,7 @@ public class EPUBImportRequest {
private String seriesName; private String seriesName;
private Integer seriesVolume; private Double seriesVolume;
private List<String> tags; private List<String> tags;
@@ -75,11 +75,11 @@ public class EPUBImportRequest {
this.seriesName = seriesName; this.seriesName = seriesName;
} }
public Integer getSeriesVolume() { public Double getSeriesVolume() {
return seriesVolume; return seriesVolume;
} }
public void setSeriesVolume(Integer seriesVolume) { public void setSeriesVolume(Double seriesVolume) {
this.seriesVolume = seriesVolume; this.seriesVolume = seriesVolume;
} }

View File

@@ -19,7 +19,7 @@ public class PDFImportRequest {
private String seriesName; private String seriesName;
private Integer seriesVolume; private Double seriesVolume;
private List<String> tags; private List<String> tags;
@@ -71,11 +71,11 @@ public class PDFImportRequest {
this.seriesName = seriesName; this.seriesName = seriesName;
} }
public Integer getSeriesVolume() { public Double getSeriesVolume() {
return seriesVolume; return seriesVolume;
} }
public void setSeriesVolume(Integer seriesVolume) { public void setSeriesVolume(Double seriesVolume) {
this.seriesVolume = seriesVolume; this.seriesVolume = seriesVolume;
} }

View File

@@ -26,7 +26,7 @@ public class StoryDto {
private String coverPath; private String coverPath;
private Integer wordCount; private Integer wordCount;
private Integer rating; private Integer rating;
private Integer volume; private Double volume;
// Reading progress fields // Reading progress fields
private Boolean isRead; private Boolean isRead;
@@ -125,11 +125,11 @@ public class StoryDto {
this.rating = rating; this.rating = rating;
} }
public Integer getVolume() { public Double getVolume() {
return volume; return volume;
} }
public void setVolume(Integer volume) { public void setVolume(Double volume) {
this.volume = volume; this.volume = volume;
} }

View File

@@ -20,7 +20,7 @@ public class StoryReadingDto {
private String coverPath; private String coverPath;
private Integer wordCount; private Integer wordCount;
private Integer rating; private Integer rating;
private Integer volume; private Double volume;
// Reading progress fields // Reading progress fields
private Boolean isRead; private Boolean isRead;
@@ -114,11 +114,11 @@ public class StoryReadingDto {
this.rating = rating; this.rating = rating;
} }
public Integer getVolume() { public Double getVolume() {
return volume; return volume;
} }
public void setVolume(Integer volume) { public void setVolume(Double volume) {
this.volume = volume; this.volume = volume;
} }

View File

@@ -13,7 +13,7 @@ public class StorySearchDto {
private String coverPath; private String coverPath;
private Integer wordCount; private Integer wordCount;
private Integer rating; private Integer rating;
private Integer volume; private Double volume;
// Reading status // Reading status
private Boolean isRead; private Boolean isRead;
@@ -103,11 +103,11 @@ public class StorySearchDto {
this.rating = rating; this.rating = rating;
} }
public Integer getVolume() { public Double getVolume() {
return volume; return volume;
} }
public void setVolume(Integer volume) { public void setVolume(Double volume) {
this.volume = volume; this.volume = volume;
} }

View File

@@ -18,7 +18,7 @@ public class StorySummaryDto {
private String coverPath; private String coverPath;
private Integer wordCount; private Integer wordCount;
private Integer rating; private Integer rating;
private Integer volume; private Double volume;
// Reading progress fields // Reading progress fields
private Boolean isRead; private Boolean isRead;
@@ -105,11 +105,11 @@ public class StorySummaryDto {
this.rating = rating; this.rating = rating;
} }
public Integer getVolume() { public Double getVolume() {
return volume; return volume;
} }
public void setVolume(Integer volume) { public void setVolume(Double volume) {
this.volume = volume; this.volume = volume;
} }

View File

@@ -35,7 +35,7 @@ public class ZIPImportRequest {
private String authorName; private String authorName;
private UUID seriesId; private UUID seriesId;
private String seriesName; private String seriesName;
private Integer seriesVolume; private Double seriesVolume;
private List<String> tags; private List<String> tags;
public UUID getAuthorId() { public UUID getAuthorId() {
@@ -70,11 +70,11 @@ public class ZIPImportRequest {
this.seriesName = seriesName; this.seriesName = seriesName;
} }
public Integer getSeriesVolume() { public Double getSeriesVolume() {
return seriesVolume; return seriesVolume;
} }
public void setSeriesVolume(Integer seriesVolume) { public void setSeriesVolume(Double seriesVolume) {
this.seriesVolume = seriesVolume; this.seriesVolume = seriesVolume;
} }

View File

@@ -8,6 +8,7 @@ import com.fasterxml.jackson.annotation.JsonManagedReference;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Comparator;
import java.util.List; import java.util.List;
import java.util.UUID; import java.util.UUID;
@@ -62,21 +63,21 @@ public class Series {
public Story getNextStory(Story currentStory) { public Story getNextStory(Story currentStory) {
if (currentStory.getVolume() == null) return null; if (currentStory.getVolume() == null) return null;
return stories.stream() return stories.stream()
.filter(story -> story.getVolume() != null) .filter(story -> story.getVolume() != null)
.filter(story -> story.getVolume().equals(currentStory.getVolume() + 1)) .filter(story -> story.getVolume() > currentStory.getVolume())
.findFirst() .min(Comparator.comparingDouble(Story::getVolume))
.orElse(null); .orElse(null);
} }
public Story getPreviousStory(Story currentStory) { public Story getPreviousStory(Story currentStory) {
if (currentStory.getVolume() == null || currentStory.getVolume() <= 1) return null; if (currentStory.getVolume() == null) return null;
return stories.stream() return stories.stream()
.filter(story -> story.getVolume() != null) .filter(story -> story.getVolume() != null)
.filter(story -> story.getVolume().equals(currentStory.getVolume() - 1)) .filter(story -> story.getVolume() < currentStory.getVolume())
.findFirst() .max(Comparator.comparingDouble(Story::getVolume))
.orElse(null); .orElse(null);
} }

View File

@@ -52,8 +52,8 @@ public class Story {
@Column(name = "rating") @Column(name = "rating")
private Integer rating; private Integer rating;
@Column(name = "volume") @Column(name = "volume", columnDefinition = "float8")
private Integer volume; private Double volume;
@Column(name = "is_read") @Column(name = "is_read")
private Boolean isRead = false; private Boolean isRead = false;
@@ -216,11 +216,11 @@ public class Story {
this.rating = rating; this.rating = rating;
} }
public Integer getVolume() { public Double getVolume() {
return volume; return volume;
} }
public void setVolume(Integer volume) { public void setVolume(Double volume) {
this.volume = volume; this.volume = volume;
} }

View File

@@ -37,7 +37,7 @@ public interface StoryRepository extends JpaRepository<Story, UUID> {
List<Story> findBySeriesOrderByVolume(@Param("seriesId") UUID seriesId); List<Story> findBySeriesOrderByVolume(@Param("seriesId") UUID seriesId);
@Query("SELECT s FROM Story s WHERE s.series.id = :seriesId AND s.volume = :volume") @Query("SELECT s FROM Story s WHERE s.series.id = :seriesId AND s.volume = :volume")
Optional<Story> findBySeriesAndVolume(@Param("seriesId") UUID seriesId, @Param("volume") Integer volume); Optional<Story> findBySeriesAndVolume(@Param("seriesId") UUID seriesId, @Param("volume") Double volume);
@Query("SELECT s FROM Story s JOIN s.tags t WHERE t = :tag") @Query("SELECT s FROM Story s JOIN s.tags t WHERE t = :tag")

View File

@@ -420,7 +420,7 @@ public class CollectionService {
.orElseThrow(() -> new ResourceNotFoundException("Series not found with id: " + seriesId)); .orElseThrow(() -> new ResourceNotFoundException("Series not found with id: " + seriesId));
List<UUID> storyIds = series.getStories().stream() List<UUID> storyIds = series.getStories().stream()
.sorted(java.util.Comparator.comparingInt(s -> s.getVolume() != null ? s.getVolume() : Integer.MAX_VALUE)) .sorted(java.util.Comparator.comparingDouble((Story s) -> s.getVolume() != null ? s.getVolume() : Double.MAX_VALUE))
.map(Story::getId) .map(Story::getId)
.collect(Collectors.toList()); .collect(Collectors.toList());

View File

@@ -1061,7 +1061,8 @@ public class SolrService {
story.setCoverPath((String) doc.getFieldValue("coverPath")); story.setCoverPath((String) doc.getFieldValue("coverPath"));
story.setWordCount((Integer) doc.getFieldValue("wordCount")); story.setWordCount((Integer) doc.getFieldValue("wordCount"));
story.setRating((Integer) doc.getFieldValue("rating")); story.setRating((Integer) doc.getFieldValue("rating"));
story.setVolume((Integer) doc.getFieldValue("volume")); Number volumeVal = (Number) doc.getFieldValue("volume");
story.setVolume(volumeVal != null ? volumeVal.doubleValue() : null);
story.setIsRead((Boolean) doc.getFieldValue("isRead")); story.setIsRead((Boolean) doc.getFieldValue("isRead"));
story.setReadingPosition((Integer) doc.getFieldValue("readingPosition")); story.setReadingPosition((Integer) doc.getFieldValue("readingPosition"));
story.setReadingProgressPercentage((Integer) doc.getFieldValue("readingProgressPercentage")); story.setReadingProgressPercentage((Integer) doc.getFieldValue("readingProgressPercentage"));

View File

@@ -139,7 +139,7 @@ public class StoryService {
} }
@Transactional(readOnly = true) @Transactional(readOnly = true)
public Optional<Story> findBySeriesAndVolume(UUID seriesId, Integer volume) { public Optional<Story> findBySeriesAndVolume(UUID seriesId, Double volume) {
return storyRepository.findBySeriesAndVolume(seriesId, volume); return storyRepository.findBySeriesAndVolume(seriesId, volume);
} }
@@ -499,7 +499,7 @@ public class StoryService {
return storyRepository.save(story); return storyRepository.save(story);
} }
public Story addToSeries(UUID storyId, UUID seriesId, Integer volume) { public Story addToSeries(UUID storyId, UUID seriesId, Double volume) {
Story story = findById(storyId); Story story = findById(storyId);
Series series = seriesService.findById(seriesId); Series series = seriesService.findById(seriesId);
@@ -550,7 +550,7 @@ public class StoryService {
} }
} }
private void validateSeriesVolume(Series series, Integer volume) { private void validateSeriesVolume(Series series, Double volume) {
if (volume != null) { if (volume != null) {
Optional<Story> existingPart = storyRepository.findBySeriesAndVolume(series.getId(), volume); Optional<Story> existingPart = storyRepository.findBySeriesAndVolume(series.getId(), volume);
if (existingPart.isPresent()) { if (existingPart.isPresent()) {

View File

@@ -104,11 +104,11 @@ class SeriesTest {
@DisplayName("Should get next story correctly") @DisplayName("Should get next story correctly")
void shouldGetNextStoryCorrectly() { void shouldGetNextStoryCorrectly() {
Story story1 = new Story("Part 1"); Story story1 = new Story("Part 1");
story1.setVolume(1); story1.setVolume(1.0);
Story story2 = new Story("Part 2"); Story story2 = new Story("Part 2");
story2.setVolume(2); story2.setVolume(2.0);
Story story3 = new Story("Part 3"); Story story3 = new Story("Part 3");
story3.setVolume(3); story3.setVolume(3.0);
series.addStory(story1); series.addStory(story1);
series.addStory(story2); series.addStory(story2);
@@ -123,11 +123,11 @@ class SeriesTest {
@DisplayName("Should get previous story correctly") @DisplayName("Should get previous story correctly")
void shouldGetPreviousStoryCorrectly() { void shouldGetPreviousStoryCorrectly() {
Story story1 = new Story("Part 1"); Story story1 = new Story("Part 1");
story1.setVolume(1); story1.setVolume(1.0);
Story story2 = new Story("Part 2"); Story story2 = new Story("Part 2");
story2.setVolume(2); story2.setVolume(2.0);
Story story3 = new Story("Part 3"); Story story3 = new Story("Part 3");
story3.setVolume(3); story3.setVolume(3.0);
series.addStory(story1); series.addStory(story1);
series.addStory(story2); series.addStory(story2);

View File

@@ -140,7 +140,7 @@ class StoryTest {
story.setSeries(series); story.setSeries(series);
assertFalse(story.isPartOfSeries()); // Still false because no volume assertFalse(story.isPartOfSeries()); // Still false because no volume
story.setVolume(1); story.setVolume(1.0);
assertTrue(story.isPartOfSeries()); assertTrue(story.isPartOfSeries());
story.setSeries(null); story.setSeries(null);

View File

@@ -69,14 +69,14 @@ class StoryRepositoryTest extends BaseRepositoryTest {
story2.setDescription("The sequel to the great adventure"); story2.setDescription("The sequel to the great adventure");
story2.setAuthor(author); story2.setAuthor(author);
story2.setSeries(series); story2.setSeries(series);
story2.setVolume(1); story2.setVolume(1.0);
story2.addTag(tag1); story2.addTag(tag1);
story3 = new Story("The Final Chapter"); story3 = new Story("The Final Chapter");
story3.setDescription("The final chapter"); story3.setDescription("The final chapter");
story3.setAuthor(author); story3.setAuthor(author);
story3.setSeries(series); story3.setSeries(series);
story3.setVolume(2); story3.setVolume(2.0);
storyRepository.saveAll(List.of(story1, story2, story3)); storyRepository.saveAll(List.of(story1, story2, story3));
} }
@@ -126,11 +126,11 @@ class StoryRepositoryTest extends BaseRepositoryTest {
@Test @Test
@DisplayName("Should find story by series and volume") @DisplayName("Should find story by series and volume")
void shouldFindStoryBySeriesAndVolume() { void shouldFindStoryBySeriesAndVolume() {
Optional<Story> found = storyRepository.findBySeriesAndVolume(series.getId(), 1); Optional<Story> found = storyRepository.findBySeriesAndVolume(series.getId(), 1.0);
assertTrue(found.isPresent()); assertTrue(found.isPresent());
assertEquals("The Sequel", found.get().getTitle()); assertEquals("The Sequel", found.get().getTitle());
found = storyRepository.findBySeriesAndVolume(series.getId(), 99); found = storyRepository.findBySeriesAndVolume(series.getId(), 99.0);
assertFalse(found.isPresent()); assertFalse(found.isPresent());
} }

View File

@@ -285,7 +285,7 @@ export default function AddStoryContent() {
summary: formData.summary || undefined, summary: formData.summary || undefined,
contentHtml: formData.contentHtml, contentHtml: formData.contentHtml,
sourceUrl: formData.sourceUrl || undefined, sourceUrl: formData.sourceUrl || undefined,
volume: formData.seriesName ? parseInt(formData.volume) : undefined, volume: formData.seriesName ? parseFloat(formData.volume) : undefined,
// Send seriesId if we have it (existing series), otherwise send seriesName (new series) // Send seriesId if we have it (existing series), otherwise send seriesName (new series)
...(formData.seriesId ? { seriesId: formData.seriesId } : { seriesName: formData.seriesName || undefined }), ...(formData.seriesId ? { seriesId: formData.seriesId } : { seriesName: formData.seriesName || undefined }),
// Send authorId if we have it (existing author), otherwise send authorName (new author) // Send authorId if we have it (existing author), otherwise send authorName (new author)
@@ -308,7 +308,7 @@ export default function AddStoryContent() {
summary: formData.summary || undefined, summary: formData.summary || undefined,
contentHtml: imageResult.processedContent, contentHtml: imageResult.processedContent,
sourceUrl: formData.sourceUrl || undefined, sourceUrl: formData.sourceUrl || undefined,
volume: formData.seriesName ? parseInt(formData.volume) : undefined, volume: formData.seriesName ? parseFloat(formData.volume) : undefined,
...(formData.seriesId ? { seriesId: formData.seriesId } : { seriesName: formData.seriesName || undefined }), ...(formData.seriesId ? { seriesId: formData.seriesId } : { seriesName: formData.seriesName || undefined }),
...(formData.authorId ? { authorId: formData.authorId } : { authorName: formData.authorName }), ...(formData.authorId ? { authorId: formData.authorId } : { authorName: formData.authorName }),
tagNames: formData.tags.length > 0 ? formData.tags : undefined, tagNames: formData.tags.length > 0 ? formData.tags : undefined,

View File

@@ -199,7 +199,7 @@ export default function EditStoryPage() {
summary: formData.summary || undefined, summary: formData.summary || undefined,
contentHtml: formData.contentHtml, contentHtml: formData.contentHtml,
sourceUrl: formData.sourceUrl || undefined, sourceUrl: formData.sourceUrl || undefined,
volume: formData.seriesName && formData.volume ? parseInt(formData.volume) : undefined, volume: formData.seriesName && formData.volume ? parseFloat(formData.volume) : undefined,
// Send seriesId if we have it (existing series), otherwise send seriesName (new/changed series) // Send seriesId if we have it (existing series), otherwise send seriesName (new/changed series)
...(formData.seriesId ? { seriesId: formData.seriesId } : { seriesName: formData.seriesName }), ...(formData.seriesId ? { seriesId: formData.seriesId } : { seriesName: formData.seriesName }),
// Send authorId if we have it (existing author), otherwise send authorName (new/changed author) // Send authorId if we have it (existing author), otherwise send authorName (new/changed author)
@@ -222,7 +222,7 @@ export default function EditStoryPage() {
summary: formData.summary || undefined, summary: formData.summary || undefined,
contentHtml: imageResult.processedContent, contentHtml: imageResult.processedContent,
sourceUrl: formData.sourceUrl || undefined, sourceUrl: formData.sourceUrl || undefined,
volume: formData.seriesName && formData.volume ? parseInt(formData.volume) : undefined, volume: formData.seriesName && formData.volume ? parseFloat(formData.volume) : undefined,
...(formData.seriesId ? { seriesId: formData.seriesId } : { seriesName: formData.seriesName }), ...(formData.seriesId ? { seriesId: formData.seriesId } : { seriesName: formData.seriesName }),
...(formData.authorId ? { authorId: formData.authorId } : { authorName: formData.authorName }), ...(formData.authorId ? { authorId: formData.authorId } : { authorName: formData.authorName }),
tagNames: formData.tags, tagNames: formData.tags,