feat: implement search by manga's alternative titles

This commit is contained in:
Rodrigo Verdiani 2025-10-25 12:45:40 -03:00
parent 1526dc4bc9
commit 69ee91b9c6
7 changed files with 76 additions and 15 deletions

View File

@ -3,6 +3,7 @@ package com.magamochi.mangamochi.model.dto;
import static java.util.Objects.isNull;
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.MangaProvider;
import jakarta.validation.constraints.NotBlank;
@ -35,7 +36,7 @@ public record MangaDTO(
manga.getPublishedTo(),
manga.getSynopsis(),
manga.getMangaProviders().size(),
manga.getAlternativeTitles(),
manga.getAlternativeTitles().stream().map(MangaAlternativeTitle::getTitle).toList(),
manga.getMangaGenres().stream().map(mangaGenre -> mangaGenre.getGenre().getName()).toList(),
manga.getMangaAuthors().stream()
.map(mangaAuthor -> mangaAuthor.getAuthor().getName())

View File

@ -1,13 +1,11 @@
package com.magamochi.mangamochi.model.entity;
import io.hypersistence.utils.hibernate.type.array.ListArrayType;
import jakarta.persistence.*;
import java.time.Instant;
import java.time.OffsetDateTime;
import java.util.List;
import lombok.*;
import org.hibernate.annotations.CreationTimestamp;
import org.hibernate.annotations.Type;
import org.hibernate.annotations.UpdateTimestamp;
@Entity
@ -26,10 +24,6 @@ public class Manga {
private String title;
@Type(ListArrayType.class)
@Column(name = "alternative_titles", columnDefinition = "text[]")
private List<String> alternativeTitles;
// @Enumerated(EnumType.STRING)
private String status;
@ -61,5 +55,8 @@ public class Manga {
@OneToMany(mappedBy = "manga")
private List<UserFavoriteManga> userFavoriteMangas;
@OneToMany(mappedBy = "manga")
private List<MangaAlternativeTitle> alternativeTitles;
private Integer chapterCount;
}

View File

@ -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;
}

View File

@ -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> {}

View File

@ -27,9 +27,14 @@ public class MangaSpecification {
var titlePredicate =
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);
}
@ -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(
Root<Manga> root,
CriteriaQuery<?> query,
CriteriaBuilder criteriaBuilder,
String searchPattern) {
Root<Manga> root, CriteriaBuilder criteriaBuilder, String searchPattern) {
Join<Manga, ?> mangaAuthorsJoin = root.join("mangaAuthors", JoinType.LEFT);
Join<Object, Author> authorJoin = mangaAuthorsJoin.join("author", JoinType.LEFT);

View File

@ -42,6 +42,7 @@ public class MangaImportService {
private final JikanClient jikanClient;
private final MangaChapterImageRepository mangaChapterImageRepository;
private final MangaAlternativeTitlesRepository mangaAlternativeTitlesRepository;
RateLimiter rateLimiter = RateLimiter.create(1);
@ -125,7 +126,6 @@ public class MangaImportService {
rateLimiter.acquire();
var mangaData = jikanClient.getMangaById(manga.getMalId());
manga.setAlternativeTitles(mangaData.data().title_synonyms());
manga.setSynopsis(mangaData.data().synopsis());
manga.setStatus(mangaData.data().status());
manga.setScore(DoubleUtil.round((double) mangaData.data().score(), 2));
@ -205,7 +205,12 @@ public class MangaImportService {
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) {
log.warn("Error updating manga data for manga {}. {}", manga.getTitle(), e);

View File

@ -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;