Compare commits
2 Commits
e3ba04a087
...
03c273abb8
| Author | SHA1 | Date | |
|---|---|---|---|
| 03c273abb8 | |||
| aab2f52938 |
4
.env
4
.env
@ -2,8 +2,8 @@ DB_URL=jdbc:postgresql://localhost:5432/mangamochi?currentSchema=mangamochi
|
|||||||
DB_USER=mangamochi
|
DB_USER=mangamochi
|
||||||
DB_PASS=mangamochi
|
DB_PASS=mangamochi
|
||||||
|
|
||||||
MINIO_ENDPOINT=http://omv.badger-pirarucu.ts.net:9000
|
MINIO_ENDPOINT=http://omv2.badger-pirarucu.ts.net:9000
|
||||||
MINIO_USER=admin
|
MINIO_USER=rov
|
||||||
MINIO_PASS=!E9v4i0v3
|
MINIO_PASS=!E9v4i0v3
|
||||||
|
|
||||||
WEBSCRAPPER_ENDPOINT=http://mangamochi.badger-pirarucu.ts.net:8090/url
|
WEBSCRAPPER_ENDPOINT=http://mangamochi.badger-pirarucu.ts.net:8090/url
|
||||||
|
|||||||
@ -0,0 +1,15 @@
|
|||||||
|
package com.magamochi.mangamochi.client;
|
||||||
|
|
||||||
|
import io.github.resilience4j.retry.annotation.Retry;
|
||||||
|
import org.springframework.cloud.openfeign.FeignClient;
|
||||||
|
import org.springframework.http.MediaType;
|
||||||
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
|
@FeignClient(name = "ntfy", url = "${ntfy.endpoint}")
|
||||||
|
@Retry(name = "JikanRetry")
|
||||||
|
public interface NtfyClient {
|
||||||
|
@PostMapping(consumes = MediaType.APPLICATION_JSON_VALUE)
|
||||||
|
void notify(@RequestBody Request dto);
|
||||||
|
|
||||||
|
record Request(String topic, String message, String title) {}
|
||||||
|
}
|
||||||
@ -12,6 +12,7 @@ public class RabbitConfig {
|
|||||||
public static final String MANGA_DATA_UPDATE_QUEUE = "mangaDataUpdateQueue";
|
public static final String MANGA_DATA_UPDATE_QUEUE = "mangaDataUpdateQueue";
|
||||||
public static final String MANGA_CHAPTER_DOWNLOAD_QUEUE = "mangaChapterDownloadQueue";
|
public static final String MANGA_CHAPTER_DOWNLOAD_QUEUE = "mangaChapterDownloadQueue";
|
||||||
public static final String MANGA_LIST_UPDATE_QUEUE = "mangaListUpdateQueue";
|
public static final String MANGA_LIST_UPDATE_QUEUE = "mangaListUpdateQueue";
|
||||||
|
public static final String MANGA_FOLLOW_UPDATE_CHAPTER_QUEUE = "mangaFollowUpdateChapterQueue";
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
public Queue mangaDataUpdateQueue() {
|
public Queue mangaDataUpdateQueue() {
|
||||||
@ -28,6 +29,11 @@ public class RabbitConfig {
|
|||||||
return new Queue(MANGA_LIST_UPDATE_QUEUE, false);
|
return new Queue(MANGA_LIST_UPDATE_QUEUE, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public Queue mangaFollowUpdateChapterQueue() {
|
||||||
|
return new Queue(MANGA_FOLLOW_UPDATE_CHAPTER_QUEUE, false);
|
||||||
|
}
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
public Jackson2JsonMessageConverter messageConverter() {
|
public Jackson2JsonMessageConverter messageConverter() {
|
||||||
return new Jackson2JsonMessageConverter();
|
return new Jackson2JsonMessageConverter();
|
||||||
|
|||||||
@ -1,6 +1,8 @@
|
|||||||
package com.magamochi.mangamochi.controller;
|
package com.magamochi.mangamochi.controller;
|
||||||
|
|
||||||
|
import com.magamochi.mangamochi.client.NtfyClient;
|
||||||
import com.magamochi.mangamochi.model.dto.DefaultResponseDTO;
|
import com.magamochi.mangamochi.model.dto.DefaultResponseDTO;
|
||||||
|
import com.magamochi.mangamochi.model.repository.UserRepository;
|
||||||
import com.magamochi.mangamochi.task.ImageCleanupTask;
|
import com.magamochi.mangamochi.task.ImageCleanupTask;
|
||||||
import com.magamochi.mangamochi.task.UpdateMangaListTask;
|
import com.magamochi.mangamochi.task.UpdateMangaListTask;
|
||||||
import io.swagger.v3.oas.annotations.Operation;
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
@ -13,6 +15,9 @@ import org.springframework.web.bind.annotation.*;
|
|||||||
public class ManagementController {
|
public class ManagementController {
|
||||||
private final UpdateMangaListTask updateMangaListTask;
|
private final UpdateMangaListTask updateMangaListTask;
|
||||||
private final ImageCleanupTask imageCleanupTask;
|
private final ImageCleanupTask imageCleanupTask;
|
||||||
|
private final UserRepository userRepository;
|
||||||
|
|
||||||
|
private final NtfyClient ntfyClient;
|
||||||
|
|
||||||
@Operation(
|
@Operation(
|
||||||
summary = "Queue update manga list",
|
summary = "Queue update manga list",
|
||||||
@ -37,4 +42,24 @@ public class ManagementController {
|
|||||||
|
|
||||||
return DefaultResponseDTO.ok().build();
|
return DefaultResponseDTO.ok().build();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Operation(
|
||||||
|
summary = "Test notification",
|
||||||
|
description = "Sends a test notification to all users",
|
||||||
|
tags = {"Management"},
|
||||||
|
operationId = "testNotification")
|
||||||
|
@PostMapping("test-notification")
|
||||||
|
public DefaultResponseDTO<Void> testNotification() {
|
||||||
|
var users = userRepository.findAll();
|
||||||
|
|
||||||
|
users.forEach(
|
||||||
|
user ->
|
||||||
|
ntfyClient.notify(
|
||||||
|
new NtfyClient.Request(
|
||||||
|
"mangamochi/" + user.getId().toString(),
|
||||||
|
"Mangamochi",
|
||||||
|
"This is a test notification :)")));
|
||||||
|
|
||||||
|
return DefaultResponseDTO.ok().build();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -74,4 +74,28 @@ public class MangaController {
|
|||||||
|
|
||||||
return DefaultResponseDTO.ok().build();
|
return DefaultResponseDTO.ok().build();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Operation(
|
||||||
|
summary = "Follow the manga specified by its ID",
|
||||||
|
description = "Follow the manga specified by its ID.",
|
||||||
|
tags = {"Manga"},
|
||||||
|
operationId = "followManga")
|
||||||
|
@PostMapping("/{mangaId}/followManga")
|
||||||
|
public DefaultResponseDTO<Void> followManga(@PathVariable Long mangaId) {
|
||||||
|
mangaService.follow(mangaId);
|
||||||
|
|
||||||
|
return DefaultResponseDTO.ok().build();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Operation(
|
||||||
|
summary = "Unfollow the manga specified by its ID",
|
||||||
|
description = "Unfollow the manga specified by its ID.",
|
||||||
|
tags = {"Manga"},
|
||||||
|
operationId = "unfollowManga")
|
||||||
|
@PostMapping("/{mangaId}/unfollowManga")
|
||||||
|
public DefaultResponseDTO<Void> unfollowManga(@PathVariable Long mangaId) {
|
||||||
|
mangaService.unfollow(mangaId);
|
||||||
|
|
||||||
|
return DefaultResponseDTO.ok().build();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -25,8 +25,10 @@ public record MangaDTO(
|
|||||||
@NotNull List<String> authors,
|
@NotNull List<String> authors,
|
||||||
@NotNull Double score,
|
@NotNull Double score,
|
||||||
@NotNull List<MangaProviderDTO> providers,
|
@NotNull List<MangaProviderDTO> providers,
|
||||||
@NotNull Integer chapterCount) {
|
@NotNull Integer chapterCount,
|
||||||
public static MangaDTO from(Manga manga) {
|
@NotNull Boolean favorite,
|
||||||
|
@NotNull Boolean following) {
|
||||||
|
public static MangaDTO from(Manga manga, Boolean favorite, Boolean following) {
|
||||||
return new MangaDTO(
|
return new MangaDTO(
|
||||||
manga.getId(),
|
manga.getId(),
|
||||||
manga.getTitle(),
|
manga.getTitle(),
|
||||||
@ -43,7 +45,9 @@ public record MangaDTO(
|
|||||||
.toList(),
|
.toList(),
|
||||||
manga.getScore(),
|
manga.getScore(),
|
||||||
manga.getMangaProviders().stream().map(MangaProviderDTO::from).toList(),
|
manga.getMangaProviders().stream().map(MangaProviderDTO::from).toList(),
|
||||||
manga.getChapterCount());
|
manga.getChapterCount(),
|
||||||
|
favorite,
|
||||||
|
following);
|
||||||
}
|
}
|
||||||
|
|
||||||
public record MangaProviderDTO(
|
public record MangaProviderDTO(
|
||||||
|
|||||||
@ -0,0 +1,3 @@
|
|||||||
|
package com.magamochi.mangamochi.model.dto;
|
||||||
|
|
||||||
|
public record UpdateMangaFollowChapterListCommand(Long mangaProviderId) {}
|
||||||
@ -24,7 +24,6 @@ public class Manga {
|
|||||||
|
|
||||||
private String title;
|
private String title;
|
||||||
|
|
||||||
// @Enumerated(EnumType.STRING)
|
|
||||||
private String status;
|
private String status;
|
||||||
|
|
||||||
private String synopsis;
|
private String synopsis;
|
||||||
@ -59,4 +58,6 @@ public class Manga {
|
|||||||
private List<MangaAlternativeTitle> alternativeTitles;
|
private List<MangaAlternativeTitle> alternativeTitles;
|
||||||
|
|
||||||
private Integer chapterCount;
|
private Integer chapterCount;
|
||||||
|
|
||||||
|
private Boolean follow;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -0,0 +1,25 @@
|
|||||||
|
package com.magamochi.mangamochi.model.entity;
|
||||||
|
|
||||||
|
import jakarta.persistence.*;
|
||||||
|
import lombok.*;
|
||||||
|
|
||||||
|
@Entity
|
||||||
|
@Table(name = "user_manga_follow")
|
||||||
|
@Builder
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
@Getter
|
||||||
|
@Setter
|
||||||
|
public class UserMangaFollow {
|
||||||
|
@Id
|
||||||
|
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||||
|
private Long id;
|
||||||
|
|
||||||
|
@ManyToOne
|
||||||
|
@JoinColumn(name = "user_id")
|
||||||
|
private User user;
|
||||||
|
|
||||||
|
@ManyToOne
|
||||||
|
@JoinColumn(name = "manga_id")
|
||||||
|
private Manga manga;
|
||||||
|
}
|
||||||
@ -1,6 +1,7 @@
|
|||||||
package com.magamochi.mangamochi.model.repository;
|
package com.magamochi.mangamochi.model.repository;
|
||||||
|
|
||||||
import com.magamochi.mangamochi.model.entity.Manga;
|
import com.magamochi.mangamochi.model.entity.Manga;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import org.springframework.data.jpa.repository.JpaRepository;
|
import org.springframework.data.jpa.repository.JpaRepository;
|
||||||
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
|
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
|
||||||
@ -9,5 +10,5 @@ public interface MangaRepository
|
|||||||
extends JpaRepository<Manga, Long>, JpaSpecificationExecutor<Manga> {
|
extends JpaRepository<Manga, Long>, JpaSpecificationExecutor<Manga> {
|
||||||
Optional<Manga> findByTitleIgnoreCase(String title);
|
Optional<Manga> findByTitleIgnoreCase(String title);
|
||||||
|
|
||||||
Optional<Manga> findByMalId(Long malId);
|
List<Manga> findByFollowTrue();
|
||||||
}
|
}
|
||||||
|
|||||||
@ -0,0 +1,20 @@
|
|||||||
|
package com.magamochi.mangamochi.model.repository;
|
||||||
|
|
||||||
|
import com.magamochi.mangamochi.model.entity.Manga;
|
||||||
|
import com.magamochi.mangamochi.model.entity.User;
|
||||||
|
import com.magamochi.mangamochi.model.entity.UserMangaFollow;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
|
import org.springframework.data.jpa.repository.JpaRepository;
|
||||||
|
|
||||||
|
public interface UserMangaFollowRepository extends JpaRepository<UserMangaFollow, Long> {
|
||||||
|
boolean existsByUserAndManga(User user, Manga manga);
|
||||||
|
|
||||||
|
Optional<UserMangaFollow> findByUserAndManga(User user, Manga manga);
|
||||||
|
|
||||||
|
boolean existsByManga(Manga manga);
|
||||||
|
|
||||||
|
List<UserMangaFollow> findByUser(User user);
|
||||||
|
|
||||||
|
List<UserMangaFollow> findByManga(Manga manga);
|
||||||
|
}
|
||||||
@ -0,0 +1,22 @@
|
|||||||
|
package com.magamochi.mangamochi.queue;
|
||||||
|
|
||||||
|
import com.magamochi.mangamochi.config.RabbitConfig;
|
||||||
|
import com.magamochi.mangamochi.model.dto.UpdateMangaFollowChapterListCommand;
|
||||||
|
import com.magamochi.mangamochi.service.MangaService;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.extern.log4j.Log4j2;
|
||||||
|
import org.springframework.amqp.rabbit.annotation.RabbitListener;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
@Log4j2
|
||||||
|
@Service
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class UpdateMangaFollowChapterListConsumer {
|
||||||
|
private final MangaService mangaService;
|
||||||
|
|
||||||
|
@RabbitListener(queues = RabbitConfig.MANGA_FOLLOW_UPDATE_CHAPTER_QUEUE)
|
||||||
|
public void receiveMangaFollowUpdateChapterCommand(UpdateMangaFollowChapterListCommand command) {
|
||||||
|
log.info("Received update followed manga chapter list command: {}", command);
|
||||||
|
mangaService.fetchFollowedMangaChapters(command.mangaProviderId());
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,20 @@
|
|||||||
|
package com.magamochi.mangamochi.queue;
|
||||||
|
|
||||||
|
import com.magamochi.mangamochi.config.RabbitConfig;
|
||||||
|
import com.magamochi.mangamochi.model.dto.UpdateMangaFollowChapterListCommand;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.extern.log4j.Log4j2;
|
||||||
|
import org.springframework.amqp.rabbit.core.RabbitTemplate;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
@Log4j2
|
||||||
|
@Service
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class UpdateMangaFollowChapterListProducer {
|
||||||
|
private final RabbitTemplate rabbitTemplate;
|
||||||
|
|
||||||
|
public void sendUpdateMangaFollowChapterListCommand(UpdateMangaFollowChapterListCommand command) {
|
||||||
|
rabbitTemplate.convertAndSend(RabbitConfig.MANGA_FOLLOW_UPDATE_CHAPTER_QUEUE, command);
|
||||||
|
log.info("Sent update followed manga chapter list command: {}", command);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -2,11 +2,13 @@ package com.magamochi.mangamochi.service;
|
|||||||
|
|
||||||
import static java.util.Objects.nonNull;
|
import static java.util.Objects.nonNull;
|
||||||
|
|
||||||
|
import com.magamochi.mangamochi.client.NtfyClient;
|
||||||
import com.magamochi.mangamochi.exception.NotFoundException;
|
import com.magamochi.mangamochi.exception.NotFoundException;
|
||||||
import com.magamochi.mangamochi.model.dto.*;
|
import com.magamochi.mangamochi.model.dto.*;
|
||||||
import com.magamochi.mangamochi.model.entity.Manga;
|
import com.magamochi.mangamochi.model.entity.Manga;
|
||||||
import com.magamochi.mangamochi.model.entity.MangaChapter;
|
import com.magamochi.mangamochi.model.entity.MangaChapter;
|
||||||
import com.magamochi.mangamochi.model.entity.MangaProvider;
|
import com.magamochi.mangamochi.model.entity.MangaProvider;
|
||||||
|
import com.magamochi.mangamochi.model.entity.UserMangaFollow;
|
||||||
import com.magamochi.mangamochi.model.repository.*;
|
import com.magamochi.mangamochi.model.repository.*;
|
||||||
import com.magamochi.mangamochi.model.specification.MangaSpecification;
|
import com.magamochi.mangamochi.model.specification.MangaSpecification;
|
||||||
import com.magamochi.mangamochi.queue.MangaChapterDownloadProducer;
|
import com.magamochi.mangamochi.queue.MangaChapterDownloadProducer;
|
||||||
@ -21,6 +23,7 @@ import lombok.extern.log4j.Log4j2;
|
|||||||
import org.springframework.data.domain.Page;
|
import org.springframework.data.domain.Page;
|
||||||
import org.springframework.data.domain.Pageable;
|
import org.springframework.data.domain.Pageable;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
|
||||||
@Log4j2
|
@Log4j2
|
||||||
@Service
|
@Service
|
||||||
@ -34,7 +37,12 @@ public class MangaService {
|
|||||||
private final ContentProviderFactory contentProviderFactory;
|
private final ContentProviderFactory contentProviderFactory;
|
||||||
private final UserFavoriteMangaRepository userFavoriteMangaRepository;
|
private final UserFavoriteMangaRepository userFavoriteMangaRepository;
|
||||||
|
|
||||||
|
private final UserMangaFollowRepository userMangaFollowRepository;
|
||||||
|
|
||||||
private final MangaChapterDownloadProducer mangaChapterDownloadProducer;
|
private final MangaChapterDownloadProducer mangaChapterDownloadProducer;
|
||||||
|
private final MangaChapterRepository mangaChapterRepository;
|
||||||
|
|
||||||
|
private final NtfyClient ntfyClient;
|
||||||
|
|
||||||
public void fetchAllNotDownloadedChapters(Long mangaProviderId) {
|
public void fetchAllNotDownloadedChapters(Long mangaProviderId) {
|
||||||
var mangaProvider =
|
var mangaProvider =
|
||||||
@ -87,8 +95,58 @@ public class MangaService {
|
|||||||
|
|
||||||
public MangaDTO getManga(Long mangaId) {
|
public MangaDTO getManga(Long mangaId) {
|
||||||
var manga = findMangaByIdThrowIfNotFound(mangaId);
|
var manga = findMangaByIdThrowIfNotFound(mangaId);
|
||||||
|
var user = userService.getLoggedUser();
|
||||||
|
|
||||||
return MangaDTO.from(manga);
|
var favoriteMangasIds =
|
||||||
|
nonNull(user)
|
||||||
|
? userFavoriteMangaRepository.findByUser(user).stream()
|
||||||
|
.map(ufm -> ufm.getManga().getId())
|
||||||
|
.collect(Collectors.toSet())
|
||||||
|
: Set.of();
|
||||||
|
|
||||||
|
var followingMangaIds =
|
||||||
|
nonNull(user)
|
||||||
|
? userMangaFollowRepository.findByUser(user).stream()
|
||||||
|
.map(umf -> umf.getManga().getId())
|
||||||
|
.collect(Collectors.toSet())
|
||||||
|
: Set.of();
|
||||||
|
|
||||||
|
return MangaDTO.from(
|
||||||
|
manga,
|
||||||
|
favoriteMangasIds.contains(manga.getId()),
|
||||||
|
followingMangaIds.contains(manga.getId()));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void fetchFollowedMangaChapters(Long mangaProviderId) {
|
||||||
|
var mangaProvider =
|
||||||
|
mangaProviderRepository
|
||||||
|
.findById(mangaProviderId)
|
||||||
|
.orElseThrow(
|
||||||
|
() -> new NotFoundException("Manga Provider not found for ID: " + mangaProviderId));
|
||||||
|
|
||||||
|
var currentAvailableChapterCount =
|
||||||
|
mangaChapterRepository.findByMangaProviderId(mangaProviderId).size();
|
||||||
|
|
||||||
|
fetchMangaChapters(mangaProviderId);
|
||||||
|
mangaChapterRepository.flush();
|
||||||
|
|
||||||
|
var availableChapterCount =
|
||||||
|
mangaChapterRepository.findByMangaProviderId(mangaProviderId).size();
|
||||||
|
|
||||||
|
if (availableChapterCount <= currentAvailableChapterCount) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
log.info("New chapters found for Manga Provider {}", mangaProviderId);
|
||||||
|
|
||||||
|
var userMangaFollows = userMangaFollowRepository.findByManga(mangaProvider.getManga());
|
||||||
|
userMangaFollows.forEach(
|
||||||
|
umf ->
|
||||||
|
ntfyClient.notify(
|
||||||
|
new NtfyClient.Request(
|
||||||
|
"mangamochi/" + umf.getUser().getId().toString(),
|
||||||
|
umf.getManga().getTitle(),
|
||||||
|
"New chapter available on " + mangaProvider.getProvider().getName())));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void fetchMangaChapters(Long mangaProviderId) {
|
public void fetchMangaChapters(Long mangaProviderId) {
|
||||||
@ -114,4 +172,31 @@ public class MangaService {
|
|||||||
.orElseThrow(
|
.orElseThrow(
|
||||||
() -> new NotFoundException("Manga Provider not found for ID: " + mangaProviderId));
|
() -> new NotFoundException("Manga Provider not found for ID: " + mangaProviderId));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Transactional
|
||||||
|
public void follow(Long mangaId) {
|
||||||
|
var user = userService.getLoggedUserThrowIfNotFound();
|
||||||
|
|
||||||
|
var manga = findMangaByIdThrowIfNotFound(mangaId);
|
||||||
|
manga.setFollow(true);
|
||||||
|
|
||||||
|
if (userMangaFollowRepository.existsByUserAndManga(user, manga)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
userMangaFollowRepository.save(UserMangaFollow.builder().user(user).manga(manga).build());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Transactional
|
||||||
|
public void unfollow(Long mangaId) {
|
||||||
|
var user = userService.getLoggedUserThrowIfNotFound();
|
||||||
|
var manga = findMangaByIdThrowIfNotFound(mangaId);
|
||||||
|
|
||||||
|
var userMangaFollow = userMangaFollowRepository.findByUserAndManga(user, manga);
|
||||||
|
userMangaFollow.ifPresent(userMangaFollowRepository::delete);
|
||||||
|
|
||||||
|
if (!userMangaFollowRepository.existsByManga(manga)) {
|
||||||
|
manga.setFollow(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -0,0 +1,53 @@
|
|||||||
|
package com.magamochi.mangamochi.task;
|
||||||
|
|
||||||
|
import com.magamochi.mangamochi.model.dto.UpdateMangaFollowChapterListCommand;
|
||||||
|
import com.magamochi.mangamochi.model.entity.Manga;
|
||||||
|
import com.magamochi.mangamochi.model.repository.MangaRepository;
|
||||||
|
import com.magamochi.mangamochi.queue.UpdateMangaFollowChapterListProducer;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.extern.log4j.Log4j2;
|
||||||
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
|
import org.springframework.scheduling.annotation.Scheduled;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
|
||||||
|
@Log4j2
|
||||||
|
@Component
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class MangaFollowUpdateTask {
|
||||||
|
@Value("${manga-follow.update-enabled}")
|
||||||
|
private Boolean updateEnabled;
|
||||||
|
|
||||||
|
private final MangaRepository mangaRepository;
|
||||||
|
|
||||||
|
private final UpdateMangaFollowChapterListProducer producer;
|
||||||
|
|
||||||
|
@Scheduled(cron = "${manga-follow.cron-expression}")
|
||||||
|
@Transactional
|
||||||
|
public void updateMangaFollow() {
|
||||||
|
if (!updateEnabled) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
updateMangaList();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void updateMangaList() {
|
||||||
|
log.info("Queuing followed manga updates...");
|
||||||
|
|
||||||
|
var followedMangas = mangaRepository.findByFollowTrue();
|
||||||
|
followedMangas.forEach(this::updateFollowedManga);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateFollowedManga(Manga manga) {
|
||||||
|
log.info("Fetching available mangas for followed Manga {}", manga.getTitle());
|
||||||
|
|
||||||
|
var mangaProviders = manga.getMangaProviders();
|
||||||
|
mangaProviders.forEach(
|
||||||
|
mangaProvider ->
|
||||||
|
producer.sendUpdateMangaFollowChapterListCommand(
|
||||||
|
new UpdateMangaFollowChapterListCommand(mangaProvider.getId())));
|
||||||
|
|
||||||
|
log.info("Followed Manga ({}) chapter list update queued.", manga.getTitle());
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -44,6 +44,9 @@ minio:
|
|||||||
secretKey: ${MINIO_PASS}
|
secretKey: ${MINIO_PASS}
|
||||||
bucket: mangamochi
|
bucket: mangamochi
|
||||||
|
|
||||||
|
ntfy:
|
||||||
|
endpoint: ${NTFY_ENDPOINT:https://ntfy.badger-pirarucu.ts.net}
|
||||||
|
|
||||||
jwt:
|
jwt:
|
||||||
secret: /JcSdxjeyeuMGoK5GD9w7OfqK/j+nvHR7uVUY12pNis=
|
secret: /JcSdxjeyeuMGoK5GD9w7OfqK/j+nvHR7uVUY12pNis=
|
||||||
expiration: 3600000
|
expiration: 3600000
|
||||||
@ -77,3 +80,7 @@ content-providers:
|
|||||||
update-enabled: ${CONTENT_PROVIDER_UPDATE_ENABLED:false}
|
update-enabled: ${CONTENT_PROVIDER_UPDATE_ENABLED:false}
|
||||||
cron-expression: "@weekly"
|
cron-expression: "@weekly"
|
||||||
|
|
||||||
|
manga-follow:
|
||||||
|
update-enabled: ${CONTENT_PROVIDER_UPDATE_ENABLED:true}
|
||||||
|
cron-expression: "@daily"
|
||||||
|
|
||||||
|
|||||||
9
src/main/resources/db/migration/V0016__MANGA_FOLLOW.sql
Normal file
9
src/main/resources/db/migration/V0016__MANGA_FOLLOW.sql
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
ALTER TABLE mangas
|
||||||
|
ADD COLUMN follow BOOLEAN NOT NULL DEFAULT FALSE;
|
||||||
|
|
||||||
|
CREATE TABLE user_manga_follow
|
||||||
|
(
|
||||||
|
id BIGSERIAL NOT NULL PRIMARY KEY,
|
||||||
|
user_id BIGINT REFERENCES users (id),
|
||||||
|
manga_id BIGINT REFERENCES mangas (id)
|
||||||
|
);
|
||||||
Loading…
x
Reference in New Issue
Block a user