diff --git a/.gitignore b/.gitignore index 0527f6ba..78a9957a 100644 --- a/.gitignore +++ b/.gitignore @@ -5,7 +5,7 @@ build/ !**/src/main/**/build/ !**/src/test/**/build/ application*.yml -**/src/main/resources/static/swagger-ui/openapi3.yaml +src/main/resources/static/swagger-ui/openapi3.yaml ### STS ### .apt_generated diff --git a/src/main/java/org/sopt/confeti/api/performance/controller/PerformanceController.java b/src/main/java/org/sopt/confeti/api/performance/controller/PerformanceController.java index b44165c2..2f4e9aeb 100644 --- a/src/main/java/org/sopt/confeti/api/performance/controller/PerformanceController.java +++ b/src/main/java/org/sopt/confeti/api/performance/controller/PerformanceController.java @@ -3,8 +3,11 @@ import jakarta.validation.constraints.Max; import jakarta.validation.constraints.Min; import jakarta.validation.constraints.NotBlank; -import java.util.Arrays; import java.util.List; +import java.util.Optional; +import java.util.Arrays; +import java.util.Objects; +import java.util.Collections; import lombok.RequiredArgsConstructor; import org.sopt.confeti.api.performance.dto.request.GetExpectedPerformanceRequest; import org.sopt.confeti.api.performance.dto.response.ArtistPerformancesResponse; @@ -144,9 +147,14 @@ public ResponseEntity> searchAutoComplete( public ResponseEntity> getRecommendPerformanceId( @UserId(require = false) Long userId ) { - RecommendMusicsPerformanceDTO recommendMusicsDTO = performanceFacade.getRecommendPerformanceId(userId); - return ApiResponseUtil.success(SuccessMessage.SUCCESS, - RecommendMusicsPerformanceResponse.from(recommendMusicsDTO)); + Optional performanceDto = Objects.isNull(userId) + ? performanceFacade.getRecommendPerformanceIdByRand() + : performanceFacade.getRecommendPerformanceIdByUserId(userId); + + return performanceDto + .map(performance -> + ApiResponseUtil.success(SuccessMessage.SUCCESS, RecommendMusicsPerformanceResponse.from(performance))) + .orElseGet(()-> ApiResponseUtil.success(SuccessMessage.SUCCESS, Collections.emptyMap())); } @Permission(role = {Role.GENERAL}) diff --git a/src/main/java/org/sopt/confeti/api/performance/facade/PerformanceFacade.java b/src/main/java/org/sopt/confeti/api/performance/facade/PerformanceFacade.java index 72d9b14e..c22e5f13 100644 --- a/src/main/java/org/sopt/confeti/api/performance/facade/PerformanceFacade.java +++ b/src/main/java/org/sopt/confeti/api/performance/facade/PerformanceFacade.java @@ -1,14 +1,15 @@ package org.sopt.confeti.api.performance.facade; import java.time.LocalDate; -import java.util.ArrayList; -import java.util.Arrays; import java.util.Collections; -import java.util.HashMap; -import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Arrays; +import java.util.ArrayList; +import java.util.Optional; import java.util.stream.Collectors; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; @@ -26,6 +27,7 @@ import org.sopt.confeti.api.performance.facade.dto.response.RecommendPerformancesDTO; import org.sopt.confeti.api.performance.facade.dto.response.SearchACPerformancesDTO; import org.sopt.confeti.api.performance.facade.dto.response.PerformanceIdsDTO; +import org.sopt.confeti.api.performance.vo.UserPerformanceRecordVO; import org.sopt.confeti.domain.artist_favorite.ArtistFavorite; import org.sopt.confeti.domain.artist_favorite.application.ArtistFavoriteService; import org.sopt.confeti.domain.concert.Concert; @@ -36,6 +38,7 @@ import org.sopt.confeti.domain.festival.Festival; import org.sopt.confeti.domain.festival.application.FestivalService; import org.sopt.confeti.domain.festival_favorite.application.FestivalFavoriteService; +import org.sopt.confeti.domain.setlist.SetlistType; import org.sopt.confeti.domain.setlist.application.SetlistService; import org.sopt.confeti.domain.timetable_festival.application.TimetableFestivalService; import org.sopt.confeti.domain.user.application.UserService; @@ -61,6 +64,8 @@ public class PerformanceFacade { private static final int RECENT_PERFORMANCES_SIZE = 7; private static final int RECOMMEND_MUSIC_SIZE = 3; + private static final int MUSIC_FETCH_SIZE = 5; + private static final int SELECTED_ARTISTS_SIZE = 3; private static final boolean PERSONALIZED = true; private static final boolean UNPERSONALIZED = false; @@ -261,40 +266,26 @@ public SearchACPerformancesDTO searchACPerformances(String term, int limit, Perf ); } - @Transactional(readOnly = true) - public RecommendMusicsPerformanceDTO getRecommendPerformanceId(final Long userId) { - - Performance performance = performanceService.getPerformanceByUserFavorites(userId); - if (userId == null || performance == null) { - performance = performanceService.getPerformanceByRand(); - } - - return RecommendMusicsPerformanceDTO.from(performance); + public Optional getRecommendPerformanceIdByRand() { + return performanceService.getPerformanceByRand() + .map(RecommendMusicsPerformanceDTO::from); } - protected Set setArtistsByRandom(Performance performance) { - List performanceArtists = performance.getArtists(); - - if (performanceArtists.isEmpty()) { - return Collections.emptySet(); - } - - List artistList = performanceArtists.stream() - .map(PerformanceArtist::getArtistId).distinct().collect(Collectors.toList()); - Collections.shuffle(artistList); - - int artistCount = Math.min(artistList.size(), 3); - return new HashSet<>(artistList.subList(0, artistCount)); + public Optional getRecommendPerformanceIdByUserId(final Long userId) { + return performanceService.getPerformanceByUserFavorites(userId) + .map(RecommendMusicsPerformanceDTO::from); } @Transactional(readOnly = true) public RecommendMusicsDTO getNewRecommendMusics(long performanceId, List musicIds) { Performance performance = performanceService.getPerformanceById(performanceId); - Set selectedArtistIds = setArtistsByRandom(performance); + Set selectedArtistIds = selectRandomArtistIds(performance); Set existingMusicIds = (musicIds == null || musicIds.isEmpty()) ? Collections.emptySet() - : musicIds.stream().flatMap(ids -> Arrays.stream(ids.split(","))) - .map(String::trim).collect(Collectors.toSet()); + : musicIds.stream() + .flatMap(ids -> Arrays.stream(ids.split(","))) + .map(String::trim) + .collect(Collectors.toSet()); List artistIdList = new ArrayList<>(selectedArtistIds); List recommendMusics = recommendMusicsByArtistCount(artistIdList, existingMusicIds); @@ -302,57 +293,62 @@ public RecommendMusicsDTO getNewRecommendMusics(long performanceId, List return RecommendMusicsDTO.from(recommendMusics); } - private List recommendMusicsByArtistCount(List artistIdList, Set existingMusicIds) { - int artistCount = artistIdList.size(); - if (artistCount == 1) { - return recommendForSingleArtist(artistIdList, existingMusicIds); - } - if (artistCount == 2) { - return recommendForTwoArtists(artistIdList, existingMusicIds); + protected Set selectRandomArtistIds(Performance performance) { + List performanceArtists = performance.getArtists(); + + if (performanceArtists.isEmpty()) { + return Collections.emptySet(); } - return recommendForMultipleArtists(artistIdList, existingMusicIds); - } - private List recommendForSingleArtist(List artistIdList, Set existingMusicIds) { - return musicAPIHandler.getFilteredTopSongsByArtist( - artistIdList.getFirst(), RECOMMEND_MUSIC_SIZE, existingMusicIds - ); - } + List artistList = performanceArtists.stream() + .map(PerformanceArtist::getArtistId).distinct().collect(Collectors.toList()); + Collections.shuffle(artistList); - private List recommendForTwoArtists(List artistIdList, Set existingMusicIds) { - List musics = new ArrayList<>(); - musics.addAll(musicAPIHandler.getFilteredTopSongsByArtist(artistIdList.getFirst(), 2, existingMusicIds)); - musics.addAll(musicAPIHandler.getFilteredTopSongsByArtist(artistIdList.getLast(), 1, existingMusicIds)); - return musics; + int artistCount = Math.min(artistList.size(), SELECTED_ARTISTS_SIZE); + return new HashSet<>(artistList.subList(0, artistCount)); } - private List recommendForMultipleArtists(List artistIdList, Set existingMusicIds) { + private List recommendMusicsByArtistCount(List artistIdList, Set existingMusicIds) { List musics = new ArrayList<>(); - for (String artistId : artistIdList) { - if (musics.size() >= RECOMMEND_MUSIC_SIZE) { - break; + Set seenMusicIds = new HashSet<>(existingMusicIds); + int round = 0; + + while (musics.size() < RECOMMEND_MUSIC_SIZE && round < MUSIC_FETCH_SIZE) { + for (String artistId : artistIdList) { + if (musics.size() >= RECOMMEND_MUSIC_SIZE) break; + + List topSongs = musicAPIHandler.getFilteredTopSongsByArtist( + artistId, MUSIC_FETCH_SIZE, seenMusicIds + ); + + if (round < topSongs.size()) { + ConfetiMusic song = topSongs.get(round); + if (!seenMusicIds.contains(song.getId())) { + musics.add(song); + seenMusicIds.add(song.getId()); + } + } } - musics.addAll(musicAPIHandler.getFilteredTopSongsByArtist(artistId, 1, existingMusicIds)); + round++; } + return musics; } - @Transactional(readOnly = true) public ConfetiRecordDTO getConfetiRecord(final long userId) { validateExistUser(userId); List timetableFestivalIds = timetableFestivalService.findFestivalIdsByUserId(userId); - List setListFestivalIds = setlistService.findFestivalIdsByUserId(userId); - List setListConcertIds = setlistService.findConcertIdsByUserId(userId); + List setListFestivalIds = setlistService.findMusicIdsByUserId(userId, SetlistType.FESTIVAL); + List setListConcertIds = setlistService.findMusicIdsByUserId(userId, SetlistType.CONCERT); - Set uniqueFestivalIds = new HashSet<>(); - uniqueFestivalIds.addAll(timetableFestivalIds); - uniqueFestivalIds.addAll(setListFestivalIds); - - int totalCount = uniqueFestivalIds.size() + setListConcertIds.size(); + UserPerformanceRecordVO record = new UserPerformanceRecordVO( + timetableFestivalIds, + setListFestivalIds, + setListConcertIds + ); - return ConfetiRecordDTO.of(totalCount, timetableFestivalIds.size(), - setListFestivalIds.size() + setListConcertIds.size()); + return ConfetiRecordDTO.from(record); } protected void validateExistUser(final long userId) { diff --git a/src/main/java/org/sopt/confeti/api/performance/facade/dto/response/ConfetiRecordDTO.java b/src/main/java/org/sopt/confeti/api/performance/facade/dto/response/ConfetiRecordDTO.java index 1f7ffac3..bb372336 100644 --- a/src/main/java/org/sopt/confeti/api/performance/facade/dto/response/ConfetiRecordDTO.java +++ b/src/main/java/org/sopt/confeti/api/performance/facade/dto/response/ConfetiRecordDTO.java @@ -1,13 +1,17 @@ package org.sopt.confeti.api.performance.facade.dto.response; +import org.sopt.confeti.api.performance.vo.UserPerformanceRecordVO; + public record ConfetiRecordDTO( long totalCount, long timetableCount, long setlistCount ) { - public static ConfetiRecordDTO of(final long totalCount, final long timetableCount, final long setlistCount) { + public static ConfetiRecordDTO from(UserPerformanceRecordVO record) { return new ConfetiRecordDTO( - totalCount, timetableCount, setlistCount + record.getTotalUniquePerformanceCount(), + record.getTimetableFestivalCount(), + record.getSetListPerformanceCount() ); } } \ No newline at end of file diff --git a/src/main/java/org/sopt/confeti/api/performance/vo/.gitkeep b/src/main/java/org/sopt/confeti/api/performance/vo/.gitkeep deleted file mode 100644 index e69de29b..00000000 diff --git a/src/main/java/org/sopt/confeti/api/performance/vo/UserPerformanceRecordVO.java b/src/main/java/org/sopt/confeti/api/performance/vo/UserPerformanceRecordVO.java new file mode 100644 index 00000000..d7418719 --- /dev/null +++ b/src/main/java/org/sopt/confeti/api/performance/vo/UserPerformanceRecordVO.java @@ -0,0 +1,25 @@ +package org.sopt.confeti.api.performance.vo; + +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +public record UserPerformanceRecordVO( + List timetableFestivalIds, + List setListFestivalIds, + List setListConcertIds +) { + public int getTotalUniquePerformanceCount() { + Set uniqueFestivalIds = new HashSet<>(timetableFestivalIds); + uniqueFestivalIds.addAll(setListFestivalIds); + return uniqueFestivalIds.size() + setListConcertIds.size(); + } + + public int getTimetableFestivalCount() { + return timetableFestivalIds.size(); + } + + public int getSetListPerformanceCount() { + return setListFestivalIds.size() + setListConcertIds.size(); + } +} \ No newline at end of file diff --git a/src/main/java/org/sopt/confeti/api/user/controller/UserFavoriteController.java b/src/main/java/org/sopt/confeti/api/user/controller/UserFavoriteController.java index ab7948d2..90d92613 100644 --- a/src/main/java/org/sopt/confeti/api/user/controller/UserFavoriteController.java +++ b/src/main/java/org/sopt/confeti/api/user/controller/UserFavoriteController.java @@ -2,6 +2,7 @@ import jakarta.validation.constraints.Min; import java.util.Collections; +import java.util.Optional; import lombok.RequiredArgsConstructor; import org.sopt.confeti.api.user.dto.response.UpcomingPerformanceResponse; import org.sopt.confeti.api.user.dto.response.UserFavoriteArtistsPreviewResponse; @@ -137,12 +138,11 @@ public ResponseEntity> getFavoritePerformancesAll( public ResponseEntity> getUpcomingPerformance( @UserId Long userId ) { - UpcomingPerformanceDTO upcomingPerformanceDTO = userFavoriteFacade.getUpcomingPerformance(userId); - if (upcomingPerformanceDTO == null) { - return ApiResponseUtil.success(SuccessMessage.SUCCESS, Collections.emptyMap()); - } - return ApiResponseUtil.success(SuccessMessage.SUCCESS, - UpcomingPerformanceResponse.of(upcomingPerformanceDTO, s3FileHandler)); + Optional upcomingPerformanceDTO = userFavoriteFacade.getUpcomingPerformance(userId); + return upcomingPerformanceDTO + .map(performanceDTO -> + ApiResponseUtil.success(SuccessMessage.SUCCESS, UpcomingPerformanceResponse.of(performanceDTO, s3FileHandler))) + .orElseGet(() -> ApiResponseUtil.success(SuccessMessage.SUCCESS, Collections.emptyMap())); } @Permission(role = {Role.GENERAL}) diff --git a/src/main/java/org/sopt/confeti/api/user/controller/UserFavoriteSortType.java b/src/main/java/org/sopt/confeti/api/user/controller/UserFavoriteSortType.java new file mode 100644 index 00000000..ad4d5f1b --- /dev/null +++ b/src/main/java/org/sopt/confeti/api/user/controller/UserFavoriteSortType.java @@ -0,0 +1,28 @@ +package org.sopt.confeti.api.user.controller; + +import org.sopt.confeti.global.exception.ConfetiException; +import org.sopt.confeti.global.message.ErrorMessage; + +import java.util.Arrays; + +public enum UserFavoriteSortType { + CREATED_AT("createdAt"), + ALPHABETICALLY("alphabetically"); + + private final String value; + + UserFavoriteSortType(String value) { + this.value = value; + } + + public static UserFavoriteSortType from(String value) { + return Arrays.stream(values()) + .filter(type -> type.value.equalsIgnoreCase(value)) + .findFirst() + .orElseThrow(() -> new ConfetiException(ErrorMessage.BAD_REQUEST)); + } + + public String getValue() { + return value; + } +} diff --git a/src/main/java/org/sopt/confeti/api/user/controller/UserTimetableSortType.java b/src/main/java/org/sopt/confeti/api/user/controller/UserTimetableSortType.java new file mode 100644 index 00000000..eb3d0105 --- /dev/null +++ b/src/main/java/org/sopt/confeti/api/user/controller/UserTimetableSortType.java @@ -0,0 +1,28 @@ +package org.sopt.confeti.api.user.controller; + +import org.sopt.confeti.global.exception.ConfetiException; +import org.sopt.confeti.global.message.ErrorMessage; + +import java.util.Arrays; + +public enum UserTimetableSortType { + OLDEST_FIRST("oldestFirst"), + CREATED_AT("createdAt"); + + private final String value; + + UserTimetableSortType(String value) { + this.value = value; + } + + public static UserTimetableSortType from(String value) { + return Arrays.stream(values()) + .filter(type -> type.value.equalsIgnoreCase(value)) + .findFirst() + .orElseThrow(() -> new ConfetiException(ErrorMessage.BAD_REQUEST)); + } + + public String getValue() { + return value; + } +} diff --git a/src/main/java/org/sopt/confeti/api/user/facade/UserFavoriteFacade.java b/src/main/java/org/sopt/confeti/api/user/facade/UserFavoriteFacade.java index 2ddfb396..5bb702c8 100644 --- a/src/main/java/org/sopt/confeti/api/user/facade/UserFavoriteFacade.java +++ b/src/main/java/org/sopt/confeti/api/user/facade/UserFavoriteFacade.java @@ -1,8 +1,10 @@ package org.sopt.confeti.api.user.facade; import java.util.List; +import java.util.Objects; import java.util.Optional; import lombok.RequiredArgsConstructor; +import org.sopt.confeti.api.user.controller.UserFavoriteSortType; import org.sopt.confeti.api.user.facade.dto.response.UpcomingPerformanceDTO; import org.sopt.confeti.api.user.facade.dto.response.UserFavoriteArtistsDTO; import org.sopt.confeti.api.user.facade.dto.response.UserFavoriteArtistsPreviewDTO; @@ -176,7 +178,6 @@ protected void validateNotExistConcertFavorite(final long userId, final long con } } - @Transactional(readOnly = true) protected void validateExistUser(final long userId) { if (!userService.existsById(userId)) { throw new NotFoundException(ErrorMessage.NOT_FOUND); @@ -198,30 +199,22 @@ protected void validateType(final String type) { } } - @Transactional(readOnly = true) - public UpcomingPerformanceDTO getUpcomingPerformance(final long userId) { + public Optional getUpcomingPerformance(final long userId) { validateExistUser(userId); Performance performance = performanceService.getUpcomingPerformanceByUserId(userId); - if (performance == null) { - return null; + if (Objects.isNull(performance)) { + return Optional.empty(); } - return UpcomingPerformanceDTO.from(performance); + return Optional.of(UpcomingPerformanceDTO.from(performance)); } @Transactional(readOnly = true) public UserFavoriteArtistsDTO getFavoriteArtists(long userId, String sortBy) { validateExistUser(userId); - validateSortType(sortBy); + UserFavoriteSortType sortType = UserFavoriteSortType.from(sortBy); - List artists = artistFavoriteService.getFavoriteArtists(userId, sortBy); + List artists = artistFavoriteService.getFavoriteArtists(userId, sortType); return UserFavoriteArtistsDTO.from(artists); } - - @Transactional(readOnly = true) - protected void validateSortType(final String sortBy) { - if (!sortBy.equalsIgnoreCase("createdAt") && !sortBy.equalsIgnoreCase("alphabetically")) { - throw new ConfetiException(ErrorMessage.BAD_REQUEST); - } - } } diff --git a/src/main/java/org/sopt/confeti/api/user/facade/UserTimetableFacade.java b/src/main/java/org/sopt/confeti/api/user/facade/UserTimetableFacade.java index f5fd005e..4b782edd 100644 --- a/src/main/java/org/sopt/confeti/api/user/facade/UserTimetableFacade.java +++ b/src/main/java/org/sopt/confeti/api/user/facade/UserTimetableFacade.java @@ -7,6 +7,7 @@ import java.util.function.Predicate; import java.util.stream.Collectors; import lombok.RequiredArgsConstructor; +import org.sopt.confeti.api.user.controller.UserTimetableSortType; import org.sopt.confeti.api.user.facade.dto.request.AddTimetableFestivalArtiestDTO; import org.sopt.confeti.api.user.facade.dto.request.AddTimetableFestivalDTO; import org.sopt.confeti.api.user.facade.dto.request.PatchTimetableDTO; @@ -207,9 +208,9 @@ public void patchTimetableFestivals(final long userId, final PatchTimetableDTO t @Transactional(readOnly = true) public UserTimetablesDTO getTimetables(final long userId, final String sortBy) { validateUserExists(userId); - validateSortType(sortBy); + UserTimetableSortType sortType = UserTimetableSortType.from(sortBy); - List userTimetables = timetableFestivalService.getTimetables(userId, sortBy); + List userTimetables = timetableFestivalService.getTimetables(userId, sortType); return UserTimetablesDTO.from(userTimetables); } @@ -263,13 +264,6 @@ protected void validateExistTimetableIds(List userTimetables, Pat } } - @Transactional(readOnly = true) - protected void validateSortType(final String sortBy) { - if (!sortBy.equalsIgnoreCase("createdAt") && !sortBy.equalsIgnoreCase("oldestFirst")) { - throw new ConfetiException(ErrorMessage.BAD_REQUEST); - } - } - @Transactional(readOnly = true) public UserTimetableHistoryDTO getHasTimetableHistory(long userId) { boolean hasTimetableHistory = userService.getHasTimetableHistory(userId); diff --git a/src/main/java/org/sopt/confeti/api/user/facade/dto/response/UpcomingPerformanceDTO.java b/src/main/java/org/sopt/confeti/api/user/facade/dto/response/UpcomingPerformanceDTO.java index 24d2e7a1..23483e2f 100644 --- a/src/main/java/org/sopt/confeti/api/user/facade/dto/response/UpcomingPerformanceDTO.java +++ b/src/main/java/org/sopt/confeti/api/user/facade/dto/response/UpcomingPerformanceDTO.java @@ -24,5 +24,4 @@ public static UpcomingPerformanceDTO from(Performance performance) { performance.getArea() ); } - } diff --git a/src/main/java/org/sopt/confeti/domain/artist_favorite/ArtistFavorite.java b/src/main/java/org/sopt/confeti/domain/artist_favorite/ArtistFavorite.java index 9ed3bf19..ff41b6a9 100644 --- a/src/main/java/org/sopt/confeti/domain/artist_favorite/ArtistFavorite.java +++ b/src/main/java/org/sopt/confeti/domain/artist_favorite/ArtistFavorite.java @@ -9,6 +9,8 @@ import jakarta.persistence.JoinColumn; import jakarta.persistence.ManyToOne; import jakarta.persistence.Table; +import jakarta.persistence.Column; +import jakarta.persistence.EntityListeners; import java.time.LocalDateTime; import lombok.AccessLevel; import lombok.Builder; @@ -17,9 +19,11 @@ import org.sopt.confeti.domain.user.User; import org.sopt.confeti.global.resolver.music_api.artist.vo.ConfetiArtist; import org.springframework.data.annotation.CreatedDate; +import org.springframework.data.jpa.domain.support.AuditingEntityListener; @Entity @Table(name = "artist_favorites") +@EntityListeners(AuditingEntityListener.class) @Getter @NoArgsConstructor(access = AccessLevel.PROTECTED) public class ArtistFavorite { @@ -33,23 +37,22 @@ public class ArtistFavorite { private User user; @CreatedDate + @Column(updatable = false) private LocalDateTime createdAt; @Embedded private ConfetiArtist artist; @Builder - private ArtistFavorite(User user, String artistId, LocalDateTime createdAt) { + private ArtistFavorite(User user, String artistId) { this.user = user; this.artist = ConfetiArtist.from(artistId); - this.createdAt = LocalDateTime.now(); } public static ArtistFavorite create(User user, String artistId) { return ArtistFavorite.builder() .user(user) .artistId(artistId) - .createdAt(LocalDateTime.now()) .build(); } } diff --git a/src/main/java/org/sopt/confeti/domain/artist_favorite/application/ArtistFavoriteService.java b/src/main/java/org/sopt/confeti/domain/artist_favorite/application/ArtistFavoriteService.java index 7b506408..0d40632b 100644 --- a/src/main/java/org/sopt/confeti/domain/artist_favorite/application/ArtistFavoriteService.java +++ b/src/main/java/org/sopt/confeti/domain/artist_favorite/application/ArtistFavoriteService.java @@ -5,6 +5,7 @@ import java.util.Set; import java.util.stream.Collectors; import lombok.AllArgsConstructor; +import org.sopt.confeti.api.user.controller.UserFavoriteSortType; import org.sopt.confeti.domain.artist_favorite.ArtistFavorite; import org.sopt.confeti.domain.artist_favorite.infra.repository.ArtistFavoriteRepository; import org.sopt.confeti.domain.user.User; @@ -63,13 +64,13 @@ public List getArtistIdsByUserId(final long userId) { } @Transactional(readOnly = true) - public List getFavoriteArtists(Long userId, String sortBy) { + public List getFavoriteArtists(Long userId, UserFavoriteSortType sortBy) { List artistList = artistFavoriteRepository.findArtistFavoritesByUserId(userId); musicAPIResolver.load(artistList); - if ("createdAt".equalsIgnoreCase(sortBy)) { + if ("createdAt".equalsIgnoreCase(sortBy.getValue())) { artistList.sort(Comparator.comparing(ArtistFavorite::getCreatedAt).reversed()); - } else if ("alphabetically".equalsIgnoreCase(sortBy)) { + } else if ("alphabetically".equalsIgnoreCase(sortBy.getValue())) { artistList.sort(Comparator.comparing(artist -> artist.getArtist().getName())); } diff --git a/src/main/java/org/sopt/confeti/domain/setlist/application/SetlistService.java b/src/main/java/org/sopt/confeti/domain/setlist/application/SetlistService.java index 17b6d330..1dead272 100644 --- a/src/main/java/org/sopt/confeti/domain/setlist/application/SetlistService.java +++ b/src/main/java/org/sopt/confeti/domain/setlist/application/SetlistService.java @@ -146,12 +146,7 @@ public GetSetlistDetailResponse getSetlistDetail(Long userId, Long setlistId) { } @Transactional(readOnly = true) - public List findFestivalIdsByUserId(final Long userId) { - return setlistRepository.findFestivalIdsByUserId(userId); - } - - @Transactional(readOnly = true) - public List findConcertIdsByUserId(final Long userId) { - return setlistRepository.findConcertIdsByUserId(userId); + public List findMusicIdsByUserId(final Long userId, final SetlistType sortType) { + return setlistRepository.findTypeIdsByUserIdAndType(userId, sortType); } } diff --git a/src/main/java/org/sopt/confeti/domain/setlist/infra/repository/SetlistRepository.java b/src/main/java/org/sopt/confeti/domain/setlist/infra/repository/SetlistRepository.java index dd80f72f..26737311 100644 --- a/src/main/java/org/sopt/confeti/domain/setlist/infra/repository/SetlistRepository.java +++ b/src/main/java/org/sopt/confeti/domain/setlist/infra/repository/SetlistRepository.java @@ -19,9 +19,6 @@ public interface SetlistRepository extends JpaRepository { Optional findByIdAndUserId(Long id, Long userId); - @Query("SELECT s.typeId FROM Setlist s WHERE s.type = org.sopt.confeti.domain.setlist.SetlistType.FESTIVAL AND s.user.id = :userId") - List findFestivalIdsByUserId(@Param("userId") Long userId); - - @Query("SELECT s.typeId FROM Setlist s WHERE s.type = org.sopt.confeti.domain.setlist.SetlistType.CONCERT AND s.user.id = :userId") - List findConcertIdsByUserId(@Param("userId") Long userId); + @Query("SELECT s.typeId FROM Setlist s WHERE s.type = :type AND s.user.id = :userId") + List findTypeIdsByUserIdAndType(@Param("userId") Long userId, @Param("type") SetlistType type); } diff --git a/src/main/java/org/sopt/confeti/domain/timetable_festival/TimetableFestival.java b/src/main/java/org/sopt/confeti/domain/timetable_festival/TimetableFestival.java index 2cd769f9..63312c7f 100644 --- a/src/main/java/org/sopt/confeti/domain/timetable_festival/TimetableFestival.java +++ b/src/main/java/org/sopt/confeti/domain/timetable_festival/TimetableFestival.java @@ -10,6 +10,8 @@ import jakarta.persistence.ManyToOne; import jakarta.persistence.OneToMany; import jakarta.persistence.Table; +import jakarta.persistence.Column; +import jakarta.persistence.EntityListeners; import java.time.LocalDateTime; import java.util.List; import lombok.AccessLevel; @@ -20,9 +22,11 @@ import org.sopt.confeti.domain.user.User; import org.sopt.confeti.domain.user_timetable.UserTimetable; import org.springframework.data.annotation.CreatedDate; +import org.springframework.data.jpa.domain.support.AuditingEntityListener; @Entity @Table(name = "timetable_festivals") +@EntityListeners(AuditingEntityListener.class) @Getter @NoArgsConstructor(access = AccessLevel.PROTECTED) public class TimetableFestival { @@ -43,14 +47,13 @@ public class TimetableFestival { private List userTimetables; @CreatedDate + @Column(updatable = false) private LocalDateTime createdAt; @Builder public TimetableFestival(User user, Festival festival) { this.user = user; this.festival = festival; - this.createdAt = LocalDateTime.now(); - this.userTimetables = festival.getDates().stream() .flatMap(festivalDate -> festivalDate.getStages().stream()) .flatMap(festivalStage -> festivalStage.getTimes().stream()) diff --git a/src/main/java/org/sopt/confeti/domain/timetable_festival/application/TimetableFestivalService.java b/src/main/java/org/sopt/confeti/domain/timetable_festival/application/TimetableFestivalService.java index 3e25e13d..9b99d14f 100644 --- a/src/main/java/org/sopt/confeti/domain/timetable_festival/application/TimetableFestivalService.java +++ b/src/main/java/org/sopt/confeti/domain/timetable_festival/application/TimetableFestivalService.java @@ -3,10 +3,13 @@ import java.util.Comparator; import java.util.List; import lombok.AllArgsConstructor; +import org.sopt.confeti.api.user.controller.UserTimetableSortType; import org.sopt.confeti.domain.festival.Festival; import org.sopt.confeti.domain.timetable_festival.TimetableFestival; import org.sopt.confeti.domain.timetable_festival.infra.repository.TimetableFestivalRepository; import org.sopt.confeti.domain.user.User; +import org.sopt.confeti.global.exception.ConfetiException; +import org.sopt.confeti.global.message.ErrorMessage; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -40,16 +43,14 @@ public void addTimetableFestivals(final User user, final List festival } @Transactional(readOnly = true) - public List getTimetables(final long userId, final String sortBy) { - List festivals = timetableFestivalRepository.findByUserId(userId); - - if ("createdAt".equalsIgnoreCase(sortBy)) { - festivals.sort(Comparator.comparing(TimetableFestival::getCreatedAt).reversed()); - } else if ("oldestFirst".equalsIgnoreCase(sortBy)) { - festivals.sort(Comparator.comparing(TimetableFestival::getCreatedAt)); + public List getTimetables(final long userId, final UserTimetableSortType sortBy) { + if (sortBy == UserTimetableSortType.CREATED_AT) { + return timetableFestivalRepository.findByUserIdOrderByCreatedAtDesc(userId); } - - return festivals; + if (sortBy == UserTimetableSortType.OLDEST_FIRST) { + return timetableFestivalRepository.findByUserIdOrderByCreatedAtAsc(userId); + } + throw new ConfetiException(ErrorMessage.BAD_REQUEST); } @Transactional(readOnly = true) diff --git a/src/main/java/org/sopt/confeti/domain/timetable_festival/infra/repository/TimetableFestivalRepository.java b/src/main/java/org/sopt/confeti/domain/timetable_festival/infra/repository/TimetableFestivalRepository.java index 6459e5f3..adb0fd30 100644 --- a/src/main/java/org/sopt/confeti/domain/timetable_festival/infra/repository/TimetableFestivalRepository.java +++ b/src/main/java/org/sopt/confeti/domain/timetable_festival/infra/repository/TimetableFestivalRepository.java @@ -10,8 +10,6 @@ public interface TimetableFestivalRepository extends JpaRepository= CURRENT_DATE") List findByUserIdWhereEndAtLENow(@Param("userId") Long userId); - List findByUserId(final long userId); - boolean existsByUserIdAndFestivalId(final long userId, final long festivalId); void deleteByUserIdAndFestivalId(final long userId, final long festivalId); @@ -20,4 +18,8 @@ public interface TimetableFestivalRepository extends JpaRepository findFestivalIdsByUserId(@Param("userId") Long userId); + + List findByUserIdOrderByCreatedAtDesc(final long userId); + + List findByUserIdOrderByCreatedAtAsc(final long userId); } diff --git a/src/main/java/org/sopt/confeti/domain/view/performance/application/PerformanceService.java b/src/main/java/org/sopt/confeti/domain/view/performance/application/PerformanceService.java index 3725457a..6ed599b0 100644 --- a/src/main/java/org/sopt/confeti/domain/view/performance/application/PerformanceService.java +++ b/src/main/java/org/sopt/confeti/domain/view/performance/application/PerformanceService.java @@ -4,6 +4,7 @@ import java.util.List; import java.util.Map; import java.util.Objects; +import java.util.Optional; import java.util.function.Function; import java.util.stream.Collectors; import lombok.RequiredArgsConstructor; @@ -161,12 +162,12 @@ public List getPerformancesByArtistIdAndType(String artistId, Pe } @Transactional(readOnly = true) - public Performance getPerformanceByRand() { + public Optional getPerformanceByRand() { return performanceRepository.findPerformanceByRand(); } @Transactional(readOnly = true) - public Performance getPerformanceByUserFavorites(final Long userId) { + public Optional getPerformanceByUserFavorites(final Long userId) { return performanceRepository.getPerformanceByUserFavorites(userId); } diff --git a/src/main/java/org/sopt/confeti/domain/view/performance/infra/repository/PerformanceRepository.java b/src/main/java/org/sopt/confeti/domain/view/performance/infra/repository/PerformanceRepository.java index a0bc7400..5083785b 100644 --- a/src/main/java/org/sopt/confeti/domain/view/performance/infra/repository/PerformanceRepository.java +++ b/src/main/java/org/sopt/confeti/domain/view/performance/infra/repository/PerformanceRepository.java @@ -82,7 +82,7 @@ List findPerformancesByUserFavorites( Optional findPerformanceByIdAndEndAtGreaterThanEqual(long performanceId, LocalDate date); @Query(value = "SELECT p FROM Performance p WHERE p.endAt >= CURRENT_DATE ORDER BY RAND() LIMIT 1") - Performance findPerformanceByRand(); + Optional findPerformanceByRand(); @Query(value = "SELECT p FROM Performance p " + "WHERE ((p.type = org.sopt.confeti.global.common.constant.PerformanceType.CONCERT " + @@ -91,7 +91,7 @@ List findPerformancesByUserFavorites( " AND p.typeId IN (SELECT tf.festival.id FROM TimetableFestival tf WHERE tf.user.id = :userId))) " + "AND p.endAt >= CURRENT_DATE " + "ORDER BY RAND() ASC LIMIT 1 ") - Performance getPerformanceByUserFavorites(final @Param("userId") Long userId); + Optional getPerformanceByUserFavorites(final @Param("userId") Long userId); List findRecentPerformancesByEndAtGreaterThanEqual(LocalDate now, PageRequest pageRequest); diff --git a/src/main/java/org/sopt/confeti/global/util/music/AppleMusicAPIHandler.java b/src/main/java/org/sopt/confeti/global/util/music/AppleMusicAPIHandler.java index 2290d3c8..f76f9870 100644 --- a/src/main/java/org/sopt/confeti/global/util/music/AppleMusicAPIHandler.java +++ b/src/main/java/org/sopt/confeti/global/util/music/AppleMusicAPIHandler.java @@ -536,6 +536,7 @@ private void cachingTopMusicsByArtistId(final String artistId, final List getTopSongsByArtistId(final String artistId, final int fetchSize) { List topMusics = new ArrayList<>(getCachedTopMusicsByArtistId(artistId, fetchSize)); if (!topMusics.isEmpty()) { @@ -561,9 +562,8 @@ public List getTopSongsByArtistId(final String artistId, final int } @Override - @RetryOnTokenExpire public List getFilteredTopSongsByArtist(String artistId, int limit, Set excludedMusicIds) { - List songs = getTopSongsByArtistId(artistId, limit * 5); + List songs = getTopSongsByArtistId(artistId, limit); List filteredSongs = songs.stream() .filter(song -> !excludedMusicIds.contains(song.getId()))