refactor(catalog): move classes to new packages

This commit is contained in:
Rodrigo Verdiani 2026-03-08 17:54:08 -03:00
parent 60a4f2cff6
commit ff6cceb742
94 changed files with 596 additions and 459 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.DefaultResponseDTO;
import com.magamochi.model.dto.GenreDTO;
import com.magamochi.service.GenreService;
import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Operation;
import java.util.List; import java.util.List;
import lombok.RequiredArgsConstructor; 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.NotBlank;
import jakarta.validation.constraints.NotNull; 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 jakarta.persistence.*;
import java.time.Instant; import java.time.Instant;
import java.util.List;
import lombok.*; import lombok.*;
import org.hibernate.annotations.CreationTimestamp; import org.hibernate.annotations.CreationTimestamp;
import org.hibernate.annotations.UpdateTimestamp; import org.hibernate.annotations.UpdateTimestamp;
@ -26,7 +25,4 @@ public class Author {
@CreationTimestamp private Instant createdAt; @CreationTimestamp private Instant createdAt;
@UpdateTimestamp private Instant updatedAt; @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 jakarta.persistence.*;
import java.util.List;
import lombok.*; import lombok.*;
@Entity @Entity
@ -19,7 +18,4 @@ public class Genre {
private Long malId; private Long malId;
private String name; 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 jakarta.persistence.*;
import lombok.*; import lombok.*;
@ -15,9 +15,7 @@ public class MangaAlternativeTitle {
@GeneratedValue(strategy = GenerationType.IDENTITY) @GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id; private Long id;
@ManyToOne private Long mangaId;
@JoinColumn(name = "manga_id")
private Manga manga;
private String title; private String title;
} }

View File

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

View File

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

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 java.util.Optional;
import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.JpaRepository;
public interface AuthorRepository extends JpaRepository<Author, Long> { public interface AuthorRepository extends JpaRepository<Author, Long> {
Optional<Author> findByMalId(Long aLong); Optional<Author> findByMalId(Long malId);
Optional<Author> findByName(String name); 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 java.util.Optional;
import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.JpaRepository;

View File

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

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,28 @@
package com.magamochi.catalog.service;
import com.magamochi.catalog.model.entity.MangaAlternativeTitle;
import com.magamochi.catalog.model.repository.MangaAlternativeTitlesRepository;
import java.util.List;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
@Service
@RequiredArgsConstructor
public class MangaAlternativeTitleService {
private final MangaAlternativeTitlesRepository mangaAlternativeTitlesRepository;
public void saveOrUpdateMangaAlternativeTitles(Long mangaId, List<String> alternativeTitles) {
mangaAlternativeTitlesRepository.deleteAllByMangaId(mangaId);
alternativeTitles.forEach(
title ->
mangaAlternativeTitlesRepository.save(
MangaAlternativeTitle.builder().mangaId(mangaId).title(title).build()));
}
public List<String> getMangaAlternativeTitles(Long mangaId) {
return mangaAlternativeTitlesRepository.findAllByMangaId(mangaId).stream()
.map(MangaAlternativeTitle::getTitle)
.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

@ -1,4 +1,4 @@
package com.magamochi.config; package com.magamochi.content.config;
import java.net.URI; import java.net.URI;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Value;

View File

@ -1,9 +1,9 @@
package com.magamochi.controller; package com.magamochi.content.controller;
import com.magamochi.content.service.MangaChapterService;
import com.magamochi.model.dto.DefaultResponseDTO; import com.magamochi.model.dto.DefaultResponseDTO;
import com.magamochi.model.dto.MangaChapterImagesDTO; import com.magamochi.content.model.dto.MangaChapterImagesDTO;
import com.magamochi.model.enumeration.ArchiveFileType; import com.magamochi.model.enumeration.ArchiveFileType;
import com.magamochi.service.MangaChapterService;
import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.media.Content; import io.swagger.v3.oas.annotations.media.Content;
import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.media.Schema;

View File

@ -1,8 +1,8 @@
package com.magamochi.model.dto; package com.magamochi.content.model.dto;
import static java.util.Objects.isNull; import static java.util.Objects.isNull;
import com.magamochi.model.entity.Language; import com.magamochi.content.model.entity.Language;
import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull; import jakarta.validation.constraints.NotNull;

View File

@ -1,6 +1,7 @@
package com.magamochi.model.dto; package com.magamochi.content.model.dto;
import com.magamochi.model.entity.MangaChapter; import com.magamochi.content.model.entity.Language;
import com.magamochi.content.model.entity.MangaChapter;
import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull; import jakarta.validation.constraints.NotNull;
@ -10,12 +11,12 @@ public record MangaChapterDTO(
@NotNull Boolean downloaded, @NotNull Boolean downloaded,
@NotNull Boolean isRead, @NotNull Boolean isRead,
LanguageDTO language) { LanguageDTO language) {
public static MangaChapterDTO from(MangaChapter mangaChapter) { public static MangaChapterDTO from(MangaChapter mangaChapter, Language language) {
return new MangaChapterDTO( return new MangaChapterDTO(
mangaChapter.getId(), mangaChapter.getId(),
mangaChapter.getTitle(), mangaChapter.getTitle(),
mangaChapter.getDownloaded(), mangaChapter.getDownloaded(),
mangaChapter.getRead(), mangaChapter.getRead(),
LanguageDTO.from(mangaChapter.getLanguage())); LanguageDTO.from(language));
} }
} }

View File

@ -0,0 +1,19 @@
package com.magamochi.content.model.dto;
import com.magamochi.content.model.entity.MangaChapter;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import java.util.List;
public record MangaChapterImagesDTO(
@NotNull Long id,
@NotBlank String mangaTitle,
Long previousChapterId,
Long nextChapterId,
@NotNull List<@NotBlank String> chapterImageKeys) {
public static MangaChapterImagesDTO from(
MangaChapter mangaChapter, Long prevId, Long nextId, List<String> chapterImageKeys) {
return new MangaChapterImagesDTO(
mangaChapter.getId(), mangaChapter.getTitle(), prevId, nextId, chapterImageKeys);
}
}

View File

@ -1,4 +1,4 @@
package com.magamochi.model.entity; package com.magamochi.content.model.entity;
import jakarta.persistence.*; import jakarta.persistence.*;
import java.time.Instant; import java.time.Instant;

View File

@ -1,4 +1,4 @@
package com.magamochi.model.entity; package com.magamochi.content.model.entity;
import jakarta.persistence.*; import jakarta.persistence.*;
import lombok.*; import lombok.*;

View File

@ -1,8 +1,8 @@
package com.magamochi.model.entity; package com.magamochi.content.model.entity;
import com.magamochi.ingestion.model.entity.MangaProvider;
import jakarta.persistence.*; import jakarta.persistence.*;
import java.time.Instant; import java.time.Instant;
import java.util.List;
import lombok.*; import lombok.*;
import org.hibernate.annotations.CreationTimestamp; import org.hibernate.annotations.CreationTimestamp;
import org.hibernate.annotations.UpdateTimestamp; import org.hibernate.annotations.UpdateTimestamp;
@ -35,12 +35,7 @@ public class MangaChapter {
@UpdateTimestamp private Instant updatedAt; @UpdateTimestamp private Instant updatedAt;
@OneToMany(mappedBy = "mangaChapter")
private List<MangaChapterImage> mangaChapterImages;
private Integer chapterNumber; private Integer chapterNumber;
@ManyToOne private Long languageId;
@JoinColumn(name = "language_id")
private Language language;
} }

View File

@ -1,7 +1,8 @@
package com.magamochi.model.entity; package com.magamochi.content.model.entity;
import jakarta.persistence.*; import jakarta.persistence.*;
import java.time.Instant; import java.time.Instant;
import java.util.UUID;
import lombok.*; import lombok.*;
import org.hibernate.annotations.CreationTimestamp; import org.hibernate.annotations.CreationTimestamp;
import org.hibernate.annotations.UpdateTimestamp; import org.hibernate.annotations.UpdateTimestamp;
@ -18,13 +19,9 @@ public class MangaChapterImage {
@GeneratedValue(strategy = GenerationType.IDENTITY) @GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id; private Long id;
@ManyToOne private Long mangaChapterId;
@JoinColumn(name = "manga_chapter_id")
private MangaChapter mangaChapter;
@OneToOne private UUID imageId;
@JoinColumn(name = "image_id")
private Image image;
private int position; private int position;

View File

@ -1,6 +1,6 @@
package com.magamochi.model.repository; package com.magamochi.content.model.repository;
import com.magamochi.model.entity.Image; import com.magamochi.content.model.entity.Image;
import java.util.UUID; import java.util.UUID;
import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.JpaRepository;

View File

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

View File

@ -0,0 +1,9 @@
package com.magamochi.content.model.repository;
import com.magamochi.content.model.entity.MangaChapterImage;
import java.util.List;
import org.springframework.data.jpa.repository.JpaRepository;
public interface MangaChapterImageRepository extends JpaRepository<MangaChapterImage, Long> {
List<MangaChapterImage> findAllByMangaChapterId(Long mangaChapterId);
}

View File

@ -1,7 +1,7 @@
package com.magamochi.model.repository; package com.magamochi.content.model.repository;
import com.magamochi.model.entity.MangaChapter; import com.magamochi.content.model.entity.MangaChapter;
import com.magamochi.model.entity.MangaProvider; import com.magamochi.ingestion.model.entity.MangaProvider;
import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotBlank;
import java.util.List; import java.util.List;
import java.util.Optional; import java.util.Optional;

View File

@ -1,8 +1,13 @@
package com.magamochi.service; package com.magamochi.content.service;
import com.magamochi.content.model.entity.Image;
import com.magamochi.content.model.repository.ImageRepository;
import com.magamochi.exception.NotFoundException;
import com.magamochi.model.entity.Image;
import com.magamochi.model.repository.ImageRepository;
import java.io.InputStream; import java.io.InputStream;
import java.util.List;
import java.util.Set;
import java.util.UUID;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.extern.log4j.Log4j2; import lombok.extern.log4j.Log4j2;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
@ -14,6 +19,10 @@ public class ImageService {
private final S3Service s3Service; private final S3Service s3Service;
private final ImageRepository imageRepository; private final ImageRepository imageRepository;
public List<String> getImageFileKeys(Set<UUID> imageIds) {
return imageRepository.findAllById(imageIds).stream().map(Image::getFileKey).toList();
}
public Image uploadImage(byte[] data, String contentType, String path) { public Image uploadImage(byte[] data, String contentType, String path) {
log.info("Uploading image {} to S3", path); log.info("Uploading image {} to S3", path);
var fileKey = s3Service.uploadFile(data, contentType, path); var fileKey = s3Service.uploadFile(data, contentType, path);
@ -21,7 +30,12 @@ public class ImageService {
return imageRepository.save(Image.builder().fileKey(fileKey).build()); return imageRepository.save(Image.builder().fileKey(fileKey).build());
} }
public InputStream getImageStream(Image image) { public InputStream getImageStream(UUID imageId) {
var image =
imageRepository
.findById(imageId)
.orElseThrow(() -> new NotFoundException("Image not found"));
return s3Service.getFile(image.getFileKey()); return s3Service.getFile(image.getFileKey());
} }
} }

View File

@ -0,0 +1,26 @@
package com.magamochi.content.service;
import com.magamochi.content.model.entity.Language;
import com.magamochi.content.model.repository.LanguageRepository;
import com.magamochi.exception.NotFoundException;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
@Service
@RequiredArgsConstructor
public class LanguageService {
public final LanguageRepository languageRepository;
public Long getLanguageIdOrThrow(String code) {
return languageRepository
.findByCodeIgnoreCase(code)
.orElseThrow(() -> new NotFoundException("Language with code " + code + " not found"))
.getId();
}
public Language getLanguageOrThrow(Long languageId) {
return languageRepository
.findById(languageId)
.orElseThrow(() -> new NotFoundException("Language not found"));
}
}

View File

@ -0,0 +1,28 @@
package com.magamochi.content.service;
import com.magamochi.content.model.entity.MangaChapterImage;
import com.magamochi.content.model.repository.MangaChapterImageRepository;
import java.util.Comparator;
import java.util.List;
import java.util.stream.Collectors;
import lombok.RequiredArgsConstructor;
import lombok.extern.log4j.Log4j2;
import org.springframework.stereotype.Service;
@Log4j2
@Service
@RequiredArgsConstructor
public class MangaChapterImageService {
private final ImageService imageService;
private final MangaChapterImageRepository mangaChapterImageRepository;
public List<String> getMangaChapterImageKeys(Long mangaChapterId) {
var mangaChapterImageIds =
mangaChapterImageRepository.findAllByMangaChapterId(mangaChapterId).stream()
.sorted(Comparator.comparing(MangaChapterImage::getPosition))
.map(MangaChapterImage::getImageId)
.collect(Collectors.toSet());
return imageService.getImageFileKeys(mangaChapterImageIds);
}
}

View File

@ -1,15 +1,15 @@
package com.magamochi.service; package com.magamochi.content.service;
import com.google.common.util.concurrent.RateLimiter; import com.google.common.util.concurrent.RateLimiter;
import com.magamochi.content.model.entity.MangaChapter;
import com.magamochi.content.model.entity.MangaChapterImage;
import com.magamochi.content.model.repository.MangaChapterImageRepository;
import com.magamochi.content.model.repository.MangaChapterRepository;
import com.magamochi.exception.UnprocessableException; import com.magamochi.exception.UnprocessableException;
import com.magamochi.model.dto.MangaChapterArchiveDTO; import com.magamochi.model.dto.MangaChapterArchiveDTO;
import com.magamochi.model.dto.MangaChapterImagesDTO; import com.magamochi.content.model.dto.MangaChapterImagesDTO;
import com.magamochi.model.entity.MangaChapter;
import com.magamochi.model.entity.MangaChapterImage;
import com.magamochi.model.enumeration.ArchiveFileType; import com.magamochi.model.enumeration.ArchiveFileType;
import com.magamochi.model.repository.MangaChapterImageRepository; import com.magamochi.ingestion.service.providers.ContentProviderFactory;
import com.magamochi.model.repository.MangaChapterRepository;
import com.magamochi.service.providers.ContentProviderFactory;
import io.github.resilience4j.retry.Retry; import io.github.resilience4j.retry.Retry;
import io.github.resilience4j.retry.RetryRegistry; import io.github.resilience4j.retry.RetryRegistry;
import java.io.BufferedInputStream; import java.io.BufferedInputStream;
@ -36,6 +36,7 @@ public class MangaChapterService {
private final MangaChapterImageRepository mangaChapterImageRepository; private final MangaChapterImageRepository mangaChapterImageRepository;
private final ImageService imageService; private final ImageService imageService;
private final MangaChapterImageService mangaChapterImageService;
private final ContentProviderFactory contentProviderFactory; private final ContentProviderFactory contentProviderFactory;
@ -104,9 +105,9 @@ public class MangaChapterService {
entry.getValue()); entry.getValue());
return MangaChapterImage.builder() return MangaChapterImage.builder()
.mangaChapter(chapter) .mangaChapterId(chapter.getId())
.position(entry.getKey()) .position(entry.getKey())
.image(image) .imageId(image.getId())
.build(); .build();
} }
}); });
@ -148,7 +149,9 @@ public class MangaChapterService {
} }
} }
return MangaChapterImagesDTO.from(chapter, prevId, nextId); var imageFileKeys = mangaChapterImageService.getMangaChapterImageKeys(chapter.getId());
return MangaChapterImagesDTO.from(chapter, prevId, nextId, imageFileKeys);
} }
public void markAsRead(Long chapterId) { public void markAsRead(Long chapterId) {
@ -162,7 +165,7 @@ public class MangaChapterService {
throws IOException { throws IOException {
var chapter = getMangaChapterThrowIfNotFound(chapterId); var chapter = getMangaChapterThrowIfNotFound(chapterId);
var chapterImages = mangaChapterImageRepository.findAllByMangaChapter(chapter); var chapterImages = mangaChapterImageRepository.findAllByMangaChapterId(chapter.getId());
var byteArrayOutputStream = var byteArrayOutputStream =
switch (archiveFileType) { switch (archiveFileType) {
@ -191,7 +194,7 @@ public class MangaChapterService {
var paddedFileName = String.format("%0" + paddingLength + "d.jpg", imgSrc.getPosition()); var paddedFileName = String.format("%0" + paddingLength + "d.jpg", imgSrc.getPosition());
zipOutputStream.putNextEntry(new ZipEntry(paddedFileName)); zipOutputStream.putNextEntry(new ZipEntry(paddedFileName));
IOUtils.copy(imageService.getImageStream(imgSrc.getImage()), zipOutputStream); IOUtils.copy(imageService.getImageStream(imgSrc.getImageId()), zipOutputStream);
zipOutputStream.closeEntry(); zipOutputStream.closeEntry();
} }

View File

@ -1,4 +1,4 @@
package com.magamochi.service; package com.magamochi.content.service;
import static java.util.Objects.nonNull; import static java.util.Objects.nonNull;

View File

@ -1,8 +1,8 @@
package com.magamochi.task; package com.magamochi.content.task;
import com.magamochi.model.entity.Image; import com.magamochi.content.model.entity.Image;
import com.magamochi.model.repository.ImageRepository; import com.magamochi.content.model.repository.ImageRepository;
import com.magamochi.service.S3Service; import com.magamochi.content.service.S3Service;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.extern.log4j.Log4j2; import lombok.extern.log4j.Log4j2;

View File

@ -4,7 +4,7 @@ import com.magamochi.client.NtfyClient;
import com.magamochi.model.dto.DefaultResponseDTO; import com.magamochi.model.dto.DefaultResponseDTO;
import com.magamochi.model.dto.UpdateMangaDataCommand; import com.magamochi.model.dto.UpdateMangaDataCommand;
import com.magamochi.queue.UpdateMangaDataProducer; import com.magamochi.queue.UpdateMangaDataProducer;
import com.magamochi.task.ImageCleanupTask; import com.magamochi.content.task.ImageCleanupTask;
import com.magamochi.task.MangaFollowUpdateTask; import com.magamochi.task.MangaFollowUpdateTask;
import com.magamochi.task.UpdateMangaListTask; import com.magamochi.task.UpdateMangaListTask;
import com.magamochi.user.model.repository.UserRepository; import com.magamochi.user.model.repository.UserRepository;

View File

@ -1,5 +1,6 @@
package com.magamochi.controller; package com.magamochi.controller;
import com.magamochi.content.model.dto.MangaChapterDTO;
import com.magamochi.model.dto.*; import com.magamochi.model.dto.*;
import com.magamochi.service.MangaService; import com.magamochi.service.MangaService;
import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Operation;

View File

@ -2,7 +2,7 @@ package com.magamochi.controller;
import com.magamochi.model.dto.DefaultResponseDTO; import com.magamochi.model.dto.DefaultResponseDTO;
import com.magamochi.model.dto.ImportReviewDTO; import com.magamochi.model.dto.ImportReviewDTO;
import com.magamochi.service.MangaImportReviewService; import com.magamochi.ingestion.service.MangaImportReviewService;
import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Operation;
import java.util.List; import java.util.List;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;

View File

@ -1,8 +1,8 @@
package com.magamochi.controller; package com.magamochi.ingestion.controller;
import com.magamochi.model.dto.DefaultResponseDTO; import com.magamochi.model.dto.DefaultResponseDTO;
import com.magamochi.model.dto.ProviderListDTO; import com.magamochi.ingestion.model.dto.ProviderListDTO;
import com.magamochi.service.ProviderService; import com.magamochi.ingestion.service.ProviderService;
import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Operation;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.*; import org.springframework.web.bind.annotation.*;
@ -19,7 +19,7 @@ public class ProviderController {
tags = {"Provider"}, tags = {"Provider"},
operationId = "getProviders") operationId = "getProviders")
@GetMapping @GetMapping
public DefaultResponseDTO<ProviderListDTO> getMangas( public DefaultResponseDTO<ProviderListDTO> getProviders(
@RequestParam(name = "manualImport", required = false) Boolean manualImport) { @RequestParam(name = "manualImport", required = false) Boolean manualImport) {
return DefaultResponseDTO.ok(providerService.getProviders(manualImport)); return DefaultResponseDTO.ok(providerService.getProviders(manualImport));
} }

View File

@ -1,6 +1,6 @@
package com.magamochi.model.dto; package com.magamochi.ingestion.model.dto;
import com.magamochi.model.entity.Provider; import com.magamochi.ingestion.model.entity.Provider;
import jakarta.validation.constraints.NotNull; import jakarta.validation.constraints.NotNull;
import java.util.List; import java.util.List;

View File

@ -0,0 +1,10 @@
package com.magamochi.ingestion.model.dto;
import java.util.List;
import lombok.Builder;
@Builder
public record TitleMatchRequestDTO(
String title, List<String> options,
Integer threshold) {
}

View File

@ -0,0 +1,10 @@
package com.magamochi.ingestion.model.dto;
import lombok.Builder;
@Builder
public record TitleMatchResponseDTO(
boolean matchFound,
String bestMatch,
Double similarity) {
}

View File

@ -1,4 +1,4 @@
package com.magamochi.model.entity; package com.magamochi.ingestion.model.entity;
import java.time.Instant; import java.time.Instant;
import lombok.Builder; import lombok.Builder;

View File

@ -1,4 +1,4 @@
package com.magamochi.model.entity; package com.magamochi.ingestion.model.entity;
import jakarta.persistence.*; import jakarta.persistence.*;
import java.time.Instant; import java.time.Instant;

View File

@ -1,5 +1,7 @@
package com.magamochi.model.entity; package com.magamochi.ingestion.model.entity;
import com.magamochi.content.model.entity.MangaChapter;
import com.magamochi.model.entity.Manga;
import jakarta.persistence.*; import jakarta.persistence.*;
import java.time.Instant; import java.time.Instant;
import java.util.List; import java.util.List;

View File

@ -1,6 +1,6 @@
package com.magamochi.model.entity; package com.magamochi.ingestion.model.entity;
import com.magamochi.model.enumeration.ProviderStatus; import com.magamochi.ingestion.model.enumeration.ProviderStatus;
import jakarta.persistence.*; import jakarta.persistence.*;
import java.time.Instant; import java.time.Instant;
import java.util.List; import java.util.List;

View File

@ -1,4 +1,4 @@
package com.magamochi.model.enumeration; package com.magamochi.ingestion.model.enumeration;
public enum ProviderStatus { public enum ProviderStatus {
ACTIVE, ACTIVE,

View File

@ -1,6 +1,6 @@
package com.magamochi.model.repository; package com.magamochi.ingestion.model.repository;
import com.magamochi.model.entity.MangaImportReview; import com.magamochi.ingestion.model.entity.MangaImportReview;
import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.JpaRepository;
public interface MangaImportReviewRepository extends JpaRepository<MangaImportReview, Long> { public interface MangaImportReviewRepository extends JpaRepository<MangaImportReview, Long> {

View File

@ -1,8 +1,8 @@
package com.magamochi.model.repository; package com.magamochi.ingestion.model.repository;
import com.magamochi.model.entity.Manga; import com.magamochi.model.entity.Manga;
import com.magamochi.model.entity.MangaProvider; import com.magamochi.ingestion.model.entity.MangaProvider;
import com.magamochi.model.entity.Provider; import com.magamochi.ingestion.model.entity.Provider;
import java.util.Optional; import java.util.Optional;
import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.JpaRepository;

View File

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

View File

@ -1,4 +1,4 @@
package com.magamochi.service; package com.magamochi.ingestion.service;
import com.magamochi.client.FlareClient; import com.magamochi.client.FlareClient;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;

View File

@ -1,11 +1,11 @@
package com.magamochi.service; package com.magamochi.ingestion.service;
import static java.util.Objects.isNull; import static java.util.Objects.isNull;
import static java.util.Objects.nonNull; import static java.util.Objects.nonNull;
import com.magamochi.client.FlareClient; import com.magamochi.client.FlareClient;
import com.magamochi.model.entity.FlareSession; import com.magamochi.ingestion.model.entity.FlareSession;
import com.magamochi.registry.FlareSessionRegistry;
import java.time.Duration; import java.time.Duration;
import java.time.Instant; import java.time.Instant;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;

View File

@ -1,6 +1,6 @@
package com.magamochi.registry; package com.magamochi.ingestion.service;
import com.magamochi.model.entity.FlareSession; import com.magamochi.ingestion.model.entity.FlareSession;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import lombok.Getter; import lombok.Getter;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;

View File

@ -1,4 +1,4 @@
package com.magamochi.service; package com.magamochi.ingestion.service;
import static java.util.Objects.isNull; import static java.util.Objects.isNull;
@ -8,10 +8,10 @@ import com.magamochi.exception.NotFoundException;
import com.magamochi.model.dto.ImportReviewDTO; import com.magamochi.model.dto.ImportReviewDTO;
import com.magamochi.model.dto.UpdateMangaDataCommand; import com.magamochi.model.dto.UpdateMangaDataCommand;
import com.magamochi.model.entity.Manga; import com.magamochi.model.entity.Manga;
import com.magamochi.model.entity.MangaImportReview; import com.magamochi.ingestion.model.entity.MangaImportReview;
import com.magamochi.model.entity.MangaProvider; import com.magamochi.ingestion.model.entity.MangaProvider;
import com.magamochi.model.repository.MangaImportReviewRepository; import com.magamochi.ingestion.model.repository.MangaImportReviewRepository;
import com.magamochi.model.repository.MangaProviderRepository; import com.magamochi.ingestion.model.repository.MangaProviderRepository;
import com.magamochi.model.repository.MangaRepository; import com.magamochi.model.repository.MangaRepository;
import com.magamochi.queue.UpdateMangaDataProducer; import com.magamochi.queue.UpdateMangaDataProducer;
import java.util.List; import java.util.List;

View File

@ -1,11 +1,11 @@
package com.magamochi.service; package com.magamochi.ingestion.service;
import static java.util.Objects.nonNull; import static java.util.Objects.nonNull;
import com.magamochi.model.dto.ProviderListDTO; import com.magamochi.ingestion.model.dto.ProviderListDTO;
import com.magamochi.model.entity.Provider; import com.magamochi.ingestion.model.entity.Provider;
import com.magamochi.model.enumeration.ProviderStatus; import com.magamochi.ingestion.model.enumeration.ProviderStatus;
import com.magamochi.model.repository.ProviderRepository; import com.magamochi.ingestion.model.repository.ProviderRepository;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;

View File

@ -1,10 +1,11 @@
package com.magamochi.service; package com.magamochi.ingestion.service;
import static java.util.Objects.isNull;
import static org.apache.commons.lang3.StringUtils.isBlank; import static org.apache.commons.lang3.StringUtils.isBlank;
import static org.springframework.util.CollectionUtils.isEmpty; import static org.springframework.util.CollectionUtils.isEmpty;
import com.magamochi.model.dto.TitleMatchRequestDTO; import com.magamochi.ingestion.model.dto.TitleMatchRequestDTO;
import com.magamochi.model.dto.TitleMatchResponseDTO; import com.magamochi.ingestion.model.dto.TitleMatchResponseDTO;
import lombok.extern.log4j.Log4j2; import lombok.extern.log4j.Log4j2;
import org.apache.commons.text.similarity.LevenshteinDistance; import org.apache.commons.text.similarity.LevenshteinDistance;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
@ -15,17 +16,23 @@ public class TitleMatcherService {
private final LevenshteinDistance levenshteinDistance = LevenshteinDistance.getDefaultInstance(); private final LevenshteinDistance levenshteinDistance = LevenshteinDistance.getDefaultInstance();
public TitleMatchResponseDTO findBestMatch(TitleMatchRequestDTO request) { public TitleMatchResponseDTO findBestMatch(TitleMatchRequestDTO request) {
if (isBlank(request.getTitle()) || isEmpty(request.getOptions())) { if (isBlank(request.title()) || isEmpty(request.options())) {
throw new IllegalArgumentException("Title and options are required"); throw new IllegalArgumentException("Title and options are required");
} }
log.info("Finding best match for {}. Options: {}", request.getTitle(), request.getOptions()); // Set the default threshold if not specified
var threshold = request.threshold();
if (isNull(threshold) || threshold == 0) {
threshold = 85;
}
log.info("Finding best match for {}. Options: {}", request.title(), request.options());
String bestMatch = null; String bestMatch = null;
double bestScore = 0.0; double bestScore = 0.0;
for (var option : request.getOptions()) { for (var option : request.options()) {
var score = calculateSimilarityScore(request.getTitle(), option); var score = calculateSimilarityScore(request.title(), option);
if (score > bestScore) { if (score > bestScore) {
bestScore = score; bestScore = score;
@ -33,9 +40,9 @@ public class TitleMatcherService {
} }
} }
if (bestScore >= request.getThreshold()) { if (bestScore >= threshold) {
log.info( log.info(
"Found best match for {}: {}. Similarity: {}", request.getTitle(), bestMatch, bestScore); "Found best match for {}: {}. Similarity: {}", request.title(), bestMatch, bestScore);
return TitleMatchResponseDTO.builder() return TitleMatchResponseDTO.builder()
.matchFound(true) .matchFound(true)
@ -44,7 +51,7 @@ public class TitleMatcherService {
.build(); .build();
} }
log.info("No match found for {}. Threshold: {}", request.getTitle(), request.getThreshold()); log.info("No match found for {}. Threshold: {}", request.title(), threshold);
return TitleMatchResponseDTO.builder().matchFound(false).build(); return TitleMatchResponseDTO.builder().matchFound(false).build();
} }

View File

@ -1,7 +1,7 @@
package com.magamochi.service.providers; package com.magamochi.ingestion.service.providers;
import com.magamochi.model.dto.ContentProviderMangaChapterResponseDTO; import com.magamochi.model.dto.ContentProviderMangaChapterResponseDTO;
import com.magamochi.model.entity.MangaProvider; import com.magamochi.ingestion.model.entity.MangaProvider;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;

View File

@ -1,4 +1,4 @@
package com.magamochi.service.providers; package com.magamochi.ingestion.service.providers;
import java.util.Map; import java.util.Map;
import java.util.Objects; import java.util.Objects;

View File

@ -1,4 +1,4 @@
package com.magamochi.service.providers; package com.magamochi.ingestion.service.providers;
public class ContentProviders { public class ContentProviders {
public static final String MANGA_LIVRE_TO = "Manga Livre.to"; public static final String MANGA_LIVRE_TO = "Manga Livre.to";

View File

@ -1,4 +1,4 @@
package com.magamochi.service.providers; package com.magamochi.ingestion.service.providers;
public interface ManualImportContentProvider { public interface ManualImportContentProvider {
String getMangaTitle(String value); String getMangaTitle(String value);

View File

@ -1,4 +1,4 @@
package com.magamochi.service.providers; package com.magamochi.ingestion.service.providers;
import java.util.Map; import java.util.Map;
import java.util.Objects; import java.util.Objects;

View File

@ -1,4 +1,4 @@
package com.magamochi.service.providers; package com.magamochi.ingestion.service.providers;
import com.magamochi.model.dto.ContentProviderMangaInfoResponseDTO; import com.magamochi.model.dto.ContentProviderMangaInfoResponseDTO;
import java.util.List; import java.util.List;

View File

@ -1,4 +1,4 @@
package com.magamochi.service.providers; package com.magamochi.ingestion.service.providers;
import java.util.Map; import java.util.Map;
import java.util.Objects; import java.util.Objects;

View File

@ -1,14 +1,14 @@
package com.magamochi.service.providers.impl; package com.magamochi.ingestion.service.providers.impl;
import static java.util.Objects.isNull; import static java.util.Objects.isNull;
import com.magamochi.exception.UnprocessableException; import com.magamochi.exception.UnprocessableException;
import com.magamochi.model.dto.ContentProviderMangaChapterResponseDTO; import com.magamochi.model.dto.ContentProviderMangaChapterResponseDTO;
import com.magamochi.model.entity.MangaProvider; import com.magamochi.ingestion.model.entity.MangaProvider;
import com.magamochi.service.FlareService; import com.magamochi.ingestion.service.FlareService;
import com.magamochi.service.providers.ContentProvider; import com.magamochi.ingestion.service.providers.ContentProvider;
import com.magamochi.service.providers.ContentProviders; import com.magamochi.ingestion.service.providers.ContentProviders;
import com.magamochi.service.providers.ManualImportContentProvider; import com.magamochi.ingestion.service.providers.ManualImportContentProvider;
import java.util.*; import java.util.*;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import java.util.stream.IntStream; import java.util.stream.IntStream;

View File

@ -1,4 +1,4 @@
package com.magamochi.service.providers.impl; package com.magamochi.ingestion.service.providers.impl;
import static java.util.Objects.isNull; import static java.util.Objects.isNull;
@ -6,10 +6,10 @@ import com.google.common.util.concurrent.RateLimiter;
import com.magamochi.client.MangaDexClient; import com.magamochi.client.MangaDexClient;
import com.magamochi.exception.UnprocessableException; import com.magamochi.exception.UnprocessableException;
import com.magamochi.model.dto.ContentProviderMangaChapterResponseDTO; import com.magamochi.model.dto.ContentProviderMangaChapterResponseDTO;
import com.magamochi.model.entity.MangaProvider; import com.magamochi.ingestion.model.entity.MangaProvider;
import com.magamochi.service.providers.ContentProvider; import com.magamochi.ingestion.service.providers.ContentProvider;
import com.magamochi.service.providers.ContentProviders; import com.magamochi.ingestion.service.providers.ContentProviders;
import com.magamochi.service.providers.ManualImportContentProvider; import com.magamochi.ingestion.service.providers.ManualImportContentProvider;
import java.util.*; import java.util.*;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import java.util.stream.IntStream; import java.util.stream.IntStream;

View File

@ -1,12 +1,12 @@
package com.magamochi.service.providers.impl; package com.magamochi.ingestion.service.providers.impl;
import com.magamochi.model.dto.ContentProviderMangaChapterResponseDTO; import com.magamochi.model.dto.ContentProviderMangaChapterResponseDTO;
import com.magamochi.model.dto.ContentProviderMangaInfoResponseDTO; import com.magamochi.model.dto.ContentProviderMangaInfoResponseDTO;
import com.magamochi.model.entity.MangaProvider; import com.magamochi.ingestion.model.entity.MangaProvider;
import com.magamochi.model.enumeration.MangaStatus; import com.magamochi.model.enumeration.MangaStatus;
import com.magamochi.service.providers.ContentProvider; import com.magamochi.ingestion.service.providers.ContentProvider;
import com.magamochi.service.providers.ContentProviders; import com.magamochi.ingestion.service.providers.ContentProviders;
import com.magamochi.service.providers.PagedContentProvider; import com.magamochi.ingestion.service.providers.PagedContentProvider;
import java.io.IOException; import java.io.IOException;
import java.util.*; import java.util.*;
import java.util.regex.Pattern; import java.util.regex.Pattern;

View File

@ -1,15 +1,15 @@
package com.magamochi.service.providers.impl; package com.magamochi.ingestion.service.providers.impl;
import static java.util.Objects.nonNull; import static java.util.Objects.nonNull;
import com.magamochi.model.dto.ContentProviderMangaChapterResponseDTO; import com.magamochi.model.dto.ContentProviderMangaChapterResponseDTO;
import com.magamochi.model.dto.ContentProviderMangaInfoResponseDTO; import com.magamochi.model.dto.ContentProviderMangaInfoResponseDTO;
import com.magamochi.model.entity.MangaProvider; import com.magamochi.ingestion.model.entity.MangaProvider;
import com.magamochi.model.enumeration.MangaStatus; import com.magamochi.model.enumeration.MangaStatus;
import com.magamochi.service.FlareService; import com.magamochi.ingestion.service.FlareService;
import com.magamochi.service.providers.ContentProvider; import com.magamochi.ingestion.service.providers.ContentProvider;
import com.magamochi.service.providers.ContentProviders; import com.magamochi.ingestion.service.providers.ContentProviders;
import com.magamochi.service.providers.PagedContentProvider; import com.magamochi.ingestion.service.providers.PagedContentProvider;
import java.util.*; import java.util.*;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import java.util.stream.IntStream; import java.util.stream.IntStream;

View File

@ -1,16 +1,16 @@
package com.magamochi.service.providers.impl; package com.magamochi.ingestion.service.providers.impl;
import static java.util.Objects.isNull; import static java.util.Objects.isNull;
import static org.apache.commons.lang3.StringUtils.isBlank; import static org.apache.commons.lang3.StringUtils.isBlank;
import com.magamochi.model.dto.ContentProviderMangaChapterResponseDTO; import com.magamochi.model.dto.ContentProviderMangaChapterResponseDTO;
import com.magamochi.model.dto.ContentProviderMangaInfoResponseDTO; import com.magamochi.model.dto.ContentProviderMangaInfoResponseDTO;
import com.magamochi.model.entity.MangaProvider; import com.magamochi.ingestion.model.entity.MangaProvider;
import com.magamochi.model.enumeration.MangaStatus; import com.magamochi.model.enumeration.MangaStatus;
import com.magamochi.service.FlareService; import com.magamochi.ingestion.service.FlareService;
import com.magamochi.service.providers.ContentProvider; import com.magamochi.ingestion.service.providers.ContentProvider;
import com.magamochi.service.providers.ContentProviders; import com.magamochi.ingestion.service.providers.ContentProviders;
import com.magamochi.service.providers.PagedContentProvider; import com.magamochi.ingestion.service.providers.PagedContentProvider;
import java.util.*; import java.util.*;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import java.util.stream.IntStream; import java.util.stream.IntStream;

View File

@ -1,7 +1,7 @@
package com.magamochi.task; package com.magamochi.ingestion.task;
import com.magamochi.client.FlareClient; import com.magamochi.client.FlareClient;
import com.magamochi.registry.FlareSessionRegistry; import com.magamochi.ingestion.service.FlareSessionRegistry;
import java.time.Duration; import java.time.Duration;
import java.time.Instant; import java.time.Instant;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;

View File

@ -1,4 +1,4 @@
package com.magamochi.task; package com.magamochi.ingestion.task;
import com.magamochi.client.FlareClient; import com.magamochi.client.FlareClient;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;

View File

@ -1,6 +1,6 @@
package com.magamochi.model.dto; package com.magamochi.model.dto;
import com.magamochi.model.entity.MangaImportReview; import com.magamochi.ingestion.model.entity.MangaImportReview;
import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull; import jakarta.validation.constraints.NotNull;
import java.time.Instant; import java.time.Instant;

View File

@ -1,27 +0,0 @@
package com.magamochi.model.dto;
import com.magamochi.model.entity.MangaChapter;
import com.magamochi.model.entity.MangaChapterImage;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import java.util.Comparator;
import java.util.List;
public record MangaChapterImagesDTO(
@NotNull Long id,
@NotBlank String mangaTitle,
Long previousChapterId,
Long nextChapterId,
@NotNull List<@NotBlank String> chapterImageKeys) {
public static MangaChapterImagesDTO from(MangaChapter mangaChapter, Long prevId, Long nextId) {
return new MangaChapterImagesDTO(
mangaChapter.getId(),
mangaChapter.getTitle(),
prevId,
nextId,
mangaChapter.getMangaChapterImages().stream()
.sorted(Comparator.comparing(MangaChapterImage::getPosition))
.map(mangaChapterImage -> mangaChapterImage.getImage().getFileKey())
.toList());
}
}

View File

@ -1,10 +0,0 @@
package com.magamochi.model.dto;
import jakarta.validation.constraints.NotNull;
import java.util.UUID;
public record MangaChapterResponseDTO(
@NotNull UUID id,
// @NotNull Long mangaProviderId,
@NotNull String chapterTitle,
@NotNull String chapterUrl) {}

View File

@ -2,11 +2,10 @@ package com.magamochi.model.dto;
import static java.util.Objects.isNull; import static java.util.Objects.isNull;
import com.magamochi.content.model.entity.MangaChapter;
import com.magamochi.model.entity.Manga; import com.magamochi.model.entity.Manga;
import com.magamochi.model.entity.MangaAlternativeTitle; import com.magamochi.ingestion.model.entity.MangaProvider;
import com.magamochi.model.entity.MangaChapter; import com.magamochi.ingestion.model.enumeration.ProviderStatus;
import com.magamochi.model.entity.MangaProvider;
import com.magamochi.model.enumeration.ProviderStatus;
import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull; import jakarta.validation.constraints.NotNull;
import java.time.OffsetDateTime; import java.time.OffsetDateTime;
@ -29,7 +28,13 @@ public record MangaDTO(
@NotNull Integer chapterCount, @NotNull Integer chapterCount,
@NotNull Boolean favorite, @NotNull Boolean favorite,
@NotNull Boolean following) { @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,
List<String> alternativeTitles) {
return new MangaDTO( return new MangaDTO(
manga.getId(), manga.getId(),
manga.getTitle(), manga.getTitle(),
@ -39,11 +44,9 @@ public record MangaDTO(
manga.getPublishedTo(), manga.getPublishedTo(),
manga.getSynopsis(), manga.getSynopsis(),
manga.getMangaProviders().size(), manga.getMangaProviders().size(),
manga.getAlternativeTitles().stream().map(MangaAlternativeTitle::getTitle).toList(), alternativeTitles,
manga.getMangaGenres().stream().map(mangaGenre -> mangaGenre.getGenre().getName()).toList(), genres,
manga.getMangaAuthors().stream() authors,
.map(mangaAuthor -> mangaAuthor.getAuthor().getName())
.toList(),
manga.getScore(), manga.getScore(),
manga.getMangaProviders().stream().map(MangaProviderDTO::from).toList(), manga.getMangaProviders().stream().map(MangaProviderDTO::from).toList(),
manga.getChapterCount(), manga.getChapterCount(),

View File

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

View File

@ -1,15 +0,0 @@
package com.magamochi.model.dto;
import java.util.List;
import lombok.Builder;
import lombok.Getter;
@Getter
@Builder
public class TitleMatchRequestDTO {
private String title;
private List<String> options;
@Builder.Default private int threshold = 85;
}

View File

@ -1,12 +0,0 @@
package com.magamochi.model.dto;
import lombok.Builder;
import lombok.Getter;
@Getter
@Builder
public class TitleMatchResponseDTO {
boolean matchFound;
String bestMatch;
Double similarity;
}

View File

@ -1,5 +1,7 @@
package com.magamochi.model.entity; package com.magamochi.model.entity;
import com.magamochi.content.model.entity.Image;
import com.magamochi.ingestion.model.entity.MangaProvider;
import jakarta.persistence.*; import jakarta.persistence.*;
import java.time.Instant; import java.time.Instant;
import java.time.OffsetDateTime; import java.time.OffsetDateTime;
@ -47,14 +49,5 @@ public class Manga {
private OffsetDateTime publishedTo; private OffsetDateTime publishedTo;
@OneToMany(mappedBy = "manga")
private List<MangaAuthor> mangaAuthors;
@OneToMany(mappedBy = "manga")
private List<MangaGenre> mangaGenres;
@OneToMany(mappedBy = "manga")
private List<MangaAlternativeTitle> alternativeTitles;
@Builder.Default private Integer chapterCount = 0; @Builder.Default private Integer chapterCount = 0;
} }

View File

@ -1,7 +0,0 @@
package com.magamochi.model.repository;
import com.magamochi.model.entity.MangaAlternativeTitle;
import org.springframework.data.jpa.repository.JpaRepository;
public interface MangaAlternativeTitlesRepository
extends JpaRepository<MangaAlternativeTitle, Long> {}

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,10 +0,0 @@
package com.magamochi.model.repository;
import com.magamochi.model.entity.MangaChapter;
import com.magamochi.model.entity.MangaChapterImage;
import java.util.List;
import org.springframework.data.jpa.repository.JpaRepository;
public interface MangaChapterImageRepository extends JpaRepository<MangaChapterImage, Long> {
List<MangaChapterImage> findAllByMangaChapter(MangaChapter mangaChapter);
}

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 static java.util.Objects.nonNull;
import com.magamochi.catalog.model.entity.Author;
import com.magamochi.model.dto.MangaListFilterDTO; import com.magamochi.model.dto.MangaListFilterDTO;
import com.magamochi.model.entity.Author;
import com.magamochi.model.entity.Manga; import com.magamochi.model.entity.Manga;
import com.magamochi.user.model.entity.User; import com.magamochi.user.model.entity.User;
import jakarta.persistence.criteria.*; import jakarta.persistence.criteria.*;

View File

@ -1,7 +1,7 @@
package com.magamochi.queue; package com.magamochi.queue;
import com.magamochi.content.service.MangaChapterService;
import com.magamochi.model.dto.MangaChapterDownloadCommand; import com.magamochi.model.dto.MangaChapterDownloadCommand;
import com.magamochi.service.MangaChapterService;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.extern.log4j.Log4j2; import lombok.extern.log4j.Log4j2;
import org.springframework.amqp.rabbit.annotation.RabbitListener; import org.springframework.amqp.rabbit.annotation.RabbitListener;

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

@ -1,19 +0,0 @@
package com.magamochi.service;
import com.magamochi.exception.NotFoundException;
import com.magamochi.model.entity.Language;
import com.magamochi.model.repository.LanguageRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
@Service
@RequiredArgsConstructor
public class LanguageService {
public final LanguageRepository languageRepository;
public Language getOrThrow(String code) {
return languageRepository
.findByCodeIgnoreCase(code)
.orElseThrow(() -> new NotFoundException("Language with code " + code + " not found"));
}
}

View File

@ -5,12 +5,13 @@ import static java.util.Objects.nonNull;
import com.google.common.util.concurrent.RateLimiter; import com.google.common.util.concurrent.RateLimiter;
import com.magamochi.client.AniListClient; import com.magamochi.client.AniListClient;
import com.magamochi.client.JikanClient; import com.magamochi.client.JikanClient;
import com.magamochi.model.dto.TitleMatchRequestDTO; import com.magamochi.ingestion.service.TitleMatcherService;
import com.magamochi.ingestion.model.dto.TitleMatchRequestDTO;
import com.magamochi.model.dto.UpdateMangaDataCommand; import com.magamochi.model.dto.UpdateMangaDataCommand;
import com.magamochi.model.entity.Manga; import com.magamochi.model.entity.Manga;
import com.magamochi.model.entity.MangaImportReview; import com.magamochi.ingestion.model.entity.MangaImportReview;
import com.magamochi.model.entity.Provider; import com.magamochi.ingestion.model.entity.Provider;
import com.magamochi.model.repository.MangaImportReviewRepository; import com.magamochi.ingestion.model.repository.MangaImportReviewRepository;
import com.magamochi.model.repository.MangaRepository; import com.magamochi.model.repository.MangaRepository;
import com.magamochi.queue.UpdateMangaDataProducer; import com.magamochi.queue.UpdateMangaDataProducer;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
@ -60,7 +61,7 @@ public class MangaCreationService {
.toList()) .toList())
.build()); .build());
if (!titleMatchResponse.isMatchFound()) { if (!titleMatchResponse.matchFound()) {
createMangaImportReview(title, url, provider); createMangaImportReview(title, url, provider);
log.warn("No match found for manga with title {}", title); log.warn("No match found for manga with title {}", title);
return null; return null;
@ -73,7 +74,7 @@ public class MangaCreationService {
results.titles().stream() results.titles().stream()
.map(JikanClient.SearchResponse.MangaData.TitleData::title) .map(JikanClient.SearchResponse.MangaData.TitleData::title)
.toList() .toList()
.contains(titleMatchResponse.getBestMatch())) .contains(titleMatchResponse.bestMatch()))
.findFirst(); .findFirst();
if (resultOptional.isEmpty()) { if (resultOptional.isEmpty()) {
createMangaImportReview(title, url, provider); createMangaImportReview(title, url, provider);

View File

@ -4,9 +4,20 @@ import static java.util.Objects.isNull;
import static java.util.Objects.nonNull; import static java.util.Objects.nonNull;
import com.google.common.util.concurrent.RateLimiter; import com.google.common.util.concurrent.RateLimiter;
import com.magamochi.catalog.service.*;
import com.magamochi.client.AniListClient; import com.magamochi.client.AniListClient;
import com.magamochi.client.JikanClient; import com.magamochi.client.JikanClient;
import com.magamochi.content.model.entity.MangaChapter;
import com.magamochi.content.model.entity.MangaChapterImage;
import com.magamochi.content.model.repository.MangaChapterImageRepository;
import com.magamochi.content.model.repository.MangaChapterRepository;
import com.magamochi.content.service.ImageService;
import com.magamochi.content.service.LanguageService;
import com.magamochi.exception.NotFoundException; import com.magamochi.exception.NotFoundException;
import com.magamochi.ingestion.model.entity.MangaProvider;
import com.magamochi.ingestion.model.entity.Provider;
import com.magamochi.ingestion.model.repository.MangaProviderRepository;
import com.magamochi.ingestion.service.ProviderService;
import com.magamochi.model.dto.ContentProviderMangaChapterResponseDTO; import com.magamochi.model.dto.ContentProviderMangaChapterResponseDTO;
import com.magamochi.model.entity.*; import com.magamochi.model.entity.*;
import com.magamochi.model.repository.*; import com.magamochi.model.repository.*;
@ -20,6 +31,7 @@ import java.time.ZoneOffset;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Comparator; import java.util.Comparator;
import java.util.List; import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.IntStream; import java.util.stream.IntStream;
import java.util.zip.ZipEntry; import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream; import java.util.zip.ZipInputStream;
@ -38,20 +50,22 @@ public class MangaImportService {
private final ImageService imageService; private final ImageService imageService;
private final LanguageService languageService; private final LanguageService languageService;
private final GenreRepository genreRepository; private final GenreService genreService;
private final MangaGenreRepository mangaGenreRepository; private final MangaGenreService mangaGenreService;
private final AuthorService authorService;
private final MangaAuthorService mangaAuthorService;
private final MangaProviderRepository mangaProviderRepository; private final MangaProviderRepository mangaProviderRepository;
private final AuthorRepository authorRepository;
private final MangaAuthorRepository mangaAuthorRepository;
private final MangaChapterRepository mangaChapterRepository; private final MangaChapterRepository mangaChapterRepository;
private final MangaRepository mangaRepository; private final MangaRepository mangaRepository;
private final JikanClient jikanClient; private final JikanClient jikanClient;
private final AniListClient aniListClient; private final AniListClient aniListClient;
private final MangaChapterImageRepository mangaChapterImageRepository; private final MangaChapterImageRepository mangaChapterImageRepository;
private final MangaAlternativeTitlesRepository mangaAlternativeTitlesRepository;
private final RateLimiter jikanRateLimiter; private final RateLimiter jikanRateLimiter;
private final MangaAlternativeTitleService mangaAlternativeTitleService;
public void importMangaFiles(String malId, List<MultipartFile> files) { public void importMangaFiles(String malId, List<MultipartFile> files) {
log.info("Importing manga files for MAL ID {}", malId); log.info("Importing manga files for MAL ID {}", malId);
@ -105,8 +119,8 @@ public class MangaImportService {
var chapterImage = var chapterImage =
MangaChapterImage.builder() MangaChapterImage.builder()
.position(position++) .position(position++)
.image(image) .imageId(image.getId())
.mangaChapter(chapter) .mangaChapterId(chapter.getId())
.build(); .build();
allChapterImages.add(chapterImage); allChapterImages.add(chapterImage);
@ -177,50 +191,30 @@ public class MangaImportService {
manga.setPublishedTo(mangaData.data().published().to()); manga.setPublishedTo(mangaData.data().published().to());
manga.setChapterCount(mangaData.data().chapters()); manga.setChapterCount(mangaData.data().chapters());
var authors = var authorIds =
mangaData.data().authors().stream() mangaData.data().authors().stream()
.map( .map(
authorData -> authorData ->
authorRepository authorService.findOrCreateAuthor(authorData.mal_id(), authorData.name()))
.findByMalId(authorData.mal_id()) .collect(Collectors.toSet());
.orElseGet(
() ->
authorRepository.save(
Author.builder()
.malId(authorData.mal_id())
.name(authorData.name())
.build())))
.toList();
updateMangaAuthors(manga, authors); mangaAuthorService.saveOrUpdateMangaAuthors(manga.getId(), authorIds);
var genres = var genreIds =
mangaData.data().genres().stream() mangaData.data().genres().stream()
.map( .map(genreData -> genreService.findOrCreateGenre(genreData.mal_id(), genreData.name()))
genreData -> .collect(Collectors.toSet());
genreRepository
.findByMalId(genreData.mal_id())
.orElseGet(
() ->
genreRepository.save(
Genre.builder()
.malId(genreData.mal_id())
.name(genreData.name())
.build())))
.toList();
updateMangaGenres(manga, genres); mangaGenreService.saveOrUpdateMangaGenres(manga.getId(), genreIds);
if (isNull(manga.getCoverImage())) { if (isNull(manga.getCoverImage())) {
downloadCoverImage(manga, mangaData.data().images().jpg().large_image_url()); downloadCoverImage(manga, mangaData.data().images().jpg().large_image_url());
} }
var mangaEntity = mangaRepository.save(manga); mangaRepository.save(manga);
var alternativeTitles =
mangaData.data().title_synonyms().stream() mangaAlternativeTitleService.saveOrUpdateMangaAlternativeTitles(
.map(at -> MangaAlternativeTitle.builder().manga(mangaEntity).title(at).build()) manga.getId(), mangaData.data().title_synonyms());
.toList();
mangaAlternativeTitlesRepository.saveAll(alternativeTitles);
} }
private void updateFromAniList(Manga manga) throws IOException, URISyntaxException { private void updateFromAniList(Manga manga) throws IOException, URISyntaxException {
@ -261,31 +255,22 @@ public class MangaImportService {
manga.setPublishedTo(convertFuzzyDate(media.endDate())); manga.setPublishedTo(convertFuzzyDate(media.endDate()));
manga.setChapterCount(media.chapters()); manga.setChapterCount(media.chapters());
var authors = var authorIds =
media.staff().edges().stream() media.staff().edges().stream()
.filter(edge -> isAuthorRole(edge.role())) .filter(edge -> isAuthorRole(edge.role()))
.map(edge -> edge.node().name().full()) .map(edge -> edge.node().name().full())
.distinct() .distinct()
.map( .map(name -> authorService.findOrCreateAuthor(null, name))
name -> .collect(Collectors.toSet());
authorRepository
.findByName(name)
.orElseGet(
() -> authorRepository.save(Author.builder().name(name).build())))
.toList();
updateMangaAuthors(manga, authors); mangaAuthorService.saveOrUpdateMangaAuthors(manga.getId(), authorIds);
var genres = var genreIds =
media.genres().stream() media.genres().stream()
.map( .map(name -> genreService.findOrCreateGenre(null, name))
name -> .collect(Collectors.toSet());
genreRepository
.findByName(name)
.orElseGet(() -> genreRepository.save(Genre.builder().name(name).build())))
.toList();
updateMangaGenres(manga, genres); mangaGenreService.saveOrUpdateMangaGenres(manga.getId(), genreIds);
if (isNull(manga.getCoverImage())) { if (isNull(manga.getCoverImage())) {
downloadCoverImage(manga, media.coverImage().large()); downloadCoverImage(manga, media.coverImage().large());
@ -324,36 +309,6 @@ public class MangaImportService {
ZoneOffset.UTC); 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) private void downloadCoverImage(Manga manga, String imageUrl)
throws IOException, URISyntaxException { throws IOException, URISyntaxException {
var inputStream = var inputStream =
@ -378,8 +333,8 @@ public class MangaImportService {
mangaChapter.setTitle(chapter.chapterTitle()); mangaChapter.setTitle(chapter.chapterTitle());
mangaChapter.setUrl(chapter.chapterUrl()); mangaChapter.setUrl(chapter.chapterUrl());
var language = languageService.getOrThrow(chapter.languageCode()); var languageId = languageService.getLanguageIdOrThrow(chapter.languageCode());
mangaChapter.setLanguage(language); mangaChapter.setLanguageId(languageId);
if (nonNull(chapter.chapter())) { if (nonNull(chapter.chapter())) {
try { try {

View File

@ -2,9 +2,10 @@ package com.magamochi.service;
import static java.util.Objects.isNull; import static java.util.Objects.isNull;
import com.magamochi.model.entity.MangaProvider; import com.magamochi.ingestion.model.entity.MangaProvider;
import com.magamochi.model.repository.MangaProviderRepository; import com.magamochi.ingestion.model.repository.MangaProviderRepository;
import com.magamochi.service.providers.PagedContentProviderFactory; import com.magamochi.ingestion.service.ProviderService;
import com.magamochi.ingestion.service.providers.PagedContentProviderFactory;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.extern.log4j.Log4j2; import lombok.extern.log4j.Log4j2;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;

View File

@ -2,14 +2,23 @@ package com.magamochi.service;
import static java.util.Objects.nonNull; import static java.util.Objects.nonNull;
import com.magamochi.catalog.service.MangaAlternativeTitleService;
import com.magamochi.catalog.service.MangaAuthorService;
import com.magamochi.catalog.service.MangaGenreService;
import com.magamochi.client.NtfyClient; import com.magamochi.client.NtfyClient;
import com.magamochi.content.model.dto.MangaChapterDTO;
import com.magamochi.content.model.entity.MangaChapter;
import com.magamochi.content.model.repository.MangaChapterRepository;
import com.magamochi.content.service.LanguageService;
import com.magamochi.exception.NotFoundException; import com.magamochi.exception.NotFoundException;
import com.magamochi.ingestion.model.entity.MangaProvider;
import com.magamochi.ingestion.model.repository.MangaProviderRepository;
import com.magamochi.model.dto.*; import com.magamochi.model.dto.*;
import com.magamochi.model.entity.*; import com.magamochi.model.entity.*;
import com.magamochi.model.repository.*; import com.magamochi.model.repository.*;
import com.magamochi.model.specification.MangaSpecification; import com.magamochi.model.specification.MangaSpecification;
import com.magamochi.queue.MangaChapterDownloadProducer; import com.magamochi.queue.MangaChapterDownloadProducer;
import com.magamochi.service.providers.ContentProviderFactory; import com.magamochi.ingestion.service.providers.ContentProviderFactory;
import com.magamochi.user.service.UserService; import com.magamochi.user.service.UserService;
import com.magamochi.userinteraction.service.UserFavoriteMangaService; import com.magamochi.userinteraction.service.UserFavoriteMangaService;
import com.magamochi.userinteraction.service.UserFollowMangaService; import com.magamochi.userinteraction.service.UserFollowMangaService;
@ -35,11 +44,17 @@ public class MangaService {
private final MangaRepository mangaRepository; private final MangaRepository mangaRepository;
private final MangaProviderRepository mangaProviderRepository; private final MangaProviderRepository mangaProviderRepository;
private final MangaGenreService mangaGenreService;
private final MangaAuthorService mangaAuthorService;
private final MangaAlternativeTitleService mangaAlternativeTitleService;
private final ContentProviderFactory contentProviderFactory; private final ContentProviderFactory contentProviderFactory;
private final MangaChapterDownloadProducer mangaChapterDownloadProducer; private final MangaChapterDownloadProducer mangaChapterDownloadProducer;
private final MangaChapterRepository mangaChapterRepository; private final MangaChapterRepository mangaChapterRepository;
private final LanguageService languageService;
private final NtfyClient ntfyClient; private final NtfyClient ntfyClient;
public void fetchAllNotDownloadedChapters(Long mangaProviderId) { public void fetchAllNotDownloadedChapters(Long mangaProviderId) {
@ -73,8 +88,10 @@ public class MangaService {
.findAll(specification, pageable) .findAll(specification, pageable)
.map( .map(
manga -> { manga -> {
var genres = mangaGenreService.getMangaGenres(manga.getId());
var authors = mangaAuthorService.getMangaAuthors(manga.getId());
var favorite = favoriteMangasIds.contains(manga.getId()); var favorite = favoriteMangasIds.contains(manga.getId());
return MangaListDTO.from(manga, favorite); return MangaListDTO.from(manga, favorite, genres, authors);
}); });
} }
@ -83,7 +100,11 @@ public class MangaService {
return mangaProvider.getMangaChapters().stream() return mangaProvider.getMangaChapters().stream()
.sorted(Comparator.comparing(MangaChapter::getId)) .sorted(Comparator.comparing(MangaChapter::getId))
.map(MangaChapterDTO::from) .map(
mangaChapter -> {
var language = languageService.getLanguageOrThrow(mangaChapter.getLanguageId());
return MangaChapterDTO.from(mangaChapter, language);
})
.toList(); .toList();
} }
@ -97,10 +118,17 @@ public class MangaService {
var followingMangaIds = var followingMangaIds =
nonNull(user) ? userFollowMangaService.getFollowedMangaIdsForLoggedUser() : Set.of(); nonNull(user) ? userFollowMangaService.getFollowedMangaIdsForLoggedUser() : Set.of();
var genres = mangaGenreService.getMangaGenres(manga.getId());
var authors = mangaAuthorService.getMangaAuthors(manga.getId());
var alternativeTitles = mangaAlternativeTitleService.getMangaAlternativeTitles(manga.getId());
return MangaDTO.from( return MangaDTO.from(
manga, manga,
favoriteMangasIds.contains(manga.getId()), favoriteMangasIds.contains(manga.getId()),
followingMangaIds.contains(manga.getId())); followingMangaIds.contains(manga.getId()),
genres,
authors,
alternativeTitles);
} }
public void fetchFollowedMangaChapters(Long mangaProviderId) { public void fetchFollowedMangaChapters(Long mangaProviderId) {

View File

@ -6,12 +6,12 @@ import static java.util.Objects.nonNull;
import com.magamochi.exception.NotFoundException; import com.magamochi.exception.NotFoundException;
import com.magamochi.model.dto.ImportMangaResponseDTO; import com.magamochi.model.dto.ImportMangaResponseDTO;
import com.magamochi.model.dto.ImportRequestDTO; import com.magamochi.model.dto.ImportRequestDTO;
import com.magamochi.model.entity.MangaProvider; import com.magamochi.ingestion.model.entity.MangaProvider;
import com.magamochi.model.entity.Provider; import com.magamochi.ingestion.model.entity.Provider;
import com.magamochi.model.enumeration.ProviderStatus; import com.magamochi.ingestion.model.enumeration.ProviderStatus;
import com.magamochi.model.repository.MangaProviderRepository; import com.magamochi.ingestion.model.repository.MangaProviderRepository;
import com.magamochi.model.repository.ProviderRepository; import com.magamochi.ingestion.model.repository.ProviderRepository;
import com.magamochi.service.providers.ManualImportContentProviderFactory; import com.magamochi.ingestion.service.providers.ManualImportContentProviderFactory;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.extern.log4j.Log4j2; import lombok.extern.log4j.Log4j2;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;

View File

@ -2,7 +2,7 @@ package com.magamochi.task;
import com.magamochi.model.dto.UpdateMangaFollowChapterListCommand; import com.magamochi.model.dto.UpdateMangaFollowChapterListCommand;
import com.magamochi.model.entity.Manga; import com.magamochi.model.entity.Manga;
import com.magamochi.model.enumeration.ProviderStatus; import com.magamochi.ingestion.model.enumeration.ProviderStatus;
import com.magamochi.model.repository.MangaRepository; import com.magamochi.model.repository.MangaRepository;
import com.magamochi.queue.UpdateMangaFollowChapterListProducer; import com.magamochi.queue.UpdateMangaFollowChapterListProducer;
import com.magamochi.userinteraction.service.UserFollowMangaService; import com.magamochi.userinteraction.service.UserFollowMangaService;

View File

@ -2,10 +2,10 @@ package com.magamochi.task;
import com.magamochi.exception.NotFoundException; import com.magamochi.exception.NotFoundException;
import com.magamochi.model.dto.MangaListUpdateCommand; import com.magamochi.model.dto.MangaListUpdateCommand;
import com.magamochi.model.repository.ProviderRepository; import com.magamochi.ingestion.model.repository.ProviderRepository;
import com.magamochi.queue.UpdateMangaListProducer; import com.magamochi.queue.UpdateMangaListProducer;
import com.magamochi.service.providers.PagedContentProvider; import com.magamochi.ingestion.service.providers.PagedContentProvider;
import com.magamochi.service.providers.PagedContentProviderFactory; import com.magamochi.ingestion.service.providers.PagedContentProviderFactory;
import java.util.stream.IntStream; import java.util.stream.IntStream;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.extern.log4j.Log4j2; import lombok.extern.log4j.Log4j2;