feat: user-interaction favorite

This commit is contained in:
Rodrigo Verdiani 2026-03-19 10:51:35 -03:00
parent 21859c5e09
commit 1827e39471
10 changed files with 62 additions and 74 deletions

View File

@ -3,7 +3,7 @@ package com.magamochi.catalog.model.entity;
import com.magamochi.catalog.model.enumeration.MangaState; import com.magamochi.catalog.model.enumeration.MangaState;
import com.magamochi.catalog.model.enumeration.MangaStatus; import com.magamochi.catalog.model.enumeration.MangaStatus;
import com.magamochi.image.model.entity.Image; import com.magamochi.image.model.entity.Image;
import com.magamochi.model.entity.UserFavoriteManga; import com.magamochi.userinteraction.model.entity.UserFavoriteManga;
import jakarta.persistence.*; import jakarta.persistence.*;
import java.time.Instant; import java.time.Instant;
import java.time.OffsetDateTime; import java.time.OffsetDateTime;

View File

@ -8,10 +8,10 @@ import com.magamochi.catalog.model.dto.MangaListFilterDTO;
import com.magamochi.catalog.model.entity.Manga; import com.magamochi.catalog.model.entity.Manga;
import com.magamochi.catalog.model.repository.MangaRepository; import com.magamochi.catalog.model.repository.MangaRepository;
import com.magamochi.common.exception.NotFoundException; import com.magamochi.common.exception.NotFoundException;
import com.magamochi.model.repository.UserFavoriteMangaRepository;
import com.magamochi.model.repository.UserMangaFollowRepository; import com.magamochi.model.repository.UserMangaFollowRepository;
import com.magamochi.model.specification.MangaSpecification; import com.magamochi.model.specification.MangaSpecification;
import com.magamochi.user.service.UserService; import com.magamochi.user.service.UserService;
import com.magamochi.userinteraction.model.repository.UserFavoriteMangaRepository;
import java.util.List; import java.util.List;
import java.util.Set; import java.util.Set;
import java.util.stream.Collectors; import java.util.stream.Collectors;

View File

@ -6,7 +6,7 @@ import org.springframework.amqp.core.Queue;
import org.springframework.amqp.core.TopicExchange; import org.springframework.amqp.core.TopicExchange;
import org.springframework.amqp.rabbit.connection.ConnectionFactory; import org.springframework.amqp.rabbit.connection.ConnectionFactory;
import org.springframework.amqp.rabbit.core.RabbitTemplate; import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter; import org.springframework.amqp.support.converter.JacksonJsonMessageConverter;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
@ -130,8 +130,8 @@ public class RabbitConfig {
} }
@Bean @Bean
public Jackson2JsonMessageConverter messageConverter() { public JacksonJsonMessageConverter messageConverter() {
return new Jackson2JsonMessageConverter(); return new JacksonJsonMessageConverter();
} }
@Bean @Bean

View File

