diff --git a/src/main/java/org/atdev/artrip/domain/exhibit/repository/ExhibitRepositoryImpl.java b/src/main/java/org/atdev/artrip/domain/exhibit/repository/ExhibitRepositoryImpl.java index f7483f8..e0b7b8a 100644 --- a/src/main/java/org/atdev/artrip/domain/exhibit/repository/ExhibitRepositoryImpl.java +++ b/src/main/java/org/atdev/artrip/domain/exhibit/repository/ExhibitRepositoryImpl.java @@ -8,6 +8,7 @@ import lombok.RequiredArgsConstructor; import org.atdev.artrip.domain.Enum.KeywordType; import org.atdev.artrip.domain.Enum.SortType; +import org.atdev.artrip.domain.Enum.Status; import org.atdev.artrip.domain.exhibit.data.Exhibit; import org.atdev.artrip.domain.exhibit.data.QExhibit; import org.atdev.artrip.domain.exhibit.web.dto.request.ExhibitFilterRequestDto; @@ -64,6 +65,7 @@ public Slice findExhibitByFilters(ExhibitFilterRequestDto dto, Pageable .leftJoin(e.keywords, k) .leftJoin(f).on(f.exhibit.eq(e)) .where( + e.status.ne(Status.FINISHED), typeFilter(dto, h), dateFilter(dto.getStartDate(), dto.getEndDate(),e), cursorCondition(cursor, cursorFavoriteCount, dto.getSortType(), e, f), @@ -92,7 +94,7 @@ public List findRandomExhibits(RandomExhibitRequest c) { QKeyword k = QKeyword.keyword; return queryFactory - .selectDistinct(Projections.constructor( + .selectDistinct(Projections.constructor(// select 순서와 DTO 생성자 파라미터 순서를 1:1 매핑함! HomeListResponse.class, e.exhibitId, e.title, @@ -103,12 +105,15 @@ public List findRandomExhibits(RandomExhibitRequest c) { e.startDate.stringValue(), e.endDate.stringValue() ), - h.name + h.name, + h.country, + h.region )) .from(e) .join(e.exhibitHall, h) .leftJoin(e.keywords, k) .where( + e.status.ne(Status.FINISHED), isDomesticEq(c.getIsDomestic()), countryEq(c.getCountry()), regionEq(c.getRegion()), diff --git a/src/main/java/org/atdev/artrip/domain/favortie/data/FavoriteExhibit.java b/src/main/java/org/atdev/artrip/domain/favortie/data/FavoriteExhibit.java index 0e85909..968fe04 100644 --- a/src/main/java/org/atdev/artrip/domain/favortie/data/FavoriteExhibit.java +++ b/src/main/java/org/atdev/artrip/domain/favortie/data/FavoriteExhibit.java @@ -25,6 +25,9 @@ public class FavoriteExhibit { @JoinColumn(name = "user_id", nullable = false) private User user; + @Column(nullable = false) + private boolean status = false; + @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "exhibit_id", nullable = false) private Exhibit exhibit; diff --git a/src/main/java/org/atdev/artrip/domain/favortie/repository/FavoriteExhibitRepository.java b/src/main/java/org/atdev/artrip/domain/favortie/repository/FavoriteExhibitRepository.java index 196fc82..658f79c 100644 --- a/src/main/java/org/atdev/artrip/domain/favortie/repository/FavoriteExhibitRepository.java +++ b/src/main/java/org/atdev/artrip/domain/favortie/repository/FavoriteExhibitRepository.java @@ -10,54 +10,80 @@ import java.util.Optional; public interface FavoriteExhibitRepository extends JpaRepository { - boolean existsByUser_UserIdAndExhibit_ExhibitId(Long userId, Long exhibitId); + @Query(""" + SELECT COUNT(f) > 0 + FROM FavoriteExhibit f + WHERE f.user.userId = :userId + AND f.exhibit.exhibitId = :exhibitId + And f.status = true + """) + boolean existsActive(@Param("userId") Long userId, @Param("exhibitId") Long exhibitId); - @Query("SELECT f FROM FavoriteExhibit f " + + @Query("SELECT f " + + "FROM FavoriteExhibit f " + "INNER JOIN FETCH f.exhibit e " + "INNER JOIN FETCH e.exhibitHall " + "WHERE f.user.userId = :userId " + + "AND f.status = true " + "ORDER BY f.createdAt DESC ") - List findAllByUserIdWithExhibit(@Param("userId") Long userId); + List findAllActive(@Param("userId") Long userId); - @Query("SELECT f FROM FavoriteExhibit f " + + @Query("SELECT f " + + "FROM FavoriteExhibit f " + "INNER JOIN FETCH f.exhibit e " + "INNER JOIN fetch e.exhibitHall eh " + "WHERE f.user.userId = :userId " + + "AND f.status = true " + "AND DATE(e.startDate) <= :date " + "AND DATE(e.endDate) >= :date " + "ORDER BY e.startDate ASC") - List findByUserIdAndDate(@Param("userId") Long userId, @Param("date") LocalDate date); + List findActiveByDate(@Param("userId") Long userId, @Param("date") LocalDate date); - @Query("SELECT f FROM FavoriteExhibit f " + + @Query("SELECT f " + + "FROM FavoriteExhibit f " + "INNER JOIN f.exhibit e " + "INNER JOIN e.exhibitHall eh " + "WHERE f.user.userId = :userId " + + "AND f.status = true " + "AND eh.country = :country " + "ORDER BY e.startDate DESC") - List findByUserIdAndCountry(@Param("userId") Long userId, @Param("country") String country); + List findActiveByCountry(@Param("userId") Long userId, @Param("country") String country); @Query(value = "SELECT DISTINCT DATE(e.start_date) " + "FROM favorite_exhibit f " + "INNER JOIN exhibit e ON f.exhibit_id = e.exhibit_id " + "WHERE f.user_id = :userId " + + "AND f.status = true " + "AND YEAR (e.start_date) = :year " + "AND MONTH (e.start_date) = :month " + "ORDER BY DATE(e.start_date) ASC", nativeQuery = true) - List findExhibitDatesByUserIdAndYearMonth( + List findDatesByYearMonth( @Param("userId") Long userId, @Param("year") int year, @Param("month") int month); - @Query("SELECT DISTINCT eh.country FROM FavoriteExhibit f " + + @Query("SELECT DISTINCT eh.country " + + "FROM FavoriteExhibit f " + "INNER JOIN f.exhibit e " + "INNER JOIN e.exhibitHall eh " + "WHERE f.user.userId = :userId " + + "AND f.status = true " + "AND eh.country IS NOT NULL " + "ORDER BY eh.country ASC") - List findDistinctCountriesByUserId(@Param("userId") Long userId); + List findDistinctCountries(@Param("userId") Long userId); - @Query("SELECT f FROM FavoriteExhibit f " + + @Query("SELECT f " + + "FROM FavoriteExhibit f " + "WHERE f.user.userId = :userId " + - "AND f.exhibit.exhibitId = :exhibitId") - Optional findByUserIdAndExhibitId(@Param("userId") Long userId, @Param("exhibitId") Long exhibitId); + "AND f.exhibit.exhibitId = :exhibitId " + + "AND f.status = true") + Optional findActive(@Param("userId") Long userId, @Param("exhibitId") Long exhibitId); + + @Query(""" + SELECT f + FROM FavoriteExhibit f + WHERE f.user.userId = :userId + AND f.exhibit.exhibitId = :exhibitId + """) + Optional findByUserAndExhibit(@Param("userId") Long userId, @Param("exhibitId") Long exhibitId); } diff --git a/src/main/java/org/atdev/artrip/domain/favortie/service/FavoriteExhibitService.java b/src/main/java/org/atdev/artrip/domain/favortie/service/FavoriteExhibitService.java index 91da2d3..5898207 100644 --- a/src/main/java/org/atdev/artrip/domain/favortie/service/FavoriteExhibitService.java +++ b/src/main/java/org/atdev/artrip/domain/favortie/service/FavoriteExhibitService.java @@ -10,11 +10,9 @@ import org.atdev.artrip.domain.favortie.web.dto.response.CalenderResponse; import org.atdev.artrip.domain.favortie.web.dto.response.FavoriteResponse; import org.atdev.artrip.domain.favortie.repository.FavoriteExhibitRepository; -import org.atdev.artrip.global.apipayload.code.status.CommonError; import org.atdev.artrip.global.apipayload.code.status.ExhibitError; import org.atdev.artrip.global.apipayload.code.status.UserError; import org.atdev.artrip.global.apipayload.exception.GeneralException; -import org.atdev.artrip.global.apipayload.exception.handler.ErrorHandler; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -22,6 +20,7 @@ import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; import java.util.List; +import java.util.Optional; import java.util.stream.Collectors; @Slf4j @@ -42,22 +41,35 @@ public FavoriteResponse addFavorite(Long userId, Long exhibitId) { Exhibit exhibit = exhibitRepository.findById(exhibitId).orElseThrow(() -> new GeneralException(ExhibitError._EXHIBIT_NOT_FOUND)); - if (favoriteExhibitRepository.existsByUser_UserIdAndExhibit_ExhibitId(userId, exhibitId)) { - log.info("Exhibit with id {} already exists", exhibitId); - throw new ErrorHandler(CommonError._BAD_REQUEST); + Optional existing = favoriteExhibitRepository.findByUserAndExhibit(userId, exhibitId); + + FavoriteExhibit favorite; + + if (existing.isPresent()) { + favorite = existing.get(); + if (favorite.isStatus()) { + log.warn("Exhibit Already in favorites: userId: {}, exhibitId: {}", userId, exhibitId); + return toFavoriteResponse(favorite); + } + favorite.setStatus(true); + log.info("Reactivating favorite exhibit: {}", favorite.getFavoriteId()); + } else { + favorite = FavoriteExhibit.builder() + .user(user) + .exhibit(exhibit) + .status(true) + .createdAt(LocalDateTime.now()) + .build(); + log.info("Creating new favorite"); } - FavoriteExhibit favorite = FavoriteExhibit.builder() - .user(user) - .exhibit(exhibit) - .createdAt(LocalDateTime.now()) - .build(); FavoriteExhibit saved = favoriteExhibitRepository.save(favorite); log.info("Favorite exhibit added successfully. favoriteId: {}", saved.getFavoriteId()); return toFavoriteResponse(saved); } + @Transactional(readOnly = true) public List getAllFavorites(Long userId) { log.info("Getting favorites for exhibit. userId: {}", userId); @@ -65,7 +77,7 @@ public List getAllFavorites(Long userId) { throw new GeneralException(UserError._USER_NOT_FOUND); } - List favorites = favoriteExhibitRepository.findAllByUserIdWithExhibit(userId); + List favorites = favoriteExhibitRepository.findAllActive(userId); log.info("Favorites found: {}", favorites); return favorites.stream() @@ -73,6 +85,7 @@ public List getAllFavorites(Long userId) { .collect(Collectors.toList()); } + @Transactional(readOnly = true) public List getFavoritesByDate(Long userId, LocalDate date) { log.info("Getting favorites for exhibit by date. userID: {}, date: {}", userId, date); @@ -80,7 +93,7 @@ public List getFavoritesByDate(Long userId, LocalDate date) { throw new GeneralException(UserError._USER_NOT_FOUND); } - List favorites = favoriteExhibitRepository.findByUserIdAndDate(userId, date); + List favorites = favoriteExhibitRepository.findActiveByDate(userId, date); log.info("Favorites found: {}", favorites.size()); return favorites.stream() @@ -88,6 +101,7 @@ public List getFavoritesByDate(Long userId, LocalDate date) { .collect(Collectors.toList()); } + @Transactional(readOnly = true) public List getFavoritesByCountry(Long userId, String country) { log.info("Getting favorites for exhibit by country. userId: {}, country: {}", userId, country); @@ -95,7 +109,7 @@ public List getFavoritesByCountry(Long userId, String country) throw new GeneralException(UserError._USER_NOT_FOUND); } - List favorites = favoriteExhibitRepository.findByUserIdAndCountry(userId, country); + List favorites = favoriteExhibitRepository.findActiveByCountry(userId, country); log.info("Favorites found: {}", favorites.size()); return favorites.stream() @@ -103,6 +117,7 @@ public List getFavoritesByCountry(Long userId, String country) .collect(Collectors.toList()); } + @Transactional(readOnly = true) public CalenderResponse getCalenderDates(Long userId, int year, int month) { log.info("Getting calendar dates for userId: {}, year: {}, month: {}", userId, year, month); @@ -110,7 +125,7 @@ public CalenderResponse getCalenderDates(Long userId, int year, int month) { throw new GeneralException(UserError._USER_NOT_FOUND); } - List dateStrings = favoriteExhibitRepository.findExhibitDatesByUserIdAndYearMonth(userId, year, month); + List dateStrings = favoriteExhibitRepository.findDatesByYearMonth(userId, year, month); List exhibitDates = dateStrings.stream() .map(LocalDate::parse) @@ -124,29 +139,34 @@ public CalenderResponse getCalenderDates(Long userId, int year, int month) { .build(); } + @Transactional(readOnly = true) public List getFavoriteCountries(Long userId) { log.info("Getting favorite countries for exhibit. userId: {}", userId); if (!userRepository.existsById(userId)) { throw new GeneralException(UserError._USER_NOT_FOUND); } - List countries = favoriteExhibitRepository.findDistinctCountriesByUserId(userId); + List countries = favoriteExhibitRepository.findDistinctCountries(userId); log.info("Favorite countries found: {}", countries); return countries; } + @Transactional(readOnly = true) public boolean isFavorite(Long userId, Long exhibitId) { - return favoriteExhibitRepository.existsByUser_UserIdAndExhibit_ExhibitId(userId, exhibitId); + return favoriteExhibitRepository.existsActive(userId, exhibitId); } + @Transactional public void removeFavorite(Long userId, Long exhibitId) { log.info("Removing favorite exhibit. userId: {}, exhibitId: {}", userId, exhibitId); - FavoriteExhibit favorite = favoriteExhibitRepository.findByUserIdAndExhibitId(userId, exhibitId) + FavoriteExhibit favorite = favoriteExhibitRepository.findActive(userId, exhibitId) .orElseThrow(() -> new GeneralException(UserError._USER_NOT_FOUND)); - favoriteExhibitRepository.delete(favorite); + favorite.setStatus(false); + + favoriteExhibitRepository.save(favorite); log.info("Favorite exhibit removed successfully. favoriteId: {}", favorite.getFavoriteId()); } @@ -161,7 +181,8 @@ private FavoriteResponse toFavoriteResponse(FavoriteExhibit favorite) { .exhibitId(exhibit.getExhibitId()) .title(exhibit.getTitle()) .posterUrl(exhibit.getPosterUrl()) - .status(exhibit.getStatus()) + .exhibitStatus(exhibit.getStatus()) + .favoriteStatus(favorite.isStatus()) .exhibitPeriod(period) .exhibitHallName(hall != null ? hall.getName() : null ) .country(hall != null ? hall.getCountry() : null) diff --git a/src/main/java/org/atdev/artrip/domain/favortie/web/dto/response/FavoriteResponse.java b/src/main/java/org/atdev/artrip/domain/favortie/web/dto/response/FavoriteResponse.java index 0316291..a21e2cd 100644 --- a/src/main/java/org/atdev/artrip/domain/favortie/web/dto/response/FavoriteResponse.java +++ b/src/main/java/org/atdev/artrip/domain/favortie/web/dto/response/FavoriteResponse.java @@ -14,7 +14,8 @@ public class FavoriteResponse { private Long exhibitId; private String title; private String posterUrl; - private Status status; + private Status exhibitStatus; + private boolean favoriteStatus; private String exhibitPeriod; private String exhibitHallName; private String country; diff --git a/src/main/java/org/atdev/artrip/domain/home/converter/HomeConverter.java b/src/main/java/org/atdev/artrip/domain/home/converter/HomeConverter.java index 67315b7..e2735f8 100644 --- a/src/main/java/org/atdev/artrip/domain/home/converter/HomeConverter.java +++ b/src/main/java/org/atdev/artrip/domain/home/converter/HomeConverter.java @@ -136,4 +136,5 @@ private String normalize(String value) { return value; } + } diff --git a/src/main/java/org/atdev/artrip/domain/home/response/HomeListResponse.java b/src/main/java/org/atdev/artrip/domain/home/response/HomeListResponse.java index 7f588fb..2e038fa 100644 --- a/src/main/java/org/atdev/artrip/domain/home/response/HomeListResponse.java +++ b/src/main/java/org/atdev/artrip/domain/home/response/HomeListResponse.java @@ -1,5 +1,6 @@ package org.atdev.artrip.domain.home.response; +import com.fasterxml.jackson.annotation.JsonInclude; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Getter; @@ -11,6 +12,7 @@ @Setter @Builder @AllArgsConstructor +@JsonInclude(JsonInclude.Include.NON_NULL) public class HomeListResponse { private Long exhibit_id; @@ -20,5 +22,8 @@ public class HomeListResponse { private String exhibitPeriod; private String hallName; + private String countryName; + private String regionName; + } diff --git a/src/main/java/org/atdev/artrip/domain/home/service/HomeService.java b/src/main/java/org/atdev/artrip/domain/home/service/HomeService.java index 4c0e06f..d70774e 100644 --- a/src/main/java/org/atdev/artrip/domain/home/service/HomeService.java +++ b/src/main/java/org/atdev/artrip/domain/home/service/HomeService.java @@ -138,7 +138,7 @@ public FilterResponse getFilterExhibit(ExhibitFilterRequestDto dto, Pageable pag } @Transactional - public List getRandomPersonalized(Long userId, PersonalizedRequestDto requestDto){ + public List getRandomPersonalized(Long userId, PersonalizedRequestDto request){ if (!userRepository.existsById(userId)) { throw new GeneralException(UserError._USER_NOT_FOUND); @@ -150,31 +150,86 @@ public List getRandomPersonalized(Long userId, PersonalizedReq .toList(); RandomExhibitRequest filter = homeConverter.from( - requestDto, + request, userKeywords ); - return exhibitRepository.findRandomExhibits(filter); + List results = exhibitRepository.findRandomExhibits(filter); + + adjustLocationFields( + results, + request.getIsDomestic(), + request.getRegion(), + request.getCountry() + ); + + return results; } public List getRandomSchedule(ScheduleRandomRequestDto request){ RandomExhibitRequest filter = homeConverter.from(request); - return exhibitRepository.findRandomExhibits(filter); + List results = exhibitRepository.findRandomExhibits(filter); + + adjustLocationFields( + results, + request.getIsDomestic(), + request.getRegion(), + request.getCountry() + ); + + return results; } public List getRandomGenre(GenreRandomRequestDto request){ RandomExhibitRequest filter = homeConverter.fromGenre(request); - return exhibitRepository.findRandomExhibits(filter); + List results = exhibitRepository.findRandomExhibits(filter); + + adjustLocationFields( + results, + request.getIsDomestic(), + request.getRegion(), + request.getCountry() + ); + + return results; } public List getToday(TodayRandomRequestDto request){ RandomExhibitRequest filter = homeConverter.fromToday(request); - return exhibitRepository.findRandomExhibits(filter); + List results = exhibitRepository.findRandomExhibits(filter); + + adjustLocationFields( + results, + request.getIsDomestic(), + request.getRegion(), + request.getCountry() + ); + + return results; } + private void adjustLocationFields(List results, boolean isDomestic, String region, String country) { + + boolean isWhole = ("전체".equals(region) || region == null) && ("전체".equals(country) || country == null); + + if (!isWhole) { + results.forEach(r -> { + r.setRegionName(null); + r.setCountryName(null); + }); + return; + } + + if (isDomestic) { + results.forEach(r -> r.setCountryName(null)); + } else { + results.forEach(r -> r.setRegionName(null)); + } + } + } \ No newline at end of file diff --git a/src/main/java/org/atdev/artrip/domain/review/repository/ReviewRepository.java b/src/main/java/org/atdev/artrip/domain/review/repository/ReviewRepository.java index fbbcfd6..8f10e35 100644 --- a/src/main/java/org/atdev/artrip/domain/review/repository/ReviewRepository.java +++ b/src/main/java/org/atdev/artrip/domain/review/repository/ReviewRepository.java @@ -30,4 +30,6 @@ Slice findByUserIdAndIdLessThan(@Param("userId") Long userId, Slice findByExhibitIdAndIdLessThan(@Param("exhibitId") Long exhibitId, @Param("cursor") Long cursor, Pageable pageable); + + long countByExhibit_ExhibitId(Long exhibitId); } diff --git a/src/main/java/org/atdev/artrip/domain/review/service/ReviewService.java b/src/main/java/org/atdev/artrip/domain/review/service/ReviewService.java index 419e87e..0680d3e 100644 --- a/src/main/java/org/atdev/artrip/domain/review/service/ReviewService.java +++ b/src/main/java/org/atdev/artrip/domain/review/service/ReviewService.java @@ -167,6 +167,8 @@ public ReviewSliceResponse getAllReview(Long userId, Long cursor, int size){ @Transactional public ExhibitReviewSliceResponse getExhibitReview(Long exhibitId, Long cursor, int size){ + long totalCount = reviewRepository.countByExhibit_ExhibitId(exhibitId); + Slice slice; if (cursor == null) { @@ -184,6 +186,6 @@ public ExhibitReviewSliceResponse getExhibitReview(Long exhibitId, Long cursor, .map(ReviewConverter::toExhibitReviewSummary) .toList(); - return new ExhibitReviewSliceResponse(summaries, nextCursor, slice.hasNext()); + return new ExhibitReviewSliceResponse(summaries, nextCursor, slice.hasNext(),totalCount); } -} +} \ No newline at end of file diff --git a/src/main/java/org/atdev/artrip/domain/review/web/dto/response/ExhibitReviewSliceResponse.java b/src/main/java/org/atdev/artrip/domain/review/web/dto/response/ExhibitReviewSliceResponse.java index eb6e815..eeb8809 100644 --- a/src/main/java/org/atdev/artrip/domain/review/web/dto/response/ExhibitReviewSliceResponse.java +++ b/src/main/java/org/atdev/artrip/domain/review/web/dto/response/ExhibitReviewSliceResponse.java @@ -13,4 +13,5 @@ public class ExhibitReviewSliceResponse { private List reviews; private Long nextCursor; private boolean hasNext; + private long reviewTotalCount; } diff --git a/src/main/resources/db/migration/V20251220__add_status_to_favorite.sql b/src/main/resources/db/migration/V20251220__add_status_to_favorite.sql new file mode 100644 index 0000000..e9b45d4 --- /dev/null +++ b/src/main/resources/db/migration/V20251220__add_status_to_favorite.sql @@ -0,0 +1 @@ +alter table favorite_exhibit add status TINYINT(1) NOT NULL default 0; \ No newline at end of file