refactor(catalog): move classes to new package

This commit is contained in:
Rodrigo Verdiani 2026-03-08 17:54:08 -03:00
parent 60a4f2cff6
commit 4d55f97298
23 changed files with 222 additions and 171 deletions

View File

@ -1,8 +1,8 @@
package com.magamochi.controller;
package com.magamochi.catalog.controller;
import com.magamochi.catalog.model.dto.GenreDTO;
import com.magamochi.catalog.service.GenreService;
import com.magamochi.model.dto.DefaultResponseDTO;
import com.magamochi.model.dto.GenreDTO;
import com.magamochi.service.GenreService;
import io.swagger.v3.oas.annotations.Operation;
import java.util.List;
import lombok.RequiredArgsConstructor;

View File

@ -1,6 +1,6 @@
package com.magamochi.model.dto;
package com.magamochi.catalog.model.dto;
import com.magamochi.model.entity.Genre;
import com.magamochi.catalog.model.entity.Genre;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;

View File

@ -1,8 +1,7 @@
package com.magamochi.model.entity;
package com.magamochi.catalog.model.entity;
import jakarta.persistence.*;
import java.time.Instant;
import java.util.List;
import lombok.*;
import org.hibernate.annotations.CreationTimestamp;
import org.hibernate.annotations.UpdateTimestamp;
@ -26,7 +25,4 @@ public class Author {
@CreationTimestamp private Instant createdAt;
@UpdateTimestamp private Instant updatedAt;
@OneToMany(mappedBy = "author")
private List<MangaAuthor> mangaAuthors;
}

View File

@ -1,7 +1,6 @@
package com.magamochi.model.entity;
package com.magamochi.catalog.model.entity;
import jakarta.persistence.*;
import java.util.List;
import lombok.*;
@Entity
@ -19,7 +18,4 @@ public class Genre {
private Long malId;
private String name;
@OneToMany(mappedBy = "genre")
private List<MangaGenre> mangaGenres;
}

View File

@ -1,4 +1,4 @@
package com.magamochi.model.entity;
package com.magamochi.catalog.model.entity;
import jakarta.persistence.*;
import lombok.*;
@ -15,11 +15,7 @@ public class MangaAuthor {
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@ManyToOne
@JoinColumn(name = "manga_id")
private Manga manga;
private Long mangaId;
@ManyToOne
@JoinColumn(name = "author_id")
private Author author;
private Long authorId;
}

View File

@ -1,4 +1,4 @@
package com.magamochi.model.entity;
package com.magamochi.catalog.model.entity;
import jakarta.persistence.*;
import lombok.*;
@ -15,11 +15,7 @@ public class MangaGenre {
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@ManyToOne
@JoinColumn(name = "manga_id")
private Manga manga;
private Long mangaId;
@ManyToOne
@JoinColumn(name = "genre_id")
private Genre genre;
private Long genreId;
}

View File

@ -1,11 +1,11 @@
package com.magamochi.model.repository;
package com.magamochi.catalog.model.repository;
import com.magamochi.model.entity.Author;
import com.magamochi.catalog.model.entity.Author;
import java.util.Optional;
import org.springframework.data.jpa.repository.JpaRepository;
public interface AuthorRepository extends JpaRepository<Author, Long> {
Optional<Author> findByMalId(Long aLong);
Optional<Author> findByMalId(Long malId);
Optional<Author> findByName(String name);
}

View File

@ -1,6 +1,6 @@
package com.magamochi.model.repository;
package com.magamochi.catalog.model.repository;
import com.magamochi.model.entity.Genre;
import com.magamochi.catalog.model.entity.Genre;
import java.util.Optional;
import org.springframework.data.jpa.repository.JpaRepository;

View File

