From 4ff2cd1d1883d3045374aa71cb6e6cd04ef51269 Mon Sep 17 00:00:00 2001 From: bayang Date: Sun, 15 Sep 2024 17:05:46 +0200 Subject: [PATCH] feat: also import reviews from goodreads #132 --- .../bayang/jelu/dao/ImportRepository.kt | 2 ++ .../io/github/bayang/jelu/dao/ImportTable.kt | 4 +++ .../io/github/bayang/jelu/dto/ImportDto.kt | 2 ++ .../jelu/service/imports/CsvImportService.kt | 30 +++++++++++++++++++ src/main/resources/liquibase.xml | 6 ++++ .../service/imports/CsvImportServiceTest.kt | 21 +++++++++++++ ...ibrary_export_one_line_modified_review.csv | 6 ++++ 7 files changed, 71 insertions(+) create mode 100644 src/test/resources/csv-import/goodreads_library_export_one_line_modified_review.csv diff --git a/src/main/kotlin/io/github/bayang/jelu/dao/ImportRepository.kt b/src/main/kotlin/io/github/bayang/jelu/dao/ImportRepository.kt index b5d7823f..4fc29bac 100644 --- a/src/main/kotlin/io/github/bayang/jelu/dao/ImportRepository.kt +++ b/src/main/kotlin/io/github/bayang/jelu/dao/ImportRepository.kt @@ -69,6 +69,8 @@ class ImportRepository { this.importSource = entity.importSource!! this.librarythingId = entity.librarythingId this.owned = entity.owned + this.rating = entity.rating + this.review = entity.review } } diff --git a/src/main/kotlin/io/github/bayang/jelu/dao/ImportTable.kt b/src/main/kotlin/io/github/bayang/jelu/dao/ImportTable.kt index 4347fce2..b93e0351 100644 --- a/src/main/kotlin/io/github/bayang/jelu/dao/ImportTable.kt +++ b/src/main/kotlin/io/github/bayang/jelu/dao/ImportTable.kt @@ -29,6 +29,8 @@ object ImportEntityTable : UUIDTable("import_entity") { var userId: Column = uuid(name = "userId") var shouldFetchMetadata: Column = bool("should_fetch_metadata") var owned: Column = bool("owned").nullable() + var rating: Column = integer(name = "rating").nullable() + var review: Column = varchar("review", 500000).nullable() } class ImportEntity(id: EntityID) : UUIDEntity(id) { @@ -53,6 +55,8 @@ class ImportEntity(id: EntityID) : UUIDEntity(id) { var userId by ImportEntityTable.userId var shouldFetchMetadata by ImportEntityTable.shouldFetchMetadata var owned by ImportEntityTable.owned + var rating by ImportEntityTable.rating + var review by ImportEntityTable.review } enum class ProcessingStatus { diff --git a/src/main/kotlin/io/github/bayang/jelu/dto/ImportDto.kt b/src/main/kotlin/io/github/bayang/jelu/dto/ImportDto.kt index 0890be81..fbb7bb0f 100644 --- a/src/main/kotlin/io/github/bayang/jelu/dto/ImportDto.kt +++ b/src/main/kotlin/io/github/bayang/jelu/dto/ImportDto.kt @@ -19,6 +19,8 @@ class ImportDto { var readCount: Int? = null var owned: Boolean? = null var importSource: ImportSource? = null + var review: String? = null + var rating: Int? = null } data class ImportConfigurationDto( var shouldFetchMetadata: Boolean, diff --git a/src/main/kotlin/io/github/bayang/jelu/service/imports/CsvImportService.kt b/src/main/kotlin/io/github/bayang/jelu/service/imports/CsvImportService.kt index a3b727f2..67ab2341 100644 --- a/src/main/kotlin/io/github/bayang/jelu/service/imports/CsvImportService.kt +++ b/src/main/kotlin/io/github/bayang/jelu/service/imports/CsvImportService.kt @@ -6,10 +6,12 @@ import io.github.bayang.jelu.dao.ImportSource import io.github.bayang.jelu.dao.MessageCategory import io.github.bayang.jelu.dao.ProcessingStatus import io.github.bayang.jelu.dao.ReadingEventType +import io.github.bayang.jelu.dao.Visibility import io.github.bayang.jelu.dto.AuthorDto import io.github.bayang.jelu.dto.BookCreateDto import io.github.bayang.jelu.dto.BookDto import io.github.bayang.jelu.dto.CreateReadingEventDto +import io.github.bayang.jelu.dto.CreateReviewDto import io.github.bayang.jelu.dto.CreateUserBookDto import io.github.bayang.jelu.dto.CreateUserMessageDto import io.github.bayang.jelu.dto.ImportConfigurationDto @@ -24,6 +26,7 @@ import io.github.bayang.jelu.dto.UserBookUpdateDto import io.github.bayang.jelu.service.BookService import io.github.bayang.jelu.service.ImportService import io.github.bayang.jelu.service.ReadingEventService +import io.github.bayang.jelu.service.ReviewService import io.github.bayang.jelu.service.UserMessageService import io.github.bayang.jelu.service.UserService import io.github.bayang.jelu.service.metadata.FetchMetadataService @@ -40,6 +43,7 @@ import org.springframework.scheduling.annotation.Async import org.springframework.stereotype.Service import org.springframework.transaction.annotation.Transactional import java.io.File +import java.time.Instant import java.time.LocalDate import java.time.OffsetDateTime import java.time.ZoneId @@ -78,6 +82,7 @@ class CsvImportService( private val userService: UserService, private val readingEventService: ReadingEventService, private val userMessageService: UserMessageService, + private val reviewService: ReviewService, ) { // maybe later : use coroutines ? @@ -321,6 +326,29 @@ class CsvImportService( userEntity, ) } + val rating: Int = if (importEntity.rating != null) (importEntity.rating!! * 2) else 0 + if (!importEntity.review.isNullOrEmpty() || rating > 0) { + val existing = reviewService.find( + userEntity.id.value, + savedUserBook.book.id!!, + null, + null, + null, + Pageable.ofSize(10), + ) + if (existing.isEmpty) { + reviewService.save( + CreateReviewDto( + Instant.now(), + if (importEntity.review.isNullOrEmpty()) "" else importEntity.review!!, + rating.toDouble(), + Visibility.PUBLIC, + savedUserBook.book.id, + ), + userEntity, + ) + } + } importService.updateStatus(importEntity.id.value, ProcessingStatus.IMPORTED) return ProcessingStatus.IMPORTED } catch (e: Exception) { @@ -651,6 +679,8 @@ class CsvImportService( if (ownedCopies != null && ownedCopies > 0) { dto.owned = true } + dto.review = cleanString(record.get(19)) + dto.rating = parseNumber(record.get(7)) dto.importSource = ImportSource.GOODREADS return dto } diff --git a/src/main/resources/liquibase.xml b/src/main/resources/liquibase.xml index 5cd6044a..882a3a2f 100644 --- a/src/main/resources/liquibase.xml +++ b/src/main/resources/liquibase.xml @@ -544,4 +544,10 @@ + + + ALTER TABLE import_entity ADD rating INTEGER; + ALTER TABLE import_entity ADD review varchar(500000); + + diff --git a/src/test/kotlin/io/github/bayang/jelu/service/imports/CsvImportServiceTest.kt b/src/test/kotlin/io/github/bayang/jelu/service/imports/CsvImportServiceTest.kt index 1b5f8b09..58180bc7 100644 --- a/src/test/kotlin/io/github/bayang/jelu/service/imports/CsvImportServiceTest.kt +++ b/src/test/kotlin/io/github/bayang/jelu/service/imports/CsvImportServiceTest.kt @@ -109,6 +109,27 @@ class CsvImportServiceTest( } } + @Test + fun testParseRatingAndReview() { + val userId = user().id.value + val csv = File(this::class.java.getResource("/csv-import/goodreads_library_export_one_line_modified_review.csv").file) + csvImportService.parse(csv, userId, importConfigurationDto()) + val nb = importService.countByprocessingStatusAndUser(ProcessingStatus.SAVED, userId) + Assertions.assertEquals(4, nb) + val dtos = importService.getByprocessingStatusAndUser(ProcessingStatus.SAVED, userId) + dtos.forEach { + if (it.title.equals("Dr Jekyll and Mr Hyde")) { + Assertions.assertEquals("9780141973821", it.isbn13) + Assertions.assertEquals("Robert Louis Stevenson", it.authors) + Assertions.assertEquals(5, it.rating) + Assertions.assertTrue(it.review?.equals("Despite knowing the twist, I still enjoyed the book greatly. Excellently written.")!!) + } else if (it.title.equals("Povídky I")) { + Assertions.assertEquals(3, it.rating) + Assertions.assertTrue(it.review?.contains("Josefine (die Sängerin, oder Das Volk der Mäuse), and First Sorrow (Erstes Leid).

