Saving reading position
This commit is contained in:
@@ -152,6 +152,20 @@ public class StoryController {
|
||||
return ResponseEntity.ok(convertToDto(story));
|
||||
}
|
||||
|
||||
@PostMapping("/{id}/reading-progress")
|
||||
public ResponseEntity<StoryDto> updateReadingProgress(@PathVariable UUID id, @RequestBody ReadingProgressRequest request) {
|
||||
logger.info("Updating reading progress for story {} to position {}", id, request.getPosition());
|
||||
Story story = storyService.updateReadingProgress(id, request.getPosition());
|
||||
return ResponseEntity.ok(convertToDto(story));
|
||||
}
|
||||
|
||||
@PostMapping("/{id}/reading-status")
|
||||
public ResponseEntity<StoryDto> updateReadingStatus(@PathVariable UUID id, @RequestBody ReadingStatusRequest request) {
|
||||
logger.info("Updating reading status for story {} to {}", id, request.getIsRead() ? "read" : "unread");
|
||||
Story story = storyService.updateReadingStatus(id, request.getIsRead());
|
||||
return ResponseEntity.ok(convertToDto(story));
|
||||
}
|
||||
|
||||
@PostMapping("/reindex")
|
||||
public ResponseEntity<String> manualReindex() {
|
||||
if (typesenseService == null) {
|
||||
@@ -402,6 +416,11 @@ public class StoryController {
|
||||
dto.setCreatedAt(story.getCreatedAt());
|
||||
dto.setUpdatedAt(story.getUpdatedAt());
|
||||
|
||||
// Reading progress fields
|
||||
dto.setIsRead(story.getIsRead());
|
||||
dto.setReadingPosition(story.getReadingPosition());
|
||||
dto.setLastReadAt(story.getLastReadAt());
|
||||
|
||||
if (story.getAuthor() != null) {
|
||||
dto.setAuthorId(story.getAuthor().getId());
|
||||
dto.setAuthorName(story.getAuthor().getName());
|
||||
@@ -434,6 +453,11 @@ public class StoryController {
|
||||
dto.setUpdatedAt(story.getUpdatedAt());
|
||||
dto.setPartOfSeries(story.isPartOfSeries());
|
||||
|
||||
// Reading progress fields
|
||||
dto.setIsRead(story.getIsRead());
|
||||
dto.setReadingPosition(story.getReadingPosition());
|
||||
dto.setLastReadAt(story.getLastReadAt());
|
||||
|
||||
if (story.getAuthor() != null) {
|
||||
dto.setAuthorId(story.getAuthor().getId());
|
||||
dto.setAuthorName(story.getAuthor().getName());
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
package com.storycove.dto;
|
||||
|
||||
import jakarta.validation.constraints.Min;
|
||||
|
||||
public class ReadingProgressRequest {
|
||||
|
||||
@Min(value = 0, message = "Reading position must be non-negative")
|
||||
private Integer position;
|
||||
|
||||
public ReadingProgressRequest() {}
|
||||
|
||||
public ReadingProgressRequest(Integer position) {
|
||||
this.position = position;
|
||||
}
|
||||
|
||||
public Integer getPosition() {
|
||||
return position;
|
||||
}
|
||||
|
||||
public void setPosition(Integer position) {
|
||||
this.position = position;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package com.storycove.dto;
|
||||
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
|
||||
public class ReadingStatusRequest {
|
||||
|
||||
@NotNull(message = "Reading status is required")
|
||||
private Boolean isRead;
|
||||
|
||||
public ReadingStatusRequest() {}
|
||||
|
||||
public ReadingStatusRequest(Boolean isRead) {
|
||||
this.isRead = isRead;
|
||||
}
|
||||
|
||||
public Boolean getIsRead() {
|
||||
return isRead;
|
||||
}
|
||||
|
||||
public void setIsRead(Boolean isRead) {
|
||||
this.isRead = isRead;
|
||||
}
|
||||
}
|
||||
@@ -28,6 +28,11 @@ public class StoryDto {
|
||||
private Integer rating;
|
||||
private Integer volume;
|
||||
|
||||
// Reading progress fields
|
||||
private Boolean isRead;
|
||||
private Integer readingPosition;
|
||||
private LocalDateTime lastReadAt;
|
||||
|
||||
// Related entities as simple references
|
||||
private UUID authorId;
|
||||
private String authorName;
|
||||
@@ -133,6 +138,30 @@ public class StoryDto {
|
||||
this.volume = volume;
|
||||
}
|
||||
|
||||
public Boolean getIsRead() {
|
||||
return isRead;
|
||||
}
|
||||
|
||||
public void setIsRead(Boolean isRead) {
|
||||
this.isRead = isRead;
|
||||
}
|
||||
|
||||
public Integer getReadingPosition() {
|
||||
return readingPosition;
|
||||
}
|
||||
|
||||
public void setReadingPosition(Integer readingPosition) {
|
||||
this.readingPosition = readingPosition;
|
||||
}
|
||||
|
||||
public LocalDateTime getLastReadAt() {
|
||||
return lastReadAt;
|
||||
}
|
||||
|
||||
public void setLastReadAt(LocalDateTime lastReadAt) {
|
||||
this.lastReadAt = lastReadAt;
|
||||
}
|
||||
|
||||
public UUID getAuthorId() {
|
||||
return authorId;
|
||||
}
|
||||
|
||||
@@ -16,6 +16,9 @@ public class StorySearchDto {
|
||||
private Integer rating;
|
||||
private Integer volume;
|
||||
|
||||
// Reading status
|
||||
private Boolean isRead;
|
||||
|
||||
// Author info
|
||||
private UUID authorId;
|
||||
private String authorName;
|
||||
@@ -109,6 +112,14 @@ public class StorySearchDto {
|
||||
this.volume = volume;
|
||||
}
|
||||
|
||||
public Boolean getIsRead() {
|
||||
return isRead;
|
||||
}
|
||||
|
||||
public void setIsRead(Boolean isRead) {
|
||||
this.isRead = isRead;
|
||||
}
|
||||
|
||||
public UUID getAuthorId() {
|
||||
return authorId;
|
||||
}
|
||||
|
||||
@@ -20,6 +20,11 @@ public class StorySummaryDto {
|
||||
private Integer rating;
|
||||
private Integer volume;
|
||||
|
||||
// Reading progress fields
|
||||
private Boolean isRead;
|
||||
private Integer readingPosition;
|
||||
private LocalDateTime lastReadAt;
|
||||
|
||||
// Related entities as simple references
|
||||
private UUID authorId;
|
||||
private String authorName;
|
||||
@@ -106,6 +111,30 @@ public class StorySummaryDto {
|
||||
this.volume = volume;
|
||||
}
|
||||
|
||||
public Boolean getIsRead() {
|
||||
return isRead;
|
||||
}
|
||||
|
||||
public void setIsRead(Boolean isRead) {
|
||||
this.isRead = isRead;
|
||||
}
|
||||
|
||||
public Integer getReadingPosition() {
|
||||
return readingPosition;
|
||||
}
|
||||
|
||||
public void setReadingPosition(Integer readingPosition) {
|
||||
this.readingPosition = readingPosition;
|
||||
}
|
||||
|
||||
public LocalDateTime getLastReadAt() {
|
||||
return lastReadAt;
|
||||
}
|
||||
|
||||
public void setLastReadAt(LocalDateTime lastReadAt) {
|
||||
this.lastReadAt = lastReadAt;
|
||||
}
|
||||
|
||||
public UUID getAuthorId() {
|
||||
return authorId;
|
||||
}
|
||||
|
||||
@@ -55,6 +55,15 @@ public class Story {
|
||||
@Column(name = "volume")
|
||||
private Integer volume;
|
||||
|
||||
@Column(name = "is_read")
|
||||
private Boolean isRead = false;
|
||||
|
||||
@Column(name = "reading_position")
|
||||
private Integer readingPosition = 0;
|
||||
|
||||
@Column(name = "last_read_at")
|
||||
private LocalDateTime lastReadAt;
|
||||
|
||||
@ManyToOne(fetch = FetchType.LAZY)
|
||||
@JoinColumn(name = "author_id")
|
||||
@JsonBackReference("author-stories")
|
||||
@@ -212,6 +221,30 @@ public class Story {
|
||||
this.volume = volume;
|
||||
}
|
||||
|
||||
public Boolean getIsRead() {
|
||||
return isRead;
|
||||
}
|
||||
|
||||
public void setIsRead(Boolean isRead) {
|
||||
this.isRead = isRead;
|
||||
}
|
||||
|
||||
public Integer getReadingPosition() {
|
||||
return readingPosition;
|
||||
}
|
||||
|
||||
public void setReadingPosition(Integer readingPosition) {
|
||||
this.readingPosition = readingPosition;
|
||||
}
|
||||
|
||||
public LocalDateTime getLastReadAt() {
|
||||
return lastReadAt;
|
||||
}
|
||||
|
||||
public void setLastReadAt(LocalDateTime lastReadAt) {
|
||||
this.lastReadAt = lastReadAt;
|
||||
}
|
||||
|
||||
public Author getAuthor() {
|
||||
return author;
|
||||
}
|
||||
@@ -252,6 +285,37 @@ public class Story {
|
||||
this.updatedAt = updatedAt;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the reading progress and timestamp
|
||||
*/
|
||||
public void updateReadingProgress(Integer position) {
|
||||
this.readingPosition = position;
|
||||
this.lastReadAt = LocalDateTime.now();
|
||||
}
|
||||
|
||||
/**
|
||||
* Marks the story as read and updates the reading position to the end
|
||||
*/
|
||||
public void markAsRead() {
|
||||
this.isRead = true;
|
||||
this.lastReadAt = LocalDateTime.now();
|
||||
// Set reading position to the end of content if available
|
||||
if (contentPlain != null) {
|
||||
this.readingPosition = contentPlain.length();
|
||||
} else if (contentHtml != null) {
|
||||
this.readingPosition = contentHtml.length();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Marks the story as unread and resets reading position
|
||||
*/
|
||||
public void markAsUnread() {
|
||||
this.isRead = false;
|
||||
this.readingPosition = 0;
|
||||
this.lastReadAt = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
@@ -272,6 +336,8 @@ public class Story {
|
||||
", title='" + title + '\'' +
|
||||
", wordCount=" + wordCount +
|
||||
", rating=" + rating +
|
||||
", isRead=" + isRead +
|
||||
", readingPosition=" + readingPosition +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
@@ -271,6 +271,45 @@ public class StoryService {
|
||||
return savedStory;
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public Story updateReadingProgress(UUID id, Integer position) {
|
||||
if (position != null && position < 0) {
|
||||
throw new IllegalArgumentException("Reading position must be non-negative");
|
||||
}
|
||||
|
||||
Story story = findById(id);
|
||||
story.updateReadingProgress(position);
|
||||
Story savedStory = storyRepository.save(story);
|
||||
|
||||
// Update Typesense index with new reading progress
|
||||
if (typesenseService != null) {
|
||||
typesenseService.updateStory(savedStory);
|
||||
}
|
||||
|
||||
return savedStory;
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public Story updateReadingStatus(UUID id, Boolean isRead) {
|
||||
Story story = findById(id);
|
||||
|
||||
if (Boolean.TRUE.equals(isRead)) {
|
||||
story.markAsRead();
|
||||
} else {
|
||||
story.setIsRead(false);
|
||||
story.setLastReadAt(LocalDateTime.now());
|
||||
}
|
||||
|
||||
Story savedStory = storyRepository.save(story);
|
||||
|
||||
// Update Typesense index with new reading status
|
||||
if (typesenseService != null) {
|
||||
typesenseService.updateStory(savedStory);
|
||||
}
|
||||
|
||||
return savedStory;
|
||||
}
|
||||
|
||||
@Transactional(readOnly = true)
|
||||
public List<Story> findBySeriesOrderByVolume(UUID seriesId) {
|
||||
return storyRepository.findBySeriesOrderByVolume(seriesId);
|
||||
|
||||
Reference in New Issue
Block a user