@ -0,0 +1,11 @@
package com.magamochi.catalog.model.repository;
import com.magamochi.catalog.model.entity.MangaAuthor;
import java.util.Set;
import org.springframework.data.jpa.repository.JpaRepository;
public interface MangaAuthorRepository extends JpaRepository<MangaAuthor, Long> {
void deleteAllByMangaId(Long mangaId);
Set<MangaAuthor> findAllByMangaId(Long mangaId);
}

View File

@ -0,0 +1,11 @@
package com.magamochi.catalog.model.repository;
import com.magamochi.catalog.model.entity.MangaGenre;
import java.util.List;
import org.springframework.data.jpa.repository.JpaRepository;
public interface MangaGenreRepository extends JpaRepository<MangaGenre, Long> {
void deleteAllByMangaId(Long mangaId);
List<MangaGenre> findAllByMangaId(Long mangaId);
}

View File

@ -0,0 +1,29 @@
package com.magamochi.catalog.service;
import com.magamochi.catalog.model.entity.Author;
import com.magamochi.catalog.model.repository.AuthorRepository;
import java.util.List;
import java.util.Set;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
@Service
@RequiredArgsConstructor
public class AuthorService {
private final AuthorRepository authorRepository;
public Long findOrCreateAuthor(Long malAuthorId, String authorName) {
return authorRepository
.findByName(authorName)
.orElseGet(
() ->
authorRepository.save(Author.builder().malId(malAuthorId).name(authorName).build()))
.getId();
}
public List<String> getAuthorNamesByIds(Set<Long> genreIds) {
var genres = authorRepository.findAllById(genreIds);
return genres.stream().map(Author::getName).toList();
}
}

View File

@ -0,0 +1,35 @@
package com.magamochi.catalog.service;
import com.magamochi.catalog.model.dto.GenreDTO;
import com.magamochi.catalog.model.entity.Genre;
import com.magamochi.catalog.model.repository.GenreRepository;
import java.util.List;
import java.util.Set;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
@Service
@RequiredArgsConstructor
public class GenreService {
private final GenreRepository genreRepository;
public Long findOrCreateGenre(Long malGenreId, String genreName) {
return genreRepository
.findByName(genreName)
.orElseGet(
() -> genreRepository.save(Genre.builder().malId(malGenreId).name(genreName).build()))
.getId();
}
public List<String> getGenreNamesByIds(Set<Long> genreIds) {
var genres = genreRepository.findAllById(genreIds);
return genres.stream().map(Genre::getName).toList();
}
public List<GenreDTO> getGenres() {
var genres = genreRepository.findAll();
return genres.stream().map(GenreDTO::from).toList();
}
}

View File

@ -0,0 +1,34 @@
package com.magamochi.catalog.service;
import com.magamochi.catalog.model.entity.MangaAuthor;
import com.magamochi.catalog.model.repository.MangaAuthorRepository;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
@Service
@RequiredArgsConstructor
public class MangaAuthorService {
private final AuthorService authorService;
private final MangaAuthorRepository mangaAuthorRepository;
public void saveOrUpdateMangaAuthors(Long mangaId, Set<Long> authorIds) {
mangaAuthorRepository.deleteAllByMangaId(mangaId);
authorIds.forEach(
authorId ->
mangaAuthorRepository.save(
MangaAuthor.builder().mangaId(mangaId).authorId(authorId).build()));
}
public List<String> getMangaAuthors(Long mangaId) {
var mangaAuthorIds =
mangaAuthorRepository.findAllByMangaId(mangaId).stream()
.map(MangaAuthor::getAuthorId)
.collect(Collectors.toSet());
return authorService.getAuthorNamesByIds(mangaAuthorIds);
}
}

View File