It should come as no surprise, then, that I like Kafka's more narrative-driven works better. In this collection, that would be:

(longer ones)
* In the Penal Colony (In der Strafkolonie)
* The Metamorphosis (Die Verwandlung)
* The Stoker (Der Heizer)
(shorter ones)
* Before the Law (Vor dem Gesetz, amazing!)
* The Dream (Ein Traum)
* The Bucket")!!) + } + } + } + @Test fun testParseIsbnList() { val userId = user().id.value diff --git a/src/test/resources/csv-import/goodreads_library_export_one_line_modified_review.csv b/src/test/resources/csv-import/goodreads_library_export_one_line_modified_review.csv new file mode 100644 index 00000000..bccfc21a --- /dev/null +++ b/src/test/resources/csv-import/goodreads_library_export_one_line_modified_review.csv @@ -0,0 +1,6 @@ +Book Id,Title,Author,Author l-f,Additional Authors,ISBN,ISBN13,My Rating,Average Rating,Publisher,Binding,Number of Pages,Year Published,Original Publication Year,Date Read,Date Added,Bookshelves,Bookshelves with positions,Exclusive Shelf,My Review,Spoiler,Private Notes,Read Count,Owned Copies +19308181,Dr Jekyll and Mr Hyde,Robert Louis Stevenson,"Stevenson, Robert Louis",,"=""014197382X""","=""9780141973821""",5,3.81,Penguin English Library,Kindle Edition,117,2012,,2020/01/23,2020/01/22,"brain, classic, fantasy, horror, mind, psychology","brain (#3), classic (#4), fantasy (#13), horror (#3), mind (#7), psychology (#6)",read,"Despite knowing the twist, I still enjoyed the book greatly. Excellently written.",,,1,0 +18114322,The Grapes of Wrath,John Steinbeck,"Steinbeck, John",,"=""067001690X""","=""9780670016907""",4,4.00,Viking,Hardcover,479,2014,1939,2023/04/17,2023/04/17,,,read,Incredible story of a family trying to survive during the Great Depression that is strangely relevant even today. Loved the characters (especially Má and Tom) and the colloquial language.,,,1,0 +13560311,The Handmaid's Tale,Margaret Atwood,"Atwood, Margaret","Elena Balbusso, Anna Balbusso","=""""","=""""",5,4.13,Folio Society,Hardcover,336,2012,1985,2021/10/09,2019/05/15,"owned-physical, dystopia, feminism","owned-physical (#31), dystopia (#1), feminism (#5)",read,"The narrator's account of what has happened to her seems to be woven with great care from many different pieces of our own history. It's appalling how many of the things in the book remind me of the various events that took place in the past decade, especially in the US.

