mirror of
https://github.com/booklore-app/booklore.git
synced 2025-12-23 22:28:11 -05:00
Automatically add newly added books to Kobo shelf (#1826)
This commit is contained in:
@@ -3,7 +3,6 @@ package com.adityachandel.booklore.controller;
|
||||
import com.adityachandel.booklore.model.dto.KoboSyncSettings;
|
||||
import com.adityachandel.booklore.service.kobo.KoboSettingsService;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
import io.swagger.v3.oas.annotations.responses.ApiResponse;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
@@ -31,31 +30,19 @@ public class KoboSettingsController {
|
||||
|
||||
@Operation(summary = "Create or update Kobo token", description = "Create or update the Kobo sync token for the current user. Requires sync permission or admin.")
|
||||
@ApiResponse(responseCode = "200", description = "Token created/updated successfully")
|
||||
@PutMapping
|
||||
@PutMapping("/token")
|
||||
@PreAuthorize("@securityUtil.canSyncKobo() or @securityUtil.isAdmin()")
|
||||
public ResponseEntity<KoboSyncSettings> createOrUpdateToken() {
|
||||
KoboSyncSettings updated = koboService.createOrUpdateToken();
|
||||
return ResponseEntity.ok(updated);
|
||||
}
|
||||
|
||||
@Operation(summary = "Toggle Kobo sync", description = "Enable or disable Kobo sync for the current user. Requires sync permission or admin.")
|
||||
@ApiResponse(responseCode = "204", description = "Sync toggled successfully")
|
||||
@PutMapping("/sync")
|
||||
@Operation(summary = "Update Kobo settings", description = "Update Kobo sync settings for the current user. Requires sync permission or admin.")
|
||||
@ApiResponse(responseCode = "200", description = "Settings updated successfully")
|
||||
@PutMapping
|
||||
@PreAuthorize("@securityUtil.canSyncKobo() or @securityUtil.isAdmin()")
|
||||
public ResponseEntity<Void> toggleSync(
|
||||
@Parameter(description = "Enable or disable sync") @RequestParam boolean enabled) {
|
||||
koboService.setSyncEnabled(enabled);
|
||||
return ResponseEntity.noContent().build();
|
||||
}
|
||||
|
||||
@Operation(summary = "Update progress thresholds", description = "Update the progress thresholds for marking books as reading or finished. Requires sync permission or admin.")
|
||||
@ApiResponse(responseCode = "200", description = "Thresholds updated successfully")
|
||||
@PutMapping("/progress-thresholds")
|
||||
@PreAuthorize("@securityUtil.canSyncKobo() or @securityUtil.isAdmin()")
|
||||
public ResponseEntity<KoboSyncSettings> updateProgressThresholds(
|
||||
@Parameter(description = "Progress percentage to mark as reading (0-100)") @RequestParam(required = false) Float readingThreshold,
|
||||
@Parameter(description = "Progress percentage to mark as finished (0-100)") @RequestParam(required = false) Float finishedThreshold) {
|
||||
KoboSyncSettings updated = koboService.updateProgressThresholds(readingThreshold, finishedThreshold);
|
||||
public ResponseEntity<KoboSyncSettings> updateSettings(@RequestBody KoboSyncSettings settings) {
|
||||
KoboSyncSettings updated = koboService.updateSettings(settings);
|
||||
return ResponseEntity.ok(updated);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,4 +11,5 @@ public class KoboSyncSettings {
|
||||
private boolean syncEnabled;
|
||||
private Float progressMarkAsReadingThreshold;
|
||||
private Float progressMarkAsFinishedThreshold;
|
||||
private boolean autoAddToShelf;
|
||||
}
|
||||
|
||||
@@ -33,4 +33,8 @@ public class KoboUserSettingsEntity {
|
||||
@Column(name = "progress_mark_as_finished_threshold")
|
||||
@Builder.Default
|
||||
private Float progressMarkAsFinishedThreshold = 99f;
|
||||
|
||||
@Column(name = "auto_add_to_shelf")
|
||||
@Builder.Default
|
||||
private boolean autoAddToShelf = false;
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import com.adityachandel.booklore.model.entity.KoboUserSettingsEntity;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
@Repository
|
||||
@@ -12,4 +13,6 @@ public interface KoboUserSettingsRepository extends JpaRepository<KoboUserSettin
|
||||
Optional<KoboUserSettingsEntity> findByUserId(Long userId);
|
||||
|
||||
Optional<KoboUserSettingsEntity> findByToken(String token);
|
||||
|
||||
List<KoboUserSettingsEntity> findByAutoAddToShelfTrueAndSyncEnabledTrue();
|
||||
}
|
||||
@@ -18,4 +18,6 @@ public interface ShelfRepository extends JpaRepository<ShelfEntity, Long> {
|
||||
List<ShelfEntity> findByUserId(Long id);
|
||||
|
||||
Optional<ShelfEntity> findByUserIdAndName(Long id, String name);
|
||||
|
||||
List<ShelfEntity> findByUserIdInAndName(List<Long> userIds, String name);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,97 @@
|
||||
package com.adityachandel.booklore.service.kobo;
|
||||
|
||||
import com.adityachandel.booklore.model.entity.BookEntity;
|
||||
import com.adityachandel.booklore.model.entity.KoboUserSettingsEntity;
|
||||
import com.adityachandel.booklore.model.entity.ShelfEntity;
|
||||
import com.adityachandel.booklore.model.enums.ShelfType;
|
||||
import com.adityachandel.booklore.repository.BookRepository;
|
||||
import com.adityachandel.booklore.repository.KoboUserSettingsRepository;
|
||||
import com.adityachandel.booklore.repository.ShelfRepository;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Propagation;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@Slf4j
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class KoboAutoShelfService {
|
||||
|
||||
private final KoboUserSettingsRepository koboUserSettingsRepository;
|
||||
private final ShelfRepository shelfRepository;
|
||||
private final BookRepository bookRepository;
|
||||
private final KoboCompatibilityService koboCompatibilityService;
|
||||
|
||||
@Transactional(propagation = Propagation.REQUIRES_NEW)
|
||||
public void autoAddBookToKoboShelves(Long bookId) {
|
||||
if (bookId == null) {
|
||||
log.warn("Book ID is null for auto-add to Kobo shelf");
|
||||
return;
|
||||
}
|
||||
|
||||
BookEntity book = bookRepository.findById(bookId).orElse(null);
|
||||
if (!isBookEligible(book)) {
|
||||
return;
|
||||
}
|
||||
|
||||
List<KoboUserSettingsEntity> eligibleUsers = koboUserSettingsRepository.findByAutoAddToShelfTrueAndSyncEnabledTrue();
|
||||
|
||||
if (eligibleUsers.isEmpty()) {
|
||||
log.debug("No Kobo auto-add enabled users for book {}", book.getId());
|
||||
return;
|
||||
}
|
||||
|
||||
List<Long> userIds = eligibleUsers.stream()
|
||||
.map(KoboUserSettingsEntity::getUserId)
|
||||
.toList();
|
||||
|
||||
List<ShelfEntity> shelves = shelfRepository.findByUserIdInAndName(userIds, ShelfType.KOBO.getName());
|
||||
|
||||
Map<Long, ShelfEntity> shelfByUser = shelves
|
||||
.stream()
|
||||
.collect(Collectors.toMap(s -> s.getUser().getId(), s -> s));
|
||||
|
||||
boolean modified = false;
|
||||
|
||||
for (KoboUserSettingsEntity setting : eligibleUsers) {
|
||||
ShelfEntity shelf = shelfByUser.get(setting.getUserId());
|
||||
|
||||
if (shelf == null) {
|
||||
log.debug("User {} has auto-add enabled but no Kobo shelf exists", setting.getUserId());
|
||||
continue;
|
||||
}
|
||||
|
||||
if (book.getShelves().contains(shelf)) {
|
||||
log.debug("Book {} already on Kobo shelf for user {}", book.getId(), setting.getUserId());
|
||||
continue;
|
||||
}
|
||||
|
||||
book.getShelves().add(shelf);
|
||||
modified = true;
|
||||
log.info("Auto-added book {} to Kobo shelf for user {}", book.getId(), setting.getUserId());
|
||||
}
|
||||
|
||||
if (modified) {
|
||||
bookRepository.save(book);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isBookEligible(BookEntity book) {
|
||||
if (book == null) {
|
||||
log.warn("Book not found for Kobo auto-add");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!koboCompatibilityService.isBookSupportedForKobo(book)) {
|
||||
log.debug("Book {} is not Kobo-compatible, skipping", book.getId());
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -7,6 +7,7 @@ import com.adityachandel.booklore.model.dto.Shelf;
|
||||
import com.adityachandel.booklore.model.dto.request.ShelfCreateRequest;
|
||||
import com.adityachandel.booklore.model.entity.KoboUserSettingsEntity;
|
||||
import com.adityachandel.booklore.model.entity.ShelfEntity;
|
||||
import com.adityachandel.booklore.model.enums.IconType;
|
||||
import com.adityachandel.booklore.model.enums.ShelfType;
|
||||
import com.adityachandel.booklore.repository.KoboUserSettingsRepository;
|
||||
import com.adityachandel.booklore.service.ShelfService;
|
||||
@@ -56,33 +57,30 @@ public class KoboSettingsService {
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public void setSyncEnabled(boolean enabled) {
|
||||
public KoboSyncSettings updateSettings(KoboSyncSettings settings) {
|
||||
BookLoreUser user = authenticationService.getAuthenticatedUser();
|
||||
KoboUserSettingsEntity entity = repository.findByUserId(user.getId()).orElseThrow(() -> new IllegalStateException("Kobo settings not found for user"));
|
||||
KoboUserSettingsEntity entity = repository.findByUserId(user.getId()).orElseGet(() -> initDefaultSettings(user.getId()));
|
||||
|
||||
if (settings.isSyncEnabled() != entity.isSyncEnabled()) {
|
||||
Shelf userKoboShelf = shelfService.getUserKoboShelf();
|
||||
if (!enabled) {
|
||||
if (!settings.isSyncEnabled()) {
|
||||
if (userKoboShelf != null) {
|
||||
shelfService.deleteShelf(userKoboShelf.getId());
|
||||
}
|
||||
} else {
|
||||
ensureKoboShelfExists(user.getId());
|
||||
}
|
||||
entity.setSyncEnabled(enabled);
|
||||
repository.save(entity);
|
||||
entity.setSyncEnabled(settings.isSyncEnabled());
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public KoboSyncSettings updateProgressThresholds(Float readingThreshold, Float finishedThreshold) {
|
||||
BookLoreUser user = authenticationService.getAuthenticatedUser();
|
||||
KoboUserSettingsEntity entity = repository.findByUserId(user.getId())
|
||||
.orElseGet(() -> initDefaultSettings(user.getId()));
|
||||
if (settings.getProgressMarkAsReadingThreshold() != null) {
|
||||
entity.setProgressMarkAsReadingThreshold(settings.getProgressMarkAsReadingThreshold());
|
||||
}
|
||||
if (settings.getProgressMarkAsFinishedThreshold() != null) {
|
||||
entity.setProgressMarkAsFinishedThreshold(settings.getProgressMarkAsFinishedThreshold());
|
||||
}
|
||||
|
||||
if (readingThreshold != null) {
|
||||
entity.setProgressMarkAsReadingThreshold(readingThreshold);
|
||||
}
|
||||
if (finishedThreshold != null) {
|
||||
entity.setProgressMarkAsFinishedThreshold(finishedThreshold);
|
||||
}
|
||||
entity.setAutoAddToShelf(settings.isAutoAddToShelf());
|
||||
|
||||
repository.save(entity);
|
||||
return mapToDto(entity);
|
||||
@@ -105,6 +103,7 @@ public class KoboSettingsService {
|
||||
ShelfCreateRequest.builder()
|
||||
.name(ShelfType.KOBO.getName())
|
||||
.icon(ShelfType.KOBO.getIcon())
|
||||
.iconType(IconType.PRIME_NG)
|
||||
.build()
|
||||
);
|
||||
}
|
||||
@@ -122,6 +121,7 @@ public class KoboSettingsService {
|
||||
dto.setSyncEnabled(entity.isSyncEnabled());
|
||||
dto.setProgressMarkAsReadingThreshold(entity.getProgressMarkAsReadingThreshold());
|
||||
dto.setProgressMarkAsFinishedThreshold(entity.getProgressMarkAsFinishedThreshold());
|
||||
dto.setAutoAddToShelf(entity.isAutoAddToShelf());
|
||||
return dto;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ import com.adityachandel.booklore.model.enums.LibraryScanMode;
|
||||
import com.adityachandel.booklore.service.event.BookEventBroadcaster;
|
||||
import com.adityachandel.booklore.service.fileprocessor.BookFileProcessor;
|
||||
import com.adityachandel.booklore.service.fileprocessor.BookFileProcessorRegistry;
|
||||
import com.adityachandel.booklore.service.kobo.KoboAutoShelfService;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Component;
|
||||
@@ -22,6 +23,7 @@ public class FileAsBookProcessor implements LibraryFileProcessor {
|
||||
|
||||
private final BookEventBroadcaster bookEventBroadcaster;
|
||||
private final BookFileProcessorRegistry processorRegistry;
|
||||
private final KoboAutoShelfService koboAutoShelfService;
|
||||
|
||||
@Override
|
||||
public LibraryScanMode getScanMode() {
|
||||
@@ -43,6 +45,7 @@ public class FileAsBookProcessor implements LibraryFileProcessor {
|
||||
FileProcessResult result = processLibraryFile(libraryFile);
|
||||
if (result != null) {
|
||||
bookEventBroadcaster.broadcastBookAddEvent(result.getBook());
|
||||
koboAutoShelfService.autoAddBookToKoboShelves(result.getBook().getId());
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("Failed to process file '{}': {}", libraryFile.getFileName(), e.getMessage());
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
ALTER TABLE kobo_user_settings
|
||||
ADD COLUMN IF NOT EXISTS auto_add_to_shelf BOOLEAN NOT NULL DEFAULT FALSE;
|
||||
@@ -0,0 +1,268 @@
|
||||
package com.adityachandel.booklore.service.kobo;
|
||||
|
||||
import com.adityachandel.booklore.model.entity.BookEntity;
|
||||
import com.adityachandel.booklore.model.entity.BookLoreUserEntity;
|
||||
import com.adityachandel.booklore.model.entity.KoboUserSettingsEntity;
|
||||
import com.adityachandel.booklore.model.entity.ShelfEntity;
|
||||
import com.adityachandel.booklore.model.enums.ShelfType;
|
||||
import com.adityachandel.booklore.repository.BookRepository;
|
||||
import com.adityachandel.booklore.repository.KoboUserSettingsRepository;
|
||||
import com.adityachandel.booklore.repository.ShelfRepository;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.InjectMocks;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
import static org.mockito.ArgumentMatchers.*;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class KoboAutoShelfServiceTest {
|
||||
|
||||
@Mock
|
||||
private KoboUserSettingsRepository koboUserSettingsRepository;
|
||||
|
||||
@Mock
|
||||
private ShelfRepository shelfRepository;
|
||||
|
||||
@Mock
|
||||
private BookRepository bookRepository;
|
||||
|
||||
@Mock
|
||||
private KoboCompatibilityService koboCompatibilityService;
|
||||
|
||||
@InjectMocks
|
||||
private KoboAutoShelfService koboAutoShelfService;
|
||||
|
||||
private BookEntity testBook;
|
||||
private BookLoreUserEntity testUser1;
|
||||
private BookLoreUserEntity testUser2;
|
||||
private ShelfEntity koboShelf1;
|
||||
private ShelfEntity koboShelf2;
|
||||
private KoboUserSettingsEntity settings1;
|
||||
private KoboUserSettingsEntity settings2;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
testBook = BookEntity.builder()
|
||||
.id(1L)
|
||||
.shelves(new HashSet<>())
|
||||
.build();
|
||||
|
||||
testUser1 = BookLoreUserEntity.builder()
|
||||
.id(100L)
|
||||
.build();
|
||||
|
||||
testUser2 = BookLoreUserEntity.builder()
|
||||
.id(200L)
|
||||
.build();
|
||||
|
||||
koboShelf1 = ShelfEntity.builder()
|
||||
.id(10L)
|
||||
.name(ShelfType.KOBO.getName())
|
||||
.user(testUser1)
|
||||
.build();
|
||||
|
||||
koboShelf2 = ShelfEntity.builder()
|
||||
.id(20L)
|
||||
.name(ShelfType.KOBO.getName())
|
||||
.user(testUser2)
|
||||
.build();
|
||||
|
||||
settings1 = KoboUserSettingsEntity.builder()
|
||||
.userId(100L)
|
||||
.autoAddToShelf(true)
|
||||
.syncEnabled(true)
|
||||
.build();
|
||||
|
||||
settings2 = KoboUserSettingsEntity.builder()
|
||||
.userId(200L)
|
||||
.autoAddToShelf(true)
|
||||
.syncEnabled(true)
|
||||
.build();
|
||||
}
|
||||
|
||||
@Test
|
||||
void autoAddBookToKoboShelves_withNullBookId_shouldReturnEarly() {
|
||||
koboAutoShelfService.autoAddBookToKoboShelves(null);
|
||||
|
||||
verify(bookRepository, never()).findById(anyLong());
|
||||
verify(koboUserSettingsRepository, never()).findByAutoAddToShelfTrueAndSyncEnabledTrue();
|
||||
verify(shelfRepository, never()).findByUserIdInAndName(anyList(), anyString());
|
||||
verify(bookRepository, never()).save(any());
|
||||
}
|
||||
|
||||
@Test
|
||||
void autoAddBookToKoboShelves_withNonExistentBook_shouldReturnEarly() {
|
||||
when(bookRepository.findById(1L)).thenReturn(Optional.empty());
|
||||
|
||||
koboAutoShelfService.autoAddBookToKoboShelves(1L);
|
||||
|
||||
verify(bookRepository).findById(1L);
|
||||
verify(koboUserSettingsRepository, never()).findByAutoAddToShelfTrueAndSyncEnabledTrue();
|
||||
verify(shelfRepository, never()).findByUserIdInAndName(anyList(), anyString());
|
||||
verify(bookRepository, never()).save(any());
|
||||
}
|
||||
|
||||
@Test
|
||||
void autoAddBookToKoboShelves_withIncompatibleBook_shouldReturnEarly() {
|
||||
when(bookRepository.findById(1L)).thenReturn(Optional.of(testBook));
|
||||
when(koboCompatibilityService.isBookSupportedForKobo(testBook)).thenReturn(false);
|
||||
|
||||
koboAutoShelfService.autoAddBookToKoboShelves(1L);
|
||||
|
||||
verify(bookRepository).findById(1L);
|
||||
verify(koboCompatibilityService).isBookSupportedForKobo(testBook);
|
||||
verify(koboUserSettingsRepository, never()).findByAutoAddToShelfTrueAndSyncEnabledTrue();
|
||||
verify(shelfRepository, never()).findByUserIdInAndName(anyList(), anyString());
|
||||
verify(bookRepository, never()).save(any());
|
||||
}
|
||||
|
||||
@Test
|
||||
void autoAddBookToKoboShelves_withNoEligibleUsers_shouldReturnEarly() {
|
||||
when(bookRepository.findById(1L)).thenReturn(Optional.of(testBook));
|
||||
when(koboCompatibilityService.isBookSupportedForKobo(testBook)).thenReturn(true);
|
||||
when(koboUserSettingsRepository.findByAutoAddToShelfTrueAndSyncEnabledTrue())
|
||||
.thenReturn(Collections.emptyList());
|
||||
|
||||
koboAutoShelfService.autoAddBookToKoboShelves(1L);
|
||||
|
||||
verify(bookRepository).findById(1L);
|
||||
verify(koboCompatibilityService).isBookSupportedForKobo(testBook);
|
||||
verify(koboUserSettingsRepository).findByAutoAddToShelfTrueAndSyncEnabledTrue();
|
||||
verify(shelfRepository, never()).findByUserIdInAndName(anyList(), anyString());
|
||||
verify(bookRepository, never()).save(any());
|
||||
}
|
||||
|
||||
@Test
|
||||
void autoAddBookToKoboShelves_successfully_shouldAddBookToShelves() {
|
||||
when(bookRepository.findById(1L)).thenReturn(Optional.of(testBook));
|
||||
when(koboCompatibilityService.isBookSupportedForKobo(testBook)).thenReturn(true);
|
||||
when(koboUserSettingsRepository.findByAutoAddToShelfTrueAndSyncEnabledTrue())
|
||||
.thenReturn(List.of(settings1, settings2));
|
||||
when(shelfRepository.findByUserIdInAndName(List.of(100L, 200L), ShelfType.KOBO.getName()))
|
||||
.thenReturn(List.of(koboShelf1, koboShelf2));
|
||||
|
||||
koboAutoShelfService.autoAddBookToKoboShelves(1L);
|
||||
|
||||
verify(bookRepository).findById(1L);
|
||||
verify(koboCompatibilityService).isBookSupportedForKobo(testBook);
|
||||
verify(koboUserSettingsRepository).findByAutoAddToShelfTrueAndSyncEnabledTrue();
|
||||
verify(shelfRepository).findByUserIdInAndName(List.of(100L, 200L), ShelfType.KOBO.getName());
|
||||
verify(bookRepository).save(testBook);
|
||||
|
||||
assert testBook.getShelves().contains(koboShelf1);
|
||||
assert testBook.getShelves().contains(koboShelf2);
|
||||
assert testBook.getShelves().size() == 2;
|
||||
}
|
||||
|
||||
@Test
|
||||
void autoAddBookToKoboShelves_withOneUserMissingShelf_shouldAddOnlyToExistingShelves() {
|
||||
when(bookRepository.findById(1L)).thenReturn(Optional.of(testBook));
|
||||
when(koboCompatibilityService.isBookSupportedForKobo(testBook)).thenReturn(true);
|
||||
when(koboUserSettingsRepository.findByAutoAddToShelfTrueAndSyncEnabledTrue())
|
||||
.thenReturn(List.of(settings1, settings2));
|
||||
when(shelfRepository.findByUserIdInAndName(List.of(100L, 200L), ShelfType.KOBO.getName()))
|
||||
.thenReturn(List.of(koboShelf1));
|
||||
|
||||
koboAutoShelfService.autoAddBookToKoboShelves(1L);
|
||||
|
||||
verify(bookRepository).save(testBook);
|
||||
assert testBook.getShelves().contains(koboShelf1);
|
||||
assert !testBook.getShelves().contains(koboShelf2);
|
||||
assert testBook.getShelves().size() == 1;
|
||||
}
|
||||
|
||||
@Test
|
||||
void autoAddBookToKoboShelves_withBookAlreadyOnShelf_shouldNotDuplicate() {
|
||||
testBook.getShelves().add(koboShelf1);
|
||||
|
||||
when(bookRepository.findById(1L)).thenReturn(Optional.of(testBook));
|
||||
when(koboCompatibilityService.isBookSupportedForKobo(testBook)).thenReturn(true);
|
||||
when(koboUserSettingsRepository.findByAutoAddToShelfTrueAndSyncEnabledTrue())
|
||||
.thenReturn(List.of(settings1, settings2));
|
||||
when(shelfRepository.findByUserIdInAndName(List.of(100L, 200L), ShelfType.KOBO.getName()))
|
||||
.thenReturn(List.of(koboShelf1, koboShelf2));
|
||||
|
||||
koboAutoShelfService.autoAddBookToKoboShelves(1L);
|
||||
|
||||
verify(bookRepository).save(testBook);
|
||||
assert testBook.getShelves().contains(koboShelf1);
|
||||
assert testBook.getShelves().contains(koboShelf2);
|
||||
assert testBook.getShelves().size() == 2;
|
||||
}
|
||||
|
||||
@Test
|
||||
void autoAddBookToKoboShelves_withBookOnAllShelves_shouldNotSave() {
|
||||
testBook.getShelves().add(koboShelf1);
|
||||
testBook.getShelves().add(koboShelf2);
|
||||
|
||||
when(bookRepository.findById(1L)).thenReturn(Optional.of(testBook));
|
||||
when(koboCompatibilityService.isBookSupportedForKobo(testBook)).thenReturn(true);
|
||||
when(koboUserSettingsRepository.findByAutoAddToShelfTrueAndSyncEnabledTrue())
|
||||
.thenReturn(List.of(settings1, settings2));
|
||||
when(shelfRepository.findByUserIdInAndName(List.of(100L, 200L), ShelfType.KOBO.getName()))
|
||||
.thenReturn(List.of(koboShelf1, koboShelf2));
|
||||
|
||||
koboAutoShelfService.autoAddBookToKoboShelves(1L);
|
||||
|
||||
verify(bookRepository, never()).save(any());
|
||||
assert testBook.getShelves().size() == 2;
|
||||
}
|
||||
|
||||
@Test
|
||||
void autoAddBookToKoboShelves_withNoKoboShelvesFound_shouldNotSave() {
|
||||
when(bookRepository.findById(1L)).thenReturn(Optional.of(testBook));
|
||||
when(koboCompatibilityService.isBookSupportedForKobo(testBook)).thenReturn(true);
|
||||
when(koboUserSettingsRepository.findByAutoAddToShelfTrueAndSyncEnabledTrue())
|
||||
.thenReturn(List.of(settings1, settings2));
|
||||
when(shelfRepository.findByUserIdInAndName(List.of(100L, 200L), ShelfType.KOBO.getName()))
|
||||
.thenReturn(Collections.emptyList());
|
||||
|
||||
koboAutoShelfService.autoAddBookToKoboShelves(1L);
|
||||
|
||||
verify(shelfRepository).findByUserIdInAndName(List.of(100L, 200L), ShelfType.KOBO.getName());
|
||||
verify(bookRepository, never()).save(any());
|
||||
assert testBook.getShelves().isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
void autoAddBookToKoboShelves_withSingleUser_shouldAddToSingleShelf() {
|
||||
when(bookRepository.findById(1L)).thenReturn(Optional.of(testBook));
|
||||
when(koboCompatibilityService.isBookSupportedForKobo(testBook)).thenReturn(true);
|
||||
when(koboUserSettingsRepository.findByAutoAddToShelfTrueAndSyncEnabledTrue())
|
||||
.thenReturn(List.of(settings1));
|
||||
when(shelfRepository.findByUserIdInAndName(List.of(100L), ShelfType.KOBO.getName()))
|
||||
.thenReturn(List.of(koboShelf1));
|
||||
|
||||
koboAutoShelfService.autoAddBookToKoboShelves(1L);
|
||||
|
||||
verify(bookRepository).save(testBook);
|
||||
assert testBook.getShelves().contains(koboShelf1);
|
||||
assert testBook.getShelves().size() == 1;
|
||||
}
|
||||
|
||||
@Test
|
||||
void autoAddBookToKoboShelves_withNullShelves_shouldInitializeAndAdd() {
|
||||
testBook = BookEntity.builder()
|
||||
.id(1L)
|
||||
.shelves(null)
|
||||
.build();
|
||||
|
||||
when(bookRepository.findById(1L)).thenReturn(Optional.of(testBook));
|
||||
when(koboCompatibilityService.isBookSupportedForKobo(testBook)).thenReturn(true);
|
||||
when(koboUserSettingsRepository.findByAutoAddToShelfTrueAndSyncEnabledTrue())
|
||||
.thenReturn(List.of(settings1));
|
||||
when(shelfRepository.findByUserIdInAndName(List.of(100L), ShelfType.KOBO.getName()))
|
||||
.thenReturn(List.of(koboShelf1));
|
||||
|
||||
try {
|
||||
koboAutoShelfService.autoAddBookToKoboShelves(1L);
|
||||
} catch (NullPointerException e) {
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,279 @@
|
||||
package com.adityachandel.booklore.service.kobo;
|
||||
|
||||
import com.adityachandel.booklore.config.security.service.AuthenticationService;
|
||||
import com.adityachandel.booklore.model.dto.BookLoreUser;
|
||||
import com.adityachandel.booklore.model.dto.KoboSyncSettings;
|
||||
import com.adityachandel.booklore.model.dto.Shelf;
|
||||
import com.adityachandel.booklore.model.dto.request.ShelfCreateRequest;
|
||||
import com.adityachandel.booklore.model.entity.KoboUserSettingsEntity;
|
||||
import com.adityachandel.booklore.model.entity.ShelfEntity;
|
||||
import com.adityachandel.booklore.model.enums.IconType;
|
||||
import com.adityachandel.booklore.model.enums.ShelfType;
|
||||
import com.adityachandel.booklore.repository.KoboUserSettingsRepository;
|
||||
import com.adityachandel.booklore.service.ShelfService;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.*;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class KoboSettingsServiceTest {
|
||||
|
||||
@Mock
|
||||
private KoboUserSettingsRepository repository;
|
||||
@Mock
|
||||
private AuthenticationService authenticationService;
|
||||
@Mock
|
||||
private ShelfService shelfService;
|
||||
|
||||
@InjectMocks
|
||||
private KoboSettingsService service;
|
||||
|
||||
private BookLoreUser user;
|
||||
private KoboUserSettingsEntity settingsEntity;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
user = BookLoreUser.builder().id(1L).build();
|
||||
settingsEntity = KoboUserSettingsEntity.builder()
|
||||
.id(10L)
|
||||
.userId(1L)
|
||||
.token("token")
|
||||
.syncEnabled(true)
|
||||
.autoAddToShelf(true)
|
||||
.progressMarkAsReadingThreshold(0.5f)
|
||||
.progressMarkAsFinishedThreshold(0.9f)
|
||||
.build();
|
||||
}
|
||||
|
||||
@Test
|
||||
void getCurrentUserSettings_existingSettings() {
|
||||
when(authenticationService.getAuthenticatedUser()).thenReturn(user);
|
||||
when(repository.findByUserId(1L)).thenReturn(Optional.of(settingsEntity));
|
||||
|
||||
KoboSyncSettings dto = service.getCurrentUserSettings();
|
||||
|
||||
assertEquals(settingsEntity.getId(), dto.getId());
|
||||
assertEquals(settingsEntity.getUserId().toString(), dto.getUserId());
|
||||
assertEquals(settingsEntity.getToken(), dto.getToken());
|
||||
assertTrue(dto.isSyncEnabled());
|
||||
assertTrue(dto.isAutoAddToShelf());
|
||||
assertEquals(settingsEntity.getProgressMarkAsReadingThreshold(), dto.getProgressMarkAsReadingThreshold());
|
||||
assertEquals(settingsEntity.getProgressMarkAsFinishedThreshold(), dto.getProgressMarkAsFinishedThreshold());
|
||||
}
|
||||
|
||||
@Test
|
||||
void getCurrentUserSettings_noSettings_createsDefault() {
|
||||
when(authenticationService.getAuthenticatedUser()).thenReturn(user);
|
||||
when(repository.findByUserId(1L)).thenReturn(Optional.empty());
|
||||
when(repository.save(any())).thenAnswer(invocation -> invocation.getArgument(0));
|
||||
when(shelfService.getShelf(eq(1L), eq(ShelfType.KOBO.getName()))).thenReturn(Optional.empty());
|
||||
doReturn(Shelf.builder().id(100L).build()).when(shelfService).createShelf(any(ShelfCreateRequest.class));
|
||||
|
||||
KoboSyncSettings dto = service.getCurrentUserSettings();
|
||||
|
||||
assertEquals(user.getId().toString(), dto.getUserId());
|
||||
assertNotNull(dto.getToken());
|
||||
assertFalse(dto.isSyncEnabled());
|
||||
}
|
||||
|
||||
@Test
|
||||
void createOrUpdateToken_existingSettings() {
|
||||
when(authenticationService.getAuthenticatedUser()).thenReturn(user);
|
||||
when(repository.findByUserId(1L)).thenReturn(Optional.of(settingsEntity));
|
||||
when(repository.save(any())).thenAnswer(invocation -> invocation.getArgument(0));
|
||||
when(shelfService.getShelf(eq(1L), eq(ShelfType.KOBO.getName()))).thenReturn(Optional.of(ShelfEntity.builder().id(100L).build()));
|
||||
|
||||
KoboSyncSettings dto = service.createOrUpdateToken();
|
||||
|
||||
assertEquals(settingsEntity.getUserId().toString(), dto.getUserId());
|
||||
assertNotNull(dto.getToken());
|
||||
assertNotEquals("token", dto.getToken());
|
||||
}
|
||||
|
||||
@Test
|
||||
void createOrUpdateToken_noSettings_createsNew() {
|
||||
when(authenticationService.getAuthenticatedUser()).thenReturn(user);
|
||||
when(repository.findByUserId(1L)).thenReturn(Optional.empty());
|
||||
when(repository.save(any())).thenAnswer(invocation -> invocation.getArgument(0));
|
||||
when(shelfService.getShelf(eq(1L), eq(ShelfType.KOBO.getName()))).thenReturn(Optional.empty());
|
||||
doReturn(Shelf.builder().id(100L).build()).when(shelfService).createShelf(any(ShelfCreateRequest.class));
|
||||
|
||||
KoboSyncSettings dto = service.createOrUpdateToken();
|
||||
|
||||
assertEquals(user.getId().toString(), dto.getUserId());
|
||||
assertNotNull(dto.getToken());
|
||||
assertFalse(dto.isSyncEnabled());
|
||||
}
|
||||
|
||||
@Test
|
||||
void updateSettings_disableSync_deletesShelf() {
|
||||
when(authenticationService.getAuthenticatedUser()).thenReturn(user);
|
||||
when(repository.findByUserId(1L)).thenReturn(Optional.of(settingsEntity));
|
||||
Shelf shelf = Shelf.builder().id(100L).build();
|
||||
when(shelfService.getUserKoboShelf()).thenReturn(shelf);
|
||||
when(repository.save(any())).thenAnswer(invocation -> invocation.getArgument(0));
|
||||
|
||||
KoboSyncSettings update = new KoboSyncSettings();
|
||||
update.setSyncEnabled(false);
|
||||
update.setAutoAddToShelf(false);
|
||||
|
||||
KoboSyncSettings dto = service.updateSettings(update);
|
||||
|
||||
verify(shelfService).deleteShelf(100L);
|
||||
assertFalse(dto.isSyncEnabled());
|
||||
assertFalse(dto.isAutoAddToShelf());
|
||||
}
|
||||
|
||||
@Test
|
||||
void updateSettings_enableSync_createsShelf() {
|
||||
when(authenticationService.getAuthenticatedUser()).thenReturn(user);
|
||||
settingsEntity.setSyncEnabled(false);
|
||||
when(repository.findByUserId(1L)).thenReturn(Optional.of(settingsEntity));
|
||||
when(shelfService.getUserKoboShelf()).thenReturn(null);
|
||||
when(repository.save(any())).thenAnswer(invocation -> invocation.getArgument(0));
|
||||
when(shelfService.getShelf(eq(1L), eq(ShelfType.KOBO.getName()))).thenReturn(Optional.empty());
|
||||
doReturn(Shelf.builder().id(100L).build()).when(shelfService).createShelf(any(ShelfCreateRequest.class));
|
||||
|
||||
KoboSyncSettings update = new KoboSyncSettings();
|
||||
update.setSyncEnabled(true);
|
||||
update.setAutoAddToShelf(true);
|
||||
|
||||
KoboSyncSettings dto = service.updateSettings(update);
|
||||
|
||||
verify(shelfService).createShelf(any(ShelfCreateRequest.class));
|
||||
assertTrue(dto.isSyncEnabled());
|
||||
assertTrue(dto.isAutoAddToShelf());
|
||||
}
|
||||
|
||||
@Test
|
||||
void updateSettings_updatesThresholds() {
|
||||
when(authenticationService.getAuthenticatedUser()).thenReturn(user);
|
||||
when(repository.findByUserId(1L)).thenReturn(Optional.of(settingsEntity));
|
||||
when(repository.save(any())).thenAnswer(invocation -> invocation.getArgument(0));
|
||||
|
||||
KoboSyncSettings update = new KoboSyncSettings();
|
||||
update.setSyncEnabled(true);
|
||||
update.setAutoAddToShelf(true);
|
||||
update.setProgressMarkAsReadingThreshold(0.7f);
|
||||
update.setProgressMarkAsFinishedThreshold(0.95f);
|
||||
|
||||
KoboSyncSettings dto = service.updateSettings(update);
|
||||
|
||||
assertEquals((Float)0.7f, dto.getProgressMarkAsReadingThreshold());
|
||||
assertEquals((Float)0.95f, dto.getProgressMarkAsFinishedThreshold());
|
||||
}
|
||||
|
||||
@Test
|
||||
void updateSettings_nullThresholds_shouldNotChangeExisting() {
|
||||
when(authenticationService.getAuthenticatedUser()).thenReturn(user);
|
||||
when(repository.findByUserId(1L)).thenReturn(Optional.of(settingsEntity));
|
||||
when(repository.save(any())).thenAnswer(invocation -> invocation.getArgument(0));
|
||||
|
||||
Float originalReading = settingsEntity.getProgressMarkAsReadingThreshold();
|
||||
Float originalFinished = settingsEntity.getProgressMarkAsFinishedThreshold();
|
||||
|
||||
KoboSyncSettings update = new KoboSyncSettings();
|
||||
update.setSyncEnabled(true);
|
||||
update.setAutoAddToShelf(true);
|
||||
update.setProgressMarkAsReadingThreshold(null);
|
||||
update.setProgressMarkAsFinishedThreshold(null);
|
||||
|
||||
KoboSyncSettings dto = service.updateSettings(update);
|
||||
|
||||
assertEquals(originalReading, dto.getProgressMarkAsReadingThreshold());
|
||||
assertEquals(originalFinished, dto.getProgressMarkAsFinishedThreshold());
|
||||
}
|
||||
|
||||
@Test
|
||||
void getCurrentUserSettings_settingsWithNullToken_shouldReturnDtoWithNullToken() {
|
||||
settingsEntity.setToken(null);
|
||||
when(authenticationService.getAuthenticatedUser()).thenReturn(user);
|
||||
when(repository.findByUserId(1L)).thenReturn(Optional.of(settingsEntity));
|
||||
|
||||
KoboSyncSettings dto = service.getCurrentUserSettings();
|
||||
|
||||
assertNull(dto.getToken());
|
||||
}
|
||||
|
||||
@Test
|
||||
void getCurrentUserSettings_noAuthenticatedUser_shouldThrowException() {
|
||||
when(authenticationService.getAuthenticatedUser()).thenReturn(null);
|
||||
assertThrows(NullPointerException.class, () -> service.getCurrentUserSettings());
|
||||
}
|
||||
|
||||
@Test
|
||||
void updateSettings_getUserKoboShelfReturnsNull_shouldNotThrow() {
|
||||
when(authenticationService.getAuthenticatedUser()).thenReturn(user);
|
||||
when(repository.findByUserId(1L)).thenReturn(Optional.of(settingsEntity));
|
||||
when(shelfService.getUserKoboShelf()).thenReturn(null);
|
||||
when(repository.save(any())).thenAnswer(invocation -> invocation.getArgument(0));
|
||||
|
||||
KoboSyncSettings update = new KoboSyncSettings();
|
||||
update.setSyncEnabled(false);
|
||||
update.setAutoAddToShelf(false);
|
||||
|
||||
assertDoesNotThrow(() -> service.updateSettings(update));
|
||||
}
|
||||
|
||||
@Test
|
||||
void ensureKoboShelfExists_doesNotCreateIfExists() throws Exception {
|
||||
when(shelfService.getShelf(eq(1L), eq(ShelfType.KOBO.getName()))).thenReturn(Optional.of(ShelfEntity.builder().id(100L).build()));
|
||||
|
||||
var method = service.getClass().getDeclaredMethod("ensureKoboShelfExists", Long.class);
|
||||
method.setAccessible(true);
|
||||
assertDoesNotThrow(() -> method.invoke(service, 1L));
|
||||
verify(shelfService, never()).createShelf(any());
|
||||
}
|
||||
|
||||
@Test
|
||||
void ensureKoboShelfExists_createsIfMissing() throws Exception {
|
||||
when(shelfService.getShelf(eq(1L), eq(ShelfType.KOBO.getName()))).thenReturn(Optional.empty());
|
||||
doReturn(Shelf.builder().id(100L).build()).when(shelfService).createShelf(any(ShelfCreateRequest.class));
|
||||
|
||||
var method = service.getClass().getDeclaredMethod("ensureKoboShelfExists", Long.class);
|
||||
method.setAccessible(true);
|
||||
assertDoesNotThrow(() -> method.invoke(service, 1L));
|
||||
verify(shelfService).createShelf(any(ShelfCreateRequest.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void ensureKoboShelfExists_idempotentIfCalledTwice() throws Exception {
|
||||
when(shelfService.getShelf(eq(1L), eq(ShelfType.KOBO.getName())))
|
||||
.thenReturn(Optional.empty())
|
||||
.thenReturn(Optional.of(ShelfEntity.builder().id(100L).build()));
|
||||
doReturn(Shelf.builder().id(100L).build()).when(shelfService).createShelf(any(ShelfCreateRequest.class));
|
||||
|
||||
var method = service.getClass().getDeclaredMethod("ensureKoboShelfExists", Long.class);
|
||||
method.setAccessible(true);
|
||||
|
||||
method.invoke(service, 1L);
|
||||
method.invoke(service, 1L);
|
||||
|
||||
verify(shelfService, times(1)).createShelf(any(ShelfCreateRequest.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void createOrUpdateToken_multipleCalls_generateDifferentTokens() {
|
||||
when(authenticationService.getAuthenticatedUser()).thenReturn(user);
|
||||
when(repository.findByUserId(1L))
|
||||
.thenReturn(Optional.empty())
|
||||
.thenReturn(Optional.of(settingsEntity));
|
||||
when(repository.save(any())).thenAnswer(invocation -> invocation.getArgument(0));
|
||||
when(shelfService.getShelf(eq(1L), eq(ShelfType.KOBO.getName()))).thenReturn(Optional.empty());
|
||||
doReturn(Shelf.builder().id(100L).build()).when(shelfService).createShelf(any(ShelfCreateRequest.class));
|
||||
|
||||
KoboSyncSettings dto1 = service.createOrUpdateToken();
|
||||
// Simulate a new call with an existing entity
|
||||
KoboSyncSettings dto2 = service.createOrUpdateToken();
|
||||
|
||||
assertNotEquals(dto1.getToken(), dto2.getToken());
|
||||
}
|
||||
}
|
||||
@@ -44,6 +44,26 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@if (koboSyncSettings.syncEnabled) {
|
||||
<div class="setting-item">
|
||||
<div class="setting-info">
|
||||
<div class="setting-label-row">
|
||||
<label class="setting-label">Auto-Add New Books to Kobo Shelf</label>
|
||||
<p-toggle-switch
|
||||
id="autoAddToShelf"
|
||||
name="autoAddToShelf"
|
||||
[(ngModel)]="koboSyncSettings.autoAddToShelf"
|
||||
(ngModelChange)="onAutoAddToggle()">
|
||||
</p-toggle-switch>
|
||||
</div>
|
||||
<p class="setting-description">
|
||||
When enabled, newly added books will be automatically added to your Kobo shelf for syncing.
|
||||
This eliminates the need to manually add each book to the shelf.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
@if (koboSyncSettings.syncEnabled) {
|
||||
<div class="setting-item">
|
||||
<div class="setting-info">
|
||||
|
||||
@@ -37,6 +37,7 @@ export class KoboSyncSettingsComponent implements OnInit, OnDestroy {
|
||||
|
||||
private readonly destroy$ = new Subject<void>();
|
||||
private readonly sliderChange$ = new Subject<void>();
|
||||
private readonly progressThresholdChange$ = new Subject<void>();
|
||||
|
||||
hasKoboTokenPermission = false;
|
||||
isAdmin = false;
|
||||
@@ -55,7 +56,8 @@ export class KoboSyncSettingsComponent implements OnInit, OnDestroy {
|
||||
token: '',
|
||||
syncEnabled: false,
|
||||
progressMarkAsReadingThreshold: 1,
|
||||
progressMarkAsFinishedThreshold: 99
|
||||
progressMarkAsFinishedThreshold: 99,
|
||||
autoAddToShelf: true
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
@@ -70,6 +72,13 @@ export class KoboSyncSettingsComponent implements OnInit, OnDestroy {
|
||||
).subscribe(() => {
|
||||
this.saveSettings();
|
||||
});
|
||||
|
||||
this.progressThresholdChange$.pipe(
|
||||
debounceTime(500),
|
||||
takeUntil(this.destroy$)
|
||||
).subscribe(() => {
|
||||
this.updateKoboSettings('Progress thresholds updated successfully');
|
||||
});
|
||||
}
|
||||
|
||||
private setupUserStateSubscription() {
|
||||
@@ -108,6 +117,7 @@ export class KoboSyncSettingsComponent implements OnInit, OnDestroy {
|
||||
this.koboSyncSettings.syncEnabled = settings.syncEnabled;
|
||||
this.koboSyncSettings.progressMarkAsReadingThreshold = settings.progressMarkAsReadingThreshold ?? 1;
|
||||
this.koboSyncSettings.progressMarkAsFinishedThreshold = settings.progressMarkAsFinishedThreshold ?? 99;
|
||||
this.koboSyncSettings.autoAddToShelf = settings.autoAddToShelf ?? false;
|
||||
this.credentialsSaved = !!settings.token;
|
||||
},
|
||||
error: () => {
|
||||
@@ -191,55 +201,44 @@ export class KoboSyncSettingsComponent implements OnInit, OnDestroy {
|
||||
message: 'Disabling Kobo sync will delete your Kobo shelf. Are you sure you want to proceed?',
|
||||
header: 'Confirm Disable',
|
||||
icon: 'pi pi-exclamation-triangle',
|
||||
accept: () => this.performToggle(false),
|
||||
accept: () => this.updateKoboSettings('Kobo sync disabled'),
|
||||
reject: () => {
|
||||
this.koboSyncSettings.syncEnabled = true;
|
||||
}
|
||||
});
|
||||
} else {
|
||||
this.performToggle(true);
|
||||
this.updateKoboSettings('Kobo sync enabled');
|
||||
}
|
||||
}
|
||||
|
||||
private performToggle(enabled: boolean) {
|
||||
this.koboService.toggleSync(enabled).subscribe({
|
||||
next: () => {
|
||||
this.messageService.add({
|
||||
severity: 'success',
|
||||
summary: 'Sync Updated',
|
||||
detail: enabled
|
||||
? 'Kobo sync enabled'
|
||||
: 'Kobo sync disabled'
|
||||
});
|
||||
this.shelfService.reloadShelves();
|
||||
},
|
||||
error: () => {
|
||||
this.messageService.add({
|
||||
severity: 'error',
|
||||
summary: 'Error',
|
||||
detail: 'Failed to update sync setting'
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
onProgressThresholdsChange() {
|
||||
this.koboService.updateProgressThresholds(
|
||||
this.koboSyncSettings.progressMarkAsReadingThreshold,
|
||||
this.koboSyncSettings.progressMarkAsFinishedThreshold
|
||||
).subscribe({
|
||||
this.progressThresholdChange$.next();
|
||||
}
|
||||
|
||||
onAutoAddToggle() {
|
||||
const message = this.koboSyncSettings.autoAddToShelf
|
||||
? 'New books will be automatically added to Kobo shelf'
|
||||
: 'Auto-add to Kobo shelf disabled';
|
||||
this.updateKoboSettings(message);
|
||||
}
|
||||
|
||||
private updateKoboSettings(successMessage: string) {
|
||||
this.koboService.updateSettings(this.koboSyncSettings).subscribe({
|
||||
next: () => {
|
||||
this.messageService.add({
|
||||
severity: 'success',
|
||||
summary: 'Thresholds Updated',
|
||||
detail: 'Progress thresholds updated successfully'
|
||||
summary: 'Settings Updated',
|
||||
detail: successMessage
|
||||
});
|
||||
if (!this.koboSyncSettings.syncEnabled) {
|
||||
this.shelfService.reloadShelves();
|
||||
}
|
||||
},
|
||||
error: () => {
|
||||
this.messageService.add({
|
||||
severity: 'error',
|
||||
summary: 'Error',
|
||||
detail: 'Failed to update progress thresholds'
|
||||
detail: 'Failed to update Kobo settings'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
@@ -8,6 +8,7 @@ export interface KoboSyncSettings {
|
||||
syncEnabled: boolean;
|
||||
progressMarkAsReadingThreshold?: number;
|
||||
progressMarkAsFinishedThreshold?: number;
|
||||
autoAddToShelf: boolean;
|
||||
}
|
||||
|
||||
@Injectable({
|
||||
@@ -22,22 +23,10 @@ export class KoboService {
|
||||
}
|
||||
|
||||
createOrUpdateToken(): Observable<KoboSyncSettings> {
|
||||
return this.http.put<KoboSyncSettings>(`${this.baseUrl}`, null);
|
||||
return this.http.put<KoboSyncSettings>(`${this.baseUrl}/token`, null);
|
||||
}
|
||||
|
||||
toggleSync(enabled: boolean): Observable<void> {
|
||||
const params = new HttpParams().set('enabled', enabled.toString());
|
||||
return this.http.put<void>(`${this.baseUrl}/sync`, null, { params });
|
||||
}
|
||||
|
||||
updateProgressThresholds(readingThreshold?: number, finishedThreshold?: number): Observable<KoboSyncSettings> {
|
||||
let params = new HttpParams();
|
||||
if (readingThreshold !== undefined && readingThreshold !== null) {
|
||||
params = params.set('readingThreshold', readingThreshold.toString());
|
||||
}
|
||||
if (finishedThreshold !== undefined && finishedThreshold !== null) {
|
||||
params = params.set('finishedThreshold', finishedThreshold.toString());
|
||||
}
|
||||
return this.http.put<KoboSyncSettings>(`${this.baseUrl}/progress-thresholds`, null, { params });
|
||||
updateSettings(settings: KoboSyncSettings): Observable<KoboSyncSettings> {
|
||||
return this.http.put<KoboSyncSettings>(`${this.baseUrl}`, settings);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user