@ -0,0 +1,34 @@
package com.magamochi.catalog.service;
import com.magamochi.catalog.model.entity.MangaGenre;
import com.magamochi.catalog.model.repository.MangaGenreRepository;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
@Service
@RequiredArgsConstructor
public class MangaGenreService {
private final GenreService genreService;
private final MangaGenreRepository mangaGenreRepository;
public void saveOrUpdateMangaGenres(Long mangaId, Set<Long> genreIds) {
mangaGenreRepository.deleteAllByMangaId(mangaId);
genreIds.forEach(
genreId ->
mangaGenreRepository.save(
MangaGenre.builder().mangaId(mangaId).genreId(genreId).build()));
}
public List<String> getMangaGenres(Long mangaId) {
var mangaGenreIds =
mangaGenreRepository.findAllByMangaId(mangaId).stream()
.map(MangaGenre::getGenreId)
.collect(Collectors.toSet());
return genreService.getGenreNamesByIds(mangaGenreIds);
}
}

View File

@ -29,7 +29,8 @@ public record MangaDTO(
@NotNull Integer chapterCount,
@NotNull Boolean favorite,
@NotNull Boolean following) {
public static MangaDTO from(Manga manga, Boolean favorite, Boolean following) {
public static MangaDTO from(
Manga manga, Boolean favorite, Boolean following, List<String> genres, List<String> authors) {
return new MangaDTO(
manga.getId(),
manga.getTitle(),
@ -40,10 +41,8 @@ public record MangaDTO(
manga.getSynopsis(),
manga.getMangaProviders().size(),
manga.getAlternativeTitles().stream().map(MangaAlternativeTitle::getTitle).toList(),
manga.getMangaGenres().stream().map(mangaGenre -> mangaGenre.getGenre().getName()).toList(),
manga.getMangaAuthors().stream()
.map(mangaAuthor -> mangaAuthor.getAuthor().getName())
.toList(),
genres,
authors,
manga.getScore(),
manga.getMangaProviders().stream().map(MangaProviderDTO::from).toList(),
manga.getChapterCount(),

View File

@ -20,7 +20,8 @@ public record MangaListDTO(
@NotNull List<String> authors,
@NotNull Double score,
@NotNull Boolean favorite) {
public static MangaListDTO from(Manga manga, boolean favorite) {
public static MangaListDTO from(
Manga manga, boolean favorite, List<String> genres, List<String> authors) {
return new MangaListDTO(
manga.getId(),
manga.getTitle(),
@ -29,10 +30,8 @@ public record MangaListDTO(
manga.getPublishedFrom(),
manga.getPublishedTo(),
manga.getMangaProviders().size(),
manga.getMangaGenres().stream().map(mangaGenre -> mangaGenre.getGenre().getName()).toList(),
manga.getMangaAuthors().stream()
.map(mangaAuthor -> mangaAuthor.getAuthor().getName())
.toList(),
genres,
authors,
manga.getScore(),
favorite);
}

View File

@ -47,12 +47,6 @@ public class Manga {
private OffsetDateTime publishedTo;
@OneToMany(mappedBy = "manga")
private List<MangaAuthor> mangaAuthors;
@OneToMany(mappedBy = "manga")
private List<MangaGenre> mangaGenres;
@OneToMany(mappedBy = "manga")
private List<MangaAlternativeTitle> alternativeTitles;

View File

@ -1,11 +0,0 @@
package com.magamochi.model.repository;
import com.magamochi.model.entity.Author;
import com.magamochi.model.entity.Manga;
import com.magamochi.model.entity.MangaAuthor;
import java.util.Optional;
import org.springframework.data.jpa.repository.JpaRepository;
public interface MangaAuthorRepository extends JpaRepository<MangaAuthor, Long> {
Optional<MangaAuthor> findByMangaAndAuthor(Manga manga, Author author);
}

View File

@ -1,11 +0,0 @@
package com.magamochi.model.repository;
import com.magamochi.model.entity.Genre;
import com.magamochi.model.entity.Manga;
import com.magamochi.model.entity.MangaGenre;
import java.util.Optional;
import org.springframework.data.jpa.repository.JpaRepository;
public interface MangaGenreRepository extends JpaRepository<MangaGenre, Long> {
Optional<MangaGenre> findByMangaAndGenre(Manga manga, Genre genre);
}

View File

@ -2,8 +2,8 @@ package com.magamochi.model.specification;
import static java.util.Objects.nonNull;
import com.magamochi.catalog.model.entity.Author;
import com.magamochi.model.dto.MangaListFilterDTO;
import com.magamochi.model.entity.Author;
import com.magamochi.model.entity.Manga;
import com.magamochi.user.model.entity.User;
import jakarta.persistence.criteria.*;

View File

@ -1,19 +0,0 @@
package com.magamochi.service;
import com.magamochi.model.dto.GenreDTO;
import com.magamochi.model.repository.GenreRepository;
import java.util.List;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
@Service
@RequiredArgsConstructor
public class GenreService {
private final GenreRepository genreRepository;
public List<GenreDTO> getGenres() {
var genres = genreRepository.findAll();
return genres.stream().map(GenreDTO::from).toList();
}
}

View File

@ -4,6 +4,10 @@ import static java.util.Objects.isNull;
import static java.util.Objects.nonNull;
import com.google.common.util.concurrent.RateLimiter;
import com.magamochi.catalog.service.AuthorService;
import com.magamochi.catalog.service.GenreService;
import com.magamochi.catalog.service.MangaAuthorService;
import com.magamochi.catalog.service.MangaGenreService;
import com.magamochi.client.AniListClient;
import com.magamochi.client.JikanClient;
import com.magamochi.exception.NotFoundException;
@ -20,6 +24,7 @@ import java.time.ZoneOffset;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
@ -38,11 +43,13 @@ public class MangaImportService {
private final ImageService imageService;
private final LanguageService languageService;
private final GenreRepository genreRepository;
private final MangaGenreRepository mangaGenreRepository;
private final GenreService genreService;
private final MangaGenreService mangaGenreService;
private final AuthorService authorService;
private final MangaAuthorService mangaAuthorService;
private final MangaProviderRepository mangaProviderRepository;
private final AuthorRepository authorRepository;
private final MangaAuthorRepository mangaAuthorRepository;
private final MangaChapterRepository mangaChapterRepository;
private final MangaRepository mangaRepository;
@ -177,39 +184,21 @@ public class MangaImportService {
manga.setPublishedTo(mangaData.data().published().to());
manga.setChapterCount(mangaData.data().chapters());
var authors =
var authorIds =
mangaData.data().authors().stream()
.map(
authorData ->
authorRepository
.findByMalId(authorData.mal_id())
.orElseGet(
() ->
authorRepository.save(
Author.builder()
.malId(authorData.mal_id())
.name(authorData.name())
.build())))
.toList();
authorService.findOrCreateAuthor(authorData.mal_id(), authorData.name()))
.collect(Collectors.toSet());
updateMangaAuthors(manga, authors);
mangaAuthorService.saveOrUpdateMangaAuthors(manga.getId(), authorIds);
var genres =
var genreIds =
mangaData.data().genres().stream()
.map(
genreData ->
genreRepository
.findByMalId(genreData.mal_id())
.orElseGet(
() ->
genreRepository.save(
Genre.builder()
.malId(genreData.mal_id())
.name(genreData.name())
.build())))
.toList();
.map(genreData -> genreService.findOrCreateGenre(genreData.mal_id(), genreData.name()))
.collect(Collectors.toSet());
updateMangaGenres(manga, genres);
mangaGenreService.saveOrUpdateMangaGenres(manga.getId(), genreIds);
if (isNull(manga.getCoverImage())) {
downloadCoverImage(manga, mangaData.data().images().jpg().large_image_url());
@ -261,31 +250,22 @@ public class MangaImportService {
manga.setPublishedTo(convertFuzzyDate(media.endDate()));
manga.setChapterCount(media.chapters());
var authors =
var authorIds =
media.staff().edges().stream()
.filter(edge -> isAuthorRole(edge.role()))
.map(edge -> edge.node().name().full())
.distinct()
.map(
name ->
authorRepository
.findByName(name)
.orElseGet(
() -> authorRepository.save(Author.builder().name(name).build())))
.toList();
.map(name -> authorService.findOrCreateAuthor(null, name))
.collect(Collectors.toSet());
updateMangaAuthors(manga, authors);
mangaAuthorService.saveOrUpdateMangaAuthors(manga.getId(), authorIds);
var genres =
var genreIds =
media.genres().stream()
.map(
name ->
genreRepository
.findByName(name)
.orElseGet(() -> genreRepository.save(Genre.builder().name(name).build())))
.toList();
.map(name -> genreService.findOrCreateGenre(null, name))
.collect(Collectors.toSet());
updateMangaGenres(manga, genres);
mangaGenreService.saveOrUpdateMangaGenres(manga.getId(), genreIds);
if (isNull(manga.getCoverImage())) {
downloadCoverImage(manga, media.coverImage().large());
@ -324,36 +304,6 @@ public class MangaImportService {
ZoneOffset.UTC);
}
private void updateMangaAuthors(Manga manga, List<Author> authors) {
var mangaAuthors =
authors.stream()
.map(
author ->
mangaAuthorRepository
.findByMangaAndAuthor(manga, author)
.orElseGet(
() ->
mangaAuthorRepository.save(
MangaAuthor.builder().manga(manga).author(author).build())))
.toList();
manga.setMangaAuthors(mangaAuthors);
}
private void updateMangaGenres(Manga manga, List<Genre> genres) {
var mangaGenres =
genres.stream()
.map(
genre ->
mangaGenreRepository
.findByMangaAndGenre(manga, genre)
.orElseGet(
() ->
mangaGenreRepository.save(
MangaGenre.builder().manga(manga).genre(genre).build())))
.toList();
manga.setMangaGenres(mangaGenres);
}
private void downloadCoverImage(Manga manga, String imageUrl)
throws IOException, URISyntaxException {
var inputStream =

View File

@ -2,6 +2,8 @@ package com.magamochi.service;
import static java.util.Objects.nonNull;
import com.magamochi.catalog.service.MangaAuthorService;
import com.magamochi.catalog.service.MangaGenreService;
import com.magamochi.client.NtfyClient;
import com.magamochi.exception.NotFoundException;
import com.magamochi.model.dto.*;
@ -35,6 +37,9 @@ public class MangaService {
private final MangaRepository mangaRepository;
private final MangaProviderRepository mangaProviderRepository;
private final MangaGenreService mangaGenreService;
private final MangaAuthorService mangaAuthorService;
private final ContentProviderFactory contentProviderFactory;
private final MangaChapterDownloadProducer mangaChapterDownloadProducer;
@ -73,8 +78,10 @@ public class MangaService {
.findAll(specification, pageable)
.map(
manga -> {
var genres = mangaGenreService.getMangaGenres(manga.getId());
var authors = mangaAuthorService.getMangaAuthors(manga.getId());
var favorite = favoriteMangasIds.contains(manga.getId());
return MangaListDTO.from(manga, favorite);
return MangaListDTO.from(manga, favorite, genres, authors);
});
}
@ -97,10 +104,15 @@ public class MangaService {
var followingMangaIds =
nonNull(user) ? userFollowMangaService.getFollowedMangaIdsForLoggedUser() : Set.of();
var genres = mangaGenreService.getMangaGenres(manga.getId());
var authors = mangaAuthorService.getMangaAuthors(manga.getId());
return MangaDTO.from(
manga,
favoriteMangasIds.contains(manga.getId()),
followingMangaIds.contains(manga.getId()));
followingMangaIds.contains(manga.getId()),
genres,
authors);
}
public void fetchFollowedMangaChapters(Long mangaProviderId) {