Thinking back on the ending, I especially like it because it avoids being both a clichéd happy-end and its polar opposite. It's both bleak and hopeful, but also quite sudden; a story suddenly cut off. This reinforces its ""found diary"" quality.",,,1,0 +430803,Povídky I,Franz Kafka,"Kafka, Franz",,"=""8085844508""","=""9788085844504""",3,4.00,Nakladatelství Franze Kafky,Hardcover,352,1999,1999,2023/01/12,2022/02/03,,,read,"Not an easy read for someone who like me who is probably just a surface reader at best.

Although the surreal feeling of his writing is probably what I like the most about it, sometimes in can lean into incomprehensible stream-of-consciousness style that is almost impossible to follow. This is especially true about the whole first part (called Contemplation, or Betrachtung in German) and the four stories united under the theme of a starving artist: A Hunger Artist (Ein Hungerkünstler), A Little Woman (Eine kleine Frau), Josephine the Singer, or the Mouse Folk or Josefine (die Sängerin, oder Das Volk der Mäuse), and First Sorrow (Erstes Leid).

It should come as no surprise, then, that I like Kafka's more narrative-driven works better. In this collection, that would be:

(longer ones)
* In the Penal Colony (In der Strafkolonie)
* The Metamorphosis (Die Verwandlung)
* The Stoker (Der Heizer)
(shorter ones)
* Before the Law (Vor dem Gesetz, amazing!)
* The Dream (Ein Traum)
* The Bucket Rider (Der Kübelreiter)",,,1,0 +92625,The Ones Who Walk Away from Omelas,Ursula K. Le Guin,"Guin, Ursula K. Le",,"=""0886825016""","=""9780886825010""",5,4.37,"Creative Education, Inc.",Library Binding,32,1997,1973,,2022/12/30,,,read,Inredible short story that doesn't lose any of its impact even when knowing its rough content in advance.,,,1,0