show reading progress in author page. Allow deletion of tags, even if assigned to story.
This commit is contained in:
@@ -691,6 +691,7 @@ public class StoryController {
|
|||||||
// Reading progress fields
|
// Reading progress fields
|
||||||
dto.setIsRead(story.getIsRead());
|
dto.setIsRead(story.getIsRead());
|
||||||
dto.setReadingPosition(story.getReadingPosition());
|
dto.setReadingPosition(story.getReadingPosition());
|
||||||
|
dto.setReadingProgressPercentage(calculateReadingProgressPercentage(story));
|
||||||
dto.setLastReadAt(story.getLastReadAt());
|
dto.setLastReadAt(story.getLastReadAt());
|
||||||
|
|
||||||
if (story.getAuthor() != null) {
|
if (story.getAuthor() != null) {
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ public class StorySummaryDto {
|
|||||||
// Reading progress fields
|
// Reading progress fields
|
||||||
private Boolean isRead;
|
private Boolean isRead;
|
||||||
private Integer readingPosition;
|
private Integer readingPosition;
|
||||||
|
private Integer readingProgressPercentage; // Pre-calculated percentage (0-100)
|
||||||
private LocalDateTime lastReadAt;
|
private LocalDateTime lastReadAt;
|
||||||
|
|
||||||
// Related entities as simple references
|
// Related entities as simple references
|
||||||
@@ -127,6 +128,14 @@ public class StorySummaryDto {
|
|||||||
this.readingPosition = readingPosition;
|
this.readingPosition = readingPosition;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Integer getReadingProgressPercentage() {
|
||||||
|
return readingProgressPercentage;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setReadingProgressPercentage(Integer readingProgressPercentage) {
|
||||||
|
this.readingProgressPercentage = readingProgressPercentage;
|
||||||
|
}
|
||||||
|
|
||||||
public LocalDateTime getLastReadAt() {
|
public LocalDateTime getLastReadAt() {
|
||||||
return lastReadAt;
|
return lastReadAt;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ public class TagService {
|
|||||||
|
|
||||||
private final TagRepository tagRepository;
|
private final TagRepository tagRepository;
|
||||||
private final TagAliasRepository tagAliasRepository;
|
private final TagAliasRepository tagAliasRepository;
|
||||||
|
private SolrService solrService;
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
public TagService(TagRepository tagRepository, TagAliasRepository tagAliasRepository) {
|
public TagService(TagRepository tagRepository, TagAliasRepository tagAliasRepository) {
|
||||||
@@ -40,6 +41,11 @@ public class TagService {
|
|||||||
this.tagAliasRepository = tagAliasRepository;
|
this.tagAliasRepository = tagAliasRepository;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Autowired(required = false)
|
||||||
|
public void setSolrService(SolrService solrService) {
|
||||||
|
this.solrService = solrService;
|
||||||
|
}
|
||||||
|
|
||||||
@Transactional(readOnly = true)
|
@Transactional(readOnly = true)
|
||||||
public List<Tag> findAll() {
|
public List<Tag> findAll() {
|
||||||
return tagRepository.findAll();
|
return tagRepository.findAll();
|
||||||
@@ -143,12 +149,38 @@ public class TagService {
|
|||||||
public void delete(UUID id) {
|
public void delete(UUID id) {
|
||||||
Tag tag = findById(id);
|
Tag tag = findById(id);
|
||||||
|
|
||||||
// Check if tag is used by any stories
|
// Remove tag from all stories before deletion and track for reindexing
|
||||||
|
List<Story> storiesToReindex = new ArrayList<>();
|
||||||
if (!tag.getStories().isEmpty()) {
|
if (!tag.getStories().isEmpty()) {
|
||||||
throw new IllegalStateException("Cannot delete tag that is used by stories. Remove tag from all stories first.");
|
// Create a copy to avoid ConcurrentModificationException
|
||||||
|
List<Story> storiesToUpdate = new ArrayList<>(tag.getStories());
|
||||||
|
storiesToUpdate.forEach(story -> {
|
||||||
|
story.removeTag(tag);
|
||||||
|
storiesToReindex.add(story);
|
||||||
|
});
|
||||||
|
logger.info("Removed tag '{}' from {} stories before deletion", tag.getName(), storiesToUpdate.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove tag from all collections before deletion
|
||||||
|
if (tag.getCollections() != null && !tag.getCollections().isEmpty()) {
|
||||||
|
tag.getCollections().forEach(collection -> collection.getTags().remove(tag));
|
||||||
|
logger.info("Removed tag '{}' from {} collections before deletion", tag.getName(), tag.getCollections().size());
|
||||||
}
|
}
|
||||||
|
|
||||||
tagRepository.delete(tag);
|
tagRepository.delete(tag);
|
||||||
|
logger.info("Deleted tag '{}'", tag.getName());
|
||||||
|
|
||||||
|
// Reindex affected stories in Solr
|
||||||
|
if (solrService != null && !storiesToReindex.isEmpty()) {
|
||||||
|
try {
|
||||||
|
for (Story story : storiesToReindex) {
|
||||||
|
solrService.indexStory(story);
|
||||||
|
}
|
||||||
|
logger.info("Reindexed {} stories after tag deletion", storiesToReindex.size());
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.error("Failed to reindex stories after tag deletion", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<Tag> deleteUnusedTags() {
|
public List<Tag> deleteUnusedTags() {
|
||||||
|
|||||||
@@ -137,9 +137,10 @@ export default function TagMaintenancePage() {
|
|||||||
// Reload tags and reset selection
|
// Reload tags and reset selection
|
||||||
await loadTags();
|
await loadTags();
|
||||||
setSelectedTagIds(new Set());
|
setSelectedTagIds(new Set());
|
||||||
} catch (error) {
|
} catch (error: any) {
|
||||||
console.error('Failed to delete tags:', error);
|
console.error('Failed to delete tags:', error);
|
||||||
alert('Failed to delete some tags. Please try again.');
|
const errorMessage = error.response?.data?.error || error.message || 'Failed to delete some tags. Please try again.';
|
||||||
|
alert(errorMessage);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -129,7 +129,8 @@ export default function TagEditModal({ tag, isOpen, onClose, onSave, onDelete }:
|
|||||||
onDelete(tag);
|
onDelete(tag);
|
||||||
onClose();
|
onClose();
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
setErrors({ submit: error.message });
|
const errorMessage = error.response?.data?.error || error.message || 'Failed to delete tag';
|
||||||
|
setErrors({ submit: errorMessage });
|
||||||
} finally {
|
} finally {
|
||||||
setSaving(false);
|
setSaving(false);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user