@ -3,7 +3,6 @@ package com.magamochi.controller;
import com.magamochi.client.NtfyClient; import com.magamochi.client.NtfyClient;
import com.magamochi.common.model.dto.DefaultResponseDTO; import com.magamochi.common.model.dto.DefaultResponseDTO;
import com.magamochi.image.task.ImageCleanupTask; import com.magamochi.image.task.ImageCleanupTask;
import com.magamochi.ingestion.task.IngestFromContentProvidersTask;
import com.magamochi.task.MangaFollowUpdateTask; import com.magamochi.task.MangaFollowUpdateTask;
import com.magamochi.user.repository.UserRepository; import com.magamochi.user.repository.UserRepository;
import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Operation;
@ -14,7 +13,6 @@ import org.springframework.web.bind.annotation.*;
@RequestMapping("/management") @RequestMapping("/management")
@RequiredArgsConstructor @RequiredArgsConstructor
public class ManagementController { public class ManagementController {
private final IngestFromContentProvidersTask ingestFromContentProvidersTask;
private final ImageCleanupTask imageCleanupTask; private final ImageCleanupTask imageCleanupTask;
private final MangaFollowUpdateTask mangaFollowUpdateTask; private final MangaFollowUpdateTask mangaFollowUpdateTask;
private final UserRepository userRepository; private final UserRepository userRepository;

View File

@ -1,50 +0,0 @@
package com.magamochi.service;
import com.magamochi.catalog.model.entity.Manga;
import com.magamochi.catalog.model.repository.MangaRepository;
import com.magamochi.common.exception.NotFoundException;
import com.magamochi.model.entity.UserFavoriteManga;
import com.magamochi.model.repository.UserFavoriteMangaRepository;
import com.magamochi.user.service.UserService;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
@Service
@RequiredArgsConstructor
public class UserFavoriteMangaService {
private final UserService userService;
private final MangaRepository mangaRepository;
private final UserFavoriteMangaRepository userFavoriteMangaRepository;
public void setFavorite(Long id) {
var user = userService.getLoggedUserThrowIfNotFound();
var manga = findMangaByIdThrowIfNotFound(id);
if (userFavoriteMangaRepository.existsByUserAndManga(user, manga)) {
return;
}
userFavoriteMangaRepository.save(UserFavoriteManga.builder().user(user).manga(manga).build());
}
public void setUnfavorite(Long id) {
var user = userService.getLoggedUserThrowIfNotFound();
var manga = findMangaByIdThrowIfNotFound(id);
var favoriteManga =
userFavoriteMangaRepository
.findByUserAndManga(user, manga)
.orElseThrow(
() ->
new NotFoundException(
"Error while trying to unfavorite manga. Please try again later."));
userFavoriteMangaRepository.delete(favoriteManga);
}
private Manga findMangaByIdThrowIfNotFound(Long mangaId) {
return mangaRepository
.findById(mangaId)
.orElseThrow(() -> new NotFoundException("Manga not found for ID: " + mangaId));
}
}

View File

@ -1,7 +1,7 @@
package com.magamochi.user.model.entity; package com.magamochi.user.model.entity;
import com.magamochi.model.entity.UserFavoriteManga;
import com.magamochi.user.model.enumeration.UserRole; import com.magamochi.user.model.enumeration.UserRole;
import com.magamochi.userinteraction.model.entity.UserFavoriteManga;
import jakarta.persistence.*; import jakarta.persistence.*;
import java.util.Set; import java.util.Set;
import lombok.*; import lombok.*;

View File

@ -1,25 +1,28 @@
package com.magamochi.controller; package com.magamochi.userinteraction.controller;
import com.magamochi.common.model.dto.DefaultResponseDTO; import com.magamochi.common.model.dto.DefaultResponseDTO;
import com.magamochi.service.UserFavoriteMangaService; import com.magamochi.userinteraction.service.UserFavoriteMangaService;
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.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController @RestController
@RequestMapping("/mangas") @RequestMapping("/user-interaction")
@RequiredArgsConstructor @RequiredArgsConstructor
public class UserFavoriteMangaController { public class UserInteractionController {
private final UserFavoriteMangaService userFavoriteMangaService; private final UserFavoriteMangaService userFavoriteMangaService;
@Operation( @Operation(
summary = "Favorite a manga", summary = "Favorite a manga",
description = "Set a manga as favorite for the logged user.", description = "Set a manga as favorite for the logged user.",
tags = {"Favorite Mangas"}, tags = {"User Interaction"},
operationId = "setFavorite") operationId = "setFavorite")
@PostMapping("/{id}/favorite") @PostMapping("/manga/{mangaId}/favorite")
public DefaultResponseDTO<Void> setFavorite(@PathVariable Long id) { public DefaultResponseDTO<Void> setFavorite(@PathVariable Long mangaId) {
userFavoriteMangaService.setFavorite(id); userFavoriteMangaService.setFavorite(mangaId);
return DefaultResponseDTO.ok().build(); return DefaultResponseDTO.ok().build();
} }
@ -27,11 +30,11 @@ public class UserFavoriteMangaController {
@Operation( @Operation(
summary = "Unfavorite a manga", summary = "Unfavorite a manga",
description = "Remove a manga from favorites for the logged user.", description = "Remove a manga from favorites for the logged user.",
tags = {"Favorite Mangas"}, tags = {"User Interaction"},
operationId = "setUnfavorite") operationId = "setUnfavorite")
@PostMapping("/{id}/unfavorite") @PostMapping("/manga/{mangaId}/unfavorite")
public DefaultResponseDTO<Void> setUnfavorite(@PathVariable Long id) { public DefaultResponseDTO<Void> setUnfavorite(@PathVariable Long mangaId) {
userFavoriteMangaService.setUnfavorite(id); userFavoriteMangaService.setUnfavorite(mangaId);
return DefaultResponseDTO.ok().build(); return DefaultResponseDTO.ok().build();
} }

View File

@ -1,4 +1,4 @@
package com.magamochi.model.entity; package com.magamochi.userinteraction.model.entity;
import com.magamochi.catalog.model.entity.Manga; import com.magamochi.catalog.model.entity.Manga;
import com.magamochi.user.model.entity.User; import com.magamochi.user.model.entity.User;

View File

@ -1,8 +1,8 @@
package com.magamochi.model.repository; package com.magamochi.userinteraction.model.repository;
import com.magamochi.catalog.model.entity.Manga; import com.magamochi.catalog.model.entity.Manga;
import com.magamochi.model.entity.UserFavoriteManga;
import com.magamochi.user.model.entity.User; import com.magamochi.user.model.entity.User;
import com.magamochi.userinteraction.model.entity.UserFavoriteManga;
import java.util.Optional; import java.util.Optional;
import java.util.Set; import java.util.Set;
import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.JpaRepository;

View File

@ -0,0 +1,37 @@
package com.magamochi.userinteraction.service;
import com.magamochi.catalog.service.MangaService;
import com.magamochi.user.service.UserService;
import com.magamochi.userinteraction.model.entity.UserFavoriteManga;
import com.magamochi.userinteraction.model.repository.UserFavoriteMangaRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
@Service
@RequiredArgsConstructor
public class UserFavoriteMangaService {
private final UserService userService;
private final MangaService mangaService;
private final UserFavoriteMangaRepository userFavoriteMangaRepository;
public void setFavorite(Long id) {
var user = userService.getLoggedUserThrowIfNotFound();
var manga = mangaService.find(id);
if (userFavoriteMangaRepository.existsByUserAndManga(user, manga)) {
return;
}
userFavoriteMangaRepository.save(UserFavoriteManga.builder().user(user).manga(manga).build());
}
public void setUnfavorite(Long id) {
var user = userService.getLoggedUserThrowIfNotFound();
var manga = mangaService.find(id);
var favoriteManga = userFavoriteMangaRepository.findByUserAndManga(user, manga);
favoriteManga.ifPresent(userFavoriteMangaRepository::delete);
}
}