feat: implement search by manga's alternative titles
This commit is contained in:
parent
1526dc4bc9
commit
69ee91b9c6
@ -3,6 +3,7 @@ package com.magamochi.mangamochi.model.dto;
|
|||||||
import static java.util.Objects.isNull;
|
import static java.util.Objects.isNull;
|
||||||
|
|
||||||
import com.magamochi.mangamochi.model.entity.Manga;
|
import com.magamochi.mangamochi.model.entity.Manga;
|
||||||
|
import com.magamochi.mangamochi.model.entity.MangaAlternativeTitle;
|
||||||
import com.magamochi.mangamochi.model.entity.MangaChapter;
|
import com.magamochi.mangamochi.model.entity.MangaChapter;
|
||||||
import com.magamochi.mangamochi.model.entity.MangaProvider;
|
import com.magamochi.mangamochi.model.entity.MangaProvider;
|
||||||
import jakarta.validation.constraints.NotBlank;
|
import jakarta.validation.constraints.NotBlank;
|
||||||
@ -35,7 +36,7 @@ public record MangaDTO(
|
|||||||
manga.getPublishedTo(),
|
manga.getPublishedTo(),
|
||||||
manga.getSynopsis(),
|
manga.getSynopsis(),
|
||||||
manga.getMangaProviders().size(),
|
manga.getMangaProviders().size(),
|
||||||
manga.getAlternativeTitles(),
|
manga.getAlternativeTitles().stream().map(MangaAlternativeTitle::getTitle).toList(),
|
||||||
manga.getMangaGenres().stream().map(mangaGenre -> mangaGenre.getGenre().getName()).toList(),
|
manga.getMangaGenres().stream().map(mangaGenre -> mangaGenre.getGenre().getName()).toList(),
|
||||||
manga.getMangaAuthors().stream()
|
manga.getMangaAuthors().stream()
|
||||||
.map(mangaAuthor -> mangaAuthor.getAuthor().getName())
|
.map(mangaAuthor -> mangaAuthor.getAuthor().getName())
|
||||||
|
|||||||
@ -1,13 +1,11 @@
|
|||||||
package com.magamochi.mangamochi.model.entity;
|
package com.magamochi.mangamochi.model.entity;
|
||||||
|
|
||||||
import io.hypersistence.utils.hibernate.type.array.ListArrayType;
|
|
||||||
import jakarta.persistence.*;
|
import jakarta.persistence.*;
|
||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
import java.time.OffsetDateTime;
|
import java.time.OffsetDateTime;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import lombok.*;
|
import lombok.*;
|
||||||
import org.hibernate.annotations.CreationTimestamp;
|
import org.hibernate.annotations.CreationTimestamp;
|
||||||
import org.hibernate.annotations.Type;
|
|
||||||
import org.hibernate.annotations.UpdateTimestamp;
|
import org.hibernate.annotations.UpdateTimestamp;
|
||||||
|
|
||||||
@Entity
|
@Entity
|
||||||
@ -26,10 +24,6 @@ public class Manga {
|
|||||||
|
|
||||||
private String title;
|
private String title;
|
||||||
|
|
||||||
@Type(ListArrayType.class)
|
|
||||||
@Column(name = "alternative_titles", columnDefinition = "text[]")
|
|
||||||
private List<String> alternativeTitles;
|
|
||||||
|
|
||||||
// @Enumerated(EnumType.STRING)
|
// @Enumerated(EnumType.STRING)
|
||||||
private String status;
|
private String status;
|
||||||
|
|
||||||
@ -61,5 +55,8 @@ public class Manga {
|
|||||||
@OneToMany(mappedBy = "manga")
|
@OneToMany(mappedBy = "manga")
|
||||||
private List<UserFavoriteManga> userFavoriteMangas;
|
private List<UserFavoriteManga> userFavoriteMangas;
|
||||||
|
|
||||||
|
@OneToMany(mappedBy = "manga")
|
||||||
|
private List<MangaAlternativeTitle> alternativeTitles;
|
||||||
|
|
||||||
private Integer chapterCount;
|
private Integer chapterCount;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -0,0 +1,23 @@
|
|||||||
|
package com.magamochi.mangamochi.model.entity;
|
||||||
|
|
||||||
|
import jakarta.persistence.*;
|
||||||
|
import lombok.*;
|
||||||
|
|
||||||
|
@Entity
|
||||||
|
@Table(name = "manga_alternative_titles")
|
||||||
|
@Builder
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
@Getter
|
||||||
|
@Setter
|
||||||
|
public class MangaAlternativeTitle {
|
||||||
|
@Id
|
||||||
|
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||||
|
private Long id;
|
||||||
|
|
||||||
|
@ManyToOne
|
||||||
|
@JoinColumn(name = "manga_id")
|
||||||
|
private Manga manga;
|
||||||
|
|
||||||
|
private String title;
|
||||||
|
}
|
||||||
@ -0,0 +1,7 @@
|
|||||||
|
package com.magamochi.mangamochi.model.repository;
|
||||||
|
|
||||||
|
import com.magamochi.mangamochi.model.entity.MangaAlternativeTitle;
|
||||||
|
import org.springframework.data.jpa.repository.JpaRepository;
|
||||||
|
|
||||||
|
public interface MangaAlternativeTitlesRepository
|
||||||
|
extends JpaRepository<MangaAlternativeTitle, Long> {}
|
||||||
@ -27,9 +27,14 @@ public class MangaSpecification {
|
|||||||
var titlePredicate =
|
var titlePredicate =
|
||||||
criteriaBuilder.like(criteriaBuilder.lower(root.get("title")), searchPattern);
|
criteriaBuilder.like(criteriaBuilder.lower(root.get("title")), searchPattern);
|
||||||
|
|
||||||
var authorsPredicate = searchInAuthors(root, query, criteriaBuilder, searchPattern);
|
var alternativeTitlesPredicate =
|
||||||
|
searchInAlternativeTitles(root, criteriaBuilder, searchPattern);
|
||||||
|
|
||||||
var searchPredicate = criteriaBuilder.or(titlePredicate, authorsPredicate);
|
var authorsPredicate = searchInAuthors(root, criteriaBuilder, searchPattern);
|
||||||
|
|
||||||
|
var searchPredicate =
|
||||||
|
criteriaBuilder.or(
|
||||||
|
criteriaBuilder.or(titlePredicate, alternativeTitlesPredicate), authorsPredicate);
|
||||||
|
|
||||||
predicates.add(searchPredicate);
|
predicates.add(searchPredicate);
|
||||||
}
|
}
|
||||||
@ -64,11 +69,16 @@ public class MangaSpecification {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static Predicate searchInAlternativeTitles(
|
||||||
|
Root<Manga> root, CriteriaBuilder criteriaBuilder, String searchPattern) {
|
||||||
|
Join<Manga, ?> alternativeTitlesJoin = root.join("alternativeTitles", JoinType.LEFT);
|
||||||
|
|
||||||
|
return criteriaBuilder.like(
|
||||||
|
criteriaBuilder.lower(alternativeTitlesJoin.get("title")), searchPattern);
|
||||||
|
}
|
||||||
|
|
||||||
private static Predicate searchInAuthors(
|
private static Predicate searchInAuthors(
|
||||||
Root<Manga> root,
|
Root<Manga> root, CriteriaBuilder criteriaBuilder, String searchPattern) {
|
||||||
CriteriaQuery<?> query,
|
|
||||||
CriteriaBuilder criteriaBuilder,
|
|
||||||
String searchPattern) {
|
|
||||||
Join<Manga, ?> mangaAuthorsJoin = root.join("mangaAuthors", JoinType.LEFT);
|
Join<Manga, ?> mangaAuthorsJoin = root.join("mangaAuthors", JoinType.LEFT);
|
||||||
Join<Object, Author> authorJoin = mangaAuthorsJoin.join("author", JoinType.LEFT);
|
Join<Object, Author> authorJoin = mangaAuthorsJoin.join("author", JoinType.LEFT);
|
||||||
|
|
||||||
|
|||||||
@ -42,6 +42,7 @@ public class MangaImportService {
|
|||||||
|
|
||||||
private final JikanClient jikanClient;
|
private final JikanClient jikanClient;
|
||||||
private final MangaChapterImageRepository mangaChapterImageRepository;
|
private final MangaChapterImageRepository mangaChapterImageRepository;
|
||||||
|
private final MangaAlternativeTitlesRepository mangaAlternativeTitlesRepository;
|
||||||
|
|
||||||
RateLimiter rateLimiter = RateLimiter.create(1);
|
RateLimiter rateLimiter = RateLimiter.create(1);
|
||||||
|
|
||||||
@ -125,7 +126,6 @@ public class MangaImportService {
|
|||||||
rateLimiter.acquire();
|
rateLimiter.acquire();
|
||||||
var mangaData = jikanClient.getMangaById(manga.getMalId());
|
var mangaData = jikanClient.getMangaById(manga.getMalId());
|
||||||
|
|
||||||
manga.setAlternativeTitles(mangaData.data().title_synonyms());
|
|
||||||
manga.setSynopsis(mangaData.data().synopsis());
|
manga.setSynopsis(mangaData.data().synopsis());
|
||||||
manga.setStatus(mangaData.data().status());
|
manga.setStatus(mangaData.data().status());
|
||||||
manga.setScore(DoubleUtil.round((double) mangaData.data().score(), 2));
|
manga.setScore(DoubleUtil.round((double) mangaData.data().score(), 2));
|
||||||
@ -205,7 +205,12 @@ public class MangaImportService {
|
|||||||
manga.setCoverImage(image);
|
manga.setCoverImage(image);
|
||||||
}
|
}
|
||||||
|
|
||||||
mangaRepository.save(manga);
|
var mangaEntity = mangaRepository.save(manga);
|
||||||
|
var alternativeTitles =
|
||||||
|
mangaData.data().title_synonyms().stream()
|
||||||
|
.map(at -> MangaAlternativeTitle.builder().manga(mangaEntity).title(at).build())
|
||||||
|
.toList();
|
||||||
|
mangaAlternativeTitlesRepository.saveAll(alternativeTitles);
|
||||||
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.warn("Error updating manga data for manga {}. {}", manga.getTitle(), e);
|
log.warn("Error updating manga data for manga {}. {}", manga.getTitle(), e);
|
||||||
|
|||||||
@ -0,0 +1,18 @@
|
|||||||
|
CREATE TABLE manga_alternative_titles
|
||||||
|
(
|
||||||
|
id BIGSERIAL PRIMARY KEY,
|
||||||
|
manga_id BIGINT REFERENCES mangas (id),
|
||||||
|
title VARCHAR NOT NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
INSERT INTO manga_alternative_titles (manga_id, title)
|
||||||
|
SELECT id as manga_id,
|
||||||
|
unnest(alternative_titles) as title
|
||||||
|
FROM mangas
|
||||||
|
WHERE alternative_titles IS NOT NULL;
|
||||||
|
|
||||||
|
CREATE INDEX idx_manga_alternative_titles_manga_id ON manga_alternative_titles (manga_id);
|
||||||
|
|
||||||
|
CREATE INDEX idx_manga_alternative_titles_title ON manga_alternative_titles (title);
|
||||||
|
|
||||||
|
ALTER TABLE mangas DROP COLUMN alternative_titles;
|
||||||
Loading…
x
Reference in New Issue
